Skip to content

Commit 1b11da4

Browse files
authored
Track all usages (#10)
1 parent a4c3a5f commit 1b11da4

17 files changed

+481
-421
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ Example output:
3636
Found shadow dependencies!
3737
(those are used, but not listed as dependency in composer.json)
3838
39-
symfony/service-contracts
40-
e.g. Symfony\Contracts\Service\Attribute\Required in app/Controller/ProductController.php:24
39+
nette/utils
40+
e.g. Nette\Utils\Strings in app/Controller/ProductController.php:24 (+ 6 more)
4141
4242
Found unused dependencies!
4343
(those are listed in composer.json, but no usage was found in scanned paths)
@@ -46,6 +46,8 @@ Found unused dependencies!
4646
4747
```
4848

49+
You can add `--verbose` to see more example classes & usages.
50+
4951
## Detected issues:
5052
This tool reads your `composer.json` and scans all paths listed in both `autoload` sections while analysing:
5153

bin/composer-dependency-analyser

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Usage:
1515
1616
Options:
1717
--help Print this help text and exit.
18+
--verbose Print more usage examples
1819
--ignore-unknown-classes Ignore when class is not found in classmap
1920
--composer-json <path> Provide custom path to composer.json
2021
--config <path> Provide path to php configuration file
@@ -45,7 +46,7 @@ $exit = static function (string $message) use ($printer): void {
4546
};
4647

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

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

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

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

162163

src/ComposerDependencyAnalyser.php

+31-29
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,26 @@
1010
use ReflectionClass;
1111
use ShipMonk\Composer\Config\Configuration;
1212
use ShipMonk\Composer\Config\ErrorType;
13-
use ShipMonk\Composer\Crate\ClassUsage;
14-
use ShipMonk\Composer\Error\ClassmapEntryMissingError;
15-
use ShipMonk\Composer\Error\DevDependencyInProductionCodeError;
16-
use ShipMonk\Composer\Error\ShadowDependencyError;
17-
use ShipMonk\Composer\Error\SymbolError;
18-
use ShipMonk\Composer\Error\UnusedDependencyError;
13+
use ShipMonk\Composer\Result\AnalysisResult;
14+
use ShipMonk\Composer\Result\SymbolUsage;
1915
use UnexpectedValueException;
2016
use function array_diff;
2117
use function array_filter;
2218
use function array_keys;
23-
use function array_values;
2419
use function class_exists;
2520
use function defined;
2621
use function explode;
2722
use function file_get_contents;
2823
use function function_exists;
29-
use function get_class;
3024
use function interface_exists;
3125
use function is_file;
26+
use function ksort;
3227
use function realpath;
28+
use function sort;
3329
use function str_replace;
3430
use function strlen;
3531
use function substr;
3632
use function trim;
37-
use function usort;
3833
use const DIRECTORY_SEPARATOR;
3934

4035
class ComposerDependencyAnalyser
@@ -86,12 +81,12 @@ public function __construct(
8681
$this->composerJsonDependencies = $composerJsonDependencies;
8782
}
8883

89-
/**
90-
* @return list<SymbolError>
91-
*/
92-
public function run(): array
84+
public function run(): AnalysisResult
9385
{
94-
$errors = [];
86+
$classmapErrors = [];
87+
$shadowErrors = [];
88+
$devInProdErrors = [];
89+
$unusedErrors = [];
9590
$usedPackages = [];
9691

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

103-
foreach ($this->getUsedSymbolsInFile($filePath) as $usedSymbol => $lineNumber) {
98+
foreach ($this->getUsedSymbolsInFile($filePath) as $usedSymbol => $lineNumbers) {
10499
if ($this->isInternalClass($usedSymbol)) {
105100
continue;
106101
}
@@ -115,7 +110,9 @@ public function run(): array
115110
&& !$this->config->shouldIgnoreUnknownClass($usedSymbol)
116111
&& !$this->config->shouldIgnoreError(ErrorType::UNKNOWN_CLASS, $filePath, null)
117112
) {
118-
$errors[$usedSymbol] = new ClassmapEntryMissingError(new ClassUsage($usedSymbol, $filePath, $lineNumber));
113+
foreach ($lineNumbers as $lineNumber) {
114+
$classmapErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber);
115+
}
119116
}
120117

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

139138
if (
140139
!$scanPath->isDev()
141140
&& $this->isDevDependency($packageName)
142141
&& !$this->config->shouldIgnoreError(ErrorType::DEV_DEPENDENCY_IN_PROD, $filePath, $packageName)
143142
) {
144-
$errors[$packageName] = new DevDependencyInProductionCodeError($packageName, new ClassUsage($usedSymbol, $filePath, $lineNumber));
143+
foreach ($lineNumbers as $lineNumber) {
144+
$devInProdErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber);
145+
}
145146
}
146147

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

159160
foreach ($unusedDependencies as $unusedDependency) {
160161
if (!$this->config->shouldIgnoreError(ErrorType::UNUSED_DEPENDENCY, null, $unusedDependency)) {
161-
$errors[] = new UnusedDependencyError($unusedDependency);
162+
$unusedErrors[] = $unusedDependency;
162163
}
163164
}
164165

165-
usort($errors, static function (SymbolError $a, SymbolError $b): int {
166-
$aPackageName = $a->getPackageName() ?? '';
167-
$bPackageName = $b->getPackageName() ?? '';
168-
$aClassName = $a->getExampleUsage() !== null ? $a->getExampleUsage()->getClassname() : '';
169-
$bClassName = $b->getExampleUsage() !== null ? $b->getExampleUsage()->getClassname() : '';
170-
171-
return [get_class($a), $aPackageName, $aClassName] <=> [get_class($b), $bPackageName, $bClassName];
172-
});
166+
ksort($classmapErrors);
167+
ksort($shadowErrors);
168+
ksort($devInProdErrors);
169+
sort($unusedErrors);
173170

174-
return array_values($errors);
171+
return new AnalysisResult(
172+
$classmapErrors,
173+
$shadowErrors,
174+
$devInProdErrors,
175+
$unusedErrors
176+
);
175177
}
176178

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

195197
/**
196-
* @return array<string, int>
198+
* @return array<string, list<int>>
197199
*/
198200
private function getUsedSymbolsInFile(string $filePath): array
199201
{

src/Error/ClassmapEntryMissingError.php

-32
This file was deleted.

src/Error/DevDependencyInProductionCodeError.php

-39
This file was deleted.

src/Error/ShadowDependencyError.php

-39
This file was deleted.

src/Error/SymbolError.php

-14
This file was deleted.

src/Error/UnusedDependencyError.php

-30
This file was deleted.

0 commit comments

Comments
 (0)