-
Notifications
You must be signed in to change notification settings - Fork 16
/
StubsGenerator.php
124 lines (104 loc) 路 3.99 KB
/
StubsGenerator.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?php
namespace Castor\Stub;
use Castor\Console\Application;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Finder\Finder;
/** @internal */
final class StubsGenerator
{
public function __construct(
private readonly LoggerInterface $logger = new NullLogger(),
) {
}
public function generateStubsIfNeeded(string $dest): void
{
if ($this->shouldGenerate($dest)) {
$this->logger->debug('Generating stubs...');
$this->generateStubs($dest);
}
}
public function generateStubs(string $dest): void
{
if (!is_writable(\dirname($dest))) {
$this->logger->warning("Could not generate stubs as the destination \"{$dest}\" is not writeable.");
return;
}
$basePath = \dirname(__DIR__, 2);
$finder = new Finder();
$finder
->files()
->in("{$basePath}/src")
->name('*.php')
->sortByName()
;
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$stmts = [];
$traverser = new NodeTraverser();
$traverser->addVisitor(new NodeVisitor());
foreach ($finder as $file) {
$fileStmts = $parser->parse((string) file_get_contents($file->getPathname()));
if (!$fileStmts) {
continue;
}
$stmts = array_merge($stmts, $traverser->traverse($fileStmts));
}
// Add some very frequently used classes
$frequentlyUsedClasses = [
\Symfony\Component\Console\Application::class,
\Symfony\Component\Console\Completion\CompletionInput::class,
\Symfony\Component\Console\Input\InputArgument::class,
\Symfony\Component\Console\Input\InputInterface::class,
\Symfony\Component\Console\Input\InputOption::class,
\Symfony\Component\Console\Output\OutputInterface::class,
\Symfony\Component\Console\Style\SymfonyStyle::class,
\Symfony\Component\Filesystem\Exception\ExceptionInterface::class,
\Symfony\Component\Filesystem\Filesystem::class,
\Symfony\Component\Filesystem\Path::class,
Finder::class,
\Symfony\Component\Process\Exception\ExceptionInterface::class,
\Symfony\Component\Process\ExecutableFinder::class,
\Symfony\Component\Process\Process::class,
\Symfony\Contracts\HttpClient\Exception\ExceptionInterface::class,
\Symfony\Contracts\HttpClient\HttpClientInterface::class,
\Symfony\Contracts\HttpClient\ResponseInterface::class,
];
foreach ($frequentlyUsedClasses as $class) {
$file = (new \ReflectionClass($class))->getFileName();
if (!$file) {
continue;
}
$fileStmts = $parser->parse((string) file_get_contents($file));
if (!$fileStmts) {
continue;
}
$stmts = array_merge($stmts, $traverser->traverse($fileStmts));
}
array_unshift($stmts, new \PhpParser\Node\Stmt\Nop([
'comments' => [
new \PhpParser\Comment\Doc(sprintf('// castor version: %s', Application::VERSION)),
],
]));
$code = (new Standard())->prettyPrintFile($stmts);
file_put_contents($dest, $code);
}
private function shouldGenerate(string $dest): bool
{
// Do not generate stubs when working on castor
if (($cwd = getcwd()) && str_starts_with(\dirname(__DIR__, 2), $cwd)) {
return false;
}
if (!file_exists($dest)) {
return true;
}
$content = (string) file_get_contents($dest);
preg_match('{^// castor version: (.+)$}m', $content, $matches);
if (!$matches) {
return true;
}
return Application::VERSION !== $matches[1];
}
}