diff --git a/.gitignore b/.gitignore index 803c8b5b..fb14c34a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,5 @@ .idea/* .env .phpunit.result.cache -composer.lock +/composer.lock .php_cs.cache diff --git a/composer.json b/composer.json index 84ca27a4..98f32fe2 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,9 @@ ], "require": { "php": "^7.2", - "ext-json": "*", "ext-iconv": "*", + "ext-json": "*", + "composer/composer": "^1.7", "league/container": "^3.2", "object-calisthenics/phpcs-calisthenics-rules": "^3.5", "phploc/phploc": "^5.0", diff --git a/src/Domain/Insights/ComposerFinder.php b/src/Domain/ComposerFinder.php similarity index 69% rename from src/Domain/Insights/ComposerFinder.php rename to src/Domain/ComposerFinder.php index ecb97485..51cd8750 100644 --- a/src/Domain/Insights/ComposerFinder.php +++ b/src/Domain/ComposerFinder.php @@ -2,9 +2,8 @@ declare(strict_types=1); -namespace NunoMaduro\PhpInsights\Domain\Insights; +namespace NunoMaduro\PhpInsights\Domain; -use NunoMaduro\PhpInsights\Domain\Collector; use NunoMaduro\PhpInsights\Domain\Exceptions\ComposerNotFound; final class ComposerFinder @@ -15,11 +14,16 @@ final class ComposerFinder * @return string */ public static function contents(Collector $collector): string + { + return (string) file_get_contents(self::getPath($collector)); + } + + public static function getPath(Collector $collector): string { $filePath = $collector->getDir() . '/composer.json'; if (file_exists($filePath)) { - return (string) file_get_contents($filePath); + return $filePath; } throw new ComposerNotFound('`composer.json` not found.'); diff --git a/src/Domain/ComposerLoader.php b/src/Domain/ComposerLoader.php new file mode 100644 index 00000000..3a49a3c8 --- /dev/null +++ b/src/Domain/ComposerLoader.php @@ -0,0 +1,21 @@ +collector); + + return ! $composer->getLocker()->isFresh(); + } catch (ComposerNotFound $exception) { + return true; + } + } + + public function getTitle(): string + { + return 'The lock file is not up to date with the latest changes in composer.json'; + } + + public function getDetails(): array + { + return ['You may be getting outdated dependencies. Run update to update them.']; + } +} diff --git a/src/Domain/Insights/Composer/ComposerMustBeValid.php b/src/Domain/Insights/Composer/ComposerMustBeValid.php new file mode 100644 index 00000000..7cb7008a --- /dev/null +++ b/src/Domain/Insights/Composer/ComposerMustBeValid.php @@ -0,0 +1,54 @@ + + */ + private $errors; + /** + * @var array + */ + private $publishErrors; + /** + * @var array + */ + private $warnings; + + public function hasIssue(): bool + { + $validator = new ConfigValidator(new NullIO()); + [$this->errors, $this->publishErrors, $this->warnings] = $validator->validate(ComposerFinder::getPath($this->collector)); + + return \count(\array_merge($this->errors, $this->publishErrors, $this->warnings)) > 0; + } + + public function getTitle(): string + { + return 'Composer.json is not valid'; + } + + public function getDetails(): array + { + $details = []; + + foreach (array_merge($this->errors, $this->publishErrors, $this->warnings) as $issue) { + if (strpos($issue, ' : ') !== false) { + $issue = explode(' : ', $issue)[1]; + } + $details[] = 'composer.json: ' . $issue; + } + + return $details; + } +} diff --git a/src/Domain/Insights/ComposerMustContainName.php b/src/Domain/Insights/Composer/ComposerMustContainName.php similarity index 82% rename from src/Domain/Insights/ComposerMustContainName.php rename to src/Domain/Insights/Composer/ComposerMustContainName.php index ce5d0ac1..84b3bef2 100644 --- a/src/Domain/Insights/ComposerMustContainName.php +++ b/src/Domain/Insights/Composer/ComposerMustContainName.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace NunoMaduro\PhpInsights\Domain\Insights; +namespace NunoMaduro\PhpInsights\Domain\Insights\Composer; +use NunoMaduro\PhpInsights\Domain\ComposerFinder; use NunoMaduro\PhpInsights\Domain\Exceptions\ComposerNotFound; +use NunoMaduro\PhpInsights\Domain\Insights\Insight; final class ComposerMustContainName extends Insight { diff --git a/src/Domain/Insights/ComposerMustExist.php b/src/Domain/Insights/Composer/ComposerMustExist.php similarity index 74% rename from src/Domain/Insights/ComposerMustExist.php rename to src/Domain/Insights/Composer/ComposerMustExist.php index 1873a915..0d53e610 100644 --- a/src/Domain/Insights/ComposerMustExist.php +++ b/src/Domain/Insights/Composer/ComposerMustExist.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace NunoMaduro\PhpInsights\Domain\Insights; +namespace NunoMaduro\PhpInsights\Domain\Insights\Composer; +use NunoMaduro\PhpInsights\Domain\ComposerFinder; use NunoMaduro\PhpInsights\Domain\Exceptions\ComposerNotFound; +use NunoMaduro\PhpInsights\Domain\Insights\Insight; final class ComposerMustExist extends Insight { diff --git a/src/Domain/Insights/Laravel/ComposerCheckLaravelVersion.php b/src/Domain/Insights/Laravel/ComposerCheckLaravelVersion.php index 132cc2c2..49d83cb9 100644 --- a/src/Domain/Insights/Laravel/ComposerCheckLaravelVersion.php +++ b/src/Domain/Insights/Laravel/ComposerCheckLaravelVersion.php @@ -4,8 +4,8 @@ namespace NunoMaduro\PhpInsights\Domain\Insights\Laravel; +use NunoMaduro\PhpInsights\Domain\ComposerFinder; use NunoMaduro\PhpInsights\Domain\Exceptions\ComposerNotFound; -use NunoMaduro\PhpInsights\Domain\Insights\ComposerFinder; use NunoMaduro\PhpInsights\Domain\Insights\Insight; final class ComposerCheckLaravelVersion extends Insight diff --git a/src/Domain/Metrics/Architecture/Composer.php b/src/Domain/Metrics/Architecture/Composer.php index 136de5dd..63b41729 100644 --- a/src/Domain/Metrics/Architecture/Composer.php +++ b/src/Domain/Metrics/Architecture/Composer.php @@ -5,8 +5,10 @@ namespace NunoMaduro\PhpInsights\Domain\Metrics\Architecture; use NunoMaduro\PhpInsights\Domain\Contracts\HasInsights; -use NunoMaduro\PhpInsights\Domain\Insights\ComposerMustContainName; -use NunoMaduro\PhpInsights\Domain\Insights\ComposerMustExist; +use NunoMaduro\PhpInsights\Domain\Insights\Composer\ComposerLockMustBeFresh; +use NunoMaduro\PhpInsights\Domain\Insights\Composer\ComposerMustBeValid; +use NunoMaduro\PhpInsights\Domain\Insights\Composer\ComposerMustContainName; +use NunoMaduro\PhpInsights\Domain\Insights\Composer\ComposerMustExist; final class Composer implements HasInsights { @@ -18,6 +20,8 @@ public function getInsights(): array return [ ComposerMustExist::class, ComposerMustContainName::class, + ComposerMustBeValid::class, + ComposerLockMustBeFresh::class, ]; } } diff --git a/tests/Domain/ComposerFinderTest.php b/tests/Domain/ComposerFinderTest.php new file mode 100644 index 00000000..a898605a --- /dev/null +++ b/tests/Domain/ComposerFinderTest.php @@ -0,0 +1,32 @@ +hasIssue()); + } + + public function testHasNoIssueWhenComposerLockUpToDate(): void + { + $collector = new Collector(__DIR__ . '/Fixtures/Fresh'); + $insight = new ComposerLockMustBeFresh($collector, []); + + self::assertFalse($insight->hasIssue()); + } +} diff --git a/tests/Domain/Insights/Composer/ComposerMustBeValidTest.php b/tests/Domain/Insights/Composer/ComposerMustBeValidTest.php new file mode 100644 index 00000000..27f32f67 --- /dev/null +++ b/tests/Domain/Insights/Composer/ComposerMustBeValidTest.php @@ -0,0 +1,32 @@ +hasIssue()); + self::assertIsArray($insight->getDetails()); + self::assertContains('composer.json: The property name is required', $insight->getDetails()); + self::assertContains('composer.json: The property description is required', $insight->getDetails()); + self::assertContains('composer.json: No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.', $insight->getDetails()); + } + + public function testComposerIsValid(): void + { + $collector = new Collector(__DIR__ . '/Fixtures/Valid'); + $insight = new ComposerMustBeValid($collector, []); + + self::assertFalse($insight->hasIssue()); + } +} diff --git a/tests/Domain/Insights/Composer/Fixtures/Fresh/composer.json b/tests/Domain/Insights/Composer/Fixtures/Fresh/composer.json new file mode 100644 index 00000000..1c275159 --- /dev/null +++ b/tests/Domain/Insights/Composer/Fixtures/Fresh/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "symfony/var-dumper": "^4.3" + } +} diff --git a/tests/Domain/Insights/Composer/Fixtures/Fresh/composer.lock b/tests/Domain/Insights/Composer/Fixtures/Fresh/composer.lock new file mode 100644 index 00000000..289f0382 --- /dev/null +++ b/tests/Domain/Insights/Composer/Fixtures/Fresh/composer.lock @@ -0,0 +1,208 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "7078e0184fb6a5ab492efd31a75f8f7d", + "packages": [ + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "f974f448154928d2b5fb7c412bd23b81d063f34b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f974f448154928d2b5fb7c412bd23b81d063f34b", + "reference": "f974f448154928d2b5fb7c412bd23b81d063f34b", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2019-06-05T02:08:12+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/tests/Domain/Insights/Composer/Fixtures/Unfresh/composer.json b/tests/Domain/Insights/Composer/Fixtures/Unfresh/composer.json new file mode 100644 index 00000000..2130003a --- /dev/null +++ b/tests/Domain/Insights/Composer/Fixtures/Unfresh/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "symfony/var-dumper": "4.3.0" + } +} diff --git a/tests/Domain/Insights/Composer/Fixtures/Unfresh/composer.lock b/tests/Domain/Insights/Composer/Fixtures/Unfresh/composer.lock new file mode 100644 index 00000000..d6954e34 --- /dev/null +++ b/tests/Domain/Insights/Composer/Fixtures/Unfresh/composer.lock @@ -0,0 +1,207 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d3672d861c131e1b163e498afc1a528c", + "packages": [ + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/bc88ad53e825ebacc7b190bbd360781fce381c64", + "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/process": "~3.4|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2018-04-29T07:56:09+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/tests/Domain/Insights/Composer/Fixtures/Valid/composer.json b/tests/Domain/Insights/Composer/Fixtures/Valid/composer.json new file mode 100644 index 00000000..d485d771 --- /dev/null +++ b/tests/Domain/Insights/Composer/Fixtures/Valid/composer.json @@ -0,0 +1,7 @@ +{ + "name": "dummy/foo-package", + "description": "Hey look at me", + "type": "project", + "license": "MIT", + "require": {} +}