diff --git a/.github/workflows/infection.yml b/.github/workflows/infection.yml
new file mode 100644
index 00000000..bd298a49
--- /dev/null
+++ b/.github/workflows/infection.yml
@@ -0,0 +1,69 @@
+# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
+
+name: "Mutation tests"
+
+on:
+ pull_request:
+ push:
+ branches:
+ - "master"
+
+jobs:
+ mutation-tests:
+ name: "Mutation tests"
+
+ runs-on: ${{ matrix.operating-system }}
+
+ strategy:
+ matrix:
+ dependencies:
+ - "locked"
+ php-version:
+ - "7.4"
+ - "8.0"
+ - "8.1"
+ operating-system:
+ - "ubuntu-latest"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ coverage: "pcov"
+ php-version: "${{ matrix.php-version }}"
+ ini-values: memory_limit=-1, zend.assertions=1
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "::set-output name=dir::$(composer config cache-dir)"
+
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('composer.*') }}-${{ matrix.dependencies }}
+ restore-keys: |
+ composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('composer.*') }}-
+ composer-${{ runner.os }}-${{ matrix.php-version }}-
+ composer-${{ runner.os }}-
+ composer-
+
+ - name: "Install lowest dependencies"
+ if: ${{ matrix.dependencies == 'lowest' }}
+ run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
+
+ - name: "Install highest dependencies"
+ if: ${{ matrix.dependencies == 'highest' }}
+ run: "composer update --no-interaction --no-progress --no-suggest"
+
+ - name: "Install locked dependencies"
+ if: ${{ matrix.dependencies == 'locked' }}
+ run: "composer install --no-interaction --no-progress --no-suggest"
+
+ - name: "Infection"
+ run: "vendor/bin/roave-infection-static-analysis-plugin --threads=$(nproc)"
+ env:
+ INFECTION_BADGE_API_KEY: ${{ secrets.INFECTION_BADGE_API_KEY }}
diff --git a/baseline.xml b/baseline.xml
index 55184d2c..cd52ff27 100644
--- a/baseline.xml
+++ b/baseline.xml
@@ -3,17 +3,4 @@
-
-
- $composerData['autoload']['classmap'] ?? []
- $composerData['autoload']['files'] ?? []
- $composerData['autoload']['psr-0'] ?? []
- $composerData['autoload']['psr-4'] ?? []
-
-
-
-
- $node->namespacedName
-
-
diff --git a/composer.json b/composer.json
index 5ab3d463..9849c059 100644
--- a/composer.json
+++ b/composer.json
@@ -42,6 +42,7 @@
"phing/phing": "^2.17.0",
"phpstan/phpstan": "^1.2.0",
"phpunit/phpunit": "^9.5.10",
+ "roave/infection-static-analysis-plugin": "^1.12",
"vimeo/psalm": "^4.14.0"
},
"config": {
diff --git a/composer.lock b/composer.lock
index 94ca4532..ca71eb2b 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f51ef8e7a2d6331dedb99df3642a6f75",
+ "content-hash": "24e6e5d8a8f6904bc7d25d91c7432e35",
"packages": [
{
"name": "nikic/php-parser",
@@ -1808,6 +1808,369 @@
},
"time": "2021-02-22T14:02:09+00:00"
},
+ {
+ "name": "infection/abstract-testframework-adapter",
+ "version": "0.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/infection/abstract-testframework-adapter.git",
+ "reference": "18925e20d15d1a5995bb85c9dc09e8751e1e069b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/infection/abstract-testframework-adapter/zipball/18925e20d15d1a5995bb85c9dc09e8751e1e069b",
+ "reference": "18925e20d15d1a5995bb85c9dc09e8751e1e069b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "ergebnis/composer-normalize": "^2.8",
+ "friendsofphp/php-cs-fixer": "^2.17",
+ "phpunit/phpunit": "^9.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Infection\\AbstractTestFramework\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Maks Rafalko",
+ "email": "maks.rafalko@gmail.com"
+ }
+ ],
+ "description": "Abstract Test Framework Adapter for Infection",
+ "support": {
+ "issues": "https://github.com/infection/abstract-testframework-adapter/issues",
+ "source": "https://github.com/infection/abstract-testframework-adapter/tree/0.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/infection",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/infection",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2021-08-17T18:49:12+00:00"
+ },
+ {
+ "name": "infection/extension-installer",
+ "version": "0.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/infection/extension-installer.git",
+ "reference": "ff30c0adffcdbc747c96adf92382ccbe271d0afd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/infection/extension-installer/zipball/ff30c0adffcdbc747c96adf92382ccbe271d0afd",
+ "reference": "ff30c0adffcdbc747c96adf92382ccbe271d0afd",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.1 || ^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.9",
+ "friendsofphp/php-cs-fixer": "^2.16",
+ "infection/infection": "^0.15.2",
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.12.10",
+ "phpstan/phpstan-phpunit": "^0.12.6",
+ "phpstan/phpstan-strict-rules": "^0.12.2",
+ "phpstan/phpstan-webmozart-assert": "^0.12.2",
+ "phpunit/phpunit": "^8.5",
+ "vimeo/psalm": "^3.8"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Infection\\ExtensionInstaller\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Infection\\ExtensionInstaller\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Maks Rafalko",
+ "email": "maks.rafalko@gmail.com"
+ }
+ ],
+ "description": "Infection Extension Installer",
+ "support": {
+ "issues": "https://github.com/infection/extension-installer/issues",
+ "source": "https://github.com/infection/extension-installer/tree/0.1.1"
+ },
+ "time": "2020-04-25T22:40:05+00:00"
+ },
+ {
+ "name": "infection/include-interceptor",
+ "version": "0.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/infection/include-interceptor.git",
+ "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/infection/include-interceptor/zipball/0cc76d95a79d9832d74e74492b0a30139904bdf7",
+ "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7",
+ "shasum": ""
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.16",
+ "infection/infection": "^0.15.0",
+ "phan/phan": "^2.4 || ^3",
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpstan/phpstan": "^0.12.8",
+ "phpunit/phpunit": "^8.5",
+ "vimeo/psalm": "^3.8"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Infection\\StreamWrapper\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Maks Rafalko",
+ "email": "maks.rafalko@gmail.com"
+ }
+ ],
+ "description": "Stream Wrapper: Include Interceptor. Allows to replace included (autoloaded) file with another one.",
+ "support": {
+ "issues": "https://github.com/infection/include-interceptor/issues",
+ "source": "https://github.com/infection/include-interceptor/tree/0.2.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/infection",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/infection",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2021-08-09T10:03:57+00:00"
+ },
+ {
+ "name": "infection/infection",
+ "version": "0.25.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/infection/infection.git",
+ "reference": "f7a1af285476eeef7e72cedcfd69fa11fbb61c71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/infection/infection/zipball/f7a1af285476eeef7e72cedcfd69fa11fbb61c71",
+ "reference": "f7a1af285476eeef7e72cedcfd69fa11fbb61c71",
+ "shasum": ""
+ },
+ "require": {
+ "composer-runtime-api": "^2.0",
+ "composer/xdebug-handler": "^2.0",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "infection/abstract-testframework-adapter": "^0.5.0",
+ "infection/extension-installer": "^0.1.0",
+ "infection/include-interceptor": "^0.2.5",
+ "justinrainbow/json-schema": "^5.2.10",
+ "nikic/php-parser": "^4.10.3",
+ "ondram/ci-detector": "^3.3.0",
+ "php": "^7.4.7 || ^8.0",
+ "sanmai/later": "^0.1.1",
+ "sanmai/pipeline": "^5.1",
+ "sebastian/diff": "^3.0.2 || ^4.0",
+ "seld/jsonlint": "^1.7",
+ "symfony/console": "^3.4.29 || ^4.1.19 || ^5.0",
+ "symfony/filesystem": "^3.4.29 || ^4.1.19 || ^5.0",
+ "symfony/finder": "^3.4.29 || ^4.1.19 || ^5.0",
+ "symfony/process": "^3.4.29 || ^4.1.19 || ^5.0",
+ "thecodingmachine/safe": "^1.1.3",
+ "webmozart/assert": "^1.3",
+ "webmozart/path-util": "^2.3"
+ },
+ "conflict": {
+ "phpunit/php-code-coverage": ">9 <9.1.4",
+ "symfony/console": "=4.1.5"
+ },
+ "require-dev": {
+ "brianium/paratest": "^6.3",
+ "ext-simplexml": "*",
+ "helmich/phpunit-json-assert": "^3.0",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.12.8",
+ "phpstan/phpstan-phpunit": "^0.12.6",
+ "phpstan/phpstan-strict-rules": "^0.12.5",
+ "phpstan/phpstan-webmozart-assert": "^0.12.2",
+ "phpunit/phpunit": "^9.3.11",
+ "symfony/phpunit-bridge": "^4.4.18 || ^5.1.10",
+ "symfony/yaml": "^5.0",
+ "thecodingmachine/phpstan-safe-rule": "^1.0"
+ },
+ "bin": [
+ "bin/infection"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Infection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Maks Rafalko",
+ "email": "maks.rafalko@gmail.com",
+ "homepage": "https://twitter.com/maks_rafalko"
+ },
+ {
+ "name": "Oleg Zhulnev",
+ "homepage": "https://github.com/sidz"
+ },
+ {
+ "name": "Gert de Pagter",
+ "homepage": "https://github.com/BackEndTea"
+ },
+ {
+ "name": "Théo FIDRY",
+ "email": "theo.fidry@gmail.com",
+ "homepage": "https://twitter.com/tfidry"
+ },
+ {
+ "name": "Alexey Kopytko",
+ "email": "alexey@kopytko.com",
+ "homepage": "https://www.alexeykopytko.com"
+ },
+ {
+ "name": "Andreas Möller",
+ "email": "am@localheinz.com",
+ "homepage": "https://localheinz.com"
+ }
+ ],
+ "description": "Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.",
+ "keywords": [
+ "coverage",
+ "mutant",
+ "mutation framework",
+ "mutation testing",
+ "testing",
+ "unit testing"
+ ],
+ "support": {
+ "issues": "https://github.com/infection/infection/issues",
+ "source": "https://github.com/infection/infection/tree/0.25.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/infection",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/infection",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2021-10-02T13:16:05+00:00"
+ },
+ {
+ "name": "justinrainbow/json-schema",
+ "version": "5.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/justinrainbow/json-schema.git",
+ "reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ab6744b7296ded80f8cc4f9509abbff393399aa",
+ "reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
+ "json-schema/json-schema-test-suite": "1.2.0",
+ "phpunit/phpunit": "^4.8.35"
+ },
+ "bin": [
+ "bin/validate-json"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "JsonSchema\\": "src/JsonSchema/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bruno Prieto Reis",
+ "email": "bruno.p.reis@gmail.com"
+ },
+ {
+ "name": "Justin Rainbow",
+ "email": "justin.rainbow@gmail.com"
+ },
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ },
+ {
+ "name": "Robert Schönthal",
+ "email": "seroscho@googlemail.com"
+ }
+ ],
+ "description": "A library to validate a json schema.",
+ "homepage": "https://github.com/justinrainbow/json-schema",
+ "keywords": [
+ "json",
+ "schema"
+ ],
+ "support": {
+ "issues": "https://github.com/justinrainbow/json-schema/issues",
+ "source": "https://github.com/justinrainbow/json-schema/tree/5.2.11"
+ },
+ "time": "2021-07-22T09:24:00+00:00"
+ },
{
"name": "mikey179/vfsstream",
"version": "v1.6.10",
@@ -1968,6 +2331,78 @@
},
"time": "2020-12-01T19:48:11+00:00"
},
+ {
+ "name": "ondram/ci-detector",
+ "version": "3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/OndraM/ci-detector.git",
+ "reference": "594e61252843b68998bddd48078c5058fe9028bd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/OndraM/ci-detector/zipball/594e61252843b68998bddd48078c5058fe9028bd",
+ "reference": "594e61252843b68998bddd48078c5058fe9028bd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "ergebnis/composer-normalize": "^2.2",
+ "lmc/coding-standard": "^1.3 || ^2.0",
+ "php-parallel-lint/php-parallel-lint": "^1.1",
+ "phpstan/extension-installer": "^1.0.3",
+ "phpstan/phpstan": "^0.12.0",
+ "phpstan/phpstan-phpunit": "^0.12.1",
+ "phpunit/phpunit": "^7.1 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "OndraM\\CiDetector\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ondřej Machulda",
+ "email": "ondrej.machulda@gmail.com"
+ }
+ ],
+ "description": "Detect continuous integration environment and provide unified access to properties of current build",
+ "keywords": [
+ "CircleCI",
+ "Codeship",
+ "Wercker",
+ "adapter",
+ "appveyor",
+ "aws",
+ "aws codebuild",
+ "bamboo",
+ "bitbucket",
+ "buddy",
+ "ci-info",
+ "codebuild",
+ "continuous integration",
+ "continuousphp",
+ "drone",
+ "github",
+ "gitlab",
+ "interface",
+ "jenkins",
+ "teamcity",
+ "travis"
+ ],
+ "support": {
+ "issues": "https://github.com/OndraM/ci-detector/issues",
+ "source": "https://github.com/OndraM/ci-detector/tree/main"
+ },
+ "time": "2020-09-04T11:21:14+00:00"
+ },
{
"name": "openlss/lib-array2xml",
"version": "1.0.0",
@@ -3056,44 +3491,218 @@
"time": "2021-05-03T11:20:27+00:00"
},
{
- "name": "sebastian/cli-parser",
- "version": "1.0.1",
+ "name": "roave/infection-static-analysis-plugin",
+ "version": "1.12.0",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/cli-parser.git",
- "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+ "url": "https://github.com/Roave/infection-static-analysis-plugin.git",
+ "reference": "8390a8ab36a4b73eef8ce3c270964c21e3c831b1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
- "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "url": "https://api.github.com/repos/Roave/infection-static-analysis-plugin/zipball/8390a8ab36a4b73eef8ce3c270964c21e3c831b1",
+ "reference": "8390a8ab36a4b73eef8ce3c270964c21e3c831b1",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "infection/infection": "0.25.3",
+ "ocramius/package-versions": "^1.9.0 || ^2.0.0",
+ "php": "~7.4.7|~8.0.0|~8.1.0",
+ "sanmai/later": "^0.1.2",
+ "vimeo/psalm": "^4.13.1"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "doctrine/coding-standard": "^9.0.0",
+ "phpunit/phpunit": "^9.5.9"
},
+ "bin": [
+ "bin/roave-infection-static-analysis-plugin"
+ ],
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
"autoload": {
- "classmap": [
- "src/"
- ]
+ "psr-4": {
+ "Roave\\InfectionStaticAnalysis\\": "src/Roave/InfectionStaticAnalysis"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
+ "MIT"
],
"authors": [
{
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "Static analysis on top of mutation testing - prevents escaped mutants from being invalid according to static analysis",
+ "support": {
+ "issues": "https://github.com/Roave/infection-static-analysis-plugin/issues",
+ "source": "https://github.com/Roave/infection-static-analysis-plugin/tree/1.12.0"
+ },
+ "time": "2021-11-30T01:18:25+00:00"
+ },
+ {
+ "name": "sanmai/later",
+ "version": "0.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sanmai/later.git",
+ "reference": "9b659fecef2030193fd02402955bc39629d5606f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sanmai/later/zipball/9b659fecef2030193fd02402955bc39629d5606f",
+ "reference": "9b659fecef2030193fd02402955bc39629d5606f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.13",
+ "infection/infection": ">=0.10.5",
+ "phan/phan": ">=2",
+ "php-coveralls/php-coveralls": "^2.0",
+ "phpstan/phpstan": ">=0.10",
+ "phpunit/phpunit": ">=7.4",
+ "vimeo/psalm": ">=2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Later\\": "src/"
+ },
+ "files": [
+ "src/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Alexey Kopytko",
+ "email": "alexey@kopytko.com"
+ }
+ ],
+ "description": "Later: deferred wrapper object",
+ "support": {
+ "issues": "https://github.com/sanmai/later/issues",
+ "source": "https://github.com/sanmai/later/tree/0.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sanmai",
+ "type": "github"
+ }
+ ],
+ "time": "2021-01-02T10:26:44+00:00"
+ },
+ {
+ "name": "sanmai/pipeline",
+ "version": "v5.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sanmai/pipeline.git",
+ "reference": "2b5509a7635143165041109eb1c393c8515724f1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sanmai/pipeline/zipball/2b5509a7635143165041109eb1c393c8515724f1",
+ "reference": "2b5509a7635143165041109eb1c393c8515724f1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "ergebnis/composer-normalize": "^2.8",
+ "friendsofphp/php-cs-fixer": "^3",
+ "infection/infection": ">=0.10.5",
+ "league/pipeline": "^1.0 || ^0.3",
+ "phan/phan": ">=1.1",
+ "php-coveralls/php-coveralls": "^2.4.1",
+ "phpstan/phpstan": ">=0.10",
+ "phpunit/phpunit": "^7.4 || ^8.1 || ^9.4",
+ "vimeo/psalm": ">=2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "v5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Pipeline\\": "src/"
+ },
+ "files": [
+ "src/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Alexey Kopytko",
+ "email": "alexey@kopytko.com"
+ }
+ ],
+ "description": "General-purpose collections pipeline",
+ "support": {
+ "issues": "https://github.com/sanmai/pipeline/issues",
+ "source": "https://github.com/sanmai/pipeline/tree/v5.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sanmai",
+ "type": "github"
+ }
+ ],
+ "time": "2021-11-01T10:09:55+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
@@ -4019,6 +4628,69 @@
],
"time": "2020-09-28T06:39:44+00:00"
},
+ {
+ "name": "seld/jsonlint",
+ "version": "1.8.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/jsonlint.git",
+ "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9ad6ce79c342fbd44df10ea95511a1b24dee5b57",
+ "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ },
+ "bin": [
+ "bin/jsonlint"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Seld\\JsonLint\\": "src/Seld/JsonLint/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "JSON Linter",
+ "keywords": [
+ "json",
+ "linter",
+ "parser",
+ "validator"
+ ],
+ "support": {
+ "issues": "https://github.com/Seldaek/jsonlint/issues",
+ "source": "https://github.com/Seldaek/jsonlint/tree/1.8.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Seldaek",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-11T09:19:24+00:00"
+ },
{
"name": "slevomat/coding-standard",
"version": "7.0.16",
@@ -4136,6 +4808,334 @@
},
"time": "2021-10-11T04:00:11+00:00"
},
+ {
+ "name": "symfony/filesystem",
+ "version": "v5.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "731f917dc31edcffec2c6a777f3698c33bea8f01"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/731f917dc31edcffec2c6a777f3698c33bea8f01",
+ "reference": "731f917dc31edcffec2c6a777f3698c33bea8f01",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v5.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-28T13:39:27+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v5.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "d2f29dac98e96a98be467627bd49c2efb1bc2590"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/d2f29dac98e96a98be467627bd49c2efb1bc2590",
+ "reference": "d2f29dac98e96a98be467627bd49c2efb1bc2590",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/finder/tree/v5.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-11-28T15:25:38+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v5.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "5be20b3830f726e019162b26223110c8f47cf274"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/5be20b3830f726e019162b26223110c8f47cf274",
+ "reference": "5be20b3830f726e019162b26223110c8f47cf274",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v5.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-11-28T15:25:38+00:00"
+ },
+ {
+ "name": "thecodingmachine/safe",
+ "version": "v1.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thecodingmachine/safe.git",
+ "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
+ "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12",
+ "squizlabs/php_codesniffer": "^3.2",
+ "thecodingmachine/phpstan-strict-rules": "^0.12"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Safe\\": [
+ "lib/",
+ "deprecated/",
+ "generated/"
+ ]
+ },
+ "files": [
+ "deprecated/apc.php",
+ "deprecated/libevent.php",
+ "deprecated/mssql.php",
+ "deprecated/stats.php",
+ "lib/special_cases.php",
+ "generated/apache.php",
+ "generated/apcu.php",
+ "generated/array.php",
+ "generated/bzip2.php",
+ "generated/calendar.php",
+ "generated/classobj.php",
+ "generated/com.php",
+ "generated/cubrid.php",
+ "generated/curl.php",
+ "generated/datetime.php",
+ "generated/dir.php",
+ "generated/eio.php",
+ "generated/errorfunc.php",
+ "generated/exec.php",
+ "generated/fileinfo.php",
+ "generated/filesystem.php",
+ "generated/filter.php",
+ "generated/fpm.php",
+ "generated/ftp.php",
+ "generated/funchand.php",
+ "generated/gmp.php",
+ "generated/gnupg.php",
+ "generated/hash.php",
+ "generated/ibase.php",
+ "generated/ibmDb2.php",
+ "generated/iconv.php",
+ "generated/image.php",
+ "generated/imap.php",
+ "generated/info.php",
+ "generated/ingres-ii.php",
+ "generated/inotify.php",
+ "generated/json.php",
+ "generated/ldap.php",
+ "generated/libxml.php",
+ "generated/lzf.php",
+ "generated/mailparse.php",
+ "generated/mbstring.php",
+ "generated/misc.php",
+ "generated/msql.php",
+ "generated/mysql.php",
+ "generated/mysqli.php",
+ "generated/mysqlndMs.php",
+ "generated/mysqlndQc.php",
+ "generated/network.php",
+ "generated/oci8.php",
+ "generated/opcache.php",
+ "generated/openssl.php",
+ "generated/outcontrol.php",
+ "generated/password.php",
+ "generated/pcntl.php",
+ "generated/pcre.php",
+ "generated/pdf.php",
+ "generated/pgsql.php",
+ "generated/posix.php",
+ "generated/ps.php",
+ "generated/pspell.php",
+ "generated/readline.php",
+ "generated/rpminfo.php",
+ "generated/rrd.php",
+ "generated/sem.php",
+ "generated/session.php",
+ "generated/shmop.php",
+ "generated/simplexml.php",
+ "generated/sockets.php",
+ "generated/sodium.php",
+ "generated/solr.php",
+ "generated/spl.php",
+ "generated/sqlsrv.php",
+ "generated/ssdeep.php",
+ "generated/ssh2.php",
+ "generated/stream.php",
+ "generated/strings.php",
+ "generated/swoole.php",
+ "generated/uodbc.php",
+ "generated/uopz.php",
+ "generated/url.php",
+ "generated/var.php",
+ "generated/xdiff.php",
+ "generated/xml.php",
+ "generated/xmlrpc.php",
+ "generated/yaml.php",
+ "generated/yaz.php",
+ "generated/zip.php",
+ "generated/zlib.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHP core functions that throw exceptions instead of returning FALSE on error",
+ "support": {
+ "issues": "https://github.com/thecodingmachine/safe/issues",
+ "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3"
+ },
+ "time": "2020-10-28T17:51:34+00:00"
+ },
{
"name": "theseer/tokenizer",
"version": "1.2.1",
diff --git a/infection.json.dist b/infection.json.dist
new file mode 100644
index 00000000..c35e8541
--- /dev/null
+++ b/infection.json.dist
@@ -0,0 +1,17 @@
+{
+ "$schema": "vendor/infection/infection/resources/schema.json",
+ "source": {
+ "directories": [
+ "src"
+ ]
+ },
+ "timeout": 30,
+ "logs": {
+ "text": "php://stderr"
+ },
+ "mutators": {
+ "@default": true
+ },
+ "minMsi": 95,
+ "minCoveredMsi": 95
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 9a5a7fbe..a4cbdb6a 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -13,12 +13,12 @@
processIsolation="false"
backupGlobals="false"
>
-
- ./test/ComposerRequireCheckerTest
-
-
-
- ./src
-
-
+
+ ./test/ComposerRequireCheckerTest
+
+
+
+ ./src
+
+
diff --git a/src/ComposerRequireChecker/Cli/ApplicationHeaderWriter.php b/src/ComposerRequireChecker/Cli/ApplicationHeaderWriter.php
new file mode 100644
index 00000000..3fec2ad0
--- /dev/null
+++ b/src/ComposerRequireChecker/Cli/ApplicationHeaderWriter.php
@@ -0,0 +1,33 @@
+application = $application;
+ }
+
+ public function __invoke(OutputInterface $output): void
+ {
+ if ($output->isQuiet()) {
+ return;
+ }
+
+ if ($this->application === null) {
+ $output->writeln('Unknown version');
+
+ return;
+ }
+
+ $output->writeln($this->application->getLongVersion());
+ }
+}
diff --git a/src/ComposerRequireChecker/Cli/CheckCommand.php b/src/ComposerRequireChecker/Cli/CheckCommand.php
index 7eee6d27..96caf6e4 100644
--- a/src/ComposerRequireChecker/Cli/CheckCommand.php
+++ b/src/ComposerRequireChecker/Cli/CheckCommand.php
@@ -31,7 +31,6 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-use Webmozart\Assert\Assert;
use function array_combine;
use function array_diff;
@@ -46,6 +45,9 @@
use function realpath;
use function sprintf;
+/**
+ * @psalm-import-type ComposerData from LocateComposerPackageSourceFiles
+ */
class CheckCommand extends Command
{
public const NAME = 'check';
@@ -104,10 +106,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
}
- if (! $output->isQuiet()) {
- $application = $this->getApplication();
- $output->writeln($application !== null ? $application->getLongVersion() : 'Unknown version');
- }
+ (new ApplicationHeaderWriter($this->getApplication()))->__invoke($output);
$composerJsonArgument = $input->getArgument('composer-json');
@@ -178,13 +177,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$options->getSymbolWhitelist()
);
+ // pcov which is used for coverage does not detect executed code in anonymous functions used as callable
+ // therefore we require to have closure class.
+ $outputWrapper = new class ($output) {
+ private OutputInterface $output;
+
+ public function __construct(OutputInterface $output)
+ {
+ $this->output = $output;
+ }
+
+ public function __invoke(string $string): void
+ {
+ $this->output->write($string, false, OutputInterface::VERBOSITY_QUIET);
+ }
+ };
+
switch ($input->getOption('output')) {
case 'json':
$application = $this->getApplication();
$resultsWriter = new CliJson(
- static function (string $string) use ($output): void {
- $output->write($string, false, OutputInterface::VERBOSITY_QUIET | OutputInterface::OUTPUT_RAW);
- },
+ $outputWrapper,
$application !== null ? $application->getVersion() : 'Unknown version',
static fn () => new DateTimeImmutable()
);
@@ -192,9 +205,7 @@ static function (string $string) use ($output): void {
case 'text':
$resultsWriter = new CliText(
$output,
- static function (string $string) use ($output): void {
- $output->write($string, false, OutputInterface::VERBOSITY_QUIET | OutputInterface::OUTPUT_RAW);
- }
+ $outputWrapper
);
break;
default:
@@ -232,13 +243,13 @@ private function getCheckOptions(InputInterface $input): Options
}
$config = JsonLoader::getData($fileName);
- Assert::isMap($config);
return new Options($config);
}
/**
* @return array
+ * @psalm-return ComposerData
*
* @throws InvalidJson
* @throws NotReadable
diff --git a/src/ComposerRequireChecker/Cli/Options.php b/src/ComposerRequireChecker/Cli/Options.php
index 351f984e..4f4b159a 100644
--- a/src/ComposerRequireChecker/Cli/Options.php
+++ b/src/ComposerRequireChecker/Cli/Options.php
@@ -4,13 +4,16 @@
namespace ComposerRequireChecker\Cli;
+use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles;
use InvalidArgumentException;
use function method_exists;
use function str_replace;
-use function ucfirst;
use function ucwords;
+/**
+ * @psalm-import-type ComposerData from LocateComposerPackageSourceFiles
+ */
class Options
{
/** @var array */
@@ -122,6 +125,6 @@ public function setScanFiles(array $scanFiles): void
private function getCamelCase(string $string): string
{
- return ucfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $string))));
+ return str_replace(' ', '', ucwords(str_replace('-', ' ', $string)));
}
}
diff --git a/src/ComposerRequireChecker/DefinedExtensionsResolver/DefinedExtensionsResolver.php b/src/ComposerRequireChecker/DefinedExtensionsResolver/DefinedExtensionsResolver.php
index e76dd36f..1b1be2c7 100644
--- a/src/ComposerRequireChecker/DefinedExtensionsResolver/DefinedExtensionsResolver.php
+++ b/src/ComposerRequireChecker/DefinedExtensionsResolver/DefinedExtensionsResolver.php
@@ -4,6 +4,7 @@
namespace ComposerRequireChecker\DefinedExtensionsResolver;
+use function array_keys;
use function array_merge;
use function file_get_contents;
use function json_decode;
@@ -25,7 +26,7 @@ public function __invoke(string $composerJson, array $phpCoreExtensions = []): a
$extensions = [];
$addPhpCoreExtensions = false;
- foreach ($requires as $require => $version) {
+ foreach (array_keys($requires) as $require) {
if ($require === 'php' || $require === 'php-64bit') {
$addPhpCoreExtensions = true;
continue;
diff --git a/src/ComposerRequireChecker/DefinedSymbolsLocator/LocateDefinedSymbolsFromASTRoots.php b/src/ComposerRequireChecker/DefinedSymbolsLocator/LocateDefinedSymbolsFromASTRoots.php
index 70baada9..4ae61526 100644
--- a/src/ComposerRequireChecker/DefinedSymbolsLocator/LocateDefinedSymbolsFromASTRoots.php
+++ b/src/ComposerRequireChecker/DefinedSymbolsLocator/LocateDefinedSymbolsFromASTRoots.php
@@ -19,7 +19,7 @@ final class LocateDefinedSymbolsFromASTRoots
/**
* @param Traversable> $ASTs a series of AST roots
*
- * @return string[] all the found symbols
+ * @return list all the found symbols
*/
public function __invoke(Traversable $ASTs): array
{
diff --git a/src/ComposerRequireChecker/Exception/FileParseFailed.php b/src/ComposerRequireChecker/Exception/FileParseFailed.php
index cd51114f..a1acb8fb 100755
--- a/src/ComposerRequireChecker/Exception/FileParseFailed.php
+++ b/src/ComposerRequireChecker/Exception/FileParseFailed.php
@@ -13,7 +13,12 @@ class FileParseFailed extends RuntimeException
{
public function __construct(string $file, ?Throwable $previous = null)
{
- $msg = sprintf('Parsing the file [%s] resulted in an error: %s', $file, $previous->getMessage());
+ $msg = sprintf(
+ 'Parsing the file [%s] resulted in an error: %s',
+ $file,
+ $previous !== null ? $previous->getMessage() : ''
+ );
+
parent::__construct($msg, 0, $previous);
}
}
diff --git a/src/ComposerRequireChecker/FileLocator/LocateComposerPackageDirectDependenciesSourceFiles.php b/src/ComposerRequireChecker/FileLocator/LocateComposerPackageDirectDependenciesSourceFiles.php
index 3ca69575..c29acb82 100644
--- a/src/ComposerRequireChecker/FileLocator/LocateComposerPackageDirectDependenciesSourceFiles.php
+++ b/src/ComposerRequireChecker/FileLocator/LocateComposerPackageDirectDependenciesSourceFiles.php
@@ -10,10 +10,12 @@
use Generator;
use function array_key_exists;
-use function assert;
+use function array_keys;
use function dirname;
-use function is_string;
+/**
+ * @psalm-import-type ComposerData from LocateComposerPackageSourceFiles
+ */
final class LocateComposerPackageDirectDependenciesSourceFiles
{
public function __invoke(string $composerJsonPath): Generator
@@ -22,14 +24,9 @@ public function __invoke(string $composerJsonPath): Generator
$composerJson = JsonLoader::getData($composerJsonPath);
$configVendorDir = $composerJson['config']['vendor-dir'] ?? 'vendor';
- assert(is_string($configVendorDir));
- $vendorDirs = [];
-
- /**
- * @var mixed $vendorRequiredVersion
- */
- foreach ($composerJson['require'] ?? [] as $vendorName => $vendorRequiredVersion) {
- assert(is_string($vendorName));
+ $vendorDirs = [];
+
+ foreach (array_keys($composerJson['require'] ?? []) as $vendorName) {
$vendorDirs[$vendorName] = $packageDir . '/' . $configVendorDir . '/' . $vendorName;
}
@@ -47,7 +44,7 @@ public function __invoke(string $composerJsonPath): Generator
/**
* Lookup each vendor package's composer.json info from installed.json
*
- * @return array> Keys are the package name and value is the composer.json as an array
+ * @return array Keys are the package name and value is the composer.json as an array
*
* @throws DependenciesNotInstalled When composer install/update has not been run.
*/
@@ -63,12 +60,9 @@ private function getInstalledPackages(string $vendorDir): array
$installedPackages = [];
- /** @var array $packages */
+ /** @var array $packages */
$packages = $installedData['packages'] ?? $installedData;
- /**
- * @var array{name: string} $vendorJson
- */
foreach ($packages as $vendorJson) {
$vendorName = $vendorJson['name'];
$installedPackages[$vendorName] = $vendorJson;
diff --git a/src/ComposerRequireChecker/FileLocator/LocateComposerPackageSourceFiles.php b/src/ComposerRequireChecker/FileLocator/LocateComposerPackageSourceFiles.php
index 1bca76fc..7d1d4a61 100644
--- a/src/ComposerRequireChecker/FileLocator/LocateComposerPackageSourceFiles.php
+++ b/src/ComposerRequireChecker/FileLocator/LocateComposerPackageSourceFiles.php
@@ -16,15 +16,39 @@
use function ltrim;
use function str_replace;
+/**
+ * @psalm-type ComposerConfig = array{vendor-dir?: string}
+ * @psalm-type ComposerRequire = array
+ * @psalm-type ComposerAutoload = array{
+ * exclude-from-classmap?: list,
+ * classmap?: list,
+ * files?: list,
+ * psr-0?: list,
+ * psr-4?: list
+ * }
+ * @psalm-type ComposerPackageData = array{
+ * name: string,
+ * require?: ComposerConfig,
+ * autoload?: ComposerAutoload,
+ * config?: ComposerConfig
+ * }
+ * @psalm-type ComposerData = array{
+ * name: string,
+ * require?: ComposerConfig,
+ * autoload?: ComposerAutoload,
+ * config?: ComposerConfig,
+ * packages?: list
+ * }
+ */
final class LocateComposerPackageSourceFiles
{
/**
* @param mixed[] $composerData The contents of composer.json for a package
* @param string $packageDir The path to package
+ * @psalm-param ComposerData $composerData The contents of composer.json for a package
*/
public function __invoke(array $composerData, string $packageDir): Generator
{
- /** @var array|null $blacklist */
$blacklist = $composerData['autoload']['exclude-from-classmap'] ?? null;
yield from $this->locateFilesInClassmapDefinitions(
@@ -51,7 +75,7 @@ public function __invoke(array $composerData, string $packageDir): Generator
/**
* @param array $sourceDirs
*
- * @return array
+ * @return list
*/
private function getFilePaths(array $sourceDirs, string $packageDir): array
{
diff --git a/src/ComposerRequireChecker/JsonLoader.php b/src/ComposerRequireChecker/JsonLoader.php
index c998052b..9770491a 100644
--- a/src/ComposerRequireChecker/JsonLoader.php
+++ b/src/ComposerRequireChecker/JsonLoader.php
@@ -6,22 +6,28 @@
use ComposerRequireChecker\Exception\InvalidJson;
use ComposerRequireChecker\Exception\NotReadable;
+use ComposerRequireChecker\FileLocator\LocateComposerPackageSourceFiles;
use InvalidArgumentException;
use Throwable;
use Webmozart\Assert\Assert;
+use function assert;
use function file_get_contents;
+use function is_string;
use function json_decode;
use const JSON_THROW_ON_ERROR;
/**
* @internal
+ *
+ * @psalm-import-type ComposerData from LocateComposerPackageSourceFiles
*/
class JsonLoader
{
/**
* @return array
+ * @psalm-return ComposerData
*
* @throws InvalidJson
* @throws NotReadable
@@ -37,6 +43,7 @@ public static function getData(string $path): array
throw new InvalidJson('error parsing ' . $path . ': ' . $exception->getMessage(), 0, $exception);
}
+ /** @psalm-var ComposerData $decodedData */
return $decodedData;
}
@@ -51,9 +58,7 @@ private static function getFileContentFromPath(string $path): string
$content = file_get_contents($path);
- if ($content === false) {
- throw new NotReadable('unable to read ' . $path);
- }
+ assert(is_string($content));
return $content;
}
diff --git a/src/ComposerRequireChecker/NodeVisitor/DefinedSymbolCollector.php b/src/ComposerRequireChecker/NodeVisitor/DefinedSymbolCollector.php
index 988a221d..7b6dea4b 100644
--- a/src/ComposerRequireChecker/NodeVisitor/DefinedSymbolCollector.php
+++ b/src/ComposerRequireChecker/NodeVisitor/DefinedSymbolCollector.php
@@ -10,6 +10,7 @@
use function array_keys;
use function get_class;
+use function property_exists;
use function sprintf;
final class DefinedSymbolCollector extends NodeVisitorAbstract
@@ -32,7 +33,7 @@ public function beforeTraverse(array $nodes)
}
/**
- * @return array
+ * @return list
*/
public function getDefinedSymbols(): array
{
@@ -126,6 +127,10 @@ private function recordDefinedConstDefinition(Node $node): void
}
}
+ if (! ($node->args[0] instanceof Node\Arg)) {
+ return;
+ }
+
if (! ($node->args[0]->value instanceof Node\Scalar\String_)) {
return;
}
@@ -133,9 +138,17 @@ private function recordDefinedConstDefinition(Node $node): void
$this->recordDefinitionOfStringSymbol($node->args[0]->value->value);
}
+ /**
+ * @psalm-param Node\Stmt\Function_|Node\Stmt\ClassLike|Node\Const_ $node
+ */
private function recordDefinitionOf(Node $node): void
{
- if (! isset($node->namespacedName)) {
+ $namespacedName = null;
+ if (property_exists($node, 'namespacedName')) {
+ $namespacedName = $node->namespacedName;
+ }
+
+ if ($namespacedName === null) {
throw new UnexpectedValueException(
sprintf(
'Given node of type "%s" (defined at line %s)does not have an assigned "namespacedName" property: '
@@ -146,7 +159,7 @@ private function recordDefinitionOf(Node $node): void
);
}
- $this->recordDefinitionOfStringSymbol((string) $node->namespacedName);
+ $this->recordDefinitionOfStringSymbol((string) $namespacedName);
}
private function recordDefinitionOfStringSymbol(string $symbolName): void
diff --git a/src/ComposerRequireChecker/NodeVisitor/UsedSymbolCollector.php b/src/ComposerRequireChecker/NodeVisitor/UsedSymbolCollector.php
index 3028e04d..dfc9b8ec 100644
--- a/src/ComposerRequireChecker/NodeVisitor/UsedSymbolCollector.php
+++ b/src/ComposerRequireChecker/NodeVisitor/UsedSymbolCollector.php
@@ -21,7 +21,7 @@ public function __construct()
}
/**
- * @return array
+ * @return list
*/
public function getCollectedSymbols(): array
{
diff --git a/test/ComposerRequireCheckerTest/Cli/ApplicationHeaderWriterTest.php b/test/ComposerRequireCheckerTest/Cli/ApplicationHeaderWriterTest.php
new file mode 100644
index 00000000..7fadf8b4
--- /dev/null
+++ b/test/ComposerRequireCheckerTest/Cli/ApplicationHeaderWriterTest.php
@@ -0,0 +1,36 @@
+__invoke($output);
+
+ self::assertStringContainsString('Unknown version', $output->fetch());
+ }
+
+ public function testWithApplication(): void
+ {
+ $output = new BufferedOutput();
+
+ $application = new Application('APPNAME', 'APPVERSION');
+
+ (new ApplicationHeaderWriter($application))->__invoke($output);
+
+ self::assertStringContainsString('APPNAME APPVERSION', $output->fetch());
+ }
+}
diff --git a/test/ComposerRequireCheckerTest/Cli/ApplicationTest.php b/test/ComposerRequireCheckerTest/Cli/ApplicationTest.php
index 21edc990..3e85b90f 100644
--- a/test/ComposerRequireCheckerTest/Cli/ApplicationTest.php
+++ b/test/ComposerRequireCheckerTest/Cli/ApplicationTest.php
@@ -22,4 +22,12 @@ public function testCheckCommandExists(): void
$this->assertTrue($this->application->has('check'));
$this->assertInstanceOf(Command::class, $this->application->get('check'));
}
+
+ public function testCheckCommandIsDefaultCommand(): void
+ {
+ self::assertStringContainsString(
+ 'check',
+ $this->application->getDefinition()->getOption('help')->getDescription()
+ );
+ }
}
diff --git a/test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php b/test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php
index 15be1368..34b55d2c 100644
--- a/test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php
+++ b/test/ComposerRequireCheckerTest/Cli/CheckCommandTest.php
@@ -20,6 +20,7 @@
use function version_compare;
use const JSON_THROW_ON_ERROR;
+use const PHP_EOL;
use const PHP_VERSION;
final class CheckCommandTest extends TestCase
@@ -112,6 +113,7 @@ public function testUnknownSymbolsFoundJsonReport(): void
],
$actual['unknown-symbols']
);
+ self::assertStringEndsNotWith(PHP_EOL, $display);
}
public function testUnknownSymbolsFoundTextReport(): void
@@ -131,6 +133,7 @@ public function testUnknownSymbolsFoundTextReport(): void
$this->assertStringContainsString('filter_var', $display);
$this->assertStringContainsString('Foo\Bar\Baz', $display);
$this->assertStringContainsString('libxml_clear_errors', $display);
+ $this->assertStringEndsNotWith(PHP_EOL . PHP_EOL, $display);
}
public function testSelfCheckShowsNoErrors(): void
diff --git a/test/ComposerRequireCheckerTest/Cli/OptionsTest.php b/test/ComposerRequireCheckerTest/Cli/OptionsTest.php
index 9ab0dfc2..b095d9c2 100644
--- a/test/ComposerRequireCheckerTest/Cli/OptionsTest.php
+++ b/test/ComposerRequireCheckerTest/Cli/OptionsTest.php
@@ -5,6 +5,7 @@
namespace ComposerRequireCheckerTest\Cli;
use ComposerRequireChecker\Cli\Options;
+use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use function file_get_contents;
@@ -45,9 +46,24 @@ public function testOptionsFileRepresentsDefaults(): void
public function testThrowsExceptionForUnknownOptions(): void
{
- $this->expectException('InvalidArgumentException');
- $options = new Options([
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('foo-bar is not a known option - there is no method setFooBar');
+
+ new Options([
'foo-bar' => ['foo', 'bar'],
]);
}
+
+ public function testPublicSetters(): void
+ {
+ $options = new Options();
+
+ $options->setSymbolWhitelist(['foo', 'bar']);
+ $options->setScanFiles(['one', 'two', 'three']);
+ $options->setPhpCoreExtensions(['ext-one', 'ext-two']);
+
+ self::assertSame(['foo', 'bar'], $options->getSymbolWhitelist());
+ self::assertSame(['one', 'two', 'three'], $options->getScanFiles());
+ self::assertSame(['ext-one', 'ext-two'], $options->getPhpCoreExtensions());
+ }
}
diff --git a/test/ComposerRequireCheckerTest/Cli/ResultsWriter/CliTextTest.php b/test/ComposerRequireCheckerTest/Cli/ResultsWriter/CliTextTest.php
index 1399c480..b0a1b2e8 100644
--- a/test/ComposerRequireCheckerTest/Cli/ResultsWriter/CliTextTest.php
+++ b/test/ComposerRequireCheckerTest/Cli/ResultsWriter/CliTextTest.php
@@ -80,6 +80,8 @@ public function testWriteReportQuietWithWriteCallable(): void
$buffer = $this->output->fetch();
self::assertStringContainsString('The following 3 unknown symbols were found:', $buffer);
self::assertStringNotContainsString('Foo', $buffer);
+ self::assertStringContainsString('Unknown Symbol', $output);
+ self::assertStringContainsString('Guessed Dependency', $output);
self::assertStringContainsString('Foo', $output);
self::assertStringContainsString('| opcache_get_status', $output);
self::assertStringContainsString('| ext-opcache', $output);
diff --git a/test/ComposerRequireCheckerTest/Exception/FileParseFailedTest.php b/test/ComposerRequireCheckerTest/Exception/FileParseFailedTest.php
new file mode 100644
index 00000000..6bfa5d76
--- /dev/null
+++ b/test/ComposerRequireCheckerTest/Exception/FileParseFailedTest.php
@@ -0,0 +1,28 @@
+getCode());
+ self::assertSame(
+ 'Parsing the file [file] resulted in an error: Dummy Exception',
+ $subject->getMessage()
+ );
+ }
+}
diff --git a/test/ComposerRequireCheckerTest/NodeVisitor/UsedSymbolCollectorTest.php b/test/ComposerRequireCheckerTest/NodeVisitor/UsedSymbolCollectorTest.php
index 18e45241..375e979a 100644
--- a/test/ComposerRequireCheckerTest/NodeVisitor/UsedSymbolCollectorTest.php
+++ b/test/ComposerRequireCheckerTest/NodeVisitor/UsedSymbolCollectorTest.php
@@ -13,6 +13,7 @@
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
+use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Catch_;
@@ -169,18 +170,21 @@ public function testFunctionCallUsage(): void
public function testFunctionParameterType(): void
{
- $functionName = new Name('foo');
- $node = new Function_($functionName);
- $node->name = $functionName;
- $param = new Param(new Variable('bar'));
- $param->type = new Name('Baz');
- $node->params = [$param];
+ $functionName = new Name('foo');
+ $node = new Function_($functionName);
+ $node->name = $functionName;
+ $param = new Param(new Variable('bar'));
+ $param->type = new Name('Baz');
+ $anotherParam = new Param(new Variable('quux'));
+ $anotherParam->type = new Identifier('foo');
+ $node->params = [$param, $anotherParam];
$this->visitor->enterNode($node);
$symbols = $this->visitor->getCollectedSymbols();
- $this->assertCount(1, $symbols);
+ $this->assertCount(2, $symbols);
$this->assertContains('Baz', $symbols);
+ $this->assertContains('foo', $symbols);
}
public function testMethodParameterType(): void