-
-
Notifications
You must be signed in to change notification settings - Fork 24
/
HookCallbackRule.php
129 lines (101 loc) · 3.47 KB
/
HookCallbackRule.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php
/**
* Custom rule to validate the callback function for WordPress core actions and filters.
*/
declare(strict_types=1);
namespace SzepeViktor\PHPStan\WordPress;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
/**
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\FuncCall>
*/
class HookCallbackRule implements \PHPStan\Rules\Rule
{
private const SUPPORTED_FUNCTIONS = [
'add_filter',
'add_action',
];
/** @var \PHPStan\Rules\RuleLevelHelper */
protected $ruleLevelHelper;
/** @var \PhpParser\Node\Expr\FuncCall */
protected $currentNode;
/** @var \PHPStan\Analyser\Scope */
protected $currentScope;
public function __construct(
RuleLevelHelper $ruleLevelHelper
) {
$this->ruleLevelHelper = $ruleLevelHelper;
}
public function getNodeType(): string
{
return FuncCall::class;
}
/**
* @param \PhpParser\Node\Expr\FuncCall $node
* @param \PHPStan\Analyser\Scope $scope
* @return array<int, \PHPStan\Rules\RuleError>
*/
public function processNode(Node $node, Scope $scope): array
{
$name = $node->name;
if (!($name instanceof \PhpParser\Node\Name)) {
return [];
}
if (!in_array($name->toString(), self::SUPPORTED_FUNCTIONS, true)) {
return [];
}
$args = $node->getArgs();
// If we don't have enough arguments, bail out and let PHPStan handle the error:
if (count($args) < 2) {
return [];
}
list(
$hookNameArg,
$callbackArg
) = $args;
$hookNameType = $scope->getType($hookNameArg->value);
$hookNameValue = null;
if ($hookNameType instanceof ConstantStringType) {
$hookNameValue = $hookNameType->getValue();
}
$callbackType = $scope->getType($callbackArg->value);
// If the callback is not valid, bail out and let PHPStan handle the error:
if ($callbackType->isCallable()->no()) {
return [];
}
$acceptedArgs = 1;
if (isset($args[3])) {
$acceptedArgs = null;
$argumentType = $scope->getType($args[3]->value);
if ($argumentType instanceof ConstantIntegerType) {
$acceptedArgs = $argumentType->getValue();
}
}
if ($acceptedArgs !== null) {
$callbackAcceptor = $callbackType->getCallableParametersAcceptors($scope)[0];
$callbackParameters = $callbackAcceptor->getParameters();
$expectedArgs = count($callbackParameters);
if ($expectedArgs !== $acceptedArgs && ($expectedArgs !== 0 && $acceptedArgs !== 1)) {
$message = (1 === $expectedArgs)
? 'Callback expects %1$d argument, $accepted_args is set to %2$d.'
: 'Callback expects %1$d arguments, $accepted_args is set to %2$d.'
;
return [
RuleErrorBuilder::message(
sprintf(
$message,
$expectedArgs,
$acceptedArgs
)
)->build()
];
}
}
return [];
}
}