Skip to content

Commit

Permalink
Merge pull request #45 from veewee/psalm5
Browse files Browse the repository at this point in the history
Upgrade tools and improve types
  • Loading branch information
veewee authored Dec 9, 2022
2 parents 075bb13 + f72c569 commit 8ae71e6
Show file tree
Hide file tree
Showing 27 changed files with 712 additions and 459 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/analyzers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
with:
php-version: ${{ matrix.php-versions }}
tools: 'composer:v2'
extensions: pcov, mbstring, posix
extensions: pcov, mbstring, posix, xdebug, dom, libxml, xml, xsl, xmlreader, xmlwriter
- name: Install dependencies
run: composer update --prefer-dist --no-progress --no-suggest ${{ matrix.composer-options }}
- name: Run the tests
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/stress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
php-version: ${{ matrix.php-versions }}
coverage: none
tools: 'composer:v2'
extensions: pcov, mbstring, posix, dom, libxml, xml, xsl, xmlreader, xmlwriter
- name: Install dependencies
run: composer update --prefer-dist --no-progress --no-suggest
- name: Run the stress tests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
php-version: ${{ matrix.php-versions }}
tools: 'composer:v2'
ini-values: error_reporting=E_ALL
extensions: pcov, mbstring, posix
extensions: pcov, mbstring, posix, dom, libxml, xml, xsl, xmlreader, xmlwriter
- name: Install dependencies
run: composer update --prefer-dist --no-progress --no-suggest
- name: Run the tests
Expand Down
7 changes: 3 additions & 4 deletions .phive/phars.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^9.5.10" installed="9.5.25" location="./tools/phpunit.phar" copy="true"/>
<phar name="psalm" version="^4.15.0" installed="4.27.0" location="./tools/psalm.phar" copy="true"/>
<phar name="infection" version="^0.26" installed="0.26.14" location="./tools/infection.phar" copy="true"/>
<phar name="php-cs-fixer" version="^3.3.2" installed="3.11.0" location="./tools/php-cs-fixer.phar" copy="true"/>
<phar name="phpunit" version="^9.5.10" installed="9.5.27" location="./tools/phpunit.phar" copy="true"/>
<phar name="infection" version="^0.26" installed="0.26.16" location="./tools/infection.phar" copy="true"/>
<phar name="php-cs-fixer" version="^3.3.2" installed="3.13.0" location="./tools/php-cs-fixer.phar" copy="true"/>
</phive>
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"require-dev": {
"symfony/finder": "^6.1",
"veewee/composer-run-parallel": "^1.0.0",
"php-standard-library/psalm-plugin": "^2.0"
"vimeo/psalm": "^5.1",
"php-standard-library/psalm-plugin": "^2.2"
},
"license": "MIT",
"authors": [
Expand Down Expand Up @@ -48,7 +49,7 @@
],
"cs": "PHP_CS_FIXER_IGNORE_ENV=1 php ./tools/php-cs-fixer.phar fix --dry-run",
"cs:fix": "PHP_CS_FIXER_IGNORE_ENV=1 php ./tools/php-cs-fixer.phar fix",
"psalm": "./tools/psalm.phar --no-cache",
"psalm": "./vendor/bin/psalm --no-cache --stats",
"tests": "./tools/phpunit.phar --coverage-text --color",
"stress": [
"Composer\\Config::disableProcessTimeout",
Expand Down
30 changes: 30 additions & 0 deletions docs/dom.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ Let's find out more by segregating the DOM component into its composable blocks:

Assert if a DOMNode is of a specific type.

#### assert_attribute

Assert if a node is of type `DOMAttr`.

```php
use Psl\Type\Exception\AssertException;
use function VeeWee\Xml\Dom\Assert\assert_attribute;

try {
assert_attribute($someNode)
} catch (AssertException $e) {
// Deal with it
}
```

#### assert_document

Assert if a node is of type `DOMDocument`.
Expand All @@ -75,6 +90,21 @@ try {
}
```

#### assert_dome_node_list

Assert if a variable is of type `DOMNodeList`.

```php
use Psl\Type\Exception\AssertException;
use function VeeWee\Xml\Dom\Assert\assert_dom_node_list;

try {
assert_dom_node_list($someVar)
} catch (AssertException $e) {
// Deal with it
}
```

#### assert_element

Assert if a node is of type `DOMElement`.
Expand Down
3 changes: 1 addition & 2 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
errorLevel="1"
resolveFromConfigFile="true"
strictBinaryOperands="true"
phpVersion="8.0"
phpVersion="8.1"
allowStringToStandInForClass="true"
rememberPropertyAssignmentsAfterCall="false"
skipChecksOnUnresolvableIncludes="false"
Expand Down Expand Up @@ -40,7 +40,6 @@
</issueHandlers>
<stubs>
<file name="stubs/DOM.phpstub" />
<file name="stubs/Psl.phpstub" />
<file name="stubs/XMLReader.phpstub" />
<file name="stubs/XMLWriter.phpstub" />
</stubs>
Expand Down
18 changes: 18 additions & 0 deletions src/Xml/Dom/Assert/assert_attribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Dom\Assert;

use DOMAttr;
use Psl\Type\Exception\AssertException;
use function Psl\Type\instance_of;

/**
* @psalm-assert DOMElement $node
* @throws AssertException
*/
function assert_attribute(mixed $node): DOMAttr
{
return instance_of(DOMAttr::class)->assert($node);
}
18 changes: 18 additions & 0 deletions src/Xml/Dom/Assert/assert_dom_node_list.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Dom\Assert;

use DOMNodeList;
use Psl\Type\Exception\AssertException;
use function Psl\Type\instance_of;

/**
* @psalm-assert DOMNodeList $node
* @throws AssertException
*/
function assert_dom_node_list(mixed $node): DOMNodeList
{
return instance_of(DOMNodeList::class)->assert($node);
}
1 change: 1 addition & 0 deletions src/Xml/Dom/Collection/NodeList.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static function empty(): self

/**
* @template X of DOMNode
* @param DOMNodeList<X> $list
* @return NodeList<X>
*/
public static function fromDOMNodeList(DOMNodeList $list): self
Expand Down
7 changes: 4 additions & 3 deletions src/Xml/Dom/Locator/Element/siblings.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
use function VeeWee\Xml\Dom\Predicate\is_element;

/**
* @psalm-suppress RedundantConditionGivenDocblockType - Seems better to do an additional check here psalm!
*
* @return NodeList<DOMElement>
*/
function siblings(DOMNode $node): NodeList
{
return new NodeList(...filter(
/** @var NodeList<DOMElement> $siblings */
$siblings = new NodeList(...filter(
$node->parentNode?->childNodes?->getIterator() ?? [],
static fn (DOMNode $sibling): bool => is_element($sibling) && $sibling !== $node
));

return $siblings;
}
2 changes: 1 addition & 1 deletion src/Xml/Dom/Locator/Xmlns/linked_namespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ function linked_namespaces(DOMNode $node): NodeList
{
$xpath = Xpath::fromUnsafeNode($node);

return $xpath->query('./namespace::*', $node);
return $xpath->query('./namespace::*', $node)->expectAllOfType(DOMNameSpaceNode::class);
}
2 changes: 1 addition & 1 deletion src/Xml/Dom/Locator/Xmlns/recursive_linked_namespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ function recursive_linked_namespaces(DOMNode $node): NodeList
{
$xpath = Xpath::fromUnsafeNode($node);

return $xpath->query('.//namespace::*', $node);
return $xpath->query('.//namespace::*', $node)->expectAllOfType(DOMNameSpaceNode::class);
}
3 changes: 2 additions & 1 deletion src/Xml/Dom/Manipulator/Attribute/rename.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use function VeeWee\Xml\Dom\Builder\namespaced_attribute;
use function VeeWee\Xml\Dom\Locator\Element\parent_element;
use function VeeWee\Xml\Dom\Manipulator\Node\remove;
use function VeeWee\Xml\Dom\Predicate\is_attribute;

/**
* @throws RuntimeException
Expand All @@ -34,7 +35,7 @@ function rename(DOMAttr $target, string $newQName, ?string $newNamespaceURI = nu
$result = $element->getAttributeNode($newQName);

/** @psalm-suppress TypeDoesNotContainType - It can actually be null if the exact node name is not found. */
if (!$result) {
if (!$result || !is_attribute($result)) {
throw RuntimeException::withMessage(
'Unable to rename attribute '.$target->nodeName.' into '.$newQName.'. You might need to swap xmlns prefix first!'
);
Expand Down
5 changes: 3 additions & 2 deletions src/Xml/Dom/Manipulator/Xmlns/rename.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use VeeWee\Xml\Dom\Xpath;
use VeeWee\Xml\Exception\RuntimeException;
use function Psl\invariant;
use function Psl\Type\non_empty_string;
use function sprintf;
use function VeeWee\Xml\Dom\Builder\xmlns_attribute;
use function VeeWee\Xml\Dom\Locator\Attribute\attributes_list;
Expand Down Expand Up @@ -49,7 +50,7 @@ function rename(DOMDocument $document, string $namespaceURI, string $newPrefix):
// otherwise XMLNS namespace will be removed again after dealing with the elements that declare the xmlns.
$linkedNodes = $xpath->query(
sprintf('//*[namespace-uri()=\'%1$s\' or @*[namespace-uri()=\'%1$s\'] or namespace::*]', $namespaceURI)
)->reduce(
)->expectAllOfType(DOMElement::class)->reduce(
static fn (NodeList $list, DOMElement $element): NodeList
=> new NodeList(
...[$element],
Expand All @@ -68,7 +69,7 @@ function rename(DOMDocument $document, string $namespaceURI, string $newPrefix):
$linkedNodes->forEach(
static function (DOMNode $node) use ($namespaceURI, $newPrefix, $predicate, $root): void {
// Wrapped in a closure so that psalm knows it all...
$newQname = static fn (DOMNode $node): string => $newPrefix.':'.$node->localName;
$newQname = static fn (DOMNode $node): string => $newPrefix.':'.non_empty_string()->assert($node->localName);

if (is_attribute($node)) {
rename_node($node, $newQname($node), $namespaceURI);
Expand Down
3 changes: 1 addition & 2 deletions src/Xml/Dom/Xpath.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ public function locate(callable $locator)
}

/**
* @template T of DOMNode
* @throws RuntimeException
* @return NodeList<T>
* @return NodeList<DOMNode>
*/
public function query(string $expression, DOMNode $contextNode = null): NodeList
{
Expand Down
27 changes: 12 additions & 15 deletions src/Xml/Dom/Xpath/Locator/query.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,27 @@
use DOMNodeList;
use DOMXPath;
use VeeWee\Xml\Dom\Collection\NodeList;
use function VeeWee\Xml\Dom\Assert\assert_dom_node_list;
use function VeeWee\Xml\ErrorHandling\disallow_issues;
use function VeeWee\Xml\ErrorHandling\disallow_libxml_false_returns;

/**
* @template T of DOMNode
* @return \Closure(DOMXPath): NodeList<T>
* @return \Closure(DOMXPath): NodeList<DOMNode>
*/
function query(string $query, DOMNode $node = null): Closure
{
return
/**
* @return NodeList<T>
*/
static function (DOMXPath $xpath) use ($query, $node): NodeList {
$node = $node ?? $xpath->document->documentElement;
return static function (DOMXPath $xpath) use ($query, $node): NodeList {
$node = $node ?? $xpath->document->documentElement;

/** @var DOMNodeList<T> $list */
$list = disallow_issues(
static fn (): DOMNodeList => disallow_libxml_false_returns(
$list = disallow_issues(
static fn (): DOMNodeList => assert_dom_node_list(
disallow_libxml_false_returns(
$xpath->query($query, $node),
'Failed querying XPath query: '.$query
),
);
)
),
);

return NodeList::fromDOMNodeList($list);
};
return NodeList::fromDOMNodeList($list);
};
}
9 changes: 6 additions & 3 deletions src/Xml/Dom/Xpath/Locator/query_single.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use VeeWee\Xml\Exception\RuntimeException;
use Webmozart\Assert\Assert;
use function Psl\Str\format;
use function VeeWee\Xml\Dom\Assert\assert_dom_node_list;
use function VeeWee\Xml\ErrorHandling\disallow_issues;
use function VeeWee\Xml\ErrorHandling\disallow_libxml_false_returns;

Expand All @@ -27,9 +28,11 @@ function query_single(string $query, DOMNode $node = null): Closure
static function (DOMXPath $xpath) use ($query, $node): DOMNode {
$node = $node ?? $xpath->document->documentElement;
$list = disallow_issues(
static fn (): DOMNodeList => disallow_libxml_false_returns(
$xpath->query($query, $node),
'Failed querying XPath query: '.$query
static fn (): DOMNodeList => assert_dom_node_list(
disallow_libxml_false_returns(
$xpath->query($query, $node),
'Failed querying XPath query: '.$query
)
),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
use function VeeWee\Xml\Dom\Locator\Element\children;

/**
* @psalm-type GroupedElements=array<string, DOMElement|list<DOMElement>>
* @psalm-internal VeeWee\Xml\Encoding
* @return array<string, DOMElement|list<DOMElement>>
* @return GroupedElements
*/
function group_child_elements(DOMElement $element): array
{
/** @var GroupedElements $grouped */
$grouped = [];
foreach (children($element) as $child) {
$key = name($child);
Expand Down
2 changes: 2 additions & 0 deletions src/Xml/ErrorHandling/stop_on_first_issue.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
* @psalm-param (callable(): ?Tr) $run
* @psalm-return Generator<int, Tr, null, mixed>
*
* @psalm-suppress InvalidReturnType - Psalm gets lost here and thinks it returns "never"
*
* @throws RuntimeException
*/
function stop_on_first_issue(callable $tick, callable $run): Generator
Expand Down
2 changes: 2 additions & 0 deletions src/bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php declare(strict_types=1);

require_once __DIR__.'/Xml/Dom/Assert/assert_attribute.php';
require_once __DIR__.'/Xml/Dom/Assert/assert_document.php';
require_once __DIR__.'/Xml/Dom/Assert/assert_dom_node_list.php';
require_once __DIR__.'/Xml/Dom/Assert/assert_element.php';
require_once __DIR__.'/Xml/Dom/Builder/attribute.php';
require_once __DIR__.'/Xml/Dom/Builder/attributes.php';
Expand Down
20 changes: 0 additions & 20 deletions stubs/Psl.phpstub

This file was deleted.

15 changes: 15 additions & 0 deletions stubs/XMLReader.phpstub
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@
*/
class XMLReader
{
public readonly int $attributeCount;
public readonly string $baseURI;
public readonly int $depth;
public readonly bool $hasAttributes;
public readonly bool $hasValue;
public readonly bool $isDefault;
public readonly bool $isEmptyElement;
public readonly string $localName;
public readonly string $name;
public readonly string $namespaceURI;
public readonly int $nodeType;
public readonly string $prefix;
public readonly string $value;
public readonly string $xmlLang;

public static function open(string $source, ?string $encoding = null, int $flags = 0): XMLReader|false;
public static function XML(string $uri, ?string $encoding = null, int $flags = 0): XMLReader|false;
}
Binary file modified tools/infection.phar
Binary file not shown.
Binary file modified tools/php-cs-fixer.phar
Binary file not shown.
Loading

0 comments on commit 8ae71e6

Please sign in to comment.