Passed
Pull Request — master (#3)
by Jonathan
01:54
created

FindClassMethodUsages::buildClassMethodUsages()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2.004

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 1
dl 0
loc 16
ccs 9
cts 10
cp 0.9
crap 2.004
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UsageFinder;
6
7
use RuntimeException;
8
use Symfony\Component\Process\Exception\ProcessFailedException;
9
use Symfony\Component\Process\Process;
10
use UsageFinder\Exception\PsalmError;
11
use function array_filter;
12
use function array_map;
13
use function copy;
14
use function is_array;
15
use function json_decode;
16
use function realpath;
17
use function sprintf;
18
use function trim;
19
use function unlink;
20
21
final class FindClassMethodUsages
22
{
23
    /**
24
     * @return array<int, ClassMethodUsage>
25
     */
26 5
    public function __invoke(
27
        string $path,
28
        ClassMethodReference $classMethodReference,
29
        int $threads = 1
30
    ) : array {
31 5
        $this->copyFindClassMethodUsagesPlugin($path);
32
33 5
        $configFile = (new CreateTemporaryPsalmXmlFile())->__invoke($path);
34
35 5
        $rootDir = realpath(__DIR__ . '/..');
36
37 5
        $process = new Process([
38 5
            'vendor/bin/psalm',
39 5
            sprintf('--config=%s', $configFile),
40 5
            sprintf('--root=%s', $path),
41 5
            sprintf('--threads=%d', $threads),
42 5
            '--output-format=json',
43 5
            '--no-cache',
44 5
        ], $rootDir, [
45 5
            'USAGE_FINDER_NAME'        => $classMethodReference->getName(),
46 5
            'USAGE_FINDER_CLASS_NAME'  => $classMethodReference->getClassName(),
47 5
            'USAGE_FINDER_METHOD_NAME' => $classMethodReference->getMethodName(),
48
        ]);
49 5
        $process->setTimeout(0);
50
51
        try {
52 5
            $process->mustRun();
53 1
        } catch (ProcessFailedException $e) {
54 1
            $processOutput = $this->getPsalmProcessOutput($process);
55
56 1
            if ($processOutput === null) {
57
                throw new PsalmError($e->getMessage());
58
            }
59 5
        } finally {
60 5
            $this->cleanupFindClassMethodUsagesPlugin($path);
61
62 5
            unlink($configFile);
63
        }
64
65 5
        return $this->buildClassMethodUsages($process);
66
    }
67
68
    /**
69
     * @param array<int, array<string, mixed>> $results
70
     *
71
     * @return array<int, array<string, mixed>>
72
     */
73 5
    private function filterClassMethodUsages(array $results) : array
74
    {
75
        return array_filter($results, static function (array $result) : bool {
76 3
            return $result['type'] === 'ClassMethodUsageFound';
77 5
        });
78
    }
79
80
    /**
81
     * @return array<int, ClassMethodUsage>
82
     */
83 5
    private function buildClassMethodUsages(Process $process) : array
84
    {
85 5
        $processOutput = $this->getPsalmProcessOutput($process);
86
87 5
        if ($processOutput === null) {
88
            throw new RuntimeException(sprintf('Unknown error. Psalm returned invalid JSON, Returned the following: %s', $process->getOutput()));
89
        }
90
91
        return array_map(static function (array $result) : ClassMethodUsage {
92 2
            return new ClassMethodUsage(
93 2
                $result['file_name'],
94 2
                $result['line_from'],
95 2
                $result['snippet'],
96 2
                $result['selected_text']
97
            );
98 5
        }, $this->filterClassMethodUsages($processOutput));
99
    }
100
101
    /**
102
     * @return array<int, array<string, mixed>>|null
103
     */
104 5
    private function getPsalmProcessOutput(Process $process) : ?array
105
    {
106 5
        $rawProcessOutput = trim($process->getOutput());
107
108 5
        if ($rawProcessOutput === '') {
109 2
            return [];
110
        }
111
112 3
        $processOutput = json_decode($rawProcessOutput, true);
113
114 3
        if (! is_array($processOutput)) {
115
            return null;
116
        }
117
118 3
        if (! isset($processOutput[0]['message'])) {
119
            return null;
120
        }
121
122 3
        return $processOutput;
123
    }
124
125 5
    private function copyFindClassMethodUsagesPlugin(string $path) : void
126
    {
127 5
        copy(__DIR__ . '/FindClassMethodUsagesPlugin.php', $path . '/FindClassMethodUsagesPlugin.php');
128 5
    }
129
130 5
    private function cleanupFindClassMethodUsagesPlugin(string $path) : void
131
    {
132 5
        unlink($path . '/FindClassMethodUsagesPlugin.php');
133 5
    }
134
}
135