Skip to content

Commit 71b9b5d

Browse files
authored
Add support for named arguments in add_filter and add_action (#261)
1 parent 242660b commit 71b9b5d

5 files changed

+461
-160
lines changed

src/HookCallbackRule.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\Analyser\Scope;
1515
use PHPStan\Reflection\ParametersAcceptor;
1616
use PHPStan\Reflection\ParametersAcceptorSelector;
17+
use PHPStan\Reflection\ReflectionProvider;
1718
use PHPStan\Rules\RuleErrorBuilder;
1819
use PHPStan\Type\Constant\ConstantIntegerType;
1920
use PHPStan\Type\MixedType;
@@ -26,6 +27,8 @@
2627
*/
2728
class HookCallbackRule implements \PHPStan\Rules\Rule
2829
{
30+
use NormalizedArguments;
31+
2932
private const SUPPORTED_FUNCTIONS = [
3033
'add_filter',
3134
'add_action',
@@ -42,6 +45,13 @@ class HookCallbackRule implements \PHPStan\Rules\Rule
4245

4346
protected Scope $currentScope;
4447

48+
private ReflectionProvider $reflectionProvider;
49+
50+
public function __construct(ReflectionProvider $reflectionProvider)
51+
{
52+
$this->reflectionProvider = $reflectionProvider;
53+
}
54+
4555
public function getNodeType(): string
4656
{
4757
return FuncCall::class;
@@ -52,18 +62,20 @@ public function processNode(Node $node, Scope $scope): array
5262
$this->currentScope = $scope;
5363
$this->errors = [];
5464

55-
if (! ($node->name instanceof \PhpParser\Node\Name)) {
65+
if (! ($node->name instanceof Node\Name)) {
5666
return [];
5767
}
5868

59-
if (! in_array($node->name->toString(), self::SUPPORTED_FUNCTIONS, true)) {
69+
$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);
70+
71+
if (! in_array($functionReflection->getName(), self::SUPPORTED_FUNCTIONS, true)) {
6072
return [];
6173
}
6274

63-
$args = $node->getArgs();
75+
$args = $this->getNormalizedFunctionArgs($functionReflection, $node, $scope);
6476

6577
// If we don't have enough arguments, let PHPStan handle the error:
66-
if (count($args) < self::CALLBACK_INDEX + 1) {
78+
if ($args === null || count($args) < self::CALLBACK_INDEX + 1) {
6779
return [];
6880
}
6981

src/NormalizedArguments.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SzepeViktor\PHPStan\WordPress;
6+
7+
use PhpParser\Node\Expr\FuncCall;
8+
use PHPStan\Analyser\ArgumentsNormalizer;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Reflection\FunctionReflection;
11+
use PHPStan\Reflection\ParametersAcceptorSelector;
12+
13+
trait NormalizedArguments
14+
{
15+
/**
16+
* @return ?array<int, \PhpParser\Node\Arg> $args
17+
*/
18+
private function getNormalizedFunctionArgs(
19+
FunctionReflection $functionReflection,
20+
FuncCall $functionCall,
21+
Scope $scope
22+
): ?array {
23+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
24+
$scope,
25+
$functionCall->getArgs(),
26+
$functionReflection->getVariants(),
27+
$functionReflection->getNamedArgumentsVariants(),
28+
);
29+
30+
$normalizedFunctionCall = ArgumentsNormalizer::reorderFuncArguments(
31+
$parametersAcceptor,
32+
$functionCall
33+
);
34+
35+
if ($normalizedFunctionCall === null) {
36+
return null;
37+
}
38+
39+
return $normalizedFunctionCall->getArgs();
40+
}
41+
}

tests/HookCallbackRuleTest.php

Lines changed: 44 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,46 @@
44

55
namespace SzepeViktor\PHPStan\WordPress\Tests;
66

7+
use PHPStan\Reflection\ReflectionProvider;
78
use SzepeViktor\PHPStan\WordPress\HookCallbackRule;
89

10+
use const PHP_VERSION_ID;
11+
912
/**
1013
* @extends \PHPStan\Testing\RuleTestCase<\SzepeViktor\PHPStan\WordPress\HookCallbackRule>
1114
*/
1215
class HookCallbackRuleTest extends \PHPStan\Testing\RuleTestCase
1316
{
17+
private const EXPECTED_ERRORS = [
18+
['Filter callback return statement is missing.', 17],
19+
['Filter callback return statement is missing.', 18],
20+
['Callback expects 1 parameter, $accepted_args is set to 0.', 21],
21+
['Callback expects 1 parameter, $accepted_args is set to 2.', 26],
22+
['Callback expects 0-1 parameters, $accepted_args is set to 2.', 31],
23+
['Callback expects 2 parameters, $accepted_args is set to 1.', 36],
24+
['Callback expects 2-4 parameters, $accepted_args is set to 1.', 41],
25+
['Callback expects 2-3 parameters, $accepted_args is set to 4.', 46],
26+
['Action callback returns true but should not return anything.', 51],
27+
['Action callback returns true but should not return anything.', 54],
28+
['Filter callback return statement is missing.', 61],
29+
['Action callback returns false but should not return anything.', 64],
30+
['Action callback returns int but should not return anything.', 67],
31+
['Callback expects at least 1 parameter, $accepted_args is set to 0.', 70],
32+
['Callback expects at least 1 parameter, $accepted_args is set to 0.', 73],
33+
['Callback expects 0-3 parameters, $accepted_args is set to 4.', 78],
34+
['Action callback returns int but should not return anything.', 83],
35+
['Callback expects 0 parameters, $accepted_args is set to 2.', 86],
36+
['Action callback returns false but should not return anything.', 89],
37+
['Action callback returns mixed but should not return anything.', 92],
38+
['Action callback returns null but should not return anything.', 95],
39+
];
40+
1441
protected function getRule(): \PHPStan\Rules\Rule
1542
{
43+
$reflectionProvider = self::getContainer()->getByType(ReflectionProvider::class);
44+
1645
// getRule() method needs to return an instance of the tested rule
17-
return new HookCallbackRule();
46+
return new HookCallbackRule($reflectionProvider);
1847
}
1948

2049
public function testRule(): void
@@ -26,96 +55,21 @@ public function testRule(): void
2655
[
2756
__DIR__ . '/data/hook-callback.php',
2857
],
58+
self::EXPECTED_ERRORS
59+
);
60+
}
61+
62+
public function testRuleWithNamedArguments(): void
63+
{
64+
if (PHP_VERSION_ID < 80000) {
65+
$this::markTestSkipped('Named arguments are only supported in PHP 8.0 and later.');
66+
}
67+
68+
$this->analyse(
2969
[
30-
[
31-
'Filter callback return statement is missing.',
32-
17,
33-
],
34-
[
35-
'Filter callback return statement is missing.',
36-
20,
37-
],
38-
[
39-
'Filter callback return statement is missing.',
40-
23,
41-
],
42-
[
43-
'Callback expects 1 parameter, $accepted_args is set to 0.',
44-
26,
45-
],
46-
[
47-
'Callback expects 1 parameter, $accepted_args is set to 2.',
48-
31,
49-
],
50-
[
51-
'Callback expects 0-1 parameters, $accepted_args is set to 2.',
52-
36,
53-
],
54-
[
55-
'Callback expects 2 parameters, $accepted_args is set to 1.',
56-
41,
57-
],
58-
[
59-
'Callback expects 2-4 parameters, $accepted_args is set to 1.',
60-
46,
61-
],
62-
[
63-
'Callback expects 2-3 parameters, $accepted_args is set to 4.',
64-
51,
65-
],
66-
[
67-
'Action callback returns true but should not return anything.',
68-
56,
69-
],
70-
[
71-
'Action callback returns true but should not return anything.',
72-
61,
73-
],
74-
[
75-
'Filter callback return statement is missing.',
76-
68,
77-
],
78-
[
79-
'Action callback returns false but should not return anything.',
80-
71,
81-
],
82-
[
83-
'Action callback returns int but should not return anything.',
84-
74,
85-
],
86-
[
87-
'Callback expects at least 1 parameter, $accepted_args is set to 0.',
88-
77,
89-
],
90-
[
91-
'Callback expects at least 1 parameter, $accepted_args is set to 0.',
92-
80,
93-
],
94-
[
95-
'Callback expects 0-3 parameters, $accepted_args is set to 4.',
96-
85,
97-
],
98-
[
99-
'Action callback returns int but should not return anything.',
100-
90,
101-
],
102-
[
103-
'Callback expects 0 parameters, $accepted_args is set to 2.',
104-
93,
105-
],
106-
[
107-
'Action callback returns false but should not return anything.',
108-
96,
109-
],
110-
[
111-
'Action callback returns mixed but should not return anything.',
112-
99,
113-
],
114-
[
115-
'Action callback returns null but should not return anything.',
116-
102,
117-
],
118-
]
70+
__DIR__ . '/data/hook-callback-named-args.php',
71+
],
72+
self::EXPECTED_ERRORS
11973
);
12074
}
12175

0 commit comments

Comments
 (0)