2
2
3
3
namespace PHPStan \Rules \Functions ;
4
4
5
- use Nette \Utils \Strings ;
6
5
use PhpParser \Node ;
7
6
use PhpParser \Node \Expr \FuncCall ;
8
7
use PHPStan \Analyser \Scope ;
9
- use PHPStan \Php \ PhpVersion ;
8
+ use PHPStan \Reflection \ ReflectionProvider ;
10
9
use PHPStan \Rules \Rule ;
11
10
use PHPStan \Rules \RuleErrorBuilder ;
12
- use function array_filter ;
13
11
use function array_key_exists ;
14
12
use function count ;
15
13
use function in_array ;
16
- use function max ;
17
14
use function sprintf ;
18
- use function strlen ;
19
- use function strtolower ;
20
- use const PREG_SET_ORDER ;
21
15
22
16
/**
23
17
* @implements Rule<Node\Expr\FuncCall>
24
18
*/
25
19
class PrintfParametersRule implements Rule
26
20
{
27
21
28
- public function __construct (private PhpVersion $ phpVersion )
22
+ private const FORMAT_ARGUMENT_POSITIONS = [
23
+ 'printf ' => 0 ,
24
+ 'sprintf ' => 0 ,
25
+ 'sscanf ' => 1 ,
26
+ 'fscanf ' => 1 ,
27
+ ];
28
+ private const MINIMUM_NUMBER_OF_ARGUMENTS = [
29
+ 'printf ' => 1 ,
30
+ 'sprintf ' => 1 ,
31
+ 'sscanf ' => 3 ,
32
+ 'fscanf ' => 3 ,
33
+ ];
34
+
35
+ public function __construct (
36
+ private PrintfHelper $ printfHelper ,
37
+ private ReflectionProvider $ reflectionProvider ,
38
+ )
29
39
{
30
40
}
31
41
@@ -40,25 +50,17 @@ public function processNode(Node $node, Scope $scope): array
40
50
return [];
41
51
}
42
52
43
- $ functionsArgumentPositions = [
44
- 'printf ' => 0 ,
45
- 'sprintf ' => 0 ,
46
- 'sscanf ' => 1 ,
47
- 'fscanf ' => 1 ,
48
- ];
49
- $ minimumNumberOfArguments = [
50
- 'printf ' => 1 ,
51
- 'sprintf ' => 1 ,
52
- 'sscanf ' => 3 ,
53
- 'fscanf ' => 3 ,
54
- ];
55
-
56
- $ name = strtolower ((string ) $ node ->name );
57
- if (!array_key_exists ($ name , $ functionsArgumentPositions )) {
53
+ if (!$ this ->reflectionProvider ->hasFunction ($ node ->name , $ scope )) {
58
54
return [];
59
55
}
60
56
61
- $ formatArgumentPosition = $ functionsArgumentPositions [$ name ];
57
+ $ functionReflection = $ this ->reflectionProvider ->getFunction ($ node ->name , $ scope );
58
+ $ name = $ functionReflection ->getName ();
59
+ if (!array_key_exists ($ name , self ::FORMAT_ARGUMENT_POSITIONS )) {
60
+ return [];
61
+ }
62
+
63
+ $ formatArgumentPosition = self ::FORMAT_ARGUMENT_POSITIONS [$ name ];
62
64
63
65
$ args = $ node ->getArgs ();
64
66
foreach ($ args as $ arg ) {
@@ -67,38 +69,44 @@ public function processNode(Node $node, Scope $scope): array
67
69
}
68
70
}
69
71
$ argsCount = count ($ args );
70
- if ($ argsCount < $ minimumNumberOfArguments [$ name ]) {
72
+ if ($ argsCount < self :: MINIMUM_NUMBER_OF_ARGUMENTS [$ name ]) {
71
73
return []; // caught by CallToFunctionParametersRule
72
74
}
73
75
74
76
$ formatArgType = $ scope ->getType ($ args [$ formatArgumentPosition ]->value );
75
- $ placeHoldersCount = null ;
77
+ $ maxPlaceHoldersCount = null ;
76
78
foreach ($ formatArgType ->getConstantStrings () as $ formatString ) {
77
79
$ format = $ formatString ->getValue ();
78
- $ tempPlaceHoldersCount = $ this ->getPlaceholdersCount ($ name , $ format );
79
- if ($ placeHoldersCount === null ) {
80
- $ placeHoldersCount = $ tempPlaceHoldersCount ;
81
- } elseif ($ tempPlaceHoldersCount > $ placeHoldersCount ) {
82
- $ placeHoldersCount = $ tempPlaceHoldersCount ;
80
+
81
+ if (in_array ($ name , ['sprintf ' , 'printf ' ], true )) {
82
+ $ tempPlaceHoldersCount = $ this ->printfHelper ->getPrintfPlaceholdersCount ($ format );
83
+ } else {
84
+ $ tempPlaceHoldersCount = $ this ->printfHelper ->getScanfPlaceholdersCount ($ format );
85
+ }
86
+
87
+ if ($ maxPlaceHoldersCount === null ) {
88
+ $ maxPlaceHoldersCount = $ tempPlaceHoldersCount ;
89
+ } elseif ($ tempPlaceHoldersCount > $ maxPlaceHoldersCount ) {
90
+ $ maxPlaceHoldersCount = $ tempPlaceHoldersCount ;
83
91
}
84
92
}
85
93
86
- if ($ placeHoldersCount === null ) {
94
+ if ($ maxPlaceHoldersCount === null ) {
87
95
return [];
88
96
}
89
97
90
98
$ argsCount -= $ formatArgumentPosition ;
91
99
92
- if ($ argsCount !== $ placeHoldersCount + 1 ) {
100
+ if ($ argsCount !== $ maxPlaceHoldersCount + 1 ) {
93
101
return [
94
102
RuleErrorBuilder::message (sprintf (
95
103
sprintf (
96
104
'%s, %s. ' ,
97
- $ placeHoldersCount === 1 ? 'Call to %s contains %d placeholder ' : 'Call to %s contains %d placeholders ' ,
105
+ $ maxPlaceHoldersCount === 1 ? 'Call to %s contains %d placeholder ' : 'Call to %s contains %d placeholders ' ,
98
106
$ argsCount - 1 === 1 ? '%d value given ' : '%d values given ' ,
99
107
),
100
108
$ name ,
101
- $ placeHoldersCount ,
109
+ $ maxPlaceHoldersCount ,
102
110
$ argsCount - 1 ,
103
111
))->identifier (sprintf ('argument.%s ' , $ name ))->build (),
104
112
];
@@ -107,49 +115,4 @@ public function processNode(Node $node, Scope $scope): array
107
115
return [];
108
116
}
109
117
110
- private function getPlaceholdersCount (string $ functionName , string $ format ): int
111
- {
112
- $ specifiers = in_array ($ functionName , ['sprintf ' , 'printf ' ], true ) ? '(?:[bs%s]|l?[cdeEgfFGouxX]) ' : '(?:[cdDeEfinosuxX%s]|\[[^\]]+\]) ' ;
113
- $ addSpecifier = '' ;
114
- if ($ this ->phpVersion ->supportsHhPrintfSpecifier ()) {
115
- $ addSpecifier .= 'hH ' ;
116
- }
117
-
118
- $ specifiers = sprintf ($ specifiers , $ addSpecifier );
119
-
120
- $ pattern = '~(?<before>%*)%(?:(?<position>\d+)\$)?[-+]?(?:[ 0]|(?: \'[^%]))?(?<width>\*)?-?\d*(?:\.(?:\d+|(?<precision>\*))?)? ' . $ specifiers . '~ ' ;
121
-
122
- $ matches = Strings::matchAll ($ format , $ pattern , PREG_SET_ORDER );
123
-
124
- if (count ($ matches ) === 0 ) {
125
- return 0 ;
126
- }
127
-
128
- $ placeholders = array_filter ($ matches , static fn (array $ match ): bool => strlen ($ match ['before ' ]) % 2 === 0 );
129
-
130
- if (count ($ placeholders ) === 0 ) {
131
- return 0 ;
132
- }
133
-
134
- $ maxPositionedNumber = 0 ;
135
- $ maxOrdinaryNumber = 0 ;
136
- foreach ($ placeholders as $ placeholder ) {
137
- if (isset ($ placeholder ['width ' ]) && $ placeholder ['width ' ] !== '' ) {
138
- $ maxOrdinaryNumber ++;
139
- }
140
-
141
- if (isset ($ placeholder ['precision ' ]) && $ placeholder ['precision ' ] !== '' ) {
142
- $ maxOrdinaryNumber ++;
143
- }
144
-
145
- if (isset ($ placeholder ['position ' ]) && $ placeholder ['position ' ] !== '' ) {
146
- $ maxPositionedNumber = max ((int ) $ placeholder ['position ' ], $ maxPositionedNumber );
147
- } else {
148
- $ maxOrdinaryNumber ++;
149
- }
150
- }
151
-
152
- return max ($ maxPositionedNumber , $ maxOrdinaryNumber );
153
- }
154
-
155
118
}
0 commit comments