Skip to content

Commit

Permalink
add ClassNames to open to handling parent class names
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Nov 8, 2023
1 parent ed4843a commit beb5e6e
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 37 deletions.
10 changes: 8 additions & 2 deletions app/ClassNameResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PhpParser\Parser;
use TomasVotruba\ClassLeak\NodeDecorator\FullyQualifiedNameNodeDecorator;
use TomasVotruba\ClassLeak\NodeVisitor\ClassNameNodeVisitor;
use TomasVotruba\ClassLeak\ValueObject\ClassNames;

/**
* @see \TomasVotruba\ClassLeak\Tests\ClassNameResolver\ClassNameResolverTest
Expand All @@ -20,7 +21,7 @@ public function __construct(
) {
}

public function resolveFromFromFilePath(string $filePath): ?string
public function resolveFromFromFilePath(string $filePath): ?ClassNames
{
/** @var string $fileContents */
$fileContents = file_get_contents($filePath);
Expand All @@ -37,6 +38,11 @@ public function resolveFromFromFilePath(string $filePath): ?string
$nodeTraverser->addVisitor($classNameNodeVisitor);
$nodeTraverser->traverse($stmts);

return $classNameNodeVisitor->getClassName();
$className = $classNameNodeVisitor->getClassName();
if (! is_string($className)) {
return null;
}

return new ClassNames($className, $classNameNodeVisitor->hasParentClassOrInterface());
}
}
11 changes: 6 additions & 5 deletions app/Commands/CheckCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use TomasVotruba\ClassLeak\Filtering\PossiblyUnusedClassesFilter;
use TomasVotruba\ClassLeak\Finder\ClassNamesFinder;
use TomasVotruba\ClassLeak\Finder\PhpFilesFinder;
use TomasVotruba\ClassLeak\Reporting\UnusedClassesResultFactory;
use TomasVotruba\ClassLeak\Reporting\UnusedClassReporter;
use TomasVotruba\ClassLeak\UseImportsResolver;

Expand All @@ -26,6 +27,7 @@ public function __construct(
private readonly UnusedClassReporter $unusedClassReporter,
private readonly SymfonyStyle $symfonyStyle,
private readonly PhpFilesFinder $phpFilesFinder,
private readonly UnusedClassesResultFactory $unusedClassesResultFactory,
) {
parent::__construct();
}
Expand Down Expand Up @@ -84,10 +86,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$suffixesToSkip
);

return $this->unusedClassReporter->reportResult(
$possiblyUnusedFilesWithClasses,
$existingFilesWithClasses
);
$unusedClassesResult = $this->unusedClassesResultFactory->create($possiblyUnusedFilesWithClasses);

return $this->unusedClassReporter->reportResult($unusedClassesResult, count($existingFilesWithClasses));
}

