Skip to content

Commit

Permalink
Track all usages (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal authored Jan 9, 2024
1 parent a4c3a5f commit 1b11da4
Show file tree
Hide file tree
Showing 17 changed files with 481 additions and 421 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ Example output:
Found shadow dependencies!
(those are used, but not listed as dependency in composer.json)
symfony/service-contracts
e.g. Symfony\Contracts\Service\Attribute\Required in app/Controller/ProductController.php:24
nette/utils
e.g. Nette\Utils\Strings in app/Controller/ProductController.php:24 (+ 6 more)
Found unused dependencies!
(those are listed in composer.json, but no usage was found in scanned paths)
Expand All @@ -46,6 +46,8 @@ Found unused dependencies!
```

You can add `--verbose` to see more example classes & usages.

## Detected issues:
This tool reads your `composer.json` and scans all paths listed in both `autoload` sections while analysing:

Expand Down
7 changes: 4 additions & 3 deletions bin/composer-dependency-analyser
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Usage:
Options:
--help Print this help text and exit.
--verbose Print more usage examples
--ignore-unknown-classes Ignore when class is not found in classmap
--composer-json <path> Provide custom path to composer.json
--config <path> Provide path to php configuration file
Expand Down Expand Up @@ -45,7 +46,7 @@ $exit = static function (string $message) use ($printer): void {
};

/** @var string[] $providedOptions */
$providedOptions = getopt('', ['help', 'ignore-unknown-classes', 'composer-json:', 'config:'], $restIndex);
$providedOptions = getopt('', ['help', 'verbose', 'ignore-unknown-classes', 'composer-json:', 'config:'], $restIndex);

/** @var int $restIndex */
$providedPaths = array_slice($argv, $restIndex);
Expand Down Expand Up @@ -154,9 +155,9 @@ foreach ($config->getPathsWithIgnore() as $pathWithIgnore) {
}

$analyser = new ComposerDependencyAnalyser($config, $vendorDir, $loaders[$vendorDir]->getClassMap(), $composerJson->dependencies);
$errors = $analyser->run();
$result = $analyser->run();

$exitCode = $printer->printResult($errors);
$exitCode = $printer->printResult($result, isset($providedOptions['verbose']));
exit($exitCode);


60 changes: 31 additions & 29 deletions src/ComposerDependencyAnalyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,26 @@
use ReflectionClass;
use ShipMonk\Composer\Config\Configuration;
use ShipMonk\Composer\Config\ErrorType;
use ShipMonk\Composer\Crate\ClassUsage;
use ShipMonk\Composer\Error\ClassmapEntryMissingError;
use ShipMonk\Composer\Error\DevDependencyInProductionCodeError;
use ShipMonk\Composer\Error\ShadowDependencyError;
use ShipMonk\Composer\Error\SymbolError;
use ShipMonk\Composer\Error\UnusedDependencyError;
use ShipMonk\Composer\Result\AnalysisResult;
use ShipMonk\Composer\Result\SymbolUsage;
use UnexpectedValueException;
use function array_diff;
use function array_filter;
use function array_keys;
use function array_values;
use function class_exists;
use function defined;
use function explode;
use function file_get_contents;
use function function_exists;
use function get_class;
use function interface_exists;
use function is_file;
use function ksort;
use function realpath;
use function sort;
use function str_replace;
use function strlen;
use function substr;
use function trim;
use function usort;
use const DIRECTORY_SEPARATOR;

class ComposerDependencyAnalyser
Expand Down Expand Up @@ -86,12 +81,12 @@ public function __construct(
$this->composerJsonDependencies = $composerJsonDependencies;
}

/**
* @return list<SymbolError>
*/
public function run(): array
public function run(): AnalysisResult
{
$errors = [];
$classmapErrors = [];
$shadowErrors = [];
$devInProdErrors = [];
$unusedErrors = [];
$usedPackages = [];

foreach ($this->config->getPathsToScan() as $scanPath) {
Expand All @@ -100,7 +95,7 @@ public function run(): array
continue;
}

foreach ($this->getUsedSymbolsInFile($filePath) as $usedSymbol => $lineNumber) {
foreach ($this->getUsedSymbolsInFile($filePath) as $usedSymbol => $lineNumbers) {
if ($this->isInternalClass($usedSymbol)) {
continue;
}
Expand All @@ -115,7 +110,9 @@ public function run(): array
&& !$this->config->shouldIgnoreUnknownClass($usedSymbol)
&& !$this->config->shouldIgnoreError(ErrorType::UNKNOWN_CLASS, $filePath, null)
) {
$errors[$usedSymbol] = new ClassmapEntryMissingError(new ClassUsage($usedSymbol, $filePath, $lineNumber));
foreach ($lineNumbers as $lineNumber) {
$classmapErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber);
}
}

continue;
Expand All @@ -133,15 +130,19 @@ public function run(): array
$this->isShadowDependency($packageName)
&& !$this->config->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, $filePath, $packageName)
) {
$errors[$packageName] = new ShadowDependencyError($packageName, new ClassUsage($usedSymbol, $filePath, $lineNumber));
foreach ($lineNumbers as $lineNumber) {
$shadowErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber);
}
}

if (
!$scanPath->isDev()
&& $this->isDevDependency($packageName)
&& !$this->config->shouldIgnoreError(ErrorType::DEV_DEPENDENCY_IN_PROD, $filePath, $packageName)
) {
$errors[$packageName] = new DevDependencyInProductionCodeError($packageName, new ClassUsage($usedSymbol, $filePath, $lineNumber));
foreach ($lineNumbers as $lineNumber) {
$devInProdErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber);
}
}

$usedPackages[$packageName] = true;
Expand All @@ -158,20 +159,21 @@ public function run(): array

foreach ($unusedDependencies as $unusedDependency) {
if (!$this->config->shouldIgnoreError(ErrorType::UNUSED_DEPENDENCY, null, $unusedDependency)) {
$errors[] = new UnusedDependencyError($unusedDependency);
$unusedErrors[] = $unusedDependency;
}
}

usort($errors, static function (SymbolError $a, SymbolError $b): int {
$aPackageName = $a->getPackageName() ?? '';
$bPackageName = $b->getPackageName() ?? '';
$aClassName = $a->getExampleUsage() !== null ? $a->getExampleUsage()->getClassname() : '';
$bClassName = $b->getExampleUsage() !== null ? $b->getExampleUsage()->getClassname() : '';

return [get_class($a), $aPackageName, $aClassName] <=> [get_class($b), $bPackageName, $bClassName];
});
ksort($classmapErrors);
ksort($shadowErrors);
ksort($devInProdErrors);
sort($unusedErrors);

return array_values($errors);
return new AnalysisResult(
$classmapErrors,
$shadowErrors,
$devInProdErrors,
$unusedErrors
);
}

private function isShadowDependency(string $packageName): bool
Expand All @@ -193,7 +195,7 @@ private function getPackageNameFromVendorPath(string $realPath): string
}

/**
* @return array<string, int>
* @return array<string, list<int>>
*/
private function getUsedSymbolsInFile(string $filePath): array
{
Expand Down
32 changes: 0 additions & 32 deletions src/Error/ClassmapEntryMissingError.php

This file was deleted.

39 changes: 0 additions & 39 deletions src/Error/DevDependencyInProductionCodeError.php

This file was deleted.

39 changes: 0 additions & 39 deletions src/Error/ShadowDependencyError.php

This file was deleted.

14 changes: 0 additions & 14 deletions src/Error/SymbolError.php

This file was deleted.

30 changes: 0 additions & 30 deletions src/Error/UnusedDependencyError.php

This file was deleted.

Loading

0 comments on commit 1b11da4

Please sign in to comment.