diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml deleted file mode 100644 index bed9ebf5..00000000 --- a/.github/workflows/analysis.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Psalm analysis - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - psalm: - name: Psalm - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Psalm - uses: docker://vimeo/psalm-github-actions \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bef8a5f7..c27349a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Lint and test +name: Lint, analyse and test on: push: @@ -13,12 +13,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.3', '7.4', '8.0'] + php: ['7.3', '7.4', '8.0', '8.1'] name: PHP ${{ matrix.php }} tests steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Install PHP - uses: shivammathur/setup-php@1.3.7 + uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - name: Debugging @@ -30,13 +30,31 @@ jobs: run: composer install --prefer-dist --no-suggest --no-progress - name: Run tests run: vendor/bin/phpunit --printer mheap\\GithubActionsReporter\\Printer + analysis: + runs-on: ubuntu-latest + name: Analysis of code + steps: + - uses: actions/checkout@v3 + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + - name: Debugging + run: | + php --version + php -m + composer --version + - name: Install dependencies + run: composer install --prefer-dist --no-suggest --no-progress + - name: Run validate + run: composer run analysis lint: runs-on: ubuntu-latest - name: Lint project files + name: Lint all project files steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Install PHP - uses: shivammathur/setup-php@1.3.7 + uses: shivammathur/setup-php@v2 with: php-version: '7.4' - name: Debugging diff --git a/composer.json b/composer.json index c97d2253..2b25de38 100644 --- a/composer.json +++ b/composer.json @@ -22,17 +22,30 @@ "CoenJacobs\\Mozart\\": "src/" } }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + }, "require-dev": { "phpunit/phpunit": "^8.5", "squizlabs/php_codesniffer": "^3.5", "mheap/phpunit-github-actions-printer": "^1.4", - "vimeo/psalm": "^4.4" + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/extension-installer": "^1.2" }, "scripts": { "lint": [ "composer validate", "phpcs" ], + "analysis": [ + "@analysis:phpstan" + ], + "analysis:phpstan": [ + "./vendor/bin/phpstan --memory-limit=2G" + ], "test": [ "phpunit" ] diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 00000000..eb0b0284 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,211 @@ +parameters: + ignoreErrors: + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Composer\\\\Autoload\\\\Autoloader\\:\\:getSearchNamespace\\(\\) has no return type specified\\.$#" + count: 1 + path: src/Composer/Autoload/Autoloader.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Composer\\\\Autoload\\\\Autoloader\\:\\:processConfig\\(\\) has no return type specified\\.$#" + count: 1 + path: src/Composer/Autoload/Autoloader.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Composer\\\\Autoload\\\\Autoloader\\:\\:processConfig\\(\\) has parameter \\$autoloadConfig with no type specified\\.$#" + count: 1 + path: src/Composer/Autoload/Autoloader.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Composer\\\\Autoload\\\\Classmap\\:\\:processConfig\\(\\) has parameter \\$autoloadConfig with no type specified\\.$#" + count: 1 + path: src/Composer/Autoload/Classmap.php + + - + message: "#^Property CoenJacobs\\\\Mozart\\\\Composer\\\\Autoload\\\\Classmap\\:\\:\\$files type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Composer/Autoload/Classmap.php + + - + message: "#^Property CoenJacobs\\\\Mozart\\\\Composer\\\\Autoload\\\\Classmap\\:\\:\\$paths type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Composer/Autoload/Classmap.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Composer\\\\Autoload\\\\NamespaceAutoloader\\:\\:processConfig\\(\\) has parameter \\$autoloadConfig with no type specified\\.$#" + count: 1 + path: src/Composer/Autoload/NamespaceAutoloader.php + + - + message: "#^PHPDoc tag @var has invalid value \\(\\)\\: Unexpected token \"\\*/\", expected type at offset 9$#" + count: 1 + path: src/Composer/Package.php + + - + message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#" + count: 1 + path: src/Composer/Package.php + + - + message: "#^Property CoenJacobs\\\\Mozart\\\\Composer\\\\Package\\:\\:\\$config has no type specified\\.$#" + count: 1 + path: src/Composer/Package.php + + - + message: "#^Property CoenJacobs\\\\Mozart\\\\Composer\\\\Package\\:\\:\\$dependencies type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Composer/Package.php + + - + message: "#^Access to an undefined property object\\:\\:\\$dep_namespace\\.$#" + count: 2 + path: src/Console/Commands/Compose.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:getAllDependenciesOfPackage\\(\\) has parameter \\$dependencies with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:getAllDependenciesOfPackage\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:movePackage\\(\\) has parameter \\$package with no type specified\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:movePackages\\(\\) has parameter \\$packages with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:replacePackage\\(\\) has parameter \\$package with no type specified\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:replacePackages\\(\\) has parameter \\$packages with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:replaceParentInTree\\(\\) has parameter \\$packages with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^PHPDoc tag @var has invalid value \\(\\)\\: Unexpected token \"\\*/\", expected type at offset 9$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#" + count: 2 + path: src/Console/Commands/Compose.php + + - + message: "#^Property CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:\\$config has no type specified\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Property CoenJacobs\\\\Mozart\\\\Console\\\\Commands\\\\Compose\\:\\:\\$workingDir \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/Console/Commands/Compose.php + + - + message: "#^Access to an undefined property CoenJacobs\\\\Mozart\\\\Composer\\\\Autoload\\\\Autoloader\\:\\:\\$namespace\\.$#" + count: 1 + path: src/Mover.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Mover\\:\\:__construct\\(\\) has parameter \\$config with no type specified\\.$#" + count: 1 + path: src/Mover.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Mover\\:\\:__construct\\(\\) has parameter \\$workingDir with no type specified\\.$#" + count: 1 + path: src/Mover.php + + - + message: "#^Property CoenJacobs\\\\Mozart\\\\Mover\\:\\:\\$movedPackages type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Mover.php + + - + message: "#^Anonymous function has an unused use \\$contents\\.$#" + count: 1 + path: src/Replace/ClassmapReplacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replace\\\\ClassmapReplacer\\:\\:replace\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/Replace/ClassmapReplacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replace\\\\ClassmapReplacer\\:\\:saveReplacedClass\\(\\) has parameter \\$classname with no type specified\\.$#" + count: 1 + path: src/Replace/ClassmapReplacer.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and non\\-falsy\\-string will always evaluate to false\\.$#" + count: 1 + path: src/Replace/ClassmapReplacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replace\\\\NamespaceReplacer\\:\\:replace\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/Replace/NamespaceReplacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replace\\\\Replacer\\:\\:replace\\(\\) has no return type specified\\.$#" + count: 1 + path: src/Replace/Replacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replace\\\\Replacer\\:\\:replace\\(\\) has parameter \\$contents with no type specified\\.$#" + count: 1 + path: src/Replace/Replacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replace\\\\Replacer\\:\\:setAutoloader\\(\\) has no return type specified\\.$#" + count: 1 + path: src/Replace/Replacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replacer\\:\\:__construct\\(\\) has parameter \\$config with no type specified\\.$#" + count: 1 + path: src/Replacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replacer\\:\\:__construct\\(\\) has parameter \\$workingDir with no type specified\\.$#" + count: 1 + path: src/Replacer.php + + - + message: "#^Method CoenJacobs\\\\Mozart\\\\Replacer\\:\\:replaceInFile\\(\\) has parameter \\$targetFile with no type specified\\.$#" + count: 1 + path: src/Replacer.php + + - + message: "#^Parameter \\#2 \\$contents of method League\\\\Flysystem\\\\Filesystem\\:\\:put\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/Replacer.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace_callback expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/Replacer.php + + - + message: "#^Property CoenJacobs\\\\Mozart\\\\Replacer\\:\\:\\$replacedClasses type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Replacer.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and non\\-falsy\\-string will always evaluate to false\\.$#" + count: 2 + path: src/Replacer.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..dcc32dd4 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 8 + reportUnmatchedIgnoredErrors: false + paths: + - ./src diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 30258a70..00000000 --- a/psalm.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - diff --git a/src/Console/Commands/Compose.php b/src/Console/Commands/Compose.php index c0971f78..a832cf11 100644 --- a/src/Console/Commands/Compose.php +++ b/src/Console/Commands/Compose.php @@ -160,8 +160,6 @@ public function replacePackage($package): void * @param ((int|string)|mixed)[] $slugs * * @return Package[] - * - * @psalm-return array */ private function findPackages(array $slugs): array { diff --git a/tests/replacers/ClassmapReplacerIntegrationTest.php b/tests/replacers/ClassmapReplacerIntegrationTest.php deleted file mode 100644 index 4ff6a566..00000000 --- a/tests/replacers/ClassmapReplacerIntegrationTest.php +++ /dev/null @@ -1,137 +0,0 @@ -testsWorkingDir = __DIR__ . '/temptestdir'; - if (!file_exists($this->testsWorkingDir)) { - mkdir($this->testsWorkingDir); - } - - $mozart_config = new class() { - public $dep_namespace = "Mozart"; - public $classmap_prefix = "Mozart_"; - public $dep_directory = "/dep_directory/"; - public $classmap_directory = "/classmap_directory/"; - - }; - - $composer = new class() { - public $repositories = array(); - public $require = array(); - public $minimum_stability = "dev"; - public $extra; - }; - - $composer->extra = new class() { - public $mozart; - }; - - $composer->extra->mozart = $mozart_config; - - $this->composer = $composer; - } - - /** - * Issue #93 shows a classname being updated inside a class whose namespace has also been updated - * by Mozart. - * - * This is caused by the same files being loaded by both a PSR-4 autolaoder and classmap autoloader. - * @see https://github.com/katzgrau/KLogger/blob/de2d3ab6777a393a9879e0496ebb8e0644066e3f/composer.json#L24-L29 - */ - public function test_it_does_not_make_classname_replacement_inside_namespaced_file() - { - - $composer = $this->composer; - - $composer->repositories[] = new class() { - public $url = "https://github.com/BrianHenryIE/bh-wp-logger"; - public $type = "git"; - }; - - $composer->require["brianhenryie/wp-logger"] = "dev-master#dd2bb0665e01e11b282178e76a2334198d3860c5"; - - $composer_json_string = json_encode($composer); - $composer_json_string = str_replace('minimum_stability', 'minimum-stability', $composer_json_string); - - file_put_contents($this->testsWorkingDir . '/composer.json', $composer_json_string); - - chdir($this->testsWorkingDir); - - exec('composer update'); - - $inputInterfaceMock = $this->createMock(InputInterface::class); - $outputInterfaceMock = $this->createMock(OutputInterface::class); - - $mozartCompose = new Compose(); - - $mozartCompose->run($inputInterfaceMock, $outputInterfaceMock); - - $php_string = file_get_contents($this->testsWorkingDir .'/dep_directory/BrianHenryIE/WP_Logger/class-logger.php'); - - // Confirm problem is gone. - $this->assertStringNotContainsString('class Mozart_Logger extends', $php_string); - - // Confirm solution is correct. - $this->assertStringContainsString('class Logger extends', $php_string); - } - - - /** - * Delete $this->testsWorkingDir after each test. - * - * @see https://stackoverflow.com/questions/3349753/delete-directory-with-files-in-it - */ - public function tearDown(): void - { - parent::tearDown(); - - $dir = $this->testsWorkingDir; - - $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); - $files = new RecursiveIteratorIterator( - $it, - RecursiveIteratorIterator::CHILD_FIRST - ); - foreach ($files as $file) { - if ($file->isDir()) { - rmdir($file->getRealPath()); - } else { - unlink($file->getRealPath()); - } - } - rmdir($dir); - } -} diff --git a/tests/replacers/NamespaceReplacerIntegrationTest.php b/tests/replacers/NamespaceReplacerIntegrationTest.php deleted file mode 100644 index 80bc04a3..00000000 --- a/tests/replacers/NamespaceReplacerIntegrationTest.php +++ /dev/null @@ -1,131 +0,0 @@ -testsWorkingDir = __DIR__ . '/temptestdir'; - if (!file_exists($this->testsWorkingDir)) { - mkdir($this->testsWorkingDir); - } - - $mozart_config = new class() { - public $dep_namespace = "Mozart"; - public $classmap_prefix = "Mozart_"; - public $dep_directory = "/dep_directory/"; - public $classmap_directory = "/classmap_directory/"; - - }; - - $composer = new class() { - public $require = array(); - public $extra; - }; - - $composer->extra = new class() { - public $mozart; - }; - - $composer->extra->mozart = $mozart_config; - - $this->composer = $composer; - } - - /** - * After PR #84, running Mozart on Mpdf began prefixing the class name inside the namespaced file. - * - * The problem coming from the filename matching the namespace name? - * - * dev-master#5d8041fdefc94ff57edcbe83ab468a9988c4fc11 - * - * @see https://github.com/coenjacobs/mozart/pull/84/files - * - * Should be: "class Mpdf implements" because its namespace has already been prefixed. - */ - public function test_it_does_not_make_classname_replacement_inside_namespaced_file() - { - - $composer = $this->composer; - - $composer->require["mpdf/mpdf"] = "8.0.10"; - - file_put_contents($this->testsWorkingDir . '/composer.json', json_encode($composer)); - - chdir($this->testsWorkingDir); - - exec('composer update'); - - $inputInterfaceMock = $this->createMock(InputInterface::class); - $outputInterfaceMock = $this->createMock(OutputInterface::class); - - $mozartCompose = new Compose(); - - $mozartCompose->run($inputInterfaceMock, $outputInterfaceMock); - - $mpdf_php = file_get_contents($this->testsWorkingDir .'/dep_directory/Mpdf/Mpdf.php'); - - // Confirm problem is gone. - $this->assertStringNotContainsString('class Mozart\Mpdf implements', $mpdf_php); - - // Confirm solution is correct. - $this->assertStringContainsString('class Mpdf implements', $mpdf_php); - } - - - /** - * Delete $this->testsWorkingDir after each test. - * - * @see https://stackoverflow.com/questions/3349753/delete-directory-with-files-in-it - */ - public function tearDown(): void - { - parent::tearDown(); - - $dir = $this->testsWorkingDir; - - $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); - $files = new RecursiveIteratorIterator( - $it, - RecursiveIteratorIterator::CHILD_FIRST - ); - foreach ($files as $file) { - if ($file->isDir()) { - rmdir($file->getRealPath()); - } else { - unlink($file->getRealPath()); - } - } - rmdir($dir); - } -}