/**
Expand All @@ -100,7 +101,7 @@ private function resolveUsedClassNames(array $phpFilePaths, Closure $progressClo

foreach ($phpFilePaths as $phpFilePath) {
$currentUsedNames = $this->useImportsResolver->resolve($phpFilePath);
$usedNames = array_merge($usedNames, $currentUsedNames);
$usedNames = [...$usedNames, ...$currentUsedNames];

$progressClosure();
}
Expand Down
3 changes: 3 additions & 0 deletions app/DependencyInjection/ContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
use Symfony\Component\Console\Style\SymfonyStyle;
use TomasVotruba\ClassLeak\Commands\CheckCommand;

/**
* @api
*/
final class ContainerFactory
{
/**
Expand Down
2 changes: 1 addition & 1 deletion app/Filtering/PossiblyUnusedClassesFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function filter(

$possiblyUnusedFilesWithClasses = [];

$typesToSkip = array_merge($typesToSkip, self::DEFAULT_TYPES_TO_SKIP);
$typesToSkip = [...$typesToSkip, ...self::DEFAULT_TYPES_TO_SKIP];

foreach ($filesWithClasses as $fileWithClass) {
if (in_array($fileWithClass->getClassName(), $usedClassNames, true)) {
Expand Down
11 changes: 8 additions & 3 deletions app/Finder/ClassNamesFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace TomasVotruba\ClassLeak\Finder;

use TomasVotruba\ClassLeak\ClassNameResolver;
use TomasVotruba\ClassLeak\ValueObject\ClassNames;
use TomasVotruba\ClassLeak\ValueObject\FileWithClass;

final class ClassNamesFinder
Expand All @@ -23,12 +24,16 @@ public function resolveClassNamesToCheck(array $filePaths): array
$filesWithClasses = [];

foreach ($filePaths as $filePath) {
$className = $this->classNameResolver->resolveFromFromFilePath($filePath);
if ($className === null) {
$classNames = $this->classNameResolver->resolveFromFromFilePath($filePath);
if (! $classNames instanceof ClassNames) {
continue;
}

$filesWithClasses[] = new FileWithClass($filePath, $className);
$filesWithClasses[] = new FileWithClass(
$filePath,
$classNames->getClassName(),
$classNames->hasParentClassOrInterface()
);
}

return $filesWithClasses;
Expand Down
2 changes: 1 addition & 1 deletion app/Finder/PhpFilesFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function findPhpFiles(array $paths): array
$filePaths[] = $path;
} else {
$currentFilePaths = $this->findFilesUsingGlob($path);
$filePaths = array_merge($filePaths, $currentFilePaths);
$filePaths = [...$filePaths, ...$currentFilePaths];
}
}

Expand Down
24 changes: 24 additions & 0 deletions app/NodeVisitor/ClassNameNodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;

Expand All @@ -22,13 +24,17 @@ final class ClassNameNodeVisitor extends NodeVisitorAbstract

private string|null $className = null;

private bool $hasParentClassOrInterface = false;

/**
* @param Node\Stmt[] $nodes
* @return Node\Stmt[]
*/
public function beforeTraverse(array $nodes): array
{
$this->className = null;
$this->hasParentClassOrInterface = false;

return $nodes;
}

Expand All @@ -51,6 +57,19 @@ public function enterNode(Node $node): ?int
}

$this->className = $node->namespacedName->toString();
if ($node instanceof Class_) {
if ($node->extends instanceof Name) {
$this->hasParentClassOrInterface = true;
}

if ($node->implements !== []) {
$this->hasParentClassOrInterface = true;
}
}

if ($node instanceof Interface_ && $node->extends !== []) {
$this->hasParentClassOrInterface = true;
}

return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}
Expand All @@ -60,6 +79,11 @@ public function getClassName(): ?string
return $this->className;
}

public function hasParentClassOrInterface(): bool
{
return $this->hasParentClassOrInterface;
}

