Skip to content

Commit

Permalink
Unify ext-* and package handling
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal committed Apr 10, 2024
1 parent 6cd4c6f commit 04277aa
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 136 deletions.
2 changes: 1 addition & 1 deletion bin/composer-dependency-analyser
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ try {
$configuration = $initializer->initConfiguration($options, $composerJson);
$classLoaders = $initializer->initComposerClassLoaders();

$analyser = new Analyser($stopwatch, $classLoaders, $configuration, $composerJson->dependencies, $composerJson->extensions);
$analyser = new Analyser($stopwatch, $classLoaders, $configuration, $composerJson->dependencies);
$result = $analyser->run();

$formatter = $initializer->initFormatter($options);
Expand Down
129 changes: 56 additions & 73 deletions src/Analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_merge;
use function explode;
use function file_get_contents;
use function get_declared_classes;
Expand Down Expand Up @@ -81,19 +80,12 @@ class Analyser
private $classmap = [];

/**
* package name => is dev dependency
* package or ext-* => is dev dependency
*
* @var array<string, bool>
*/
private $composerJsonDependencies;

/**
* ext-* => is dev dependency
*
* @var array<string, bool>
*/
private $composerJsonExtensions;

/**
* symbol name => true
*
Expand All @@ -117,21 +109,18 @@ class Analyser

/**
* @param array<string, ClassLoader> $classLoaders vendorDir => ClassLoader (e.g. result of \Composer\Autoload\ClassLoader::getRegisteredLoaders())
* @param array<string, bool> $composerJsonDependencies package name => is dev dependency
* @param array<string, bool> $composerJsonExtensions ext-* => is dev dependency
* @param array<string, bool> $composerJsonDependencies package or ext-* => is dev dependency
*/
public function __construct(
Stopwatch $stopwatch,
array $classLoaders,
Configuration $config,
array $composerJsonDependencies,
array $composerJsonExtensions
array $composerJsonDependencies
)
{
$this->stopwatch = $stopwatch;
$this->config = $config;
$this->composerJsonDependencies = $composerJsonDependencies;
$this->composerJsonExtensions = $composerJsonExtensions;

$this->initExistingSymbols();

Expand All @@ -150,14 +139,12 @@ public function run(): AnalysisResult
$scannedFilesCount = 0;
$unknownClassErrors = [];
$unknownFunctionErrors = [];
$missingExtensions = [];
$shadowErrors = [];
$devInProdErrors = [];
$prodOnlyInDevErrors = [];
$unusedErrors = [];

$usedPackages = [];
$usedExtensions = [];
$usedDependencies = [];
$prodPackagesUsedInProdPath = [];

$usages = [];
Expand All @@ -176,72 +163,64 @@ public function run(): AnalysisResult
}

if (isset($this->extensionSymbols[$kind][$usedSymbol])) {
$neededExtension = $this->extensionSymbols[$kind][$usedSymbol];
$usedExtensions[$neededExtension] = true;

if (!isset($this->composerJsonExtensions[$neededExtension])) {
foreach ($lineNumbers as $lineNumber) {
$missingExtensions[$neededExtension][] = new SymbolUsage($filePath, $lineNumber, $kind);
}
}

continue;
}
$dependencyName = $this->extensionSymbols[$kind][$usedSymbol];

$symbolPath = $this->getSymbolPath($usedSymbol, $kind);
} else {
$symbolPath = $this->getSymbolPath($usedSymbol, $kind);

if ($symbolPath === null) {
if ($kind === SymbolKind::CLASSLIKE && !$ignoreList->shouldIgnoreUnknownClass($usedSymbol, $filePath)) {
foreach ($lineNumbers as $lineNumber) {
$unknownClassErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
if ($symbolPath === null) {
if ($kind === SymbolKind::CLASSLIKE && !$ignoreList->shouldIgnoreUnknownClass($usedSymbol, $filePath)) {
foreach ($lineNumbers as $lineNumber) {
$unknownClassErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
}
}
}

if ($kind === SymbolKind::FUNCTION && !$ignoreList->shouldIgnoreUnknownFunction($usedSymbol, $filePath)) {
foreach ($lineNumbers as $lineNumber) {
$unknownFunctionErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
if ($kind === SymbolKind::FUNCTION && !$ignoreList->shouldIgnoreUnknownFunction($usedSymbol, $filePath)) {
foreach ($lineNumbers as $lineNumber) {
$unknownFunctionErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
}
}

continue;
}

continue;
}
if (!$this->isVendorPath($symbolPath)) {
continue; // local class
}

if (!$this->isVendorPath($symbolPath)) {
continue; // local class
$dependencyName = $this->getPackageNameFromVendorPath($symbolPath);
}

$packageName = $this->getPackageNameFromVendorPath($symbolPath);

if (
$this->isShadowDependency($packageName)
&& !$ignoreList->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, $filePath, $packageName)
$this->isShadowDependency($dependencyName)
&& !$ignoreList->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, $filePath, $dependencyName)
) {
foreach ($lineNumbers as $lineNumber) {
$shadowErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
$shadowErrors[$dependencyName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
}
}

if (
!$isDevFilePath
&& $this->isDevDependency($packageName)
&& !$ignoreList->shouldIgnoreError(ErrorType::DEV_DEPENDENCY_IN_PROD, $filePath, $packageName)
&& $this->isDevDependency($dependencyName)
&& !$ignoreList->shouldIgnoreError(ErrorType::DEV_DEPENDENCY_IN_PROD, $filePath, $dependencyName)
) {
foreach ($lineNumbers as $lineNumber) {
$devInProdErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
$devInProdErrors[$dependencyName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
}
}

if (
!$isDevFilePath
&& !$this->isDevDependency($packageName)
&& !$this->isDevDependency($dependencyName)
) {
$prodPackagesUsedInProdPath[$packageName] = true;
$prodPackagesUsedInProdPath[$dependencyName] = true;
}

$usedPackages[$packageName] = true;
$usedDependencies[$dependencyName] = true;

foreach ($lineNumbers as $lineNumber) {
$usages[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
$usages[$dependencyName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind);
}
}
}
Expand All @@ -254,36 +233,40 @@ public function run(): AnalysisResult
continue;
}

$symbolPath = $this->getSymbolPath($forceUsedSymbol, null);
if (
isset($this->extensionSymbols[SymbolKind::FUNCTION][$forceUsedSymbol])
|| isset($this->extensionSymbols[SymbolKind::CONSTANT][$forceUsedSymbol])
|| isset($this->extensionSymbols[SymbolKind::CLASSLIKE][$forceUsedSymbol])
) {
$forceUsedDependency = $this->extensionSymbols[SymbolKind::FUNCTION][$forceUsedSymbol]
?? $this->extensionSymbols[SymbolKind::CONSTANT][$forceUsedSymbol]
?? $this->extensionSymbols[SymbolKind::CLASSLIKE][$forceUsedSymbol];
} else {
$symbolPath = $this->getSymbolPath($forceUsedSymbol, null);

if ($symbolPath === null || !$this->isVendorPath($symbolPath)) {
continue;
}

if ($symbolPath === null || !$this->isVendorPath($symbolPath)) {
continue;
$forceUsedDependency = $this->getPackageNameFromVendorPath($symbolPath);
}

$forceUsedPackage = $this->getPackageNameFromVendorPath($symbolPath);
$usedPackages[$forceUsedPackage] = true;
$forceUsedPackages[$forceUsedPackage] = true;
$usedDependencies[$forceUsedDependency] = true;
$forceUsedPackages[$forceUsedDependency] = true;
}

if ($this->config->shouldReportUnusedDevDependencies()) {
$dependenciesForUnusedAnalysis = array_merge(
array_keys($this->composerJsonDependencies),
array_keys($this->composerJsonExtensions)
);
$dependenciesForUnusedAnalysis = array_keys($this->composerJsonDependencies);

} else {
$dependenciesForUnusedAnalysis = array_merge(
array_keys(array_filter($this->composerJsonDependencies, static function (bool $devDependency) {
return !$devDependency; // dev deps are typically used only in CI
})),
array_keys($this->composerJsonExtensions)
);
$dependenciesForUnusedAnalysis = array_keys(array_filter($this->composerJsonDependencies, static function (bool $devDependency) {
return !$devDependency; // dev deps are typically used only in CI
}));
}

$unusedDependencies = array_diff(
$dependenciesForUnusedAnalysis,
array_keys($usedPackages),
array_keys($usedExtensions),
array_keys($usedDependencies),
self::CORE_EXTENSIONS
);

Expand All @@ -300,7 +283,8 @@ public function run(): AnalysisResult
$prodDependencies,
array_keys($prodPackagesUsedInProdPath),
array_keys($forceUsedPackages), // we dont know where are those used, lets not report them
$unusedDependencies
$unusedDependencies,
self::CORE_EXTENSIONS
);

foreach ($prodPackagesUsedOnlyInDev as $prodPackageUsedOnlyInDev) {
Expand All @@ -313,7 +297,6 @@ public function run(): AnalysisResult
$scannedFilesCount,
$this->stopwatch->stop(),
$usages,
$missingExtensions,
$unknownClassErrors,
$unknownFunctionErrors,
$shadowErrors,
Expand Down
14 changes: 2 additions & 12 deletions src/ComposerJson.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,7 @@ class ComposerJson
public $composerAutoloadPath;

/**
* ext-* => isDev
*
* @readonly
* @var array<string, bool>
*/
public $extensions;

/**
* Package => isDev
* Package or ext-* => isDev
*
* @readonly
* @var array<string, bool>
Expand Down Expand Up @@ -90,10 +82,8 @@ public function __construct(

$this->dependencies = array_merge(
array_fill_keys(array_keys(array_filter($requiredPackages, $filterPackages, ARRAY_FILTER_USE_KEY)), false),
array_fill_keys(array_keys(array_filter($requiredDevPackages, $filterPackages, ARRAY_FILTER_USE_KEY)), true)
);
$this->extensions = array_merge(
array_fill_keys(array_keys(array_filter($requiredPackages, $filterExtensions, ARRAY_FILTER_USE_KEY)), false),
array_fill_keys(array_keys(array_filter($requiredDevPackages, $filterPackages, ARRAY_FILTER_USE_KEY)), true),
array_fill_keys(array_keys(array_filter($requiredDevPackages, $filterExtensions, ARRAY_FILTER_USE_KEY)), true)
);

Expand Down
17 changes: 0 additions & 17 deletions src/Result/AnalysisResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ class AnalysisResult
*/
private $usages;

/**
* @var array<string, list<SymbolUsage>>
*/
private $missingExtensionsErrors = [];

/**
* @var array<string, list<SymbolUsage>>
*/
Expand Down Expand Up @@ -67,7 +62,6 @@ class AnalysisResult

/**
* @param array<string, array<string, list<SymbolUsage>>> $usages package => [ classname => usage[] ]
* @param array<string, list<SymbolUsage>> $missingExtensionErrors extension => usages
* @param array<string, list<SymbolUsage>> $unknownClassErrors package => usages
* @param array<string, list<SymbolUsage>> $unknownFunctionErrors package => usages
* @param array<string, array<string, list<SymbolUsage>>> $shadowDependencyErrors package => [ classname => usage[] ]
Expand All @@ -80,7 +74,6 @@ public function __construct(
int $scannedFilesCount,
float $elapsedTime,
array $usages,
array $missingExtensionErrors,
array $unknownClassErrors,
array $unknownFunctionErrors,
array $shadowDependencyErrors,
Expand All @@ -91,7 +84,6 @@ public function __construct(
)
{
ksort($usages);
ksort($missingExtensionErrors);
ksort($unknownClassErrors);
ksort($unknownFunctionErrors);
ksort($shadowDependencyErrors);
Expand All @@ -101,7 +93,6 @@ public function __construct(

$this->scannedFilesCount = $scannedFilesCount;
$this->elapsedTime = $elapsedTime;
$this->missingExtensionsErrors = $missingExtensionErrors;
$this->unknownClassErrors = $unknownClassErrors;
$this->unknownFunctionErrors = $unknownFunctionErrors;

Expand Down Expand Up @@ -143,14 +134,6 @@ public function getUsages(): array
return $this->usages;
}

/**
* @return array<string, list<SymbolUsage>>
*/
public function getMissingExtensionsErrors(): array
{
return $this->missingExtensionsErrors;
}

/**
* @return array<string, list<SymbolUsage>>
*/
Expand Down
11 changes: 0 additions & 11 deletions src/Result/ConsoleFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,23 +117,12 @@ private function printResultErrors(
$unusedIgnores = $result->getUnusedIgnores();

$unknownClassErrors = $result->getUnknownClassErrors();
$missingExtensionErrors = $result->getMissingExtensionsErrors();
$unknownFunctionErrors = $result->getUnknownFunctionErrors();
$shadowDependencyErrors = $result->getShadowDependencyErrors();
$devDependencyInProductionErrors = $result->getDevDependencyInProductionErrors();
$prodDependencyOnlyInDevErrors = $result->getProdDependencyOnlyInDevErrors();
$unusedDependencyErrors = $result->getUnusedDependencyErrors();

if (count($missingExtensionErrors) > 0) {
$hasError = true;
$this->printSymbolBasedErrors(
'Missing extensions!',
'those are used, but not listed in composer.json',
$missingExtensionErrors,
$maxShownUsages
);
}

if (count($unknownClassErrors) > 0) {
$hasError = true;
$this->printSymbolBasedErrors(
Expand Down
Loading

0 comments on commit 04277aa

Please sign in to comment.