Skip to content

Commit 75ffa2e

Browse files
authored
Remove the unused generated classes during code generation (#1494)
Remove the unused generated classes This ensures that operations that are attempted to be added and then reverted don't leak generated objects. Tests are not cleaned automatically because they are not fully managed by the generator.
1 parent 868ebbe commit 75ffa2e

File tree

5 files changed

+101
-2
lines changed

5 files changed

+101
-2
lines changed

src/CodeGenerator/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"nette/php-generator": "^3.6.4",
1313
"nette/utils": "^3.0",
1414
"symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0",
15+
"symfony/finder": "^4.4 || ^5.0 || ^6.0 || ^7.0",
1516
"symfony/http-client": "^4.4 || ^5.0 || ^6.0 || ^7.0",
1617
"symfony/http-client-contracts": "^1.0 || ^2.0 || ^3.0",
1718
"symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0",

src/CodeGenerator/src/Command/GenerateCommand.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use AsyncAws\CodeGenerator\File\Cache;
99
use AsyncAws\CodeGenerator\File\ClassWriter;
1010
use AsyncAws\CodeGenerator\File\ComposerWriter;
11+
use AsyncAws\CodeGenerator\File\UnusedClassCleaner;
1112
use AsyncAws\CodeGenerator\Generator\ApiGenerator;
1213
use AsyncAws\CodeGenerator\Generator\Naming\ClassName;
1314
use PhpCsFixer\Config;
@@ -65,13 +66,19 @@ class GenerateCommand extends Command
6566
*/
6667
private $composerWriter;
6768

68-
public function __construct(string $manifestFile, Cache $cache, ClassWriter $classWriter, ComposerWriter $composerWriter, ApiGenerator $generator)
69+
/**
70+
* @var UnusedClassCleaner
71+
*/
72+
private $unusedClassCleaner;
73+
74+
public function __construct(string $manifestFile, Cache $cache, ClassWriter $classWriter, ComposerWriter $composerWriter, ApiGenerator $generator, UnusedClassCleaner $unusedClassCleaner)
6975
{
7076
$this->manifestFile = $manifestFile;
7177
$this->cache = $cache;
7278
$this->generator = $generator;
7379
$this->classWriter = $classWriter;
7480
$this->composerWriter = $composerWriter;
81+
$this->unusedClassCleaner = $unusedClassCleaner;
7582

7683
parent::__construct();
7784
}
@@ -317,6 +324,10 @@ private function generateService(SymfonyStyle $io, InputInterface $input, array
317324

318325
$this->composerWriter->setRequirements($namespace, $this->generator->getUpdatedRequirements(), $input->getOption('all') && 'Sts' !== $serviceName);
319326

327+
if ($input->getOption('all')) {
328+
$this->unusedClassCleaner->cleanUnusedClasses($serviceGenerator->getNamespaceRegistry(), $this->generator->getUpdatedClasses());
329+
}
330+
320331
if (!$input->getOption('raw')) {
321332
$this->fixCs($clientClass, $io);
322333
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace AsyncAws\CodeGenerator\File;
4+
5+
use AsyncAws\CodeGenerator\Definition\Shape;
6+
use AsyncAws\CodeGenerator\Definition\StructureShape;
7+
use AsyncAws\CodeGenerator\File\Location\DirectoryResolver;
8+
use AsyncAws\CodeGenerator\Generator\Naming\ClassName;
9+
use AsyncAws\CodeGenerator\Generator\Naming\NamespaceRegistry;
10+
use AsyncAws\CodeGenerator\Generator\PhpGenerator\ClassBuilder;
11+
use Symfony\Component\Finder\Finder;
12+
13+
/**
14+
* @internal
15+
*/
16+
final class UnusedClassCleaner
17+
{
18+
/**
19+
* @var DirectoryResolver
20+
*/
21+
private $directoryResolver;
22+
23+
public function __construct(DirectoryResolver $directoryResolver)
24+
{
25+
$this->directoryResolver = $directoryResolver;
26+
}
27+
28+
/**
29+
* @param iterable<ClassBuilder> $generatedClasses
30+
*/
31+
public function cleanUnusedClasses(NamespaceRegistry $namespaceRegistry, iterable $generatedClasses): void
32+
{
33+
$usedClassNames = [];
34+
foreach ($generatedClasses as $class) {
35+
$usedClassNames[$class->getClassName()->getFqdn()] = true;
36+
}
37+
38+
foreach ($this->getCleanableNamespaces($namespaceRegistry) as $className) {
39+
$namespace = $className->getNamespace();
40+
$directory = $this->directoryResolver->resolveDirectory($namespace);
41+
42+
if (!is_dir($directory)) {
43+
continue;
44+
}
45+
46+
$finder = (new Finder())
47+
->name('*.php')
48+
->files()
49+
->in($directory);
50+
51+
foreach ($finder as $file) {
52+
$fileClassName = $namespace . '\\' . str_replace('/', '\\', substr($file->getRelativePathname(), 0, -4));
53+
54+
if (isset($usedClassNames[$fileClassName])) {
55+
continue;
56+
}
57+
58+
unlink($file->getPathname());
59+
}
60+
}
61+
}
62+
63+
/**
64+
* @return iterable<ClassName>
65+
*/
66+
private function getCleanableNamespaces(NamespaceRegistry $namespaceRegistry): iterable
67+
{
68+
$fakeLocator = function () {
69+
throw new \BadMethodCallException('Unexpected call on the fake shape');
70+
};
71+
72+
$fakeStructureShape = Shape::create('AsyncAwsFakeShape', ['type' => 'structure'], $fakeLocator, $fakeLocator);
73+
\assert($fakeStructureShape instanceof StructureShape);
74+
75+
yield $namespaceRegistry->getEnum(Shape::create('AsyncAwsFakeShape', ['type' => 'string'], $fakeLocator, $fakeLocator));
76+
yield $namespaceRegistry->getException($fakeStructureShape);
77+
yield $namespaceRegistry->getInput($fakeStructureShape);
78+
yield $namespaceRegistry->getResult($fakeStructureShape);
79+
yield $namespaceRegistry->getObject($fakeStructureShape);
80+
}
81+
}

src/CodeGenerator/src/Generator/ServiceGenerator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,9 @@ public function object(): ObjectGenerator
191191
{
192192
return $this->object ?? $this->object = new ObjectGenerator($this->classRegistry, $this->namespaceRegistry, $this->requirementsRegistry, $this->managedOperations, $this->type(), $this->enum());
193193
}
194+
195+
public function getNamespaceRegistry(): NamespaceRegistry
196+
{
197+
return $this->namespaceRegistry;
198+
}
194199
}

src/CodeGenerator/src/Runner.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use AsyncAws\CodeGenerator\File\ClassWriter;
99
use AsyncAws\CodeGenerator\File\ComposerWriter;
1010
use AsyncAws\CodeGenerator\File\Location\DirectoryResolver;
11+
use AsyncAws\CodeGenerator\File\UnusedClassCleaner;
1112
use AsyncAws\CodeGenerator\Generator\ApiGenerator;
1213
use Symfony\Component\Console\Application;
1314

@@ -18,7 +19,7 @@ public static function create(string $manifest, DirectoryResolver $directoryReso
1819
$application = new Application('AsyncAws', '0.1.0');
1920

2021
$cache = new Cache($cacheDirectory);
21-
$command = new GenerateCommand($manifest, $cache, new ClassWriter($directoryResolver, new CachedFileDumper($cache)), new ComposerWriter($directoryResolver), new ApiGenerator());
22+
$command = new GenerateCommand($manifest, $cache, new ClassWriter($directoryResolver, new CachedFileDumper($cache)), new ComposerWriter($directoryResolver), new ApiGenerator(), new UnusedClassCleaner($directoryResolver));
2223
$application->add($command);
2324
$application->setDefaultCommand($command->getName(), true);
2425

0 commit comments

Comments
 (0)