diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 59dfefa..293846e 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -11,32 +11,47 @@ jobs:
-
repo: phpstan/phpstan-src
name: phpstan
- args: --config=build/composer-dependency-analyser.php
+ cdaArgs: --config=build/composer-dependency-analyser.php
+ composerArgs: ''
-
repo: shipmonk-rnd/phpstan-rules
name: shipmonk-rules
+ cdaArgs: ''
+ composerArgs: ''
+ -
+ repo: vincentlanglet/twig-cs-fixer
+ name: twig-cs-fixer
args: ''
-
repo: qossmic/deptrac-src
name: deptrac
- args: ''
+ cdaArgs: ''
+ composerArgs: ''
-
repo: kreait/firebase-php
name: firebase
- args: ''
+ cdaArgs: ''
+ composerArgs: ''
+ -
+ repo: oveleon/contao-cookiebar
+ name: contao-cookiebar
+ cdaArgs: --config=depcheck.php
+ composerArgs: --no-plugins
-
repo: rectorphp/rector-src
name: rector
- args: ''
+ cdaArgs: ''
+ composerArgs: ''
-
repo: inspirum/balikobot-php
name: balikobot
- args: ''
-
+ cdaArgs: ''
+ composerArgs: ''
-
repo: tomasvotruba/unused-public
name: unused-public
- args: ''
+ cdaArgs: ''
+ composerArgs: ''
fail-fast: false
steps:
-
@@ -65,7 +80,7 @@ jobs:
-
name: Install analyser dependencies
working-directory: analyser
- run: composer install --no-progress --prefer-dist --no-interaction
+ run: composer install --no-progress --no-interaction
-
name: Disable autoloader prepend
@@ -76,9 +91,9 @@ jobs:
-
name: Install ${{ matrix.name }} dependencies
working-directory: ${{ matrix.name }}
- run: composer install --no-progress --prefer-dist --no-interaction --ignore-platform-reqs
+ run: composer install --no-progress --no-interaction --ignore-platform-reqs ${{ matrix.composerArgs }}
-
name: Run analyser
working-directory: ${{ matrix.name }}
- run: php ../analyser/bin/composer-dependency-analyser ${{ matrix.args }}
+ run: php ../analyser/bin/composer-dependency-analyser ${{ matrix.cdaArgs }}
diff --git a/README.md b/README.md
index 1b54c4d..33c79ae 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,7 @@ This tool reads your `composer.json` and scans all paths listed in `autoload` &
- `--composer-json path/to/composer.json` for custom path to composer.json
- `--dump-usages symfony/console` to show usages of certain package(s), `*` placeholder is supported
- `--config path/to/config.php` for custom path to config file
+- `--version` display version
- `--help` display usage & cli options
- `--verbose` to see more example classes & usages
- `--show-all-usages` to see all usages
diff --git a/bin/composer-dependency-analyser b/bin/composer-dependency-analyser
index 459fba9..2e39d81 100755
--- a/bin/composer-dependency-analyser
+++ b/bin/composer-dependency-analyser
@@ -2,6 +2,7 @@
initConfiguration($options, $composerJson);
$classLoaders = $initializer->initComposerClassLoaders();
- $analyser = new Analyser($stopwatch, $classLoaders, $configuration, $composerJson->dependencies);
+ $analyser = new Analyser($stopwatch, $composerJson->composerVendorDir, $classLoaders, $configuration, $composerJson->dependencies);
$result = $analyser->run();
$formatter = $initializer->initFormatter($options);
@@ -52,6 +53,9 @@ try {
) {
$stdErrPrinter->printLine("\n{$e->getMessage()}" . PHP_EOL);
exit(255);
+
+} catch (AbortException $e) {
+ exit(0);
}
exit($exitCode);
diff --git a/composer.lock b/composer.lock
index 6fedb8d..fcec371 100644
--- a/composer.lock
+++ b/composer.lock
@@ -209,16 +209,16 @@
},
{
"name": "ergebnis/composer-normalize",
- "version": "2.42.0",
+ "version": "2.43.0",
"source": {
"type": "git",
"url": "https://github.com/ergebnis/composer-normalize.git",
- "reference": "02cf2b69ad2a74c6f11a8c3f5f054b8f949df910"
+ "reference": "4b46330c84bb8f43fac79f5c5a05162fc7c80d75"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/02cf2b69ad2a74c6f11a8c3f5f054b8f949df910",
- "reference": "02cf2b69ad2a74c6f11a8c3f5f054b8f949df910",
+ "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/4b46330c84bb8f43fac79f5c5a05162fc7c80d75",
+ "reference": "4b46330c84bb8f43fac79f5c5a05162fc7c80d75",
"shasum": ""
},
"require": {
@@ -232,17 +232,17 @@
"php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0"
},
"require-dev": {
- "composer/composer": "^2.6.6",
+ "composer/composer": "^2.7.7",
"ergebnis/license": "^2.4.0",
- "ergebnis/php-cs-fixer-config": "^6.20.0",
- "ergebnis/phpunit-slow-test-detector": "^2.9.0",
+ "ergebnis/php-cs-fixer-config": "^6.30.1",
+ "ergebnis/phpunit-slow-test-detector": "^2.14.0",
"fakerphp/faker": "^1.23.1",
"infection/infection": "~0.26.6",
- "phpunit/phpunit": "^9.6.16",
- "psalm/plugin-phpunit": "~0.18.4",
- "rector/rector": "~0.19.2",
- "symfony/filesystem": "^5.4.25",
- "vimeo/psalm": "^5.20.0"
+ "phpunit/phpunit": "^9.6.19",
+ "psalm/plugin-phpunit": "~0.19.0",
+ "rector/rector": "^1.1.0",
+ "symfony/filesystem": "^5.4.40",
+ "vimeo/psalm": "^5.24.0"
},
"type": "composer-plugin",
"extra": {
@@ -282,7 +282,7 @@
"security": "https://github.com/ergebnis/composer-normalize/blob/main/.github/SECURITY.md",
"source": "https://github.com/ergebnis/composer-normalize"
},
- "time": "2024-01-30T11:54:02+00:00"
+ "time": "2024-06-16T13:22:18+00:00"
},
{
"name": "ergebnis/json",
@@ -618,20 +618,20 @@
},
{
"name": "justinrainbow/json-schema",
- "version": "v5.2.13",
+ "version": "5.3.0",
"source": {
"type": "git",
- "url": "https://github.com/justinrainbow/json-schema.git",
- "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793"
+ "url": "https://github.com/jsonrainbow/json-schema.git",
+ "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793",
- "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793",
+ "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8",
+ "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=7.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
@@ -642,11 +642,6 @@
"bin/validate-json"
],
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.0.x-dev"
- }
- },
"autoload": {
"psr-4": {
"JsonSchema\\": "src/JsonSchema/"
@@ -681,10 +676,10 @@
"schema"
],
"support": {
- "issues": "https://github.com/justinrainbow/json-schema/issues",
- "source": "https://github.com/justinrainbow/json-schema/tree/v5.2.13"
+ "issues": "https://github.com/jsonrainbow/json-schema/issues",
+ "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0"
},
- "time": "2023-09-26T02:20:38+00:00"
+ "time": "2024-07-06T21:00:26+00:00"
},
{
"name": "localheinz/diff",
@@ -1240,16 +1235,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.11.3",
+ "version": "1.11.9",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5"
+ "reference": "e370bcddadaede0c1716338b262346f40d296f82"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e64220a05c1209fc856d58e789c3b7a32c0bb9a5",
- "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e370bcddadaede0c1716338b262346f40d296f82",
+ "reference": "e370bcddadaede0c1716338b262346f40d296f82",
"shasum": ""
},
"require": {
@@ -1294,7 +1289,7 @@
"type": "github"
}
],
- "time": "2024-05-31T13:53:37+00:00"
+ "time": "2024-08-01T16:25:18+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
diff --git a/src/Analyser.php b/src/Analyser.php
index 811d9a8..2c984bd 100644
--- a/src/Analyser.php
+++ b/src/Analyser.php
@@ -22,6 +22,7 @@
use function array_filter;
use function array_key_exists;
use function array_keys;
+use function array_values;
use function explode;
use function file_get_contents;
use function get_declared_classes;
@@ -50,9 +51,12 @@ class Analyser
private $stopwatch;
/**
- * vendorDir => ClassLoader
- *
- * @var array
+ * @var list
+ */
+ private $vendorDirs;
+
+ /**
+ * @var list
*/
private $classLoaders;
@@ -95,6 +99,7 @@ class Analyser
*/
public function __construct(
Stopwatch $stopwatch,
+ string $defaultVendorDir,
array $classLoaders,
Configuration $config,
array $composerJsonDependencies
@@ -103,7 +108,8 @@ public function __construct(
$this->stopwatch = $stopwatch;
$this->config = $config;
$this->composerJsonDependencies = $composerJsonDependencies;
- $this->classLoaders = $classLoaders;
+ $this->vendorDirs = array_keys($classLoaders + [$defaultVendorDir => null]);
+ $this->classLoaders = array_values($classLoaders);
$this->initExistingSymbols();
}
@@ -311,7 +317,7 @@ private function isDevDependency(string $packageName): bool
private function getPackageNameFromVendorPath(string $realPath): string
{
- foreach ($this->classLoaders as $vendorDir => $_) {
+ foreach ($this->vendorDirs as $vendorDir) {
if (strpos($realPath, $vendorDir) === 0) {
$filePathInVendor = trim(str_replace($vendorDir, '', $realPath), DIRECTORY_SEPARATOR);
[$vendor, $package] = explode(DIRECTORY_SEPARATOR, $filePathInVendor, 3);
@@ -366,7 +372,7 @@ private function listPhpFilesIn(string $path): Generator
private function isVendorPath(string $realPath): bool
{
- foreach ($this->classLoaders as $vendorDir => $_) {
+ foreach ($this->vendorDirs as $vendorDir) {
if (strpos($realPath, $vendorDir) === 0) {
return true;
}
diff --git a/src/Cli.php b/src/Cli.php
index a10115b..f4d90ed 100644
--- a/src/Cli.php
+++ b/src/Cli.php
@@ -13,6 +13,7 @@ class Cli
{
private const OPTIONS = [
+ 'version' => false,
'help' => false,
'verbose' => false,
'ignore-shadow-deps' => false,
@@ -126,6 +127,10 @@ public function getProvidedOptions(): CliOptions
{
$options = new CliOptions();
+ if (isset($this->providedOptions['version'])) {
+ $options->version = true;
+ }
+
if (isset($this->providedOptions['help'])) {
$options->help = true;
}
diff --git a/src/CliOptions.php b/src/CliOptions.php
index a0c06b7..3656225 100644
--- a/src/CliOptions.php
+++ b/src/CliOptions.php
@@ -5,6 +5,11 @@
class CliOptions
{
+ /**
+ * @var true|null
+ */
+ public $version = null;
+
/**
* @var true|null
*/
diff --git a/src/ComposerJson.php b/src/ComposerJson.php
index c43dc27..4e1d48f 100644
--- a/src/ComposerJson.php
+++ b/src/ComposerJson.php
@@ -31,6 +31,12 @@
class ComposerJson
{
+ /**
+ * @readonly
+ * @var string
+ */
+ public $composerVendorDir;
+
/**
* @readonly
* @var string
@@ -72,7 +78,8 @@ public function __construct(
$basePath = dirname($composerJsonPath);
$composerJsonData = $this->parseComposerJson($composerJsonPath);
- $this->composerAutoloadPath = $this->resolveComposerAutoloadPath($basePath, $composerJsonData['config']['vendor-dir'] ?? 'vendor');
+ $this->composerVendorDir = $this->resolveComposerVendorDir($basePath, $composerJsonData['config']['vendor-dir'] ?? 'vendor');
+ $this->composerAutoloadPath = Path::normalize($this->composerVendorDir . '/autoload.php');
$requiredPackages = $composerJsonData['require'] ?? [];
$requiredDevPackages = $composerJsonData['require-dev'] ?? [];
@@ -121,7 +128,11 @@ private function extractAutoloadPaths(string $basePath, array $autoload, bool $i
}
foreach ($paths as $path) {
- $absolutePath = $basePath . '/' . $path;
+ if (Path::isAbsolute($path)) {
+ $absolutePath = $path;
+ } else {
+ $absolutePath = $basePath . '/' . $path;
+ }
if (strpos($path, '*') !== false) { // https://getcomposer.org/doc/04-schema.md#classmap
$globPaths = glob($absolutePath);
@@ -261,13 +272,13 @@ private function parseComposerJson(string $composerJsonPath): array
return $composerJsonData; // @phpstan-ignore-line ignore mixed returned
}
- private function resolveComposerAutoloadPath(string $basePath, string $vendorDir): string
+ private function resolveComposerVendorDir(string $basePath, string $vendorDir): string
{
if (Path::isAbsolute($vendorDir)) {
- return Path::normalize($vendorDir . '/autoload.php');
+ return Path::normalize($vendorDir);
}
- return Path::normalize($basePath . '/' . $vendorDir . '/autoload.php');
+ return Path::normalize($basePath . '/' . $vendorDir);
}
}
diff --git a/src/Exception/AbortException.php b/src/Exception/AbortException.php
new file mode 100644
index 0000000..00835b6
--- /dev/null
+++ b/src/Exception/AbortException.php
@@ -0,0 +1,13 @@
+ $argv
+ * @throws AbortException
* @throws InvalidCliException
*/
public function initCliOptions(string $cwd, array $argv): CliOptions
@@ -227,7 +235,12 @@ public function initCliOptions(string $cwd, array $argv): CliOptions
if ($cliOptions->help !== null) {
$this->stdOutPrinter->printLine(self::$help);
- throw new InvalidCliException(''); // just exit
+ throw new AbortException();
+ }
+
+ if ($cliOptions->version !== null) {
+ $this->stdOutPrinter->printLine('Composer Dependency Analyser ' . $this->deduceVersion());
+ throw new AbortException();
}
return $cliOptions;
@@ -255,4 +268,29 @@ public function initFormatter(CliOptions $options): ResultFormatter
throw new InvalidConfigException("Invalid format option provided, allowed are 'console' or 'junit'.");
}
+ private function deduceVersion(): string
+ {
+ try {
+ if (isset($GLOBALS['_composer_autoload_path'])) {
+ require $GLOBALS['_composer_autoload_path'];
+ }
+
+ /** @throws OutOfBoundsException */
+ if (!class_exists(InstalledVersions::class)) {
+ return 'unknown';
+ }
+
+ $package = 'shipmonk/composer-dependency-analyser';
+
+ return sprintf(
+ '%s (%s)',
+ InstalledVersions::getPrettyVersion($package),
+ InstalledVersions::getReference($package)
+ );
+
+ } catch (OutOfBoundsException $e) {
+ return 'not found';
+ }
+ }
+
}
diff --git a/src/Result/ConsoleFormatter.php b/src/Result/ConsoleFormatter.php
index 7de0cea..67402dc 100644
--- a/src/Result/ConsoleFormatter.php
+++ b/src/Result/ConsoleFormatter.php
@@ -12,6 +12,7 @@
use function array_reduce;
use function count;
use function fnmatch;
+use function in_array;
use function round;
use function strlen;
use function strpos;
@@ -53,14 +54,14 @@ public function format(
private function getMaxUsagesShownForErrors(CliOptions $options): int
{
- if ($options->verbose === true) {
- return self::VERBOSE_SHOWN_USAGES;
- }
-
if ($options->showAllUsages === true) {
return PHP_INT_MAX;
}
+ if ($options->verbose === true) {
+ return self::VERBOSE_SHOWN_USAGES;
+ }
+
return 1;
}
@@ -123,60 +124,73 @@ private function printResultErrors(
$prodDependencyOnlyInDevErrors = $result->getProdDependencyOnlyInDevErrors();
$unusedDependencyErrors = $result->getUnusedDependencyErrors();
- if (count($unknownClassErrors) > 0) {
+ $unknownClassErrorsCount = count($unknownClassErrors);
+ $unknownFunctionErrorsCount = count($unknownFunctionErrors);
+ $shadowDependencyErrorsCount = count($shadowDependencyErrors);
+ $devDependencyInProductionErrorsCount = count($devDependencyInProductionErrors);
+ $prodDependencyOnlyInDevErrorsCount = count($prodDependencyOnlyInDevErrors);
+ $unusedDependencyErrorsCount = count($unusedDependencyErrors);
+
+ if ($unknownClassErrorsCount > 0) {
$hasError = true;
+ $classes = $this->pluralize($unknownClassErrorsCount, 'class');
$this->printSymbolBasedErrors(
- 'Unknown classes!',
+ "Found $unknownClassErrorsCount unknown $classes!",
'unable to autoload those, so we cannot check them',
$unknownClassErrors,
$maxShownUsages
);
}
- if (count($unknownFunctionErrors) > 0) {
+ if ($unknownFunctionErrorsCount > 0) {
$hasError = true;
+ $functions = $this->pluralize($unknownFunctionErrorsCount, 'function');
$this->printSymbolBasedErrors(
- 'Unknown functions!',
+ "Found $unknownFunctionErrorsCount unknown $functions!",
'those are not declared, so we cannot check them',
$unknownFunctionErrors,
$maxShownUsages
);
}
- if (count($shadowDependencyErrors) > 0) {
+ if ($shadowDependencyErrorsCount > 0) {
$hasError = true;
+ $dependencies = $this->pluralize($shadowDependencyErrorsCount, 'dependency');
$this->printPackageBasedErrors(
- 'Found shadow dependencies!',
+ "Found $shadowDependencyErrorsCount shadow $dependencies!",
'those are used, but not listed as dependency in composer.json',
$shadowDependencyErrors,
$maxShownUsages
);
}
- if (count($devDependencyInProductionErrors) > 0) {
+ if ($devDependencyInProductionErrorsCount > 0) {
$hasError = true;
+ $dependencies = $this->pluralize($devDependencyInProductionErrorsCount, 'dependency');
$this->printPackageBasedErrors(
- 'Found dev dependencies in production code!',
+ "Found $devDependencyInProductionErrorsCount dev $dependencies in production code!",
'those should probably be moved to "require" section in composer.json',
$devDependencyInProductionErrors,
$maxShownUsages
);
}
- if (count($prodDependencyOnlyInDevErrors) > 0) {
+ if ($prodDependencyOnlyInDevErrorsCount > 0) {
$hasError = true;
+ $dependencies = $this->pluralize($prodDependencyOnlyInDevErrorsCount, 'dependency');
$this->printPackageBasedErrors(
- 'Found prod dependencies used only in dev paths!',
+ "Found $prodDependencyOnlyInDevErrorsCount prod $dependencies used only in dev paths!",
'those should probably be moved to "require-dev" section in composer.json',
array_fill_keys($prodDependencyOnlyInDevErrors, []),
$maxShownUsages
);
}
- if (count($unusedDependencyErrors) > 0) {
+ if ($unusedDependencyErrorsCount > 0) {
$hasError = true;
+ $dependencies = $this->pluralize($unusedDependencyErrorsCount, 'dependency');
$this->printPackageBasedErrors(
- 'Found unused dependencies!',
+ "Found $unusedDependencyErrorsCount unused $dependencies!",
'those are listed in composer.json, but no usage was found in scanned paths',
array_fill_keys($unusedDependencyErrors, []),
$maxShownUsages
@@ -433,4 +447,21 @@ private function willLimitUsages(array $usages, int $limit): bool
return false;
}
+ private function pluralize(int $count, string $singular): string
+ {
+ if ($count === 1) {
+ return $singular;
+ }
+
+ if (substr($singular, -1) === 's' || substr($singular, -1) === 'x' || substr($singular, -2) === 'sh' || substr($singular, -2) === 'ch') {
+ return $singular . 'es';
+ }
+
+ if (substr($singular, -1) === 'y' && !in_array($singular[strlen($singular) - 2], ['a', 'e', 'i', 'o', 'u'], true)) {
+ return substr($singular, 0, -1) . 'ies';
+ }
+
+ return $singular . 's';
+ }
+
}
diff --git a/src/Result/JunitFormatter.php b/src/Result/JunitFormatter.php
index f206518..bf94816 100644
--- a/src/Result/JunitFormatter.php
+++ b/src/Result/JunitFormatter.php
@@ -2,6 +2,7 @@
namespace ShipMonk\ComposerDependencyAnalyser\Result;
+use DOMDocument;
use ShipMonk\ComposerDependencyAnalyser\CliOptions;
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore;
@@ -9,16 +10,17 @@
use ShipMonk\ComposerDependencyAnalyser\Printer;
use ShipMonk\ComposerDependencyAnalyser\SymbolKind;
use function array_fill_keys;
-use function array_reduce;
use function count;
+use function extension_loaded;
use function htmlspecialchars;
-use function implode;
use function sprintf;
use function strlen;
use function strpos;
use function substr;
+use function trim;
use const ENT_COMPAT;
use const ENT_XML1;
+use const LIBXML_NOEMPTYTAG;
use const PHP_INT_MAX;
class JunitFormatter implements ResultFormatter
@@ -120,9 +122,13 @@ public function format(
$xml .= $this->createUnusedIgnoresTestSuite($unusedIgnores);
}
+ if ($hasError) {
+ $xml .= sprintf('', $this->getUsagesComment($maxShownUsages));
+ }
+
$xml .= '';
- $this->printer->print($xml);
+ $this->printer->print($this->prettyPrintXml($xml));
if ($hasError) {
return 255;
@@ -133,14 +139,14 @@ public function format(
private function getMaxUsagesShownForErrors(CliOptions $options): int
{
- if ($options->verbose === true) {
- return self::VERBOSE_SHOWN_USAGES;
- }
-
if ($options->showAllUsages === true) {
return PHP_INT_MAX;
}
+ if ($options->verbose === true) {
+ return self::VERBOSE_SHOWN_USAGES;
+ }
+
return 1;
}
@@ -154,28 +160,12 @@ private function createSymbolBasedTestSuite(string $title, array $errors, int $m
foreach ($errors as $symbol => $usages) {
$xml .= sprintf('', $this->escape($symbol));
- if ($maxShownUsages > 1) {
- $failureUsage = [];
+ foreach ($usages as $index => $usage) {
+ $xml .= sprintf('%s', $this->escape($this->relativizeUsage($usage)));
- foreach ($usages as $index => $usage) {
- $failureUsage[] = $this->relativizeUsage($usage);
-
- if ($index === $maxShownUsages - 1) {
- $restUsagesCount = count($usages) - $index - 1;
-
- if ($restUsagesCount > 0) {
- $failureUsage[] = "+ {$restUsagesCount} more";
- break;
- }
- }
+ if ($index === $maxShownUsages - 1) {
+ break;
}
-
- $xml .= sprintf('%s', $this->escape(implode('\n', $failureUsage)));
- } else {
- $firstUsage = $usages[0];
- $restUsagesCount = count($usages) - 1;
- $rest = $restUsagesCount > 0 ? " (+ {$restUsagesCount} more)" : '';
- $xml .= sprintf('in %s%s', $this->escape($this->relativizeUsage($firstUsage)), $rest);
}
$xml .= '';
@@ -195,7 +185,24 @@ private function createPackageBasedTestSuite(string $title, array $errors, int $
foreach ($errors as $packageName => $usagesPerClassname) {
$xml .= sprintf('', $this->escape($packageName));
- $xml .= sprintf('%s', $this->escape(implode('\n', $this->createUsages($usagesPerClassname, $maxShownUsages))));
+
+ $printedSymbols = 0;
+
+ foreach ($usagesPerClassname as $symbol => $usages) {
+ foreach ($this->createUsages($usages, $maxShownUsages) as $usage) {
+ $printedSymbols++;
+ $xml .= sprintf(
+ '%s',
+ $symbol,
+ $this->escape($usage)
+ );
+
+ if ($printedSymbols === $maxShownUsages) {
+ break 2;
+ }
+ }
+ }
+
$xml .= '';
}
@@ -205,59 +212,19 @@ private function createPackageBasedTestSuite(string $title, array $errors, int $
}
/**
- * @param array> $usagesPerSymbol
+ * @param list $usages
* @return list
*/
- private function createUsages(array $usagesPerSymbol, int $maxShownUsages): array
+ private function createUsages(array $usages, int $maxShownUsages): array
{
$usageMessages = [];
- if ($maxShownUsages === 1) {
- $countOfAllUsages = array_reduce(
- $usagesPerSymbol,
- static function (int $carry, array $usages): int {
- return $carry + count($usages);
- },
- 0
- );
+ foreach ($usages as $index => $usage) {
+ $usageMessages[] = $this->relativizeUsage($usage);
- foreach ($usagesPerSymbol as $symbol => $usages) {
- $firstUsage = $usages[0];
- $restUsagesCount = $countOfAllUsages - 1;
- $rest = $countOfAllUsages > 1 ? " (+ {$restUsagesCount} more)" : '';
- $usageMessages[] = "e.g. {$symbol} in {$this->relativizeUsage($firstUsage)}$rest";
+ if ($index === $maxShownUsages - 1) {
break;
}
- } else {
- $classnamesPrinted = 0;
-
- foreach ($usagesPerSymbol as $symbol => $usages) {
- $classnamesPrinted++;
-
- $usageMessages[] = $symbol;
-
- foreach ($usages as $index => $usage) {
- $usageMessages[] = " {$this->relativizeUsage($usage)}";
-
- if ($index === $maxShownUsages - 1) {
- $restUsagesCount = count($usages) - $index - 1;
-
- if ($restUsagesCount > 0) {
- $usageMessages[] = " + {$restUsagesCount} more";
- break;
- }
- }
- }
-
- if ($classnamesPrinted === $maxShownUsages) {
- $restSymbolsCount = count($usagesPerSymbol) - $classnamesPrinted;
-
- if ($restSymbolsCount > 0) {
- $usageMessages[] = " + {$restSymbolsCount} more symbol" . ($restSymbolsCount > 1 ? 's' : '');
- break;
- }
- }
- }
}
return $usageMessages;
@@ -323,4 +290,37 @@ private function escape(string $string): string
return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}
+ private function prettyPrintXml(string $inputXml): string
+ {
+ if (!extension_loaded('dom') || !extension_loaded('libxml')) {
+ return $inputXml;
+ }
+
+ $dom = new DOMDocument();
+ $dom->preserveWhiteSpace = false;
+ $dom->formatOutput = true;
+ $dom->loadXML($inputXml);
+
+ $outputXml = $dom->saveXML(null, LIBXML_NOEMPTYTAG);
+
+ if ($outputXml === false) {
+ return $inputXml;
+ }
+
+ return trim($outputXml);
+ }
+
+ private function getUsagesComment(int $maxShownUsages): string
+ {
+ if ($maxShownUsages === PHP_INT_MAX) {
+ return 'showing all failure usages';
+ }
+
+ if ($maxShownUsages === 1) {
+ return 'showing only first example failure usage';
+ }
+
+ return sprintf('showing only first %d example failure usages', $maxShownUsages);
+ }
+
}
diff --git a/tests/AnalyserTest.php b/tests/AnalyserTest.php
index 720de96..0f6ed15 100644
--- a/tests/AnalyserTest.php
+++ b/tests/AnalyserTest.php
@@ -45,6 +45,7 @@ public function test(callable $editConfig, AnalysisResult $expectedResult): void
$detector = new Analyser(
$this->getStopwatchMock(),
+ $vendorDir,
[$vendorDir => $this->getClassLoaderMock()],
$config,
$dependencies
@@ -477,12 +478,14 @@ public function testNativeTypesNotReported(): void
$path = realpath(__DIR__ . '/data/not-autoloaded/builtin/native-symbols.php');
self::assertNotFalse($path);
+ $vendorDir = __DIR__;
$config = new Configuration();
$config->addPathToScan($path, false);
$detector = new Analyser(
$this->getStopwatchMock(),
- [__DIR__ => $this->getClassLoaderMock()],
+ $vendorDir,
+ [$vendorDir => $this->getClassLoaderMock()],
$config,
[]
);
@@ -503,13 +506,16 @@ public function testNoMultipleScansOfTheSameFile(): void
$path = realpath(__DIR__ . '/data/not-autoloaded/analysis/unknown-classes.php');
self::assertNotFalse($path);
+ $vendorDir = __DIR__ . '/data/autoloaded/vendor';
+
$config = new Configuration();
$config->addPathToScan($path, true);
$config->addPathToScan($path, true);
$detector = new Analyser(
$this->getStopwatchMock(),
- [__DIR__ . '/data/autoloaded/vendor' => $this->getClassLoaderMock()],
+ $vendorDir,
+ [$vendorDir => $this->getClassLoaderMock()],
$config,
[]
);
@@ -538,6 +544,7 @@ public function testDevPathInsideProdPath(): void
$detector = new Analyser(
$this->getStopwatchMock(),
+ $vendorDir,
[$vendorDir => $this->getClassLoaderMock()],
$config,
[
@@ -565,6 +572,7 @@ public function testProdPathInsideDevPath(): void
$detector = new Analyser(
$this->getStopwatchMock(),
+ $vendorDir,
[$vendorDir => $this->getClassLoaderMock()],
$config,
[
@@ -591,6 +599,7 @@ public function testOtherSymbols(): void
$detector = new Analyser(
$this->getStopwatchMock(),
+ $vendorDir,
[$vendorDir => $this->getClassLoaderMock()],
$config,
[]
@@ -626,6 +635,7 @@ public function testPharSupport(): void
$detector = new Analyser(
$this->getStopwatchMock(),
+ $vendorDir,
[$vendorDir => $this->getClassLoaderMock()],
$config,
[
@@ -665,6 +675,7 @@ public function testMultipleClassloaders(): void
$detector = new Analyser(
$this->getStopwatchMock(),
+ $vendorDir,
$classLoaders,
$config,
[
@@ -692,6 +703,7 @@ public function testFunctions(): void
$detector = new Analyser(
$this->getStopwatchMock(),
+ $vendorDir,
[$vendorDir => $this->getClassLoaderMock()],
$config,
['org/package' => false]
@@ -713,6 +725,7 @@ public function testExplicitFileWithoutExtension(): void
$detector = new Analyser(
$this->getStopwatchMock(),
+ $vendorDir,
[$vendorDir => $this->getClassLoaderMock()],
$config,
[
diff --git a/tests/BinTest.php b/tests/BinTest.php
index c4dd187..d95b3ed 100644
--- a/tests/BinTest.php
+++ b/tests/BinTest.php
@@ -24,16 +24,18 @@ public function test(): void
$okOutput = 'No composer issues found';
$dumpingOutput = 'Dumping sample usages of';
$helpOutput = 'Usage:';
+ $versionOutput = 'Composer Dependency Analyser';
$usingConfig = 'Using config';
- $junitOutput = '';
+ $junitOutput = "\n";
$this->runCommand('php bin/composer-dependency-analyser', $rootDir, 0, $okOutput, $usingConfig);
$this->runCommand('php bin/composer-dependency-analyser --verbose', $rootDir, 0, $okOutput, $usingConfig);
$this->runCommand('php ../bin/composer-dependency-analyser', $testsDir, 255, null, $noComposerJsonError);
- $this->runCommand('php bin/composer-dependency-analyser --help', $rootDir, 255, $helpOutput);
- $this->runCommand('php ../bin/composer-dependency-analyser --help', $testsDir, 255, $helpOutput);
+ $this->runCommand('php bin/composer-dependency-analyser --help', $rootDir, 0, $helpOutput);
+ $this->runCommand('php ../bin/composer-dependency-analyser --help', $testsDir, 0, $helpOutput);
+ $this->runCommand('php ../bin/composer-dependency-analyser --version', $testsDir, 0, $versionOutput);
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.json', $rootDir, 0, $okOutput, $usingConfig);
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.lock', $rootDir, 255, null, $noPackagesError);
$this->runCommand('php bin/composer-dependency-analyser --composer-json=README.md', $rootDir, 255, null, $parseError);
diff --git a/tests/ComposerJsonTest.php b/tests/ComposerJsonTest.php
index 96bf1b4..401e0c0 100644
--- a/tests/ComposerJsonTest.php
+++ b/tests/ComposerJsonTest.php
@@ -41,6 +41,7 @@ public function testComposerJson(): void
realpath(__DIR__ . '/data/not-autoloaded/composer/dir2/file1.php') => false,
realpath(__DIR__ . '/data/not-autoloaded/composer/dir1') => false,
realpath(__DIR__ . '/data/not-autoloaded/composer/dir2') => false,
+ DIRECTORY_SEPARATOR . 'absolute' . DIRECTORY_SEPARATOR . 'dir' => false,
],
$composerJson->autoloadPaths
);
diff --git a/tests/ConsoleFormatterTest.php b/tests/ConsoleFormatterTest.php
index 0f200be..19bbea2 100644
--- a/tests/ConsoleFormatterTest.php
+++ b/tests/ConsoleFormatterTest.php
@@ -83,7 +83,7 @@ public function testPrintResult(): void
$expectedRegularOutput = <<<'OUT'
-Unknown classes!
+Found 1 unknown class!
(unable to autoload those, so we cannot check them)
• Unknown\Thing
@@ -91,7 +91,7 @@ public function testPrintResult(): void
-Unknown functions!
+Found 1 unknown function!
(those are not declared, so we cannot check them)
• Unknown\function
@@ -99,7 +99,7 @@ public function testPrintResult(): void
-Found shadow dependencies!
+Found 2 shadow dependencies!
(those are used, but not listed as dependency in composer.json)
• shadow/another
@@ -110,7 +110,7 @@ public function testPrintResult(): void
-Found dev dependencies in production code!
+Found 1 dev dependency in production code!
(those should probably be moved to "require" section in composer.json)
• some/package
@@ -118,13 +118,13 @@ public function testPrintResult(): void
-Found prod dependencies used only in dev paths!
+Found 1 prod dependency used only in dev paths!
(those should probably be moved to "require-dev" section in composer.json)
• misplaced/package
-Found unused dependencies!
+Found 1 unused dependency!
(those are listed in composer.json, but no usage was found in scanned paths)
• dead/package
@@ -135,7 +135,7 @@ public function testPrintResult(): void
OUT;
$expectedVerboseOutput = <<<'OUT'
-Unknown classes!
+Found 1 unknown class!
(unable to autoload those, so we cannot check them)
• Unknown\Thing
@@ -143,7 +143,7 @@ public function testPrintResult(): void
-Unknown functions!
+Found 1 unknown function!
(those are not declared, so we cannot check them)
• Unknown\function
@@ -151,7 +151,7 @@ public function testPrintResult(): void
-Found shadow dependencies!
+Found 2 shadow dependencies!
(those are used, but not listed as dependency in composer.json)
• shadow/another
@@ -170,7 +170,7 @@ public function testPrintResult(): void
+ 1 more symbol
-Found dev dependencies in production code!
+Found 1 dev dependency in production code!
(those should probably be moved to "require" section in composer.json)
• some/package
@@ -178,13 +178,13 @@ public function testPrintResult(): void
src/ProductGenerator.php:28
-Found prod dependencies used only in dev paths!
+Found 1 prod dependency used only in dev paths!
(those should probably be moved to "require-dev" section in composer.json)
• misplaced/package
-Found unused dependencies!
+Found 1 unused dependency!
(those are listed in composer.json, but no usage was found in scanned paths)
• dead/package
diff --git a/tests/InitializerTest.php b/tests/InitializerTest.php
index 9729f41..5bd4f7f 100644
--- a/tests/InitializerTest.php
+++ b/tests/InitializerTest.php
@@ -5,7 +5,7 @@
use PHPUnit\Framework\TestCase;
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
use ShipMonk\ComposerDependencyAnalyser\Config\PathToScan;
-use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidCliException;
+use ShipMonk\ComposerDependencyAnalyser\Exception\AbortException;
use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidConfigException;
use ShipMonk\ComposerDependencyAnalyser\Result\ConsoleFormatter;
use ShipMonk\ComposerDependencyAnalyser\Result\JunitFormatter;
@@ -114,7 +114,7 @@ public function testInitCliOptionsHelp(): void
$initializer = new Initializer(__DIR__, $printer, $printer);
- $this->expectException(InvalidCliException::class);
+ $this->expectException(AbortException::class);
$initializer->initCliOptions(__DIR__, ['script.php', '--help']);
}
diff --git a/tests/JunitFormatterTest.php b/tests/JunitFormatterTest.php
index 995e2f0..5d479a2 100644
--- a/tests/JunitFormatterTest.php
+++ b/tests/JunitFormatterTest.php
@@ -2,7 +2,6 @@
namespace ShipMonk\ComposerDependencyAnalyser;
-use DOMDocument;
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore;
@@ -10,8 +9,6 @@
use ShipMonk\ComposerDependencyAnalyser\Result\JunitFormatter;
use ShipMonk\ComposerDependencyAnalyser\Result\ResultFormatter;
use ShipMonk\ComposerDependencyAnalyser\Result\SymbolUsage;
-use function trim;
-use const LIBXML_NOEMPTYTAG;
class JunitFormatterTest extends FormatterTest
{
@@ -39,17 +36,21 @@ public function testPrintResult(): void
'shadow-dependency' was globally ignored, but it was never applied.
+
OUT;
- self::assertSame($this->normalizeEol($expectedNoIssuesOutput), $this->prettyPrintXml($noIssuesOutput));
- self::assertSame($this->normalizeEol($expectedNoIssuesButWarningsOutput), $this->prettyPrintXml($noIssuesButUnusedIgnores));
+ self::assertSame($this->normalizeEol($expectedNoIssuesOutput), $noIssuesOutput);
+ self::assertSame($this->normalizeEol($expectedNoIssuesButWarningsOutput), $noIssuesButUnusedIgnores);
$analysisResult = new AnalysisResult(
10,
0.123,
[],
- ['Unknown\\Thing' => [new SymbolUsage('/app/app/init.php', 1093, SymbolKind::CLASSLIKE)]],
+ ['Unknown\\Thing' => [
+ new SymbolUsage('/app/app/init.php', 1091, SymbolKind::CLASSLIKE),
+ new SymbolUsage('/app/app/init.php', 1093, SymbolKind::CLASSLIKE),
+ ]],
['Unknown\\function' => [new SymbolUsage('/app/app/foo.php', 51, SymbolKind::FUNCTION)]],
[
'shadow/package' => [
@@ -78,7 +79,7 @@ public function testPrintResult(): void
});
$verboseOutput = $this->getFormatterNormalizedOutput(static function ($formatter) use ($analysisResult): void {
$options = new CliOptions();
- $options->verbose = true;
+ $options->showAllUsages = true;
$formatter->format($analysisResult, $options, new Configuration());
});
@@ -87,37 +88,34 @@ public function testPrintResult(): void
- in app/init.php:1093
+ app/init.php:1091
- in app/foo.php:51
+ app/foo.php:51
- e.g. Another\Controller in src/bootstrap.php:173
+ src/bootstrap.php:173
- e.g. Forth\Provider in src/bootstrap.php:873 (+ 6 more)
+ src/bootstrap.php:873
- e.g. Another\Command in src/ProductGenerator.php:28
+ src/ProductGenerator.php:28
-
-
-
+
-
-
-
+
+
OUT;
$expectedVerboseOutput = <<<'OUT'
@@ -125,6 +123,7 @@ public function testPrintResult(): void
+ app/init.php:1091
app/init.php:1093
@@ -135,48 +134,38 @@ public function testPrintResult(): void
- Another\Controller\n src/bootstrap.php:173
+ src/bootstrap.php:173
- Forth\Provider\n src/bootstrap.php:873\nShadow\Comparator\n src/Printer.php:25\nShadow\Utils\n src/Utils.php:19\n src/Utils.php:22\n src/Application.php:128\n + 1 more\n + 1 more symbol
+ src/bootstrap.php:873
+ src/Printer.php:25
+ src/Utils.php:19
+ src/Utils.php:22
+ src/Application.php:128
+ src/Controller.php:229
+ src/bootstrap.php:317
- Another\Command\n src/ProductGenerator.php:28
+ src/ProductGenerator.php:28
-
-
-
+
-
-
-
+
+
OUT;
- self::assertSame($this->normalizeEol($expectedRegularOutput), $this->prettyPrintXml($regularOutput));
- self::assertSame($this->normalizeEol($expectedVerboseOutput), $this->prettyPrintXml($verboseOutput));
+ self::assertSame($this->normalizeEol($expectedRegularOutput), $regularOutput);
+ self::assertSame($this->normalizeEol($expectedVerboseOutput), $verboseOutput);
// editorconfig-checker-enable
}
- private function prettyPrintXml(string $inputXml): string
- {
- $dom = new DOMDocument();
- $dom->preserveWhiteSpace = false;
- $dom->formatOutput = true;
- $dom->loadXML($inputXml);
-
- $outputXml = $dom->saveXML(null, LIBXML_NOEMPTYTAG);
- self::assertNotFalse($outputXml);
-
- return trim($outputXml);
- }
-
protected function createFormatter(Printer $printer): ResultFormatter
{
return new JunitFormatter('/app', $printer);
diff --git a/tests/data/not-autoloaded/composer/sample.json b/tests/data/not-autoloaded/composer/sample.json
index 56a307f..ef9ce40 100644
--- a/tests/data/not-autoloaded/composer/sample.json
+++ b/tests/data/not-autoloaded/composer/sample.json
@@ -8,7 +8,8 @@
},
"autoload": {
"classmap": [
- "dir*"
+ "dir*",
+ "/absolute/dir"
],
"files": [
"dir2/file1.php"