From ad8c01aa31063e3b0303eb86e4f9f8c6bf2b77e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Thu, 19 May 2022 22:21:13 +0200 Subject: [PATCH 1/3] feature: introduce option to disallow integers for string placeholders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new option is enabled by default and this allows `int` for `string` placeholders. Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- README.md | 2 +- composer.lock | 176 +++++++++--------- ...lyInvalidArgumentForSpecifierValidator.php | 14 +- .../SprintfFunctionReturnProvider.php | 3 +- .../StringfFunctionArgumentValidator.php | 3 +- .../UnnecessaryFunctionCallValidator.php | 3 +- .../TemplatedStringParser/Placeholder.php | 21 ++- .../SpecifierTypeGenerator.php | 26 ++- .../TemplatedStringParser.php | 20 +- src/Plugin.php | 38 +++- tests/acceptance/PrintfArgumentType.feature | 3 +- ...allowedIntegerForStringPlaceholder.feature | 44 +++++ 12 files changed, 234 insertions(+), 119 deletions(-) create mode 100644 tests/acceptance/PrintfInvalidArgumentForSpecifierWithDisallowedIntegerForStringPlaceholder.feature diff --git a/README.md b/README.md index 2c0bb54..9a6b122 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Experimental features can be enabled by extending the plugin configuration as fo ```xml - + ``` diff --git a/composer.lock b/composer.lock index 47f495b..c2beed5 100644 --- a/composer.lock +++ b/composer.lock @@ -2408,16 +2408,16 @@ }, { "name": "codeception/lib-innerbrowser", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/Codeception/lib-innerbrowser.git", - "reference": "c7bf42d5a38e2883ae9cf298ec9d15b1185815fb" + "reference": "108679cd01a297df6f9e3e6e4467a8b06f708b34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/c7bf42d5a38e2883ae9cf298ec9d15b1185815fb", - "reference": "c7bf42d5a38e2883ae9cf298ec9d15b1185815fb", + "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/108679cd01a297df6f9e3e6e4467a8b06f708b34", + "reference": "108679cd01a297df6f9e3e6e4467a8b06f708b34", "shasum": "" }, "require": { @@ -2462,9 +2462,9 @@ ], "support": { "issues": "https://github.com/Codeception/lib-innerbrowser/issues", - "source": "https://github.com/Codeception/lib-innerbrowser/tree/2.0.1" + "source": "https://github.com/Codeception/lib-innerbrowser/tree/2.0.2" }, - "time": "2021-12-21T01:40:35+00:00" + "time": "2022-01-27T15:55:51+00:00" }, { "name": "codeception/module-asserts", @@ -2774,27 +2774,27 @@ }, { "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.1", + "version": "v0.7.2", "source": { "type": "git", "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "fe390591e0241955f22eb9ba327d137e501c771c" + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c", - "reference": "fe390591e0241955f22eb9ba327d137e501c771c", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", "shasum": "" }, "require": { "composer-plugin-api": "^1.0 || ^2.0", "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0" + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" }, "require-dev": { "composer/composer": "*", - "phpcompatibility/php-compatibility": "^9.0", - "sensiolabs/security-checker": "^4.1.0" + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" }, "type": "composer-plugin", "extra": { @@ -2815,6 +2815,10 @@ "email": "franck.nijhof@dealerdirect.com", "homepage": "http://www.frenck.nl", "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" } ], "description": "PHP_CodeSniffer Standards Composer Installer Plugin", @@ -2826,6 +2830,7 @@ "codesniffer", "composer", "installer", + "phpcbf", "phpcs", "plugin", "qa", @@ -2840,7 +2845,7 @@ "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" }, - "time": "2020-12-07T18:04:37+00:00" + "time": "2022-02-04T12:51:07+00:00" }, { "name": "doctrine/coding-standard", @@ -2969,16 +2974,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.4.1", + "version": "7.4.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79" + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4", + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4", "shasum": "" }, "require": { @@ -3073,7 +3078,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.1" + "source": "https://github.com/guzzle/guzzle/tree/7.4.2" }, "funding": [ { @@ -3089,7 +3094,7 @@ "type": "tidelift" } ], - "time": "2021-12-06T18:43:05+00:00" + "time": "2022-03-20T14:16:28+00:00" }, { "name": "guzzlehttp/promises", @@ -3529,35 +3534,30 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "0.5.5", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c" + "reference": "981cc368a216c988e862a75e526b6076987d1b50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c", - "reference": "ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", + "reference": "981cc368a216c988e862a75e526b6076987d1b50", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.87", - "phpstan/phpstan-strict-rules": "^0.12.5", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5", "symfony/process": "^5.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.5-dev" - } - }, "autoload": { "psr-4": { "PHPStan\\PhpDocParser\\": [ @@ -3572,9 +3572,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.5" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" }, - "time": "2021-06-11T13:24:46+00:00" + "time": "2022-05-05T11:32:40+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3896,16 +3896,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.18", + "version": "9.5.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1b5856028273bfd855e60a887278857d872ec67a" + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1b5856028273bfd855e60a887278857d872ec67a", - "reference": "1b5856028273bfd855e60a887278857d872ec67a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", "shasum": "" }, "require": { @@ -3935,7 +3935,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.4", + "sebastian/type": "^3.0", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -3983,7 +3983,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.18" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" }, "funding": [ { @@ -3995,7 +3995,7 @@ "type": "github" } ], - "time": "2022-03-08T06:52:28+00:00" + "time": "2022-04-01T12:37:26+00:00" }, { "name": "psr/event-dispatcher", @@ -4551,16 +4551,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { @@ -4602,7 +4602,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" }, "funding": [ { @@ -4610,7 +4610,7 @@ "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2022-04-03T09:37:03+00:00" }, { "name": "sebastian/exporter", @@ -5042,28 +5042,28 @@ }, { "name": "sebastian/type", - "version": "2.3.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -5086,7 +5086,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" }, "funding": [ { @@ -5094,7 +5094,7 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2022-03-15T09:54:48+00:00" }, { "name": "sebastian/version", @@ -5151,32 +5151,32 @@ }, { "name": "slevomat/coding-standard", - "version": "7.0.12", + "version": "7.2.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "5716052725863953ddc2f8a7e934c09cb64bdf73" + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/5716052725863953ddc2f8a7e934c09cb64bdf73", - "reference": "5716052725863953ddc2f8a7e934c09cb64bdf73", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f96a8beea515d2d89141b7b9ad72f526d84071", + "reference": "b4f96a8beea515d2d89141b7b9ad72f526d84071", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", - "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.5.1 - 0.5.5", - "squizlabs/php_codesniffer": "^3.6.0" + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" }, "require-dev": { - "phing/phing": "2.16.4", - "php-parallel-lint/php-parallel-lint": "1.3.0", - "phpstan/phpstan": "0.12.91", - "phpstan/phpstan-deprecation-rules": "0.12.6", - "phpstan/phpstan-phpunit": "0.12.20", - "phpstan/phpstan-strict-rules": "0.12.10", - "phpunit/phpunit": "7.5.20|8.5.5|9.5.6" + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.6.7", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" }, "type": "phpcodesniffer-standard", "extra": { @@ -5196,7 +5196,7 @@ "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/7.0.12" + "source": "https://github.com/slevomat/coding-standard/tree/7.2.0" }, "funding": [ { @@ -5208,20 +5208,20 @@ "type": "tidelift" } ], - "time": "2021-07-13T17:47:03+00:00" + "time": "2022-05-06T10:58:42+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.6.0", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", - "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", "shasum": "" }, "require": { @@ -5264,20 +5264,20 @@ "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2021-04-09T00:54:41+00:00" + "time": "2021-12-12T21:44:58+00:00" }, { "name": "symfony/browser-kit", - "version": "v5.4.0", + "version": "v5.4.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "d250db364a35ba5d60626b2a6f10f2eaf2073bde" + "reference": "18e73179c6a33d520de1b644941eba108dd811ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/d250db364a35ba5d60626b2a6f10f2eaf2073bde", - "reference": "d250db364a35ba5d60626b2a6f10f2eaf2073bde", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/18e73179c6a33d520de1b644941eba108dd811ad", + "reference": "18e73179c6a33d520de1b644941eba108dd811ad", "shasum": "" }, "require": { @@ -5320,7 +5320,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v5.4.0" + "source": "https://github.com/symfony/browser-kit/tree/v5.4.3" }, "funding": [ { @@ -5336,7 +5336,7 @@ "type": "tidelift" } ], - "time": "2021-10-26T22:29:18+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/css-selector", @@ -5406,16 +5406,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.0", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "5b06626e940a3ad54e573511d64d4e00dc8d0fd8" + "reference": "c0bda97480d96337bd3866026159a8b358665457" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5b06626e940a3ad54e573511d64d4e00dc8d0fd8", - "reference": "5b06626e940a3ad54e573511d64d4e00dc8d0fd8", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c0bda97480d96337bd3866026159a8b358665457", + "reference": "c0bda97480d96337bd3866026159a8b358665457", "shasum": "" }, "require": { @@ -5461,7 +5461,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.0" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.6" }, "funding": [ { @@ -5477,7 +5477,7 @@ "type": "tidelift" } ], - "time": "2021-11-23T10:19:22+00:00" + "time": "2022-03-02T12:42:23+00:00" }, { "name": "symfony/event-dispatcher", diff --git a/src/EventHandler/PossiblyInvalidArgumentForSpecifierValidator.php b/src/EventHandler/PossiblyInvalidArgumentForSpecifierValidator.php index e9321b9..634188e 100644 --- a/src/EventHandler/PossiblyInvalidArgumentForSpecifierValidator.php +++ b/src/EventHandler/PossiblyInvalidArgumentForSpecifierValidator.php @@ -26,6 +26,8 @@ final class PossiblyInvalidArgumentForSpecifierValidator implements AfterEveryFunctionCallAnalysisInterface { + public static bool $allowIntegerForStringPlaceholder = true; + private const FUNCTIONS = [ 'sprintf', 'printf', @@ -95,7 +97,8 @@ public function assert( $this->functionName, $template, $context, - $phpVersion + $phpVersion, + self::$allowIntegerForStringPlaceholder ); } catch (InvalidArgumentException $exception) { return; @@ -196,4 +199,13 @@ private function invalidTypeWouldBeCoveredByPsalmItself(Atomic $type): bool return true; } + + public static function applyOptions(array $options): void + { + if (! isset($options['allowIntegerForString']) || $options['allowIntegerForString'] === 'yes') { + return; + } + + self::$allowIntegerForStringPlaceholder = false; + } } diff --git a/src/EventHandler/SprintfFunctionReturnProvider.php b/src/EventHandler/SprintfFunctionReturnProvider.php index 4308e83..4830db0 100644 --- a/src/EventHandler/SprintfFunctionReturnProvider.php +++ b/src/EventHandler/SprintfFunctionReturnProvider.php @@ -44,7 +44,8 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $functionName, $templateArgument, $context, - PhpVersion::fromStatementSource($event->getStatementsSource()) + PhpVersion::fromStatementSource($event->getStatementsSource()), + false ); } catch (InvalidArgumentException $exception) { return null; diff --git a/src/EventHandler/StringfFunctionArgumentValidator.php b/src/EventHandler/StringfFunctionArgumentValidator.php index d0d3f32..87b7683 100644 --- a/src/EventHandler/StringfFunctionArgumentValidator.php +++ b/src/EventHandler/StringfFunctionArgumentValidator.php @@ -135,7 +135,8 @@ private function validate( $this->functionName, $template, $context, - $phpVersion + $phpVersion, + false ); } catch (InvalidArgumentException $exception) { return; diff --git a/src/EventHandler/UnnecessaryFunctionCallValidator.php b/src/EventHandler/UnnecessaryFunctionCallValidator.php index 8e0caa4..b0f4e1b 100644 --- a/src/EventHandler/UnnecessaryFunctionCallValidator.php +++ b/src/EventHandler/UnnecessaryFunctionCallValidator.php @@ -89,7 +89,8 @@ private function assert( $this->functionName, $template, $context, - $phpVersion + $phpVersion, + false ); } catch (InvalidArgumentException $exception) { return; diff --git a/src/Parser/TemplatedStringParser/Placeholder.php b/src/Parser/TemplatedStringParser/Placeholder.php index 3116b77..311beff 100644 --- a/src/Parser/TemplatedStringParser/Placeholder.php +++ b/src/Parser/TemplatedStringParser/Placeholder.php @@ -27,25 +27,28 @@ final class Placeholder private ?Union $type; + private bool $allowIntegerForStringPlaceholder; + /** * @psalm-param non-empty-string $value * @psalm-param positive-int $position */ - private function __construct(string $value, int $position) + private function __construct(string $value, int $position, bool $allowIntegerForStringPlaceholder) { - $this->value = $value; - $this->position = $position; - $this->argumentValueType = null; - $this->type = null; + $this->value = $value; + $this->position = $position; + $this->argumentValueType = null; + $this->type = null; + $this->allowIntegerForStringPlaceholder = $allowIntegerForStringPlaceholder; } /** * @psalm-param non-empty-string $value * @psalm-param positive-int $position */ - public static function create(string $value, int $position): self + public static function create(string $value, int $position, bool $allowIntegerForStringPlaceholder): self { - return new self($value, $position); + return new self($value, $position, $allowIntegerForStringPlaceholder); } /** @@ -139,13 +142,13 @@ public function getSuggestedType(): ?Union } try { - $type = SpecifierTypeGenerator::create($this->value)->getSuggestedType(); + $type = SpecifierTypeGenerator::create($this->value, $this->allowIntegerForStringPlaceholder)->getSuggestedType(); } catch (InvalidArgumentException $exception) { return null; } if ($this->repeated === []) { - return $type; + return $this->type = $type; } $unions = [$type]; diff --git a/src/Parser/TemplatedStringParser/SpecifierTypeGenerator.php b/src/Parser/TemplatedStringParser/SpecifierTypeGenerator.php index c9a2de7..f9e054e 100644 --- a/src/Parser/TemplatedStringParser/SpecifierTypeGenerator.php +++ b/src/Parser/TemplatedStringParser/SpecifierTypeGenerator.php @@ -20,27 +20,30 @@ final class SpecifierTypeGenerator /** @psalm-var non-empty-string */ private string $specifier; + private bool $allowIntegerForStringPlaceholder; + /** * @psalm-param non-empty-string $specifier */ - private function __construct(string $specifier) + private function __construct(string $specifier, bool $allowIntegerForStringPlaceholder) { - $this->specifier = $this->parse($specifier); + $this->specifier = $this->parse($specifier); + $this->allowIntegerForStringPlaceholder = $allowIntegerForStringPlaceholder; } /** * @psalm-param non-empty-string $specifier */ - public static function create(string $specifier): self + public static function create(string $specifier, bool $allowIntegerForStringPlaceholder): self { - return new self($specifier); + return new self($specifier, $allowIntegerForStringPlaceholder); } public function getSuggestedType(): Type\Union { switch ($this->specifier) { case 's': - return Type::getString(); + return $this->stringable(); case 'd': case 'f': @@ -80,4 +83,17 @@ private function numeric(): Type\Union new Type\Atomic\TNumericString(), ]); } + + private function stringable(): Type\Union + { + $types = [ + new Type\Atomic\TString(), + ]; + + if ($this->allowIntegerForStringPlaceholder) { + $types[] = new Type\Atomic\TInt(); + } + + return new Type\Union($types); + } } diff --git a/src/Parser/TemplatedStringParser/TemplatedStringParser.php b/src/Parser/TemplatedStringParser/TemplatedStringParser.php index 4640393..8f9c488 100644 --- a/src/Parser/TemplatedStringParser/TemplatedStringParser.php +++ b/src/Parser/TemplatedStringParser/TemplatedStringParser.php @@ -42,24 +42,29 @@ final class TemplatedStringParser private string $template; + private bool $allowIntegerForStringPlaceholder; + /** * @psalm-param positive-int $phpVersion */ private function __construct( string $functionName, string $template, - int $phpVersion + int $phpVersion, + bool $allowIntegerForStringPlaceholder ) { $this->template = $template; $this->templateWithoutPlaceholder = $template; $this->placeholders = []; - $this->parse($functionName, $template, $phpVersion); + $this->parse($functionName, $template, $phpVersion, $allowIntegerForStringPlaceholder); + $this->allowIntegerForStringPlaceholder = $allowIntegerForStringPlaceholder; } private function parse( string $functionName, string $template, - int $phpVersion + int $phpVersion, + bool $allowIntegerForStringPlaceholder ): void { $additionalSpecifierDependingOnPhpVersion = ''; if ($phpVersion >= 80000) { @@ -133,7 +138,8 @@ static function ( $initialPlaceholderInstance = $placeholderInstances[$placeholderPosition] ?? null; $placeholderInstance = Placeholder::create( $placeholderValue, - $placeholderPosition + $placeholderPosition, + $allowIntegerForStringPlaceholder ); if ($initialPlaceholderInstance !== null) { @@ -153,12 +159,14 @@ public static function fromArgument( string $functionName, Arg $templateArgument, Context $context, - int $phpVersion + int $phpVersion, + bool $allowIntegerForStringPlaceholder ): self { return new self( $functionName, ArgumentValueParser::create($templateArgument->value, $context)->toString(), - $phpVersion + $phpVersion, + $allowIntegerForStringPlaceholder ); } diff --git a/src/Plugin.php b/src/Plugin.php index b03a431..7413074 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -46,18 +46,25 @@ private function registerExperimentalHooks(RegistrationInterface $registration, } foreach ($config->experimental->children() as $element) { - assert($element !== null); + assert($element instanceof SimpleXMLElement); $name = $element->getName(); if (! isset(self::EXPERIMENTAL_FEATURES[$name])) { continue; } - $this->registerFeatureHook($registration, $name); + $options = $this->extractOptionsFromElement($element); + $this->registerFeatureHook($registration, $name, $options); } } - private function registerFeatureHook(RegistrationInterface $registration, string $featureName): void - { + /** + * @param array $options + */ + private function registerFeatureHook( + RegistrationInterface $registration, + string $featureName, + array $options + ): void { $eventHandlerClassName = self::EXPERIMENTAL_FEATURES[$featureName]; $fileName = __DIR__ . sprintf( @@ -68,5 +75,28 @@ private function registerFeatureHook(RegistrationInterface $registration, string require_once $fileName; $registration->registerHooksFromClass($eventHandlerClassName); + if ($eventHandlerClassName !== PossiblyInvalidArgumentForSpecifierValidator::class) { + return; + } + + $eventHandlerClassName::applyOptions($options); + } + + /** + * @return array + */ + private function extractOptionsFromElement(SimpleXMLElement $element): array + { + $options = []; + + foreach ($element->attributes() ?? [] as $attribute) { + assert($attribute instanceof SimpleXMLElement); + $name = $attribute->getName(); + assert($name !== ''); + + $options[$name] = (string) $attribute; + } + + return $options; } } diff --git a/tests/acceptance/PrintfArgumentType.feature b/tests/acceptance/PrintfArgumentType.feature index 12f22f1..5aaf8c6 100644 --- a/tests/acceptance/PrintfArgumentType.feature +++ b/tests/acceptance/PrintfArgumentType.feature @@ -39,8 +39,7 @@ Feature: printf argument type verification When I run Psalm Then I see these errors | Type | Message | - | PossiblyInvalidArgument | Argument 1 inferred as "1" does not match (any of) the suggested type(s) "string" | - | PossiblyInvalidArgument | Argument 1 inferred as "float(1.035)" does not match (any of) the suggested type(s) "string" | + | PossiblyInvalidArgument | Argument 1 inferred as "float(1.035)" does not match (any of) the suggested type(s) "int\|string" | And I see no other errors Scenario: template requires double but invalid values are passed diff --git a/tests/acceptance/PrintfInvalidArgumentForSpecifierWithDisallowedIntegerForStringPlaceholder.feature b/tests/acceptance/PrintfInvalidArgumentForSpecifierWithDisallowedIntegerForStringPlaceholder.feature new file mode 100644 index 0000000..0a904b1 --- /dev/null +++ b/tests/acceptance/PrintfInvalidArgumentForSpecifierWithDisallowedIntegerForStringPlaceholder.feature @@ -0,0 +1,44 @@ +Feature: Integer is not allowed to be passed to printf string placeholder when option is set + + Background: + Given I have the following config + """ + + + + + + + + + + + + + + + + + + + + + """ + And I have the following code preamble + """ + Date: Thu, 19 May 2022 22:25:42 +0200 Subject: [PATCH 2/3] qa: add missing type-hint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- .../PossiblyInvalidArgumentForSpecifierValidator.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/EventHandler/PossiblyInvalidArgumentForSpecifierValidator.php b/src/EventHandler/PossiblyInvalidArgumentForSpecifierValidator.php index 634188e..1f258b2 100644 --- a/src/EventHandler/PossiblyInvalidArgumentForSpecifierValidator.php +++ b/src/EventHandler/PossiblyInvalidArgumentForSpecifierValidator.php @@ -200,6 +200,9 @@ private function invalidTypeWouldBeCoveredByPsalmItself(Atomic $type): bool return true; } + /** + * @param array $options + */ public static function applyOptions(array $options): void { if (! isset($options['allowIntegerForString']) || $options['allowIntegerForString'] === 'yes') { From f5a118fc3d11af143e578e3ee5f5e231f860b57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Thu, 19 May 2022 22:27:53 +0200 Subject: [PATCH 3/3] bugfix: add proper typehint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- src/Plugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin.php b/src/Plugin.php index 7413074..888cf45 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -58,7 +58,7 @@ private function registerExperimentalHooks(RegistrationInterface $registration, } /** - * @param array $options + * @param array $options */ private function registerFeatureHook( RegistrationInterface $registration,