From 666548a124fe445fdc024183e1dc56f80763529b Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 9 Feb 2025 21:46:13 +0000 Subject: [PATCH] Type hints for Safe\preg_match, fixes #40 --- phpstan-safe-rule.neon | 4 ++ .../PregMatchParameterOutTypeExtension.php | 57 +++++++++++++++++++ ...PregMatchParameterOutTypeExtensionTest.php | 32 +++++++++++ tests/Type/Php/data/preg.php | 15 +++++ 4 files changed, 108 insertions(+) create mode 100644 src/Type/Php/PregMatchParameterOutTypeExtension.php create mode 100644 tests/Type/Php/PregMatchParameterOutTypeExtensionTest.php create mode 100644 tests/Type/Php/data/preg.php diff --git a/phpstan-safe-rule.neon b/phpstan-safe-rule.neon index 9d714f1..22b037a 100644 --- a/phpstan-safe-rule.neon +++ b/phpstan-safe-rule.neon @@ -11,3 +11,7 @@ services: class: TheCodingMachine\Safe\PHPStan\Type\Php\ReplaceSafeFunctionsDynamicReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: TheCodingMachine\Safe\PHPStan\Type\Php\PregMatchParameterOutTypeExtension + tags: + - phpstan.functionParameterOutTypeExtension diff --git a/src/Type/Php/PregMatchParameterOutTypeExtension.php b/src/Type/Php/PregMatchParameterOutTypeExtension.php new file mode 100644 index 0000000..5c210fd --- /dev/null +++ b/src/Type/Php/PregMatchParameterOutTypeExtension.php @@ -0,0 +1,57 @@ +getName(), ['Safe\preg_match', 'Safe\preg_match_all'], true) + // the parameter is named different, depending on PHP version. + && in_array($parameter->getName(), ['subpatterns', 'matches'], true); + } + + public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type + { + $args = $funcCall->getArgs(); + $patternArg = $args[0] ?? null; + $matchesArg = $args[2] ?? null; + $flagsArg = $args[3] ?? null; + + if ($patternArg === null || $matchesArg === null + ) { + return null; + } + + $flagsType = null; + if ($flagsArg !== null) { + $flagsType = $scope->getType($flagsArg->value); + } + + if ($functionReflection->getName() === 'Safe\preg_match') { + return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); + } + return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); + } +} diff --git a/tests/Type/Php/PregMatchParameterOutTypeExtensionTest.php b/tests/Type/Php/PregMatchParameterOutTypeExtensionTest.php new file mode 100644 index 0000000..9a837b8 --- /dev/null +++ b/tests/Type/Php/PregMatchParameterOutTypeExtensionTest.php @@ -0,0 +1,32 @@ + + */ + public static function dataFileAsserts(): iterable + { + yield from self::gatherAssertTypes(__DIR__ . '/data/preg.php'); + } + + /** + * @dataProvider dataFileAsserts + */ + public function testFileAsserts( + string $assertType, + string $file, + mixed ...$args + ): void { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [__DIR__ . '/../../../phpstan-safe-rule.neon']; + } +} diff --git a/tests/Type/Php/data/preg.php b/tests/Type/Php/data/preg.php new file mode 100644 index 0000000..6e2819c --- /dev/null +++ b/tests/Type/Php/data/preg.php @@ -0,0 +1,15 @@ +