1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Parser Reflection API |
4
|
|
|
* |
5
|
|
|
* @copyright Copyright 2015, Lisachenko Alexander <[email protected]> |
6
|
|
|
* |
7
|
|
|
* This source file is subject to the license that is bundled |
8
|
|
|
* with this source code in the file LICENSE. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Go\ParserReflection\Traits; |
12
|
|
|
|
13
|
|
|
|
14
|
|
|
use Go\ParserReflection\NodeVisitor\GeneratorDetector; |
15
|
|
|
use Go\ParserReflection\NodeVisitor\StaticVariablesCollector; |
16
|
|
|
use Go\ParserReflection\ReflectionParameter; |
17
|
|
|
use Go\ParserReflection\ReflectionType; |
18
|
|
|
use PhpParser\Node\Expr\Closure; |
19
|
|
|
use PhpParser\Node\FunctionLike; |
20
|
|
|
use PhpParser\Node\Identifier; |
21
|
|
|
use PhpParser\Node\NullableType; |
22
|
|
|
use PhpParser\Node\Stmt\ClassMethod; |
23
|
|
|
use PhpParser\Node\Stmt\Function_; |
24
|
|
|
use PhpParser\NodeTraverser; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* General trait for all function-like reflections |
28
|
|
|
*/ |
29
|
|
|
trait ReflectionFunctionLikeTrait |
30
|
|
|
{ |
31
|
|
|
use InitializationTrait; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var FunctionLike |
35
|
|
|
*/ |
36
|
|
|
protected $functionLikeNode; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Namespace name |
40
|
|
|
* |
41
|
|
|
* @var string |
42
|
|
|
*/ |
43
|
|
|
protected $namespaceName = ''; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var array|ReflectionParameter[] |
47
|
|
|
*/ |
48
|
|
|
protected $parameters; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* {@inheritDoc} |
52
|
|
|
*/ |
53
|
65 |
|
public function getClosureScopeClass() |
54
|
|
|
{ |
55
|
65 |
|
$this->initializeInternalReflection(); |
56
|
|
|
|
57
|
65 |
|
return parent::getClosureScopeClass(); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* {@inheritDoc} |
62
|
|
|
*/ |
63
|
65 |
|
public function getClosureThis() |
64
|
|
|
{ |
65
|
65 |
|
$this->initializeInternalReflection(); |
66
|
|
|
|
67
|
65 |
|
return parent::getClosureThis(); |
68
|
|
|
} |
69
|
|
|
|
70
|
129 |
|
public function getDocComment() |
71
|
|
|
{ |
72
|
129 |
|
$docComment = $this->functionLikeNode->getDocComment(); |
73
|
|
|
|
74
|
129 |
|
return $docComment ? $docComment->getText() : false; |
75
|
|
|
} |
76
|
|
|
|
77
|
129 |
|
public function getEndLine() |
78
|
|
|
{ |
79
|
129 |
|
return $this->functionLikeNode->getAttribute('endLine'); |
80
|
|
|
} |
81
|
|
|
|
82
|
65 |
|
public function getExtension() |
83
|
|
|
{ |
84
|
65 |
|
return null; |
85
|
|
|
} |
86
|
|
|
|
87
|
65 |
|
public function getExtensionName() |
88
|
|
|
{ |
89
|
65 |
|
return false; |
90
|
|
|
} |
91
|
|
|
|
92
|
71 |
|
public function getFileName() |
93
|
|
|
{ |
94
|
71 |
|
return $this->functionLikeNode->getAttribute('fileName'); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* {@inheritDoc} |
99
|
|
|
*/ |
100
|
2028 |
|
public function getName() |
101
|
|
|
{ |
102
|
2028 |
|
if ($this->functionLikeNode instanceof Function_ || $this->functionLikeNode instanceof ClassMethod) { |
103
|
2028 |
|
$functionName = $this->functionLikeNode->name->toString(); |
104
|
|
|
|
105
|
2028 |
|
return $this->namespaceName ? $this->namespaceName . '\\' . $functionName : $functionName; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
return false; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* {@inheritDoc} |
113
|
|
|
*/ |
114
|
71 |
|
public function getNamespaceName() |
115
|
|
|
{ |
116
|
71 |
|
return $this->namespaceName; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Get the number of parameters that a function defines, both optional and required. |
121
|
|
|
* |
122
|
|
|
* @link http://php.net/manual/en/reflectionfunctionabstract.getnumberofparameters.php |
123
|
|
|
* |
124
|
|
|
* @return int |
125
|
|
|
*/ |
126
|
115 |
|
public function getNumberOfParameters() |
127
|
|
|
{ |
128
|
115 |
|
return count($this->functionLikeNode->getParams()); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Get the number of required parameters that a function defines. |
133
|
|
|
* |
134
|
|
|
* @link http://php.net/manual/en/reflectionfunctionabstract.getnumberofrequiredparameters.php |
135
|
|
|
* |
136
|
|
|
* @return int |
137
|
|
|
*/ |
138
|
65 |
|
public function getNumberOfRequiredParameters() |
139
|
|
|
{ |
140
|
65 |
|
$requiredParameters = 0; |
141
|
65 |
|
foreach ($this->getParameters() as $parameter) { |
142
|
18 |
|
if (!$parameter->isOptional()) { |
143
|
18 |
|
$requiredParameters++; |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
147
|
65 |
|
return $requiredParameters; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* {@inheritDoc} |
152
|
|
|
*/ |
153
|
207 |
|
public function getParameters() |
154
|
|
|
{ |
155
|
207 |
|
if (!isset($this->parameters)) { |
156
|
79 |
|
$parameters = []; |
157
|
|
|
|
158
|
79 |
|
foreach ($this->functionLikeNode->getParams() as $parameterIndex => $parameterNode) { |
159
|
32 |
|
$reflectionParameter = new ReflectionParameter( |
160
|
32 |
|
$this->getName(), |
161
|
32 |
|
(string)$parameterNode->var->name, |
162
|
32 |
|
$parameterNode, |
163
|
32 |
|
$parameterIndex, |
164
|
32 |
|
$this |
165
|
|
|
); |
166
|
32 |
|
$parameters[] = $reflectionParameter; |
167
|
|
|
} |
168
|
|
|
|
169
|
79 |
|
$this->parameters = $parameters; |
170
|
|
|
} |
171
|
|
|
|
172
|
207 |
|
return $this->parameters; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Gets the specified return type of a function |
177
|
|
|
* |
178
|
|
|
* @return \ReflectionType |
179
|
|
|
* |
180
|
|
|
* @link http://php.net/manual/en/reflectionfunctionabstract.getreturntype.php |
181
|
|
|
*/ |
182
|
65 |
|
public function getReturnType() |
183
|
|
|
{ |
184
|
65 |
|
$isBuiltin = false; |
185
|
65 |
|
$returnType = $this->functionLikeNode->getReturnType(); |
186
|
65 |
|
$isNullable = $returnType instanceof NullableType; |
187
|
|
|
|
188
|
65 |
|
if ($isNullable) { |
189
|
6 |
|
$returnType = $returnType->type; |
190
|
|
|
} |
191
|
65 |
|
if ($returnType instanceof Identifier) { |
192
|
11 |
|
$isBuiltin = true; |
193
|
11 |
|
$returnType = $returnType->toString(); |
194
|
55 |
|
} elseif (is_object($returnType)) { |
195
|
5 |
|
$returnType = $returnType->toString(); |
196
|
51 |
|
} elseif (is_string($returnType)) { |
197
|
|
|
$isBuiltin = true; |
198
|
|
|
} else { |
199
|
51 |
|
return null; |
200
|
|
|
} |
201
|
|
|
|
202
|
15 |
|
return new ReflectionType($returnType, $isNullable, $isBuiltin); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* {@inheritDoc} |
207
|
|
|
*/ |
208
|
65 |
|
public function getShortName() |
209
|
|
|
{ |
210
|
65 |
|
if ($this->functionLikeNode instanceof Function_ || $this->functionLikeNode instanceof ClassMethod) { |
211
|
65 |
|
return $this->functionLikeNode->name->toString(); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
return false; |
215
|
|
|
} |
216
|
|
|
|
217
|
129 |
|
public function getStartLine() |
218
|
|
|
{ |
219
|
129 |
|
return $this->functionLikeNode->getAttribute('startLine'); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* {@inheritDoc} |
224
|
|
|
*/ |
225
|
65 |
|
public function getStaticVariables() |
226
|
|
|
{ |
227
|
|
|
// In nikic/PHP-Parser < 2.0.0 the default behavior is cloning |
228
|
|
|
// nodes when traversing them. Passing FALSE to the constructor |
229
|
|
|
// prevents this. |
230
|
|
|
// In nikic/PHP-Parser >= 2.0.0 and < 3.0.0 the default behavior was |
231
|
|
|
// changed to not clone nodes, but the parameter was retained as |
232
|
|
|
// an option. |
233
|
|
|
// In nikic/PHP-Parser >= 3.0.0 the option to clone nodes was removed |
234
|
|
|
// as a constructor parameter, so Scrutinizer will pick this up as |
235
|
|
|
// an issue. It is retained for legacy compatibility. |
236
|
65 |
|
$nodeTraverser = new NodeTraverser(false); |
|
|
|
|
237
|
65 |
|
$variablesCollector = new StaticVariablesCollector($this); |
238
|
65 |
|
$nodeTraverser->addVisitor($variablesCollector); |
239
|
|
|
|
240
|
|
|
/* @see https://github.com/nikic/PHP-Parser/issues/235 */ |
241
|
65 |
|
$nodeTraverser->traverse($this->functionLikeNode->getStmts() ?: array()); |
242
|
|
|
|
243
|
65 |
|
return $variablesCollector->getStaticVariables(); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Checks if the function has a specified return type |
248
|
|
|
* |
249
|
|
|
* @return bool |
250
|
|
|
* |
251
|
|
|
* @link http://php.net/manual/en/reflectionfunctionabstract.hasreturntype.php |
252
|
|
|
*/ |
253
|
66 |
|
public function hasReturnType() |
254
|
|
|
{ |
255
|
66 |
|
$returnType = $this->functionLikeNode->getReturnType(); |
256
|
|
|
|
257
|
66 |
|
return isset($returnType); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* {@inheritDoc} |
262
|
|
|
*/ |
263
|
65 |
|
public function inNamespace() |
264
|
|
|
{ |
265
|
65 |
|
return !empty($this->namespaceName); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* {@inheritDoc} |
270
|
|
|
*/ |
271
|
64 |
|
public function isClosure() |
272
|
|
|
{ |
273
|
64 |
|
return $this->functionLikeNode instanceof Closure; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* {@inheritDoc} |
278
|
|
|
*/ |
279
|
64 |
|
public function isDeprecated() |
280
|
|
|
{ |
281
|
|
|
// userland method/function/closure can not be deprecated |
282
|
64 |
|
return false; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* {@inheritDoc} |
287
|
|
|
*/ |
288
|
64 |
|
public function isGenerator() |
289
|
|
|
{ |
290
|
|
|
// In nikic/PHP-Parser < 2.0.0 the default behavior is cloning |
291
|
|
|
// nodes when traversing them. Passing FALSE to the constructor |
292
|
|
|
// prevents this. |
293
|
|
|
// In nikic/PHP-Parser >= 2.0.0 and < 3.0.0 the default behavior was |
294
|
|
|
// changed to not clone nodes, but the parameter was retained as |
295
|
|
|
// an option. |
296
|
|
|
// In nikic/PHP-Parser >= 3.0.0 the option to clone nodes was removed |
297
|
|
|
// as a constructor parameter, so Scrutinizer will pick this up as |
298
|
|
|
// an issue. It is retained for legacy compatibility. |
299
|
64 |
|
$nodeTraverser = new NodeTraverser(false); |
|
|
|
|
300
|
64 |
|
$nodeDetector = new GeneratorDetector(); |
301
|
64 |
|
$nodeTraverser->addVisitor($nodeDetector); |
302
|
|
|
|
303
|
|
|
/* @see https://github.com/nikic/PHP-Parser/issues/235 */ |
304
|
64 |
|
$nodeTraverser->traverse($this->functionLikeNode->getStmts() ?: array()); |
305
|
|
|
|
306
|
64 |
|
return $nodeDetector->isGenerator(); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* {@inheritDoc} |
311
|
|
|
*/ |
312
|
64 |
|
public function isInternal() |
313
|
|
|
{ |
314
|
|
|
// never can be an internal method |
315
|
64 |
|
return false; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* {@inheritDoc} |
320
|
|
|
*/ |
321
|
64 |
|
public function isUserDefined() |
322
|
|
|
{ |
323
|
|
|
// always defined by user, because we parse the source code |
324
|
64 |
|
return true; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* {@inheritDoc} |
329
|
|
|
*/ |
330
|
64 |
|
public function isVariadic() |
331
|
|
|
{ |
332
|
64 |
|
foreach ($this->getParameters() as $parameter) { |
333
|
17 |
|
if ($parameter->isVariadic()) { |
334
|
17 |
|
return true; |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
62 |
|
return false; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* {@inheritDoc} |
343
|
|
|
*/ |
344
|
65 |
|
public function returnsReference() |
345
|
|
|
{ |
346
|
65 |
|
return $this->functionLikeNode->returnsByRef(); |
347
|
|
|
} |
348
|
|
|
} |
349
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.