Skip to content

Commit

Permalink
Improve phpdoc typing (#4)
Browse files Browse the repository at this point in the history
* Improve phpdoc typing

* phpcs
  • Loading branch information
jerodev authored Jun 7, 2024
1 parent a0e9491 commit 7551a9a
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 314 deletions.
576 changes: 306 additions & 270 deletions composer.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PhpCs_AnyFinder" xsi:noNamespaceSchemaLocation="./vendor/squizlabs/php_codesniffer/phpcs.xsd">
<rule ref="./vendor/jerodev/code-styles/phpcs.xml" />
<rule ref="./vendor/jerodev/code-styles/phpcs.xml">
<!-- This does not play well with tests -->
<exclude name="PSR1.Methods.CamelCapsMethodName.NotCamelCaps" />
</rule>

<file>src</file>
<file>tests</file>
</ruleset>
13 changes: 7 additions & 6 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ includes:

parameters:

level: 7
level: 7

paths:
- src
- tests
paths:
- src
- tests

checkGenericClassInNonGenericObjectType: false
checkMissingIterableValueType: false
ignoreErrors:
- identifier: missingType.generics
- identifier: missingType.iterableValue
12 changes: 10 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ If a selector is given, the current nodes will first be filtered.
$nodes->exists('a.active');
```

### `filter(closure $closure)`
Filters the current node collection based on a given closure.
```php
$nodes->filter(static function (NodeFilter $node) {
return $node->text() === 'foo';
});
```

### `first(?string $selector = null)`
Returns the first element of the node collection.
If a selector is given, the current nodes will first be filtered.
Expand Down Expand Up @@ -115,10 +123,10 @@ $nodes->texts('nav > a');
```

### `whereHas(closure $closure)`
Filters the current node collection based on a given closure.
Filter nodes that contain child nodes that fulfill the filter described by the closure
```php
$nodes->whereHas(static function (NodeFilter $node) {
return $node->text() === 'foo';
return $node->first('a[href]');
});
```

Expand Down
4 changes: 2 additions & 2 deletions src/NodeFilter/NodeCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public function text(?string $selector = null): ?string

public function texts(): array
{
return $this->each(static fn (NodeCollection $n) => $n->text());
return $this->each(static fn (NodeFilter $n) => $n->text());
}

public function whereHas(Closure $closure): NodeFilter
Expand Down Expand Up @@ -192,7 +192,7 @@ public function whereHasText(?string $value = null, bool $trim = true, bool $exa
return $text === $value;
}

return \strpos($text, $value) !== false;
return \str_contains($text, $value);
});
}

Expand Down
16 changes: 8 additions & 8 deletions src/NodeFilter/NodeFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ public function count(): int;
* Filter the current nodes and pass them to a defined closure.
* The closure will be passed each DomNode as a SingleNode object and can return any value from it.
*
* @param Closure|string|null $selector
* @param Closure|null $closure
* @template T
* @param Closure(NodeFilter, int):T|string|null $selector
* @param Closure(NodeFilter, int):T|null $closure
* @param int|null $max Optional, The maximum number of nodes to loop over.
* @return array
* @return ($closure is Closure ? array<T> : ($selector is Closure ? array<T> : array<NodeFilter>))
*/
public function each($selector = null, ?Closure $closure = null, ?int $max = null): array;

Expand All @@ -44,7 +45,7 @@ public function exists(?string $selector = null): bool;
* Filter a list of nodes using a closure that returns a boolean.
* The closure gets passed a node filter to test upon.
*
* @param Closure $closure
* @param Closure(NodeFilter):bool $closure
* @return NodeFilter
*/
public function filter(Closure $closure): NodeFilter;
Expand Down Expand Up @@ -112,15 +113,14 @@ public function text(?string $selector = null): ?string;
/**
* Returns the text content of all nodes in the current collection.
*
* @return string[]
* @return array<string>
*/
public function texts(): array;

