diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a1bba52 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,105 @@ +name: CI + +on: + pull_request: + push: + branches: + - master + workflow_dispatch: + +jobs: + testsuite: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + php-version: ['7.2', '8.0'] + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: mbstring, intl, bcmath + coverage: pcov + + - name: Composer install + run: | + composer --version + composer install + + - name: Run PHPUnit + run: | + if [[ ${{ matrix.php-version }} == '7.2' ]]; then + vendor/bin/phpunit --coverage-clover=coverage.xml + else + vendor/bin/phpunit + fi + + - name: Code Coverage Report + if: success() && matrix.php-version == '7.2' + uses: codecov/codecov-action@v1 + + validation: + name: Coding Standard & Static Analysis + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + extensions: mbstring, intl, bcmath + coverage: none + + - name: Composer Install + run: composer install + + - name: Run phpstan + run: composer stan-setup && vendor/bin/phpstan analyse --error-format=github + + - name: Run phpcs + run: composer cs-check + + prefer-lowest: + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + php-version: [ + '7.2' + ] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: mbstring, intl, bcmath + coverage: none + + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Composer cache + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Composer prefer-lowest + run: | + composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable + + - name: Run PHPUnit + run: | + vendor/bin/phpunit diff --git a/.travis.yml b/.travis.yml index 5a18efe..c6e572b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: php php: - - 5.6 - - 7.4 + - 7.3 + - 8.0 env: global: diff --git a/README.md b/README.md index 4814e46..7067dc1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Composer Prefer Lowest Validator [![Build Status](https://api.travis-ci.org/dereuromark/composer-prefer-lowest.svg?branch=master)](https://travis-ci.org/dereuromark/composer-prefer-lowest) [![Latest Stable Version](https://poser.pugx.org/dereuromark/composer-prefer-lowest/v/stable.svg)](https://packagist.org/packages/dereuromark/composer-prefer-lowest) -[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.6-8892BF.svg)](https://php.net/) +[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg)](https://php.net/) [![License](https://poser.pugx.org/dereuromark/composer-prefer-lowest/license.svg)](https://packagist.org/packages/dereuromark/composer-prefer-lowest) [![Coding Standards](https://img.shields.io/badge/cs-PSR--2--R-yellow.svg)](https://github.com/php-fig-rectified/fig-rectified-standards) [![Total Downloads](https://poser.pugx.org/dereuromark/composer-prefer-lowest/d/total.svg)](https://packagist.org/packages/dereuromark/composer-prefer-lowest) diff --git a/composer.json b/composer.json index 281421a..ba62bd0 100644 --- a/composer.json +++ b/composer.json @@ -10,12 +10,12 @@ } ], "require": { - "php": ">=5.6", + "php": ">=7.2", "ext-json": "*", "composer/semver": "^1.4" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0 || ^7.0", + "phpunit/phpunit": "^8.5 || ^9.5", "fig-r/psr2r-sniffer": "dev-master" }, "autoload": { @@ -34,8 +34,8 @@ "prefer-stable": true, "scripts": { "test": "phpunit", - "phpstan": "phpstan analyse -c tests/phpstan.neon -l 5 src/", - "phpstan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan && mv composer.backup composer.json", + "stan": "phpstan analyse", + "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan && mv composer.backup composer.json", "cs-check": "phpcs -p --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --extensions=php src/", "cs-fix": "phpcbf -v --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --extensions=php src/" }, diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..2debae0 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + level: 8 + paths: + - src/ + + bootstrapFiles: + - tests/bootstrap.php + + checkMissingIterableValueType: false diff --git a/src/Validator.php b/src/Validator.php index 192add6..6a383fa 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -5,35 +5,39 @@ use Composer\Semver\Comparator; use Composer\Semver\Semver; use Composer\Semver\VersionParser; +use RuntimeException; class Validator { - const CODE_SUCCESS = 0; - const CODE_ERROR = 1; + public const CODE_SUCCESS = 0; + public const CODE_ERROR = 1; - const MAJORS_ONLY = 'majors-only'; - const MAJORS_ONLY_SHORT = 'm'; + public const MAJORS_ONLY = 'majors-only'; + public const MAJORS_ONLY_SHORT = 'm'; /** * @param string $path * @param string[] $options * @return int Returns 0 on success, otherwise error code. */ - public function validate($path, array $options = []) { + public function validate(string $path, array $options = []): int { if (!$path) { echo 'Path to composer.lock file not found' . PHP_EOL; + return static::CODE_ERROR; } $lockFilePath = $path . 'composer.lock'; if (!file_exists($lockFilePath)) { echo 'composer.lock file not found: ' . $lockFilePath . PHP_EOL; + return static::CODE_ERROR; } $jsonFilePath = $path . 'composer.json'; if (!file_exists($jsonFilePath)) { echo 'composer.json file not found: ' . $jsonFilePath . PHP_EOL; + return static::CODE_ERROR; } @@ -49,11 +53,12 @@ public function validate($path, array $options = []) { * * @return int */ - protected function compare($lockFile, $jsonFile, array $options) { + protected function compare(string $lockFile, string $jsonFile, array $options): int { $jsonInfo = $this->parseJsonFromFile($jsonFile); $lockInfo = $jsonInfo !== null ? $this->parseLockFromFile($lockFile, $jsonInfo) : null; if ($jsonInfo === null || $lockInfo === null) { echo 'Make sure composer.json and composer.lock files are valid and that you have at least one dependency in require.'; + return static::CODE_ERROR; } @@ -103,7 +108,7 @@ protected function compare($lockFile, $jsonFile, array $options) { * * @return string */ - protected function definedMinimum(array $jsonInfo, $package) { + protected function definedMinimum(array $jsonInfo, string $package): string { $constraints = $jsonInfo[$package]['version']; // We only need the first $constraint = (new MinimumVersionParser())->parseConstraints($constraints); @@ -113,10 +118,14 @@ protected function definedMinimum(array $jsonInfo, $package) { /** * @param string $jsonFile + * @throws \RuntimeException * @return array|null */ - protected function parseJsonFromFile($jsonFile) { + protected function parseJsonFromFile(string $jsonFile): ?array { $content = file_get_contents($jsonFile); + if ($content === false) { + throw new RuntimeException('Cannot read file: ' . $jsonFile); + } $json = json_decode($content, true); if (!$json || empty($json['require'])) { @@ -154,7 +163,7 @@ protected function parseJsonFromFile($jsonFile) { * * @return string */ - protected function stripVersion($version) { + protected function stripVersion(string $version): string { $from = [ '>=', '^', @@ -167,10 +176,14 @@ protected function stripVersion($version) { /** * @param string $lockFile * @param array $jsonInfo + * @throws \RuntimeException * @return array|null */ - protected function parseLockFromFile($lockFile, array $jsonInfo) { + protected function parseLockFromFile(string $lockFile, array $jsonInfo): ?array { $content = file_get_contents($lockFile); + if ($content === false) { + throw new RuntimeException('Cannot read file: ' . $lockFile); + } $json = json_decode($content, true); if (!$json) { return null; @@ -201,7 +214,7 @@ protected function parseLockFromFile($lockFile, array $jsonInfo) { * @param string $version * @return string */ - protected function normalizeVersion($version) { + protected function normalizeVersion(string $version): string { $version = (new VersionParser())->normalize($version); if (strpos($version, '-') !== false) { @@ -216,7 +229,7 @@ protected function normalizeVersion($version) { * * @return array */ - protected function parseOptions(array $options) { + protected function parseOptions(array $options): array { $result = [ static::MAJORS_ONLY => false, ]; @@ -234,7 +247,7 @@ protected function parseOptions(array $options) { * * @return bool */ - protected function isAllowedNonMajor($definedMinimum, $version, array $options) { + protected function isAllowedNonMajor(string $definedMinimum, string $version, array $options): bool { if (!$options[static::MAJORS_ONLY]) { return false; } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 66cdd18..33423dd 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,7 @@