1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by PhpStorm. |
4
|
|
|
* User: matthias |
5
|
|
|
* Date: 28.11.15 |
6
|
|
|
* Time: 16:16 |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace ComposerRequireChecker\Cli; |
10
|
|
|
|
11
|
|
|
use ComposerRequireChecker\ASTLocator\LocateASTFromFiles; |
12
|
|
|
use ComposerRequireChecker\DefinedExtensionsResolver\DefinedExtensionsResolver; |
13
|
|
|
use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromASTRoots; |
14
|
|
|
use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromExtensions; |
15
|
|
|
use ComposerRequireChecker\DependencyGuesser\DependencyGuesser; |
16
|
|
|
use ComposerRequireChecker\Exception\InvalidInputFileException; |
17
|
|
|
use ComposerRequireChecker\FileLocator\LocateComposerPackageDirectDependenciesSourceFiles; |
18
|
|
|
use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles; |
19
|
|
|
use ComposerRequireChecker\GeneratorUtil\ComposeGenerators; |
20
|
|
|
use ComposerRequireChecker\UsedSymbolsLocator\LocateUsedSymbolsFromASTRoots; |
21
|
|
|
use PhpParser\ParserFactory; |
22
|
|
|
use Symfony\Component\Console\Command\Command; |
23
|
|
|
use Symfony\Component\Console\Helper\Table; |
24
|
|
|
use Symfony\Component\Console\Helper\TableCell; |
25
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
26
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
27
|
|
|
use Symfony\Component\Console\Input\InputOption; |
28
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
29
|
|
|
|
30
|
|
|
class CheckCommand extends Command |
31
|
|
|
{ |
32
|
|
|
protected function configure() |
33
|
|
|
{ |
34
|
|
|
$this |
35
|
|
|
->setName('check') |
36
|
|
|
->setDescription('check the defined dependencies against your code') |
37
|
|
|
->addOption( |
38
|
|
|
'config-file', |
39
|
|
|
null, |
40
|
|
|
InputOption::VALUE_REQUIRED, |
41
|
|
|
'the config.json file to configure the checking options' |
42
|
|
|
) |
43
|
|
|
->addArgument( |
44
|
|
|
'composer-json', |
45
|
|
|
InputArgument::OPTIONAL, |
46
|
|
|
'the composer.json of your package, that should be checked', |
47
|
|
|
'./composer.json' |
48
|
|
|
) |
49
|
|
|
; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) : int |
53
|
|
|
{ |
54
|
|
|
|
55
|
|
|
if(!$output->isQuiet()) { |
56
|
|
|
$output->writeln($this->getApplication()->getLongVersion()); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
$composerJson = $input->getArgument('composer-json'); |
60
|
|
|
$this->checkJsonFile($composerJson); |
61
|
|
|
|
62
|
|
|
$getPackageSourceFiles = new LocateComposerPackageSourceFiles(); |
63
|
|
|
|
64
|
|
|
$sourcesASTs = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7)); |
65
|
|
|
|
66
|
|
|
$definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs( |
67
|
|
|
(new ComposeGenerators())->__invoke( |
68
|
|
|
$getPackageSourceFiles($composerJson), |
69
|
|
|
(new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson) |
|
|
|
|
70
|
|
|
) |
71
|
|
|
)); |
72
|
|
|
|
73
|
|
|
$options = $this->getCheckOptions($input); |
74
|
|
|
|
75
|
|
|
$definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke( |
76
|
|
|
(new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions()) |
77
|
|
|
); |
78
|
|
|
|
79
|
|
|
$usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke($sourcesASTs($getPackageSourceFiles($composerJson))); |
80
|
|
|
|
81
|
|
|
$unknownSymbols = array_diff( |
82
|
|
|
$usedSymbols, |
83
|
|
|
$definedVendorSymbols, |
84
|
|
|
$definedExtensionSymbols, |
85
|
|
|
$options->getSymbolWhitelist() |
86
|
|
|
); |
87
|
|
|
|
88
|
|
|
if (!$unknownSymbols) { |
|
|
|
|
89
|
|
|
$output->writeln("There were no unknown symbols found."); |
90
|
|
|
return 0; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
$output->writeln("The following unknown symbols were found:"); |
94
|
|
|
$table = new Table($output); |
95
|
|
|
$table->setHeaders(['unknown symbol', 'guessed dependency']); |
96
|
|
|
$guesser = new DependencyGuesser(); |
97
|
|
|
foreach ($unknownSymbols as $unknownSymbol) { |
98
|
|
|
$guessedDependencies = []; |
99
|
|
|
foreach($guesser($unknownSymbol) as $guessedDependency) { |
100
|
|
|
$guessedDependencies[] = $guessedDependency; |
101
|
|
|
} |
102
|
|
|
$table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]); |
103
|
|
|
} |
104
|
|
|
$table->render(); |
105
|
|
|
|
106
|
|
|
return ((int) (bool) $unknownSymbols); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
private function getCheckOptions(InputInterface $input) : Options |
110
|
|
|
{ |
111
|
|
|
$fileName = $input->getOption('config-file'); |
112
|
|
|
if(!$fileName) { |
113
|
|
|
return new Options(); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
if(!is_readable($fileName)) { |
117
|
|
|
throw new \InvalidArgumentException('unable to read ' . $fileName); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
$jsonData = json_decode(file_get_contents($fileName), true); |
121
|
|
|
if(false === $jsonData) { |
122
|
|
|
throw new \Exception('error parsing the config file: ' . json_last_error_msg()); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
return new Options($jsonData); |
126
|
|
|
|
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @param string $jsonFile |
131
|
|
|
* @throws InvalidInputFileException |
132
|
|
|
* @internal param string $composerJson the path to composer.json |
133
|
|
|
*/ |
134
|
|
|
private function checkJsonFile(string $jsonFile) |
135
|
|
|
{ |
136
|
|
|
if(!is_readable($jsonFile)) { |
137
|
|
|
throw new InvalidInputFileException('cannot read ' . $jsonFile); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
if(false == json_decode(file_get_contents($jsonFile))) { |
141
|
|
|
throw new InvalidInputFileException('error parsing ' . $jsonFile . ': ' . json_last_error_msg()); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
} |
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.