1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of PHP CS Fixer. |
7
|
|
|
* |
8
|
|
|
* (c) Fabien Potencier <[email protected]> |
9
|
|
|
* Dariusz Rumiński <[email protected]> |
10
|
|
|
* |
11
|
|
|
* This source file is subject to the MIT license that is bundled |
12
|
|
|
* with this source code in the file LICENSE. |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
namespace PhpCsFixer\Fixer\Import; |
16
|
|
|
|
17
|
|
|
use PhpCsFixer\AbstractFixer; |
18
|
|
|
use PhpCsFixer\DocBlock\Annotation; |
19
|
|
|
use PhpCsFixer\DocBlock\DocBlock; |
20
|
|
|
use PhpCsFixer\Fixer\ConfigurableFixerInterface; |
21
|
|
|
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; |
22
|
|
|
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; |
23
|
|
|
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; |
24
|
|
|
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; |
25
|
|
|
use PhpCsFixer\FixerDefinition\CodeSample; |
26
|
|
|
use PhpCsFixer\FixerDefinition\FixerDefinition; |
27
|
|
|
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; |
28
|
|
|
use PhpCsFixer\Preg; |
29
|
|
|
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; |
30
|
|
|
use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer; |
31
|
|
|
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; |
32
|
|
|
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; |
33
|
|
|
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; |
34
|
|
|
use PhpCsFixer\Tokenizer\CT; |
35
|
|
|
use PhpCsFixer\Tokenizer\Token; |
36
|
|
|
use PhpCsFixer\Tokenizer\Tokens; |
37
|
|
|
use PhpCsFixer\Tokenizer\TokensAnalyzer; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @author Gregor Harlan <[email protected]> |
41
|
|
|
*/ |
42
|
|
|
final class GlobalNamespaceImportFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface |
43
|
|
|
{ |
44
|
|
|
/** |
45
|
|
|
* {@inheritdoc} |
46
|
|
|
*/ |
47
|
|
|
public function getDefinition(): FixerDefinitionInterface |
48
|
|
|
{ |
49
|
|
|
return new FixerDefinition( |
50
|
|
|
'Imports or fully qualifies global classes/functions/constants.', |
51
|
|
|
[ |
52
|
|
|
new CodeSample( |
53
|
|
|
'<?php |
54
|
|
|
|
55
|
|
|
namespace Foo; |
56
|
|
|
|
57
|
|
|
$d = new \DateTimeImmutable(); |
58
|
|
|
' |
59
|
|
|
), |
60
|
|
|
new CodeSample( |
61
|
|
|
'<?php |
62
|
|
|
|
63
|
|
|
namespace Foo; |
64
|
|
|
|
65
|
|
|
if (\count($x)) { |
66
|
|
|
/** @var \DateTimeImmutable $d */ |
67
|
|
|
$d = new \DateTimeImmutable(); |
68
|
|
|
$p = \M_PI; |
69
|
|
|
} |
70
|
|
|
', |
71
|
|
|
['import_classes' => true, 'import_constants' => true, 'import_functions' => true] |
72
|
|
|
), |
73
|
|
|
new CodeSample( |
74
|
|
|
'<?php |
75
|
|
|
|
76
|
|
|
namespace Foo; |
77
|
|
|
|
78
|
|
|
use DateTimeImmutable; |
79
|
|
|
use function count; |
80
|
|
|
use const M_PI; |
81
|
|
|
|
82
|
|
|
if (count($x)) { |
83
|
|
|
/** @var DateTimeImmutable $d */ |
84
|
|
|
$d = new DateTimeImmutable(); |
85
|
|
|
$p = M_PI; |
86
|
|
|
} |
87
|
|
|
', |
88
|
|
|
['import_classes' => false, 'import_constants' => false, 'import_functions' => false] |
89
|
|
|
), |
90
|
|
|
] |
91
|
|
|
); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* {@inheritdoc} |
96
|
|
|
* |
97
|
|
|
* Must run before NoUnusedImportsFixer, OrderedImportsFixer. |
98
|
|
|
* Must run after NativeConstantInvocationFixer, NativeFunctionInvocationFixer. |
99
|
|
|
*/ |
100
|
|
|
public function getPriority(): int |
101
|
|
|
{ |
102
|
|
|
return 0; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* {@inheritdoc} |
107
|
|
|
*/ |
108
|
|
|
public function isCandidate(Tokens $tokens): bool |
109
|
|
|
{ |
110
|
|
|
return $tokens->isAnyTokenKindsFound([T_DOC_COMMENT, T_NS_SEPARATOR, T_USE]) |
111
|
|
|
&& $tokens->isTokenKindFound(T_NAMESPACE) |
112
|
|
|
&& 1 === $tokens->countTokenKind(T_NAMESPACE) |
113
|
|
|
&& $tokens->isMonolithicPhp(); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* {@inheritdoc} |
118
|
|
|
*/ |
119
|
|
|
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void |
120
|
|
|
{ |
121
|
|
|
$namespaceAnalyses = (new NamespacesAnalyzer())->getDeclarations($tokens); |
122
|
|
|
|
123
|
|
|
if (1 !== \count($namespaceAnalyses) || '' === $namespaceAnalyses[0]->getFullName()) { |
124
|
|
|
return; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
$useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); |
128
|
|
|
|
129
|
|
|
$newImports = []; |
130
|
|
|
|
131
|
|
|
if (true === $this->configuration['import_constants']) { |
132
|
|
|
$newImports['const'] = $this->importConstants($tokens, $useDeclarations); |
133
|
|
|
} elseif (false === $this->configuration['import_constants']) { |
134
|
|
|
$this->fullyQualifyConstants($tokens, $useDeclarations); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
if (true === $this->configuration['import_functions']) { |
138
|
|
|
$newImports['function'] = $this->importFunctions($tokens, $useDeclarations); |
139
|
|
|
} elseif (false === $this->configuration['import_functions']) { |
140
|
|
|
$this->fullyQualifyFunctions($tokens, $useDeclarations); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
if (true === $this->configuration['import_classes']) { |
144
|
|
|
$newImports['class'] = $this->importClasses($tokens, $useDeclarations); |
145
|
|
|
} elseif (false === $this->configuration['import_classes']) { |
146
|
|
|
$this->fullyQualifyClasses($tokens, $useDeclarations); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
$newImports = array_filter($newImports); |
150
|
|
|
|
151
|
|
|
if ($newImports) { |
|
|
|
|
152
|
|
|
$this->insertImports($tokens, $newImports, $useDeclarations); |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface |
157
|
|
|
{ |
158
|
|
|
return new FixerConfigurationResolver([ |
159
|
|
|
(new FixerOptionBuilder('import_constants', 'Whether to import, not import or ignore global constants.')) |
160
|
|
|
->setDefault(null) |
161
|
|
|
->setAllowedValues([true, false, null]) |
162
|
|
|
->getOption(), |
163
|
|
|
(new FixerOptionBuilder('import_functions', 'Whether to import, not import or ignore global functions.')) |
164
|
|
|
->setDefault(null) |
165
|
|
|
->setAllowedValues([true, false, null]) |
166
|
|
|
->getOption(), |
167
|
|
|
(new FixerOptionBuilder('import_classes', 'Whether to import, not import or ignore global classes.')) |
168
|
|
|
->setDefault(true) |
169
|
|
|
->setAllowedValues([true, false, null]) |
170
|
|
|
->getOption(), |
171
|
|
|
]); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* @param NamespaceUseAnalysis[] $useDeclarations |
176
|
|
|
*/ |
177
|
|
|
private function importConstants(Tokens $tokens, array $useDeclarations): array |
178
|
|
|
{ |
179
|
|
|
[$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
180
|
|
|
return $declaration->isConstant(); |
181
|
|
|
}, true); |
182
|
|
|
|
183
|
|
|
// find namespaced const declarations (`const FOO = 1`) |
184
|
|
|
// and add them to the not importable names (already used) |
185
|
|
|
for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { |
186
|
|
|
$token = $tokens[$index]; |
187
|
|
|
|
188
|
|
|
if ($token->isClassy()) { |
189
|
|
|
$index = $tokens->getNextTokenOfKind($index, ['{']); |
190
|
|
|
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); |
|
|
|
|
191
|
|
|
|
192
|
|
|
continue; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
if (!$token->isGivenKind(T_CONST)) { |
196
|
|
|
continue; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
$index = $tokens->getNextMeaningfulToken($index); |
200
|
|
|
$other[$tokens[$index]->getContent()] = true; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
$analyzer = new TokensAnalyzer($tokens); |
204
|
|
|
|
205
|
|
|
$indexes = []; |
206
|
|
|
|
207
|
|
|
for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
208
|
|
|
$token = $tokens[$index]; |
209
|
|
|
|
210
|
|
|
if (!$token->isGivenKind(T_STRING)) { |
211
|
|
|
continue; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
$name = $token->getContent(); |
215
|
|
|
|
216
|
|
|
if (isset($other[$name])) { |
217
|
|
|
continue; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
if (!$analyzer->isConstantInvocation($index)) { |
221
|
|
|
continue; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
$nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index); |
225
|
|
|
if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) { |
226
|
|
|
if (!isset($global[$name])) { |
227
|
|
|
// found an unqualified constant invocation |
228
|
|
|
// add it to the not importable names (already used) |
229
|
|
|
$other[$name] = true; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
continue; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
$prevIndex = $tokens->getPrevMeaningfulToken($nsSeparatorIndex); |
|
|
|
|
236
|
|
|
if ($tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_STRING])) { |
237
|
|
|
continue; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
$indexes[] = $index; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
return $this->prepareImports($tokens, $indexes, $global, $other, true); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @param NamespaceUseAnalysis[] $useDeclarations |
248
|
|
|
*/ |
249
|
|
|
private function importFunctions(Tokens $tokens, array $useDeclarations): array |
250
|
|
|
{ |
251
|
|
|
[$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
252
|
|
|
return $declaration->isFunction(); |
253
|
|
|
}, false); |
254
|
|
|
|
255
|
|
|
// find function declarations |
256
|
|
|
// and add them to the not importable names (already used) |
257
|
|
|
foreach ($this->findFunctionDeclarations($tokens, 0, $tokens->count() - 1) as $name) { |
258
|
|
|
$other[strtolower($name)] = true; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
$analyzer = new FunctionsAnalyzer(); |
262
|
|
|
|
263
|
|
|
$indexes = []; |
264
|
|
|
|
265
|
|
|
for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
266
|
|
|
$token = $tokens[$index]; |
267
|
|
|
|
268
|
|
|
if (!$token->isGivenKind(T_STRING)) { |
269
|
|
|
continue; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
$name = strtolower($token->getContent()); |
273
|
|
|
|
274
|
|
|
if (isset($other[$name])) { |
275
|
|
|
continue; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
if (!$analyzer->isGlobalFunctionCall($tokens, $index)) { |
279
|
|
|
continue; |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
$nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index); |
283
|
|
|
if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) { |
284
|
|
|
if (!isset($global[$name])) { |
285
|
|
|
$other[$name] = true; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
continue; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
$indexes[] = $index; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
return $this->prepareImports($tokens, $indexes, $global, $other, false); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* @param NamespaceUseAnalysis[] $useDeclarations |
299
|
|
|
*/ |
300
|
|
|
private function importClasses(Tokens $tokens, array $useDeclarations): array |
301
|
|
|
{ |
302
|
|
|
[$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
303
|
|
|
return $declaration->isClass(); |
304
|
|
|
}, false); |
305
|
|
|
|
306
|
|
|
/** @var DocBlock[] $docBlocks */ |
307
|
|
|
$docBlocks = []; |
308
|
|
|
|
309
|
|
|
// find class declarations and class usages in docblocks |
310
|
|
|
// and add them to the not importable names (already used) |
311
|
|
|
for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { |
312
|
|
|
$token = $tokens[$index]; |
313
|
|
|
|
314
|
|
|
if ($token->isGivenKind(T_DOC_COMMENT)) { |
315
|
|
|
$docBlocks[$index] = new DocBlock($token->getContent()); |
316
|
|
|
|
317
|
|
|
$this->traverseDocBlockTypes($docBlocks[$index], static function (string $type) use ($global, &$other): void { |
318
|
|
|
if (false !== strpos($type, '\\')) { |
319
|
|
|
return; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
$name = strtolower($type); |
323
|
|
|
|
324
|
|
|
if (!isset($global[$name])) { |
325
|
|
|
$other[$name] = true; |
326
|
|
|
} |
327
|
|
|
}); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
if (!$token->isClassy()) { |
331
|
|
|
continue; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
$index = $tokens->getNextMeaningfulToken($index); |
335
|
|
|
|
336
|
|
|
if ($tokens[$index]->isGivenKind(T_STRING)) { |
337
|
|
|
$other[strtolower($tokens[$index]->getContent())] = true; |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
$analyzer = new ClassyAnalyzer(); |
342
|
|
|
|
343
|
|
|
$indexes = []; |
344
|
|
|
|
345
|
|
|
for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
346
|
|
|
$token = $tokens[$index]; |
347
|
|
|
|
348
|
|
|
if (!$token->isGivenKind(T_STRING)) { |
349
|
|
|
continue; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
$name = strtolower($token->getContent()); |
353
|
|
|
|
354
|
|
|
if (isset($other[$name])) { |
355
|
|
|
continue; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
if (!$analyzer->isClassyInvocation($tokens, $index)) { |
359
|
|
|
continue; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
$nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index); |
363
|
|
|
if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) { |
364
|
|
|
if (!isset($global[$name])) { |
365
|
|
|
$other[$name] = true; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
continue; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
if ($tokens[$tokens->getPrevMeaningfulToken($nsSeparatorIndex)]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_STRING])) { |
|
|
|
|
372
|
|
|
continue; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
$indexes[] = $index; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
$imports = []; |
379
|
|
|
|
380
|
|
|
foreach ($docBlocks as $index => $docBlock) { |
381
|
|
|
$changed = $this->traverseDocBlockTypes($docBlock, static function (string $type) use ($global, $other, &$imports) { |
382
|
|
|
if ('\\' !== $type[0]) { |
383
|
|
|
return $type; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
$name = substr($type, 1); |
387
|
|
|
$checkName = strtolower($name); |
388
|
|
|
|
389
|
|
|
if (false !== strpos($checkName, '\\') || isset($other[$checkName])) { |
390
|
|
|
return $type; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
if (isset($global[$checkName])) { |
394
|
|
|
return \is_string($global[$checkName]) ? $global[$checkName] : $name; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
$imports[$checkName] = $name; |
398
|
|
|
|
399
|
|
|
return $name; |
400
|
|
|
}); |
401
|
|
|
|
402
|
|
|
if ($changed) { |
403
|
|
|
$tokens[$index] = new Token([T_DOC_COMMENT, $docBlock->getContent()]); |
404
|
|
|
} |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
return $imports + $this->prepareImports($tokens, $indexes, $global, $other, false); |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Removes the leading slash at the given indexes (when the name is not already used). |
412
|
|
|
* |
413
|
|
|
* @param int[] $indexes |
414
|
|
|
* |
415
|
|
|
* @return array array keys contain the names that must be imported |
416
|
|
|
*/ |
417
|
|
|
private function prepareImports(Tokens $tokens, array $indexes, array $global, array $other, bool $caseSensitive): array |
418
|
|
|
{ |
419
|
|
|
$imports = []; |
420
|
|
|
|
421
|
|
|
foreach ($indexes as $index) { |
422
|
|
|
$name = $tokens[$index]->getContent(); |
423
|
|
|
$checkName = $caseSensitive ? $name : strtolower($name); |
424
|
|
|
|
425
|
|
|
if (isset($other[$checkName])) { |
426
|
|
|
continue; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
if (!isset($global[$checkName])) { |
430
|
|
|
$imports[$checkName] = $name; |
431
|
|
|
} elseif (\is_string($global[$checkName])) { |
432
|
|
|
$tokens[$index] = new Token([T_STRING, $global[$checkName]]); |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
$tokens->clearAt($tokens->getPrevMeaningfulToken($index)); |
|
|
|
|
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
return $imports; |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* @param NamespaceUseAnalysis[] $useDeclarations |
443
|
|
|
*/ |
444
|
|
|
private function insertImports(Tokens $tokens, array $imports, array $useDeclarations): void |
445
|
|
|
{ |
446
|
|
|
if ($useDeclarations) { |
|
|
|
|
447
|
|
|
$useDeclaration = end($useDeclarations); |
448
|
|
|
$index = $useDeclaration->getEndIndex() + 1; |
449
|
|
|
} else { |
450
|
|
|
$namespace = (new NamespacesAnalyzer())->getDeclarations($tokens)[0]; |
451
|
|
|
$index = $namespace->getEndIndex() + 1; |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
$lineEnding = $this->whitespacesConfig->getLineEnding(); |
455
|
|
|
|
456
|
|
|
if (!$tokens[$index]->isWhitespace() || false === strpos($tokens[$index]->getContent(), "\n")) { |
457
|
|
|
$tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding])); |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
foreach ($imports as $type => $typeImports) { |
461
|
|
|
foreach ($typeImports as $name) { |
462
|
|
|
$items = [ |
463
|
|
|
new Token([T_WHITESPACE, $lineEnding]), |
464
|
|
|
new Token([T_USE, 'use']), |
465
|
|
|
new Token([T_WHITESPACE, ' ']), |
466
|
|
|
]; |
467
|
|
|
|
468
|
|
|
if ('const' === $type) { |
469
|
|
|
$items[] = new Token([CT::T_CONST_IMPORT, 'const']); |
470
|
|
|
$items[] = new Token([T_WHITESPACE, ' ']); |
471
|
|
|
} elseif ('function' === $type) { |
472
|
|
|
$items[] = new Token([CT::T_FUNCTION_IMPORT, 'function']); |
473
|
|
|
$items[] = new Token([T_WHITESPACE, ' ']); |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
$items[] = new Token([T_STRING, $name]); |
477
|
|
|
$items[] = new Token(';'); |
478
|
|
|
|
479
|
|
|
$tokens->insertAt($index, $items); |
480
|
|
|
} |
481
|
|
|
} |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* @param NamespaceUseAnalysis[] $useDeclarations |
486
|
|
|
*/ |
487
|
|
|
private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations): void |
488
|
|
|
{ |
489
|
|
|
if (!$tokens->isTokenKindFound(CT::T_CONST_IMPORT)) { |
490
|
|
|
return; |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
[$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
494
|
|
|
return $declaration->isConstant() && !$declaration->isAliased(); |
495
|
|
|
}, true); |
496
|
|
|
|
497
|
|
|
if (!$global) { |
498
|
|
|
return; |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
$analyzer = new TokensAnalyzer($tokens); |
502
|
|
|
|
503
|
|
|
for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
504
|
|
|
$token = $tokens[$index]; |
505
|
|
|
|
506
|
|
|
if (!$token->isGivenKind(T_STRING)) { |
507
|
|
|
continue; |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
if (!isset($global[$token->getContent()])) { |
511
|
|
|
continue; |
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { |
515
|
|
|
continue; |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
if (!$analyzer->isConstantInvocation($index)) { |
519
|
|
|
continue; |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
$tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); |
523
|
|
|
} |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
/** |
527
|
|
|
* @param NamespaceUseAnalysis[] $useDeclarations |
528
|
|
|
*/ |
529
|
|
|
private function fullyQualifyFunctions(Tokens $tokens, array $useDeclarations): void |
530
|
|
|
{ |
531
|
|
|
if (!$tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) { |
532
|
|
|
return; |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
[$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
536
|
|
|
return $declaration->isFunction() && !$declaration->isAliased(); |
537
|
|
|
}, false); |
538
|
|
|
|
539
|
|
|
if (!$global) { |
540
|
|
|
return; |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
$analyzer = new FunctionsAnalyzer(); |
544
|
|
|
|
545
|
|
|
for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
546
|
|
|
$token = $tokens[$index]; |
547
|
|
|
|
548
|
|
|
if (!$token->isGivenKind(T_STRING)) { |
549
|
|
|
continue; |
550
|
|
|
} |
551
|
|
|
|
552
|
|
|
if (!isset($global[strtolower($token->getContent())])) { |
553
|
|
|
continue; |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { |
557
|
|
|
continue; |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
if (!$analyzer->isGlobalFunctionCall($tokens, $index)) { |
561
|
|
|
continue; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
$tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); |
565
|
|
|
} |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
/** |
569
|
|
|
* @param NamespaceUseAnalysis[] $useDeclarations |
570
|
|
|
*/ |
571
|
|
|
private function fullyQualifyClasses(Tokens $tokens, array $useDeclarations): void |
572
|
|
|
{ |
573
|
|
|
if (!$tokens->isTokenKindFound(T_USE)) { |
574
|
|
|
return; |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
[$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
578
|
|
|
return $declaration->isClass() && !$declaration->isAliased(); |
579
|
|
|
}, false); |
580
|
|
|
|
581
|
|
|
if (!$global) { |
582
|
|
|
return; |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
$analyzer = new ClassyAnalyzer(); |
586
|
|
|
|
587
|
|
|
for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
588
|
|
|
$token = $tokens[$index]; |
589
|
|
|
|
590
|
|
|
if ($token->isGivenKind(T_DOC_COMMENT)) { |
591
|
|
|
$doc = new DocBlock($token->getContent()); |
592
|
|
|
|
593
|
|
|
$changed = $this->traverseDocBlockTypes($doc, static function (string $type) use ($global) { |
594
|
|
|
if (!isset($global[strtolower($type)])) { |
595
|
|
|
return $type; |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
return '\\'.$type; |
599
|
|
|
}); |
600
|
|
|
|
601
|
|
|
if ($changed) { |
602
|
|
|
$tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
continue; |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
if (!$token->isGivenKind(T_STRING)) { |
609
|
|
|
continue; |
610
|
|
|
} |
611
|
|
|
|
612
|
|
|
if (!isset($global[strtolower($token->getContent())])) { |
613
|
|
|
continue; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { |
617
|
|
|
continue; |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
if (!$analyzer->isClassyInvocation($tokens, $index)) { |
621
|
|
|
continue; |
622
|
|
|
} |
623
|
|
|
|
624
|
|
|
$tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); |
625
|
|
|
} |
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
/** |
629
|
|
|
* @param NamespaceUseAnalysis[] $declarations |
630
|
|
|
*/ |
631
|
|
|
private function filterUseDeclarations(array $declarations, callable $callback, bool $caseSensitive): array |
632
|
|
|
{ |
633
|
|
|
$global = []; |
634
|
|
|
$other = []; |
635
|
|
|
|
636
|
|
|
foreach ($declarations as $declaration) { |
637
|
|
|
if (!$callback($declaration)) { |
638
|
|
|
continue; |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
$fullName = ltrim($declaration->getFullName(), '\\'); |
642
|
|
|
|
643
|
|
|
if (false !== strpos($fullName, '\\')) { |
644
|
|
|
$name = $caseSensitive ? $declaration->getShortName() : strtolower($declaration->getShortName()); |
645
|
|
|
$other[$name] = true; |
646
|
|
|
|
647
|
|
|
continue; |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
$checkName = $caseSensitive ? $fullName : strtolower($fullName); |
651
|
|
|
$alias = $declaration->getShortName(); |
652
|
|
|
$global[$checkName] = $alias === $fullName ? true : $alias; |
653
|
|
|
} |
654
|
|
|
|
655
|
|
|
return [$global, $other]; |
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
private function findFunctionDeclarations(Tokens $tokens, int $start, int $end): iterable |
659
|
|
|
{ |
660
|
|
|
for ($index = $start; $index <= $end; ++$index) { |
661
|
|
|
$token = $tokens[$index]; |
662
|
|
|
|
663
|
|
|
if ($token->isClassy()) { |
664
|
|
|
$classStart = $tokens->getNextTokenOfKind($index, ['{']); |
665
|
|
|
$classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); |
|
|
|
|
666
|
|
|
|
667
|
|
|
for ($index = $classStart; $index <= $classEnd; ++$index) { |
668
|
|
|
if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { |
669
|
|
|
continue; |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
$methodStart = $tokens->getNextTokenOfKind($index, ['{', ';']); |
|
|
|
|
673
|
|
|
|
674
|
|
|
if ($tokens[$methodStart]->equals(';')) { |
675
|
|
|
$index = $methodStart; |
676
|
|
|
|
677
|
|
|
continue; |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
$methodEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodStart); |
681
|
|
|
|
682
|
|
|
foreach ($this->findFunctionDeclarations($tokens, $methodStart, $methodEnd) as $function) { |
|
|
|
|
683
|
|
|
yield $function; |
684
|
|
|
} |
685
|
|
|
|
686
|
|
|
$index = $methodEnd; |
687
|
|
|
} |
688
|
|
|
|
689
|
|
|
continue; |
690
|
|
|
} |
691
|
|
|
|
692
|
|
|
if (!$token->isGivenKind(T_FUNCTION)) { |
693
|
|
|
continue; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
$index = $tokens->getNextMeaningfulToken($index); |
697
|
|
|
|
698
|
|
|
if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) { |
699
|
|
|
$index = $tokens->getNextMeaningfulToken($index); |
|
|
|
|
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
if ($tokens[$index]->isGivenKind(T_STRING)) { |
703
|
|
|
yield $tokens[$index]->getContent(); |
704
|
|
|
} |
705
|
|
|
} |
706
|
|
|
} |
707
|
|
|
|
708
|
|
|
private function traverseDocBlockTypes(DocBlock $doc, callable $callback): bool |
709
|
|
|
{ |
710
|
|
|
$annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes()); |
711
|
|
|
|
712
|
|
|
if (!$annotations) { |
|
|
|
|
713
|
|
|
return false; |
714
|
|
|
} |
715
|
|
|
|
716
|
|
|
$changed = false; |
717
|
|
|
|
718
|
|
|
foreach ($annotations as $annotation) { |
719
|
|
|
$types = $new = $annotation->getTypes(); |
720
|
|
|
|
721
|
|
|
foreach ($types as $i => $fullType) { |
722
|
|
|
$newFullType = $fullType; |
723
|
|
|
|
724
|
|
|
Preg::matchAll('/[\\\\\w]+/', $fullType, $matches, PREG_OFFSET_CAPTURE); |
725
|
|
|
|
726
|
|
|
foreach (array_reverse($matches[0]) as [$type, $offset]) { |
727
|
|
|
$newType = $callback($type); |
728
|
|
|
|
729
|
|
|
if (null !== $newType && $type !== $newType) { |
730
|
|
|
$newFullType = substr_replace($newFullType, $newType, $offset, \strlen($type)); |
731
|
|
|
} |
732
|
|
|
} |
733
|
|
|
|
734
|
|
|
$new[$i] = $newFullType; |
735
|
|
|
} |
736
|
|
|
|
737
|
|
|
if ($types !== $new) { |
738
|
|
|
$annotation->setTypes($new); |
739
|
|
|
$changed = true; |
740
|
|
|
} |
741
|
|
|
} |
742
|
|
|
|
743
|
|
|
return $changed; |
744
|
|
|
} |
745
|
|
|
} |
746
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.