/**
* Filter the current nodes by their child nodes.
* The closure gets a NodeFilter instance that can be used to define how the nodes should be filtered.
* Filter nodes that contain child nodes that fulfill the filter described by the closure
*
* @param Closure $closure
* @param Closure(NodeFilter):NodeFilter $closure
* @return NodeFilter
*/
public function whereHas(Closure $closure): NodeFilter;
Expand Down
12 changes: 7 additions & 5 deletions src/NodeFilter/Traits/EnumeratesValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
trait EnumeratesValues
{
/**
* @template T
* @param DOMNodeList $nodes
* @param Closure|string|null $selector
* @param Closure|null $closure
* @param Closure(NodeFilter, int):T|string|null $selector
* @param Closure(NodeFilter, int):T|null $closure
* @param int|null $max
* @return array
* @return ($closure is Closure ? array<T> : ($selector is Closure ? array<T> : array<NodeFilter>))
*/
protected function internalEach(DOMNodeList $nodes, $selector = null, ?Closure $closure = null, ?int $max = null): array
{
Expand All @@ -38,11 +39,12 @@ protected function internalEach(DOMNodeList $nodes, $selector = null, ?Closure $
}

$values = [];
$index = 0;
foreach ($nodes as $node) {
\assert($node instanceof DOMNode);

if ($closure instanceof Closure) {
$values[] = $closure(new NodeCollection($node));
$values[] = $closure(new NodeCollection($node), $index++);
} else {
$values[] = new NodeCollection($node);
}
Expand All @@ -59,7 +61,7 @@ protected function internalEach(DOMNodeList $nodes, $selector = null, ?Closure $
* filter nodes directly on DOMNode level
*
* @param DOMNodeList $nodes
* @param Closure $callback
* @param Closure(DOMNode):bool $callback
* @return NodeFilter
*/
protected function internalFilter(DOMNodeList $nodes, Closure $callback): NodeFilter
Expand Down
34 changes: 23 additions & 11 deletions tests/NodeFilter/NodeCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class NodeCollectionTest extends TestCase
protected function setUp(): void
{
$this->node = new NodeCollection(
$this->createDOMNodes()
$this->createDOMNodes(),
);
}

Expand Down Expand Up @@ -110,7 +110,7 @@ public function it_should_get_first_element(): void
{
$this->assertEquals(
'one',
$this->node->querySelector('li')->first()->text()
$this->node->querySelector('li')->first()->text(),
);
}

Expand All @@ -119,7 +119,7 @@ public function it_should_get_last_element(): void
{
$this->assertEquals(
'four',
$this->node->querySelector('li')->last()->text()
$this->node->querySelector('li')->last()->text(),
);
}

Expand All @@ -128,7 +128,7 @@ public function it_should_get_nth_element(): void
{
$this->assertEquals(
'two',
$this->node->querySelector('li')->nth(1)->text()
$this->node->querySelector('li')->nth(1)->text(),
);
}

Expand All @@ -137,12 +137,12 @@ public function it_should_get_text_from_element(): void
{
$this->assertEquals(
'one',
$this->node->querySelector('li')->text()
$this->node->querySelector('li')->text(),
);

$this->assertEquals(
'three',
$this->node->querySelector('li.third')->text()
$this->node->querySelector('li.third')->text(),
);
}

Expand All @@ -151,12 +151,12 @@ public function it_should_get_text_from_elements(): void
{
$this->assertEquals(
['one', 'two', 'three', 'four'],
$this->node->querySelector('li')->texts()
$this->node->querySelector('li')->texts(),
);

$this->assertEquals(
['three'],
$this->node->querySelector('li.third')->texts()
$this->node->querySelector('li.third')->texts(),
);
}

Expand All @@ -165,13 +165,25 @@ public function it_should_iterate_nodes(): void
{
$this->assertEquals(
['one', 'two', 'four'],
$this->node->each('li:not([class])', static fn (NodeFilter $n) => $n->text())
$this->node->each('li:not([class])', static fn (NodeFilter $n) => $n->text()),
);

$this->assertEquals(
['one', 'two', 'three', 'four'],
$this->node->querySelector('li')->each(static fn (NodeFilter $n) => $n->text())
$this->node->querySelector('li')->each(static fn (NodeFilter $n) => $n->text()),
);

$this->assertEquals(
[1, 2, 3, 4],
$this->node->querySelector('li')->each(static fn (NodeFilter $_, int $i) => $i+1),
);

// No return type should not throw PHPStan error
$result = [];
$this->node->each('li', static function () use (&$result) {
$result[] = 0;
});
$this->assertEquals([0, 0, 0, 0], $result);
}

/** @test */
Expand All @@ -194,7 +206,7 @@ private function createDOMNodes(): DOMNodeList
{
$html = <<<'HTML'
<!DOCTYPE html>
<html>
<html lang="en">
<body>
<div>
<p>Intro</p>
Expand Down
19 changes: 9 additions & 10 deletions tests/NodeFilter/NullNodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Jerodev\Diggy\Tests\NodeFilter;

use Jerodev\Diggy\NodeFilter\NodeCollection;
use Jerodev\Diggy\NodeFilter\NodeFilter;
use Jerodev\Diggy\NodeFilter\NullNode;
use PHPUnit\Framework\TestCase;
Expand All @@ -21,7 +20,7 @@ public function it_should_return_empty_array_on_each(): void
{
$this->assertEquals(
[],
$this->node->each('.foo', static fn (NodeCollection $n) => $n)
$this->node->each('.foo', static fn (NodeFilter $n) => $n),
);
}

Expand All @@ -30,7 +29,7 @@ public function it_should_return_empty_array_on_texts(): void
{
$this->assertEquals(
[],
$this->node->texts()
$this->node->texts(),
);
}

Expand All @@ -51,7 +50,7 @@ public function it_should_return_null_node_on_has_attribute(): void
{
$this->assertInstanceOf(
NullNode::class,
$this->node->whereHasAttribute('class')
$this->node->whereHasAttribute('class'),
);
}

Expand All @@ -60,7 +59,7 @@ public function it_should_return_null_node_on_has_text(): void
{
$this->assertInstanceOf(
NullNode::class,
$this->node->whereHasText('bar')
$this->node->whereHasText('bar'),
);
}

Expand All @@ -69,7 +68,7 @@ public function it_should_return_null_node_on_query_selector(): void
{
$this->assertInstanceOf(
NullNode::class,
$this->node->querySelector('.foo')
$this->node->querySelector('.foo'),
);
}

Expand All @@ -78,7 +77,7 @@ public function it_should_return_null_node_on_where_has(): void
{
$this->assertInstanceOf(
NullNode::class,
$this->node->whereHas(static fn (NodeFilter $f) => $f->querySelector('.foo'))
$this->node->whereHas(static fn (NodeFilter $f) => $f->querySelector('.foo')),
);
}

Expand All @@ -87,23 +86,23 @@ public function it_should_return_null_node_on_xpath(): void
{
$this->assertInstanceOf(
NullNode::class,
$this->node->xPath('.foo')
$this->node->xPath('.foo'),
);
}

/** @test */
public function it_should_return_null_on_get_attribute(): void
{
$this->assertNull(
$this->node->attribute('class')
$this->node->attribute('class'),
);
}

/** @test */
public function it_should_return_null_on_text(): void
{
$this->assertNull(
$this->node->text()
$this->node->text(),
);
}
}

0 comments on commit 7551a9a

Please sign in to comment.