From 7504685a2effe5211b830325080701546f8b025c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 25 Jul 2024 16:28:25 +0200 Subject: [PATCH] Update phpstan and add composer/pcre extensions (#12045) * Update phpstan and add composer/pcre extensions * Update baseline (1516) --- composer.json | 4 +- composer.lock | 62 +++++---- phpstan/baseline-8.3.neon | 7 +- phpstan/baseline.neon | 131 +----------------- phpstan/config.neon | 1 + src/Composer/Command/ConfigCommand.php | 2 +- src/Composer/Command/InitCommand.php | 2 - src/Composer/Compiler.php | 1 + src/Composer/Downloader/GitDownloader.php | 2 +- src/Composer/Installer/BinaryInstaller.php | 6 +- src/Composer/Platform/Runtime.php | 5 +- .../Repository/PlatformRepository.php | 14 +- src/Composer/Repository/Vcs/GitDriver.php | 10 +- src/Composer/Repository/Vcs/GitHubDriver.php | 2 - src/Composer/Repository/Vcs/GitLabDriver.php | 6 +- src/Composer/Repository/Vcs/SvnDriver.php | 56 ++++---- src/Composer/Util/ErrorHandler.php | 4 +- src/Composer/Util/Git.php | 39 +++--- src/Composer/Util/Hg.php | 8 +- tests/Composer/Test/AllFunctionalTest.php | 16 ++- 20 files changed, 128 insertions(+), 250 deletions(-) diff --git a/composer.json b/composer.json index 6bdfa04ad..9cb86861c 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "symfony/finder": "^5.4 || ^6.0 || ^7", "symfony/process": "^5.4 || ^6.0 || ^7", "react/promise": "^2.8 || ^3", - "composer/pcre": "^2.1 || ^3.1", + "composer/pcre": "^2.2 || ^3.2", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", "symfony/polyfill-php81": "^1.24", @@ -46,7 +46,7 @@ }, "require-dev": { "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1", - "phpstan/phpstan": "^1.11.0", + "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-deprecation-rules": "^1.2.0", "phpstan/phpstan-strict-rules": "^1.6.0", diff --git a/composer.lock b/composer.lock index d9cf31b93..019b129b5 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": "6a83e69e5c06f06ecba7db6c83213f9d", + "content-hash": "715b9529f60660d59b08b12c74c828c6", "packages": [ { "name": "composer/ca-bundle", @@ -226,30 +226,38 @@ }, { "name": "composer/pcre", - "version": "2.1.3", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "540af382c97b83c628227d5f87cf56466d476191" + "reference": "0e455b78ac53637929b29d5ab5bf3c978329c1eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/540af382c97b83c628227d5f87cf56466d476191", - "reference": "540af382c97b83c628227d5f87cf56466d476191", + "url": "https://api.github.com/repos/composer/pcre/zipball/0e455b78ac53637929b29d5ab5bf3c978329c1eb", + "reference": "0e455b78ac53637929b29d5ab5bf3c978329c1eb", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.8" + }, "require-dev": { - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { "dev-main": "2.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -277,7 +285,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/2.1.3" + "source": "https://github.com/composer/pcre/tree/2.2.0" }, "funding": [ { @@ -293,20 +301,20 @@ "type": "tidelift" } ], - "time": "2024-03-19T09:03:05+00:00" + "time": "2024-07-25T09:28:32+00:00" }, { "name": "composer/semver", - "version": "3.4.1", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "8536c1b9103405bcbd310c69e7a5739a1c2b1f0b" + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/8536c1b9103405bcbd310c69e7a5739a1c2b1f0b", - "reference": "8536c1b9103405bcbd310c69e7a5739a1c2b1f0b", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { @@ -358,7 +366,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.1" + "source": "https://github.com/composer/semver/tree/3.4.2" }, "funding": [ { @@ -374,7 +382,7 @@ "type": "tidelift" } ], - "time": "2024-07-12T09:13:09+00:00" + "time": "2024-07-12T11:35:52+00:00" }, { "name": "composer/spdx-licenses", @@ -2012,16 +2020,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.7", + "version": "1.11.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", "shasum": "" }, "require": { @@ -2066,7 +2074,7 @@ "type": "github" } ], - "time": "2024-07-06T11:17:41+00:00" + "time": "2024-07-24T07:01:22+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2218,22 +2226,22 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.5", + "version": "1.4.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d" + "reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d", - "reference": "1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/e909a075d69e0d4db262ac3407350ae2c6b6ab5f", + "reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.11.7" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -2284,9 +2292,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.5" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.6" }, - "time": "2024-06-26T12:19:42+00:00" + "time": "2024-07-16T11:48:54+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/phpstan/baseline-8.3.neon b/phpstan/baseline-8.3.neon index c6e16fee6..5e17a28f4 100644 --- a/phpstan/baseline-8.3.neon +++ b/phpstan/baseline-8.3.neon @@ -182,14 +182,9 @@ parameters: - message: "#^Parameter \\#1 \\$string of function rawurlencode expects string, string\\|null given\\.$#" - count: 15 + count: 8 path: ../src/Composer/Util/Git.php - - - message: "#^Parameter \\#1 \\$string of function rawurlencode expects string, string\\|null given\\.$#" - count: 2 - path: ../src/Composer/Util/Hg.php - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_add_handle expects CurlMultiHandle, resource\\|null given\\.$#" count: 1 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 7d1258a34..5ae3c0b3b 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -130,11 +130,6 @@ parameters: count: 1 path: ../src/Composer/Autoload/ClassLoader.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 2 - path: ../src/Composer/Cache.php - - message: "#^Only booleans are allowed in a negated boolean, int\\<0, 50\\> given\\.$#" count: 1 @@ -975,11 +970,6 @@ parameters: count: 2 path: ../src/Composer/Config.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 1 - path: ../src/Composer/Config.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 @@ -1570,11 +1560,6 @@ parameters: count: 1 path: ../src/Composer/Downloader/DownloadManager.php - - - message: "#^Method Composer\\\\Downloader\\\\FileDownloader\\:\\:getDistPath\\(\\) should return string but returns array\\\\|string\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - message: "#^Strict comparison using \\=\\=\\= between null and Composer\\\\Util\\\\Http\\\\Response\\|string will always evaluate to false\\.$#" count: 1 @@ -1860,11 +1845,6 @@ parameters: count: 1 path: ../src/Composer/EventDispatcher/EventDispatcher.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 1 - path: ../src/Composer/EventDispatcher/EventDispatcher.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -2067,7 +2047,7 @@ parameters: - message: "#^Casting to bool something that's already bool\\.$#" - count: 11 + count: 1 path: ../src/Composer/Installer.php - @@ -2130,11 +2110,6 @@ parameters: count: 2 path: ../src/Composer/Installer/BinaryInstaller.php - - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Installer/BinaryInstaller.php - - message: "#^Parameter \\#1 \\$binPath of method Composer\\\\Installer\\\\BinaryInstaller\\:\\:installFullBinaries\\(\\) expects string, string\\|false given\\.$#" count: 1 @@ -2160,11 +2135,6 @@ parameters: count: 1 path: ../src/Composer/Installer/BinaryInstaller.php - - - message: "#^Parameter \\#2 \\$subject of static method Composer\\\\Pcre\\\\Preg\\:\\:isMatch\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Installer/BinaryInstaller.php - - message: "#^Property Composer\\\\Installer\\\\BinaryInstaller\\:\\:\\$binDir \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -2410,16 +2380,6 @@ parameters: count: 1 path: ../src/Composer/Package/AliasPackage.php - - - message: "#^Call to function method_exists\\(\\) with Closure\\(SplFileInfo\\)\\: bool and 'bindTo' will always evaluate to true\\.$#" - count: 1 - path: ../src/Composer/Package/Archiver/ArchivableFilesFinder.php - - - - message: "#^Parameter \\#1 \\$path of method Composer\\\\Util\\\\Filesystem\\:\\:normalizePath\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Package/Archiver/ArchivableFilesFinder.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -2930,11 +2890,6 @@ parameters: count: 1 path: ../src/Composer/Plugin/PostFileDownloadEvent.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 1 - path: ../src/Composer/Question/StrictConfirmationQuestion.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 @@ -3240,11 +3195,6 @@ parameters: count: 1 path: ../src/Composer/Repository/PearRepository.php - - - message: "#^Call to an undefined method object\\:\\:getVersion\\(\\)\\.$#" - count: 1 - path: ../src/Composer/Repository/PlatformRepository.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -3405,11 +3355,6 @@ parameters: - message: "#^Only booleans are allowed in &&, string given on the left side\\.$#" - count: 3 - path: ../src/Composer/Repository/Vcs/GitDriver.php - - - - message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" count: 1 path: ../src/Composer/Repository/Vcs/GitDriver.php @@ -3525,7 +3470,7 @@ parameters: - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" - count: 5 + count: 4 path: ../src/Composer/Repository/Vcs/GitLabDriver.php - @@ -3648,11 +3593,6 @@ parameters: count: 2 path: ../src/Composer/Repository/Vcs/SvnDriver.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: ../src/Composer/Repository/Vcs/SvnDriver.php - - message: "#^Method Composer\\\\Repository\\\\Vcs\\\\SvnDriver\\:\\:getRootIdentifier\\(\\) should return string but returns string\\|false\\.$#" count: 1 @@ -3663,26 +3603,11 @@ parameters: count: 1 path: ../src/Composer/Repository/Vcs/SvnDriver.php - - - message: "#^Only booleans are allowed in &&, string given on the left side\\.$#" - count: 4 - path: ../src/Composer/Repository/Vcs/SvnDriver.php - - message: "#^Only booleans are allowed in &&, string\\|false given on the right side\\.$#" count: 1 path: ../src/Composer/Repository/Vcs/SvnDriver.php - - - message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" - count: 1 - path: ../src/Composer/Repository/Vcs/SvnDriver.php - - - - message: "#^Only booleans are allowed in an if condition, string given\\.$#" - count: 3 - path: ../src/Composer/Repository/Vcs/SvnDriver.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 @@ -3903,16 +3828,6 @@ parameters: count: 1 path: ../src/Composer/Util/ConfigValidator.php - - - message: "#^Only booleans are allowed in a negated boolean, int given\\.$#" - count: 1 - path: ../src/Composer/Util/ErrorHandler.php - - - - message: "#^Only booleans are allowed in an if condition, Composer\\\\IO\\\\IOInterface\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/ErrorHandler.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -3923,26 +3838,11 @@ parameters: count: 1 path: ../src/Composer/Util/Git.php - - - message: "#^Only booleans are allowed in &&, string given on the right side\\.$#" - count: 1 - path: ../src/Composer/Util/Git.php - - - - message: "#^Only booleans are allowed in &&, string\\|false given on the left side\\.$#" - count: 1 - path: ../src/Composer/Util/Git.php - - message: "#^Only booleans are allowed in &&, string\\|null given on the left side\\.$#" count: 1 path: ../src/Composer/Util/Git.php - - - message: "#^Only booleans are allowed in an if condition, int\\<0, max\\>\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Git.php - - message: "#^Only booleans are allowed in an if condition, string\\|false given\\.$#" count: 2 @@ -3950,7 +3850,7 @@ parameters: - message: "#^Parameter \\#1 \\$str of function rawurlencode expects string, string\\|null given\\.$#" - count: 15 + count: 8 path: ../src/Composer/Util/Git.php - @@ -3998,11 +3898,6 @@ parameters: count: 2 path: ../src/Composer/Util/GitLab.php - - - message: "#^Parameter \\#1 \\$str of function rawurlencode expects string, string\\|null given\\.$#" - count: 2 - path: ../src/Composer/Util/Hg.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -4703,11 +4598,6 @@ parameters: count: 2 path: ../src/bootstrap.php - - - message: "#^Call to method RecursiveDirectoryIterator\\:\\:getSubPathname\\(\\) with incorrect case\\: getSubPathName$#" - count: 1 - path: ../tests/Composer/Test/AllFunctionalTest.php - - message: "#^Method Composer\\\\Test\\\\AllFunctionalTest\\:\\:cleanOutput\\(\\) should return string but returns string\\|false\\.$#" count: 1 @@ -4723,26 +4613,11 @@ parameters: count: 1 path: ../tests/Composer/Test/AllFunctionalTest.php - - - message: "#^Only booleans are allowed in \\|\\|, string given on the right side\\.$#" - count: 1 - path: ../tests/Composer/Test/AllFunctionalTest.php - - - - message: "#^Only numeric types are allowed in \\-, int\\<0, max\\>\\|false given on the left side\\.$#" - count: 3 - path: ../tests/Composer/Test/AllFunctionalTest.php - - message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" count: 1 path: ../tests/Composer/Test/AllFunctionalTest.php - - - message: "#^Parameter \\#2 \\$subject of static method Composer\\\\Pcre\\\\Preg\\:\\:split\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../tests/Composer/Test/AllFunctionalTest.php - - message: "#^Dynamic call to static method Composer\\\\Test\\\\TestCase\\:\\:ensureDirectoryExistsAndClear\\(\\)\\.$#" count: 1 diff --git a/phpstan/config.neon b/phpstan/config.neon index 8a3062ba8..3c8e1a20c 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -4,6 +4,7 @@ includes: - ../vendor/phpstan/phpstan-deprecation-rules/rules.neon - ../vendor/phpstan/phpstan-strict-rules/rules.neon - ../vendor/phpstan/phpstan-symfony/extension.neon + - ../vendor/composer/pcre/extension.neon - ../vendor/phpstan/phpstan-symfony/rules.neon # TODO when requiring php 7.4+ we can use this #- ../vendor/staabm/phpstan-todo-by/extension.neon diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 9bbba5513..02e77df60 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -291,7 +291,7 @@ EOT $source = $this->config->getSourceOfValue($settingKey); if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) { - if (!isset($matches[1]) || $matches[1] === '') { + if (!isset($matches[1])) { $value = $data['repositories'] ?? []; } else { if (!isset($data['repositories'][$matches[1]])) { diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 7ddacd3c2..4f1398726 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -468,8 +468,6 @@ EOT private function parseAuthorString(string $author): array { if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P.+?)>)?$/u', $author, $match)) { - assert(is_string($match['name'])); - if (null !== $match['email'] && !$this->isValidEmail($match['email'])) { throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"'); } diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 9a2e27f5b..def3ff8c4 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -120,6 +120,7 @@ class Compiler ->notPath('/bin\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\.phar)(\.bat)?$/') ->notPath('justinrainbow/json-schema/demo/') ->notPath('justinrainbow/json-schema/dist/') + ->notPath('composer/pcre/extension.neon') ->notPath('composer/LICENSE') ->exclude('Tests') ->exclude('tests') diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index b29782d1a..4561328cc 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -255,7 +255,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface } $headRef = $match[1]; - if (!Preg::isMatchAllStrictGroups('{^'.$headRef.' refs/heads/(.+)$}mi', $refs, $matches)) { + if (!Preg::isMatchAllStrictGroups('{^'.preg_quote($headRef).' refs/heads/(.+)$}mi', $refs, $matches)) { // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this return null; } diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index 3e67ef486..54ecd94cf 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -213,7 +213,7 @@ class BinaryInstaller $binDir = ProcessExecutor::escape(dirname($binPath)); $binFile = basename($binPath); - $binContents = file_get_contents($bin, false, null, 0, 500); + $binContents = (string) file_get_contents($bin, false, null, 0, 500); // For php files, we generate a PHP proxy instead of a shell one, // which allows calling the proxy with a custom php process if (Preg::isMatch('{^(#!.*\r?\n)?[\r\n\t ]*<\?php}', $binContents, $match)) { @@ -224,7 +224,7 @@ class BinaryInstaller $globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;'."\n"; $phpunitHack1 = $phpunitHack2 = ''; // Don't expose autoload path when vendor dir was not set in custom installers - if ($this->vendorDir) { + if ($this->vendorDir !== null) { // ensure comparisons work accurately if the CWD is a symlink, as $link is realpath'd already $vendorDirReal = realpath($this->vendorDir); if ($vendorDirReal === false) { @@ -242,7 +242,7 @@ class BinaryInstaller $data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data); $data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);'; } - if (trim((string) $match[0]) !== ' $class + * @phpstan-return T + * * @throws \ReflectionException */ public function construct(string $class, array $arguments = []): object diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 61c70cf32..cbc2552d5 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -350,16 +350,18 @@ class PlatformRepository extends ArrayRepository break; case 'imagick': + // @phpstan-ignore staticMethod.dynamicCall (called like this for mockability) $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion(); // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org - Preg::match('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches); - $version = $matches['version']; - if (isset($matches['patch'])) { - $version .= '.'.$matches['patch']; - } + if (Preg::isMatch('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches)) { + $version = $matches['version']; + if (isset($matches['patch'])) { + $version .= '.'.$matches['patch']; + } - $this->addLibrary($name.'-imagemagick', $version, null, ['imagick']); + $this->addLibrary($name.'-imagemagick', $version, null, ['imagick']); + } break; case 'ldap': diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 75ede6910..d6f1b8213 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -153,7 +153,7 @@ class GitDriver extends VcsDriver $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir); - if (!trim($content)) { + if (trim($content) === '') { return null; } @@ -182,9 +182,9 @@ class GitDriver extends VcsDriver $this->tags = []; $this->process->execute('git show-ref --tags --dereference', $output, $this->repoDir); - foreach ($output = $this->process->splitLines($output) as $tag) { - if ($tag && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { - $this->tags[$match[2]] = (string) $match[1]; + foreach ($this->process->splitLines($output) as $tag) { + if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { + $this->tags[$match[2]] = $match[1]; } } } @@ -202,7 +202,7 @@ class GitDriver extends VcsDriver $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { - if ($branch && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { + if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 83c58887c..09beb7680 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -63,8 +63,6 @@ class GitHubDriver extends VcsDriver throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', $this->url)); } - assert(is_string($match[3])); - assert(is_string($match[4])); $this->owner = $match[3]; $this->repository = $match[4]; $this->originUrl = strtolower($match[1] ?? (string) $match[2]); diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 3721419b1..75304f46b 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -97,8 +97,6 @@ class GitLabDriver extends VcsDriver throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url)); } - assert(is_string($match['parts'])); - assert(is_string($match['repo'])); $guessedDomain = $match['domain'] ?? (string) $match['domain2']; $configuredDomains = $this->config->get('gitlab-domains'); $urlParts = explode('/', $match['parts']); @@ -115,7 +113,7 @@ class GitLabDriver extends VcsDriver if (is_string($protocol = $this->config->get('gitlab-protocol'))) { // https treated as a synonym for http. - if (!in_array($protocol, ['git', 'http', 'https'])) { + if (!in_array($protocol, ['git', 'http', 'https'], true)) { throw new \RuntimeException('gitlab-protocol must be one of git, http.'); } $this->protocol = $protocol === 'git' ? 'ssh' : 'http'; @@ -566,8 +564,6 @@ class GitLabDriver extends VcsDriver return false; } - assert(is_string($match['parts'])); - assert(is_string($match['repo'])); $scheme = $match['scheme']; $guessedDomain = $match['domain'] ?? (string) $match['domain2']; $urlParts = explode('/', $match['parts']); diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index ea8158e34..d4f318079 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -177,8 +177,7 @@ class SvnDriver extends VcsDriver { $identifier = '/' . trim($identifier, '/') . '/'; - Preg::match('{^(.+?)(@\d+)?/$}', $identifier, $match); - if (!empty($match[2])) { + if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && $match[2] !== null) { $path = $match[1]; $rev = $match[2]; } else { @@ -189,7 +188,7 @@ class SvnDriver extends VcsDriver try { $resource = $path.$file; $output = $this->execute('svn cat', $this->baseUrl . $resource . $rev); - if (!trim($output)) { + if ('' === trim($output)) { return null; } } catch (\RuntimeException $e) { @@ -206,8 +205,7 @@ class SvnDriver extends VcsDriver { $identifier = '/' . trim($identifier, '/') . '/'; - Preg::match('{^(.+?)(@\d+)?/$}', $identifier, $match); - if (null !== $match[2] && null !== $match[1]) { + if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && null !== $match[2]) { $path = $match[1]; $rev = $match[2]; } else { @@ -217,7 +215,7 @@ class SvnDriver extends VcsDriver $output = $this->execute('svn info', $this->baseUrl . $path . $rev); foreach ($this->process->splitLines($output) as $line) { - if ($line && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { + if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')); } } @@ -235,20 +233,18 @@ class SvnDriver extends VcsDriver if ($this->tagsPath !== false) { $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->tagsPath); - if ($output) { + if ($output !== '') { $lastRev = 0; foreach ($this->process->splitLines($output) as $line) { $line = trim($line); - if ($line && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { - if (isset($match[1], $match[2])) { - if ($match[2] === './') { - $lastRev = (int) $match[1]; - } else { - $tags[rtrim($match[2], '/')] = $this->buildIdentifier( - '/' . $this->tagsPath . '/' . $match[2], - max($lastRev, (int) $match[1]) - ); - } + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $lastRev = (int) $match[1]; + } else { + $tags[rtrim($match[2], '/')] = $this->buildIdentifier( + '/' . $this->tagsPath . '/' . $match[2], + max($lastRev, (int) $match[1]) + ); } } } @@ -276,11 +272,11 @@ class SvnDriver extends VcsDriver } $output = $this->execute('svn ls --verbose', $trunkParent); - if ($output) { + if ($output !== '') { foreach ($this->process->splitLines($output) as $line) { $line = trim($line); - if ($line && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { - if (isset($match[1], $match[2]) && $match[2] === './') { + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { $branches['trunk'] = $this->buildIdentifier( '/' . $this->trunkPath, (int) $match[1] @@ -295,20 +291,18 @@ class SvnDriver extends VcsDriver if ($this->branchesPath !== false) { $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->branchesPath); - if ($output) { + if ($output !== '') { $lastRev = 0; foreach ($this->process->splitLines(trim($output)) as $line) { $line = trim($line); - if ($line && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { - if (isset($match[1], $match[2])) { - if ($match[2] === './') { - $lastRev = (int) $match[1]; - } else { - $branches[rtrim($match[2], '/')] = $this->buildIdentifier( - '/' . $this->branchesPath . '/' . $match[2], - max($lastRev, (int) $match[1]) - ); - } + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $lastRev = (int) $match[1]; + } else { + $branches[rtrim($match[2], '/')] = $this->buildIdentifier( + '/' . $this->branchesPath . '/' . $match[2], + max($lastRev, (int) $match[1]) + ); } } } diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index 38cf84e44..fbd92843d 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -40,7 +40,7 @@ class ErrorHandler $isDeprecationNotice = $level === E_DEPRECATED || $level === E_USER_DEPRECATED; // error code is not included in error_reporting - if (!$isDeprecationNotice && !(error_reporting() & $level)) { + if (!$isDeprecationNotice && 0 === (error_reporting() & $level)) { return true; } @@ -53,7 +53,7 @@ class ErrorHandler throw new \ErrorException($message, 0, $level, $file, $line); } - if (self::$io) { + if (self::$io !== null) { self::$io->writeError('Deprecation Notice: '.$message.' in '.$file.':'.$line.''); if (self::$io->isVerbose()) { self::$io->writeError('Stack trace:'); diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 64b643216..5a949db70 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -69,6 +69,7 @@ class Git $protocols = $this->config->get('github-protocols'); // public github, autoswitch protocols + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups if (Preg::isMatchStrictGroups('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) { $messages = []; foreach ($protocols as $protocol) { @@ -104,7 +105,9 @@ class Git if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $commandOutput, $cwd)) { $errorMsg = $this->process->getErrorOutput(); // private github repository without ssh key access, try https with auth + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups if (Preg::isMatchStrictGroups('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match) + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups || Preg::isMatchStrictGroups('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\.git)?$}i', $url, $match) ) { if (!$this->io->hasAuthentication($match[1])) { @@ -127,6 +130,7 @@ class Git $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups } elseif (Preg::isMatchStrictGroups('{^https://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match)) { //bitbucket oauth $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process); @@ -172,7 +176,9 @@ class Git $errorMsg = $this->process->getErrorOutput(); } } elseif ( + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match) + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups || Preg::isMatchStrictGroups('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match) ) { if ($match[1] === 'git') { @@ -191,9 +197,9 @@ class Git if ($this->io->hasAuthentication($match[2])) { $auth = $this->io->getAuthentication($match[2]); if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') { - $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password + $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode((string) $auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password } else { - $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3]; + $authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3]; } $command = $commandCallable($authUrl); @@ -201,11 +207,11 @@ class Git return; } - $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; + $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } - } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github/gitlab/bitbucket repo that failed to authenticate - if (strpos($match[2], '@')) { + } elseif (null !== ($match = $this->getAuthenticationFailure($url))) { // private non-github/gitlab/bitbucket repo that failed to authenticate + if (str_contains($match[2], '@')) { [$authParts, $match[2]] = explode('@', $match[2], 2); } @@ -214,8 +220,8 @@ class Git $auth = $this->io->getAuthentication($match[2]); } elseif ($this->io->isInteractive()) { $defaultUsername = null; - if (isset($authParts) && $authParts) { - if (false !== strpos($authParts, ':')) { + if (isset($authParts) && $authParts !== '') { + if (str_contains($authParts, ':')) { [$defaultUsername, ] = explode(':', $authParts, 2); } else { $defaultUsername = $authParts; @@ -232,7 +238,7 @@ class Git } if (null !== $auth) { - $authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3]; + $authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3]; $command = $commandCallable($authUrl); if (0 === $this->process->execute($command, $commandOutput, $cwd)) { @@ -243,7 +249,7 @@ class Git return; } - $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; + $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } } @@ -262,7 +268,7 @@ class Git public function syncMirror(string $url, string $dir): bool { - if (Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') { + if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') { $this->io->writeError('Aborting git mirror sync of '.$url.' as network is disabled'); return false; @@ -357,13 +363,12 @@ class Git } /** - * @param array $match - * @param-out array $match + * @return array|null */ - private function isAuthenticationFailure(string $url, array &$match): bool + private function getAuthenticationFailure(string $url): ?array { if (!Preg::isMatchStrictGroups('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { - return false; + return null; } $authFailures = [ @@ -377,11 +382,11 @@ class Git $errorOutput = $this->process->getErrorOutput(); foreach ($authFailures as $authFailure) { if (strpos($errorOutput, $authFailure) !== false) { - return true; + return $match; } } - return false; + return null; } public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository): ?string @@ -405,7 +410,7 @@ class Git $lines = $this->process->splitLines($output); foreach ($lines as $line) { - if (Preg::match('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches) > 0) { + if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches)) { return $matches[1]; } } diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index c687542e0..28107584b 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -60,17 +60,17 @@ class Hg // Try with the authentication information available if ( Preg::isMatch('{^(?Pssh|https?)://(?:(?P[^:@]+)(?::(?P[^:@]+))?@)?(?P[^/]+)(?P/.*)?}mi', $url, $matches) - && $this->io->hasAuthentication((string) $matches['host']) + && $this->io->hasAuthentication($matches['host']) ) { if ($matches['proto'] === 'ssh') { $user = ''; - if ($matches['user'] !== '' && $matches['user'] !== null) { + if ($matches['user'] !== null) { $user = rawurlencode($matches['user']) . '@'; } $authenticatedUrl = $matches['proto'] . '://' . $user . $matches['host'] . $matches['path']; } else { - $auth = $this->io->getAuthentication((string) $matches['host']); - $authenticatedUrl = $matches['proto'] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $matches['host'] . $matches['path']; + $auth = $this->io->getAuthentication($matches['host']); + $authenticatedUrl = $matches['proto'] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $matches['host'] . $matches['path']; } $command = $commandCallable($authenticatedUrl); diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 452862273..807fe5ce9 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -78,7 +78,7 @@ class AllFunctionalTest extends TestCase $ri = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::SELF_FIRST); foreach ($ri as $file) { - $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName(); + $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathname(); if ($file->isDir()) { $fs->ensureDirectoryExists($targetPath); } else { @@ -89,7 +89,7 @@ class AllFunctionalTest extends TestCase $proc = new Process([PHP_BINARY, '-dphar.readonly=0', './bin/compile'], $target); $exitcode = $proc->run(); - if ($exitcode !== 0 || trim($proc->getOutput())) { + if ($exitcode !== 0 || trim($proc->getOutput()) !== '') { $this->fail($proc->getOutput()); } @@ -136,7 +136,9 @@ class AllFunctionalTest extends TestCase $line++; } if ($expected[$i] === '%') { - Preg::isMatchStrictGroups('{%(.+?)%}', substr($expected, $i), $match); + if (!Preg::isMatchStrictGroups('{%(.+?)%}', substr($expected, $i), $match)) { + throw new \LogicException('Failed to match %...% in '.substr($expected, $i)); + } $regex = $match[1]; if (Preg::isMatch('{'.$regex.'}', substr($output, $j), $match)) { @@ -146,7 +148,7 @@ class AllFunctionalTest extends TestCase } else { $this->fail( 'Failed to match pattern '.$regex.' at line '.$line.' / abs offset '.$i.': ' - .substr($output, $j, min(strpos($output, "\n", $j) - $j, 100)).PHP_EOL.PHP_EOL. + .substr($output, $j, min(((int) strpos($output, "\n", $j)) - $j, 100)).PHP_EOL.PHP_EOL. 'Output:'.PHP_EOL.$output ); } @@ -154,8 +156,8 @@ class AllFunctionalTest extends TestCase if ($expected[$i] !== $output[$j]) { $this->fail( 'Output does not match expectation at line '.$line.' / abs offset '.$i.': '.PHP_EOL - .'-'.substr($expected, $i, min(strpos($expected, "\n", $i) - $i, 100)).PHP_EOL - .'+'.substr($output, $j, min(strpos($output, "\n", $j) - $j, 100)).PHP_EOL.PHP_EOL + .'-'.substr($expected, $i, min(((int) strpos($expected, "\n", $i)) - $i, 100)).PHP_EOL + .'+'.substr($output, $j, min(((int) strpos($output, "\n", $j)) - $j, 100)).PHP_EOL.PHP_EOL .'Output:'.PHP_EOL.$output ); } @@ -195,7 +197,7 @@ class AllFunctionalTest extends TestCase */ private function parseTestFile(string $file): array { - $tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file), -1, PREG_SPLIT_DELIM_CAPTURE); + $tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', (string) file_get_contents($file), -1, PREG_SPLIT_DELIM_CAPTURE); $data = []; $section = null;