Skip to content

Commit e322dc5

Browse files
leongersenondrejmirtes
authored andcommitted
#6840 Report useless array filter
1 parent 8ba5f34 commit e322dc5

File tree

5 files changed

+188
-1
lines changed

5 files changed

+188
-1
lines changed

conf/config.level5.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ parameters:
88
rules:
99
- PHPStan\Rules\DateTimeInstantiationRule
1010
- PHPStan\Rules\Functions\ImplodeFunctionRule
11+
- PHPStan\Rules\Functions\ArrayFilterEmptyRule
1112

1213
services:
1314
-
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Functions;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\FuncCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\ReflectionProvider;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Type\StaticTypeFactory;
12+
use PHPStan\Type\VerbosityLevel;
13+
use function count;
14+
use function sprintf;
15+
use function strtolower;
16+
17+
/**
18+
* @implements Rule<Node\Expr\FuncCall>
19+
*/
20+
class ArrayFilterEmptyRule implements Rule
21+
{
22+
23+
public function __construct(private ReflectionProvider $reflectionProvider)
24+
{
25+
}
26+
27+
public function getNodeType(): string
28+
{
29+
return FuncCall::class;
30+
}
31+
32+
public function processNode(Node $node, Scope $scope): array
33+
{
34+
if (!($node->name instanceof Node\Name)) {
35+
return [];
36+
}
37+
38+
$functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope);
39+
40+
if ($functionName === null || strtolower($functionName) !== 'array_filter') {
41+
return [];
42+
}
43+
44+
$args = $node->getArgs();
45+
if (count($args) !== 1) {
46+
return [];
47+
}
48+
49+
$arrayType = $scope->getType($args[0]->value);
50+
51+
if ($arrayType->isIterableAtLeastOnce()->no()) {
52+
$message = 'Parameter #1 $array (%s) to function array_filter is empty, call has no effect.';
53+
return [
54+
RuleErrorBuilder::message(sprintf(
55+
$message,
56+
$arrayType->describe(VerbosityLevel::value()),
57+
))->build(),
58+
];
59+
}
60+
61+
$falsyType = StaticTypeFactory::falsey();
62+
$isSuperType = $falsyType->isSuperTypeOf($arrayType->getIterableValueType());
63+
64+
if ($isSuperType->no()) {
65+
$message = 'Parameter #1 $array (%s) to function array_filter cannot contain empty values, call has no effect.';
66+
return [
67+
RuleErrorBuilder::message(sprintf(
68+
$message,
69+
$arrayType->describe(VerbosityLevel::value()),
70+
))->build(),
71+
];
72+
}
73+
74+
if ($isSuperType->yes()) {
75+
$message = 'Parameter #1 $array (%s) to function array_filter can only contain empty values, call always results in [].';
76+
return [
77+
RuleErrorBuilder::message(sprintf(
78+
$message,
79+
$arrayType->describe(VerbosityLevel::value()),
80+
))->build(),
81+
];
82+
}
83+
84+
return [];
85+
}
86+
87+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<?php
22

3-
array_filter([]);
3+
array_filter([0, 1, 2]);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Functions;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<ArrayFilterEmptyRule>
10+
*/
11+
class ArrayFilterEmptyRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new ArrayFilterEmptyRule($this->createReflectionProvider());
17+
}
18+
19+
public function testFile(): void
20+
{
21+
$expectedErrors = [
22+
[
23+
'Parameter #1 $array (array{1, 3}) to function array_filter cannot contain empty values, call has no effect.',
24+
11,
25+
],
26+
[
27+
'Parameter #1 $array (array{\'test\'}) to function array_filter cannot contain empty values, call has no effect.',
28+
12,
29+
],
30+
[
31+
'Parameter #1 $array (array{true, true}) to function array_filter cannot contain empty values, call has no effect.',
32+
17,
33+
],
34+
[
35+
'Parameter #1 $array (array{stdClass}) to function array_filter cannot contain empty values, call has no effect.',
36+
18,
37+
],
38+
[
39+
'Parameter #1 $array (array<stdClass>) to function array_filter cannot contain empty values, call has no effect.',
40+
20,
41+
],
42+
[
43+
'Parameter #1 $array (array{0}) to function array_filter can only contain empty values, call always results in [].',
44+
23,
45+
],
46+
[
47+
'Parameter #1 $array (array{null}) to function array_filter can only contain empty values, call always results in [].',
48+
24,
49+
],
50+
[
51+
'Parameter #1 $array (array{null, null}) to function array_filter can only contain empty values, call always results in [].',
52+
25,
53+
],
54+
[
55+
'Parameter #1 $array (array{null, 0}) to function array_filter can only contain empty values, call always results in [].',
56+
26,
57+
],
58+
[
59+
'Parameter #1 $array (array<false|null>) to function array_filter can only contain empty values, call always results in [].',
60+
27,
61+
],
62+
[
63+
'Parameter #1 $array (array{}) to function array_filter is empty, call has no effect.',
64+
28,
65+
],
66+
];
67+
68+
$this->analyse([__DIR__ . '/data/array_filter_empty.php'], $expectedErrors);
69+
}
70+
71+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/** @var \stdClass[] $objects */
4+
$objects = [];
5+
/** @var array<\stdClass|null> $objectsOrNull */
6+
$objectsOrNull = [];
7+
/** @var array<false|null> $falsey */
8+
$falsey = [];
9+
10+
array_filter([0,1,3]);
11+
array_filter([1,3]);
12+
array_filter(['test']);
13+
array_filter(['', 'test']);
14+
array_filter([null, 'test']);
15+
array_filter([false, 'test']);
16+
array_filter([true, false]);
17+
array_filter([true, true]);
18+
array_filter([new \stdClass()]);
19+
array_filter([new \stdClass(), null]);
20+
array_filter($objects);
21+
array_filter($objectsOrNull);
22+
23+
array_filter([0]);
24+
array_filter([null]);
25+
array_filter([null, null]);
26+
array_filter([null, 0]);
27+
array_filter($falsey);
28+
array_filter([]);

0 commit comments

Comments
 (0)