Failed Conditions
Pull Request — master (#38)
by Matthias
03:33
created

CheckCommand::checkJsonFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
ccs 0
cts 0
cp 0
rs 9.4285
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
namespace ComposerRequireChecker\Cli;
4
5
use ComposerRequireChecker\ASTLocator\LocateASTFromFiles;
6
use ComposerRequireChecker\DefinedExtensionsResolver\DefinedExtensionsResolver;
7
use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromASTRoots;
8
use ComposerRequireChecker\DefinedSymbolsLocator\LocateDefinedSymbolsFromExtensions;
9
use ComposerRequireChecker\DependencyGuesser\DependencyGuesser;
10
use ComposerRequireChecker\FileLocator\LocateComposerPackageDirectDependenciesSourceFiles;
11
use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles;
12
use ComposerRequireChecker\GeneratorUtil\ComposeGenerators;
13
use ComposerRequireChecker\JsonLoader;
14
use ComposerRequireChecker\UsedSymbolsLocator\LocateUsedSymbolsFromASTRoots;
15
use PhpParser\ErrorHandler\Collecting as CollectingErrorHandler;
16
use PhpParser\ParserFactory;
17
use Symfony\Component\Console\Command\Command;
18
use Symfony\Component\Console\Helper\Table;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
24
class CheckCommand extends Command
25 1
{
26
    protected function configure()
27
    {
28 1
        $this
29 1
            ->setName('check')
30 1
            ->setDescription('check the defined dependencies against your code')
31 1
            ->addOption(
32 1
                'config-file',
33 1
                null,
34 1
                InputOption::VALUE_REQUIRED,
35
                'the config.json file to configure the checking options'
36 1
            )
37 1
            ->addArgument(
38 1
                'composer-json',
39 1
                InputArgument::OPTIONAL,
40 1
                'the composer.json of your package, that should be checked',
41
                './composer.json'
42
            )
43 1
            ->addOption(
44
                'fail-on-parse-error',
45
                null,
46
                InputOption::VALUE_NONE,
47
                'this will cause ComposerRequireChecker to fail when files cannot be parsed, otherwise'
48
                . ' parse errors will be ignored'
49
            )
50
        ;
51
    }
52
53
    protected function execute(InputInterface $input, OutputInterface $output) : int
54
    {
55
56
        if(!$output->isQuiet()) {
57
            $output->writeln($this->getApplication()->getLongVersion());
58
        }
59
60
        $composerJson = realpath($input->getArgument('composer-json'));
61
        if(false === $composerJson) {
62
            throw new \InvalidArgumentException('file not found: [' . $input->getArgument('composer-json') . ']');
63
        }
64
        $this->checkJsonFile($composerJson);
65
66
        $options = $this->getCheckOptions($input);
67
68
        $getPackageSourceFiles = new LocateComposerPackageSourceFiles();
69
70
        $errorHandler = $input->getOption('fail-on-parse-error') ? null : new CollectingErrorHandler();
71
        $sourcesASTs = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7), $errorHandler);
72
73
        $definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
74
            (new ComposeGenerators())->__invoke(
75
                $getPackageSourceFiles($composerJson),
76
                (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson)
77
            )
78
        ));
79
80
        $definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
81
            (new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
82
        );
83
84
        $usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke($sourcesASTs($getPackageSourceFiles($composerJson)));
85
86
        if (!count($usedSymbols)) {
87
            throw new \LogicException('There were no symbols found, please check your configuration.');
88
        }
89
90
        $unknownSymbols = array_diff(
91
            $usedSymbols,
92
            $definedVendorSymbols,
93
            $definedExtensionSymbols,
94
            $options->getSymbolWhitelist()
95
        );
96
97
        if (!$unknownSymbols) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $unknownSymbols of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
98
            $output->writeln("There were no unknown symbols found.");
99
            return 0;
100
        }
101
102
        $output->writeln("The following unknown symbols were found:");
103
        $table = new Table($output);
104
        $table->setHeaders(['unknown symbol', 'guessed dependency']);
105
        $guesser = new DependencyGuesser();
106
        foreach ($unknownSymbols as $unknownSymbol) {
107
            $guessedDependencies = [];
108
            foreach($guesser($unknownSymbol) as $guessedDependency) {
109
                $guessedDependencies[] = $guessedDependency;
110
            }
111
            $table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]);
112
        }
113
        $table->render();
114
115
        return ((int) (bool) $unknownSymbols);
116
    }
117
118
    private function getCheckOptions(InputInterface $input) : Options
119
    {
120
        $fileName = $input->getOption('config-file');
121
        if(!$fileName) {
122
            return new Options();
123
        }
124
        return new Options((new JsonLoader($fileName))->getData());
125
    }
126
127
    /**
128
     * @param string $jsonFile
129
     * @throws \ComposerRequireChecker\Exception\InvalidJsonException
130
     * @throws \ComposerRequireChecker\Exception\NotReadableException
131
     * @internal param string $composerJson the path to composer.json
132
     */
133
    private function checkJsonFile(string $jsonFile)
134
    {
135
        // JsonLoader throws an exception if it cannot load the file
136
        new JsonLoader($jsonFile);
137
    }
138
139
}
140