Failed Conditions
Pull Request — master (#91)
by Matthias
04:28 queued 13s
created

CheckCommand::getCheckOptions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 4
cts 5
cp 0.8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2.032
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\DependencyGuesser\GuessFromComposerAutoloader;
11
use ComposerRequireChecker\FileLocator\LocateComposerPackageDirectDependenciesSourceFiles;
12
use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles;
13
use ComposerRequireChecker\FileLocator\LocateFilesByGlobPattern;
14
use ComposerRequireChecker\GeneratorUtil\ComposeGenerators;
15
use ComposerRequireChecker\JsonLoader;
16
use ComposerRequireChecker\UsedSymbolsLocator\LocateUsedSymbolsFromASTRoots;
17
use PhpParser\ErrorHandler\Collecting as CollectingErrorHandler;
18
use PhpParser\ParserFactory;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Helper\Table;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
26
class CheckCommand extends Command
27
{
28 3
    protected function configure()
29
    {
30
        $this
31 3
            ->setName('check')
32 3
            ->setDescription('check the defined dependencies against your code')
33 3
            ->addOption(
34 3
                'config-file',
35 3
                null,
36 3
                InputOption::VALUE_REQUIRED,
37 3
                'the config.json file to configure the checking options'
38
            )
39 3
            ->addArgument(
40 3
                'composer-json',
41 3
                InputArgument::OPTIONAL,
42 3
                'the composer.json of your package, that should be checked',
43 3
                './composer.json'
44
            )
45 3
        ->addOption(
46 3
                'ignore-parse-errors',
47 3
                null,
48 3
                InputOption::VALUE_NONE,
49
                'this will cause ComposerRequireChecker to ignore errors when files cannot be parsed, otherwise'
50 3
                . ' errors will be thrown'
51
            );
52 3
    }
53
54 2
    protected function execute(InputInterface $input, OutputInterface $output): int
55
    {
56 2
        if (!$output->isQuiet()) {
57 2
            $output->writeln($this->getApplication()->getLongVersion());
58
        }
59
60 2
        $composerJson = realpath($input->getArgument('composer-json'));
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument('composer-json') can also be of type string[]; however, parameter $path of realpath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

60
        $composerJson = realpath(/** @scrutinizer ignore-type */ $input->getArgument('composer-json'));
Loading history...
61 2
        if (false === $composerJson) {
62 1
            throw new \InvalidArgumentException('file not found: [' . $input->getArgument('composer-json') . ']');
0 ignored issues
show
Bug introduced by
Are you sure $input->getArgument('composer-json') of type null|string|string[] can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
            throw new \InvalidArgumentException('file not found: [' . /** @scrutinizer ignore-type */ $input->getArgument('composer-json') . ']');
Loading history...
63
        }
64 1
        $this->checkJsonFile($composerJson);
65
66 1
        $options = $this->getCheckOptions($input);
67
68 1
        $getPackageSourceFiles = new LocateComposerPackageSourceFiles();
69 1
        $getAdditionalSourceFiles = new LocateFilesByGlobPattern();
70
71 1
        $sourcesASTs = $this->getASTFromFilesLocator($input);
72
73 1
        $definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
74 1
            (new ComposeGenerators())->__invoke(
75 1
                $getAdditionalSourceFiles($options->getScanFiles(), dirname($composerJson)),
76 1
                $getPackageSourceFiles($composerJson),
77 1
                (new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson)
78
            )
79
        ));
80
81 1
        $definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
82 1
            (new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
83
        );
84
85 1
        $usedSymbols = (new LocateUsedSymbolsFromASTRoots())->__invoke(
86 1
            (new ComposeGenerators())->__invoke(
87 1
                $sourcesASTs($getPackageSourceFiles($composerJson)),
88 1
                $getAdditionalSourceFiles($options->getScanFiles(), dirname($composerJson))
89
            )
90
        );
91
92 1
        if (!count($usedSymbols)) {
93
            throw new \LogicException('There were no symbols found, please check your configuration.');
94
        }
95
96 1
        $unknownSymbols = array_diff(
97 1
            $usedSymbols,
98 1
            $definedVendorSymbols,
99 1
            $definedExtensionSymbols,
100 1
            $options->getSymbolWhitelist()
101
        );
102
103 1
        if (!$unknownSymbols) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $unknownSymbols of type array 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...
104 1
            $output->writeln("There were no unknown symbols found.");
105 1
            return 0;
106
        }
107
108
        $output->writeln("The following unknown symbols were found:");
109
        $table = new Table($output);
110
        $table->setHeaders(['unknown symbol', 'guessed dependency']);
111
        $guesser = new DependencyGuesser();
112
        $guesser->addGuesser(new GuessFromComposerAutoloader($composerJson));
113
        foreach ($unknownSymbols as $unknownSymbol) {
114
            $guessedDependencies = [];
115
            foreach ($guesser($unknownSymbol) as $guessedDependency) {
116
                $guessedDependencies[] = $guessedDependency;
117
            }
118
            $table->addRow([$unknownSymbol, implode("\n", $guessedDependencies)]);
119
        }
120
        $table->render();
121
122
        return ((int)(bool)$unknownSymbols);
123
    }
124
125 1
    private function getCheckOptions(InputInterface $input): Options
126
    {
127 1
        $fileName = $input->getOption('config-file');
128 1
        if (!$fileName) {
129 1
            return new Options();
130
        }
131
        return new Options((new JsonLoader($fileName))->getData());
132
    }
133
134
    /**
135
     * @param string $jsonFile
136
     * @throws \ComposerRequireChecker\Exception\InvalidJsonException
137
     * @throws \ComposerRequireChecker\Exception\NotReadableException
138
     * @internal param string $composerJson the path to composer.json
139
     */
140 1
    private function checkJsonFile(string $jsonFile)
141
    {
142
        // JsonLoader throws an exception if it cannot load the file
143 1
        new JsonLoader($jsonFile);
144 1
    }
145
146
    /**
147
     * @param InputInterface $input
148
     * @return LocateASTFromFiles
149
     */
150 1
    private function getASTFromFilesLocator(InputInterface $input): LocateASTFromFiles
151
    {
152 1
        $errorHandler = $input->getOption('ignore-parse-errors') ? new CollectingErrorHandler() : null;
153 1
        $sourcesASTs = new LocateASTFromFiles((new ParserFactory())->create(ParserFactory::PREFER_PHP7), $errorHandler);
154 1
        return $sourcesASTs;
155
    }
156
157
}
158