private function hasApiTag(ClassLike $classLike): bool
{
$doc = $classLike->getDocComment();
Expand Down
50 changes: 33 additions & 17 deletions app/Reporting/UnusedClassReporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;
use TomasVotruba\ClassLeak\ValueObject\FileWithClass;
use TomasVotruba\ClassLeak\ValueObject\UnusedClassesResult;

final class UnusedClassReporter
{
Expand All @@ -16,36 +17,51 @@ public function __construct(
}

/**
* @param FileWithClass[] $unusedFilesWithClasses
* @param FileWithClass[] $existingFilesWithClasses
* @return Command::*
*/
public function reportResult(array $unusedFilesWithClasses, array $existingFilesWithClasses): int
public function reportResult(UnusedClassesResult $unusedClassesResult, int $classCount): int
{
$this->symfonyStyle->newLine(2);

if ($unusedFilesWithClasses === []) {
$successMessage = sprintf(
'All the %d services are used. Great job!',
count($existingFilesWithClasses),
);
$this->symfonyStyle->success($successMessage);
if ($unusedClassesResult->hasFilesWithClasses()) {
$this->symfonyStyle->success(sprintf('All the %d services are used. Great job!', $classCount));

return Command::SUCCESS;
}

foreach ($unusedFilesWithClasses as $unusedFileWithClass) {
$this->symfonyStyle->writeln(' * ' . $unusedFileWithClass->getClassName());
$this->symfonyStyle->writeln($unusedFileWithClass->getFilePath());
// separate with and without parent, as first one can be removed more easily

if ($unusedClassesResult->getWithParentsFileWithClasses() !== []) {
$this->symfonyStyle->title('Classes with a parent/interface - possibly used by type');
$this->reportFileWithClasses($unusedClassesResult->getWithParentsFileWithClasses());
}

if ($unusedClassesResult->getParentLessFileWithClasses() !== []) {
$this->symfonyStyle->newLine();

$this->symfonyStyle->title('Classes without any parent/interface - easier to remove');
$this->reportFileWithClasses($unusedClassesResult->getParentLessFileWithClasses());
}

$successMessage = sprintf(
'Found %d unused classes. Check them, remove them or correct the command.',
count($unusedFilesWithClasses)
);
$this->symfonyStyle->newLine();

$this->symfonyStyle->error($successMessage);
$this->symfonyStyle->error(sprintf(
'Found %d unused classes. Check and remove them or skip them using "--skip-type" option',
$unusedClassesResult->getCount()
));

return Command::FAILURE;
}

/**
* @param FileWithClass[] $fileWithClasses
*/
private function reportFileWithClasses(array $fileWithClasses): void
{
foreach ($fileWithClasses as $fileWithClass) {
$this->symfonyStyle->writeln(' * ' . $fileWithClass->getClassName());
$this->symfonyStyle->writeln($fileWithClass->getFilePath());
$this->symfonyStyle->newLine();
}
}
}
30 changes: 30 additions & 0 deletions app/Reporting/UnusedClassesResultFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace TomasVotruba\ClassLeak\Reporting;

use TomasVotruba\ClassLeak\ValueObject\FileWithClass;
use TomasVotruba\ClassLeak\ValueObject\UnusedClassesResult;

final class UnusedClassesResultFactory
{
/**
* @param FileWithClass[] $unusedFilesWithClasses
*/
public function create(array $unusedFilesWithClasses): UnusedClassesResult
{
$parentLessFileWithClasses = [];
$withParentsFileWithClasses = [];

foreach ($unusedFilesWithClasses as $unusedFileWithClass) {
if ($unusedFileWithClass->hasParentClassOrInterface()) {
$withParentsFileWithClasses[] = $unusedFileWithClass;
} else {
$parentLessFileWithClasses[] = $unusedFileWithClass;
}
}

return new UnusedClassesResult($parentLessFileWithClasses, $withParentsFileWithClasses);
}
}
24 changes: 24 additions & 0 deletions app/ValueObject/ClassNames.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace TomasVotruba\ClassLeak\ValueObject;

final class ClassNames
{
public function __construct(
private readonly string $className,
private readonly bool $hasParentClassOrInterface
) {
}

public function getClassName(): string
{
return $this->className;
}

public function hasParentClassOrInterface(): bool
{
return $this->hasParentClassOrInterface;
}
}
8 changes: 7 additions & 1 deletion app/ValueObject/FileWithClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ final class FileWithClass
{
public function __construct(
private readonly string $filePath,
private readonly string $className
private readonly string $className,
private readonly bool $hasParentClassOrInterface
) {
}

Expand All @@ -23,4 +24,9 @@ public function getFilePath(): string
{
return StaticRelativeFilePathHelper::resolveFromCwd($this->filePath);
}

public function hasParentClassOrInterface(): bool
{
return $this->hasParentClassOrInterface;
}
}
48 changes: 48 additions & 0 deletions app/ValueObject/UnusedClassesResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace TomasVotruba\ClassLeak\ValueObject;

final class UnusedClassesResult
{
/**
* @param FileWithClass[] $withParentsFileWithClasses
* @param FileWithClass[] $parentLessFileWithClasses
*/
public function __construct(
private readonly array $parentLessFileWithClasses,
private readonly array $withParentsFileWithClasses,
) {
}

/**
* @return FileWithClass[]
*/
public function getParentLessFileWithClasses(): array
{
return $this->parentLessFileWithClasses;
}

/**
* @return FileWithClass[]
*/
public function getWithParentsFileWithClasses(): array
{
return $this->withParentsFileWithClasses;
}

public function hasFilesWithClasses(): bool
{
if ($this->parentLessFileWithClasses !== []) {
return false;
}

return $this->withParentsFileWithClasses !== [];
}

public function getCount(): int
{
return count($this->parentLessFileWithClasses) + count($this->withParentsFileWithClasses);
}
}
Loading

0 comments on commit beb5e6e

Please sign in to comment.