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,