diff --git a/composer.json b/composer.json index c6f4d3d8..22bbc86d 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,10 @@ } }, "autoload-dev": { - "classmap": ["tests/"] + "classmap": ["tests/"], + "files": [ + "tests/Rules/Functions/data/missing-function-parameter-typehint.php", + "tests/Rules/Functions/data/missing-function-return-typehint.php" + ] } } diff --git a/rules.neon b/rules.neon index d4718ccb..a5bc9d92 100644 --- a/rules.neon +++ b/rules.neon @@ -14,6 +14,8 @@ rules: - PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule - PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule - PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule + - PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule + - PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule - PHPStan\Rules\Methods\MissingMethodParameterTypehintRule - PHPStan\Rules\Methods\MissingMethodReturnTypehintRule - PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule diff --git a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php new file mode 100644 index 00000000..8352735d --- /dev/null +++ b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php @@ -0,0 +1,67 @@ +broker = $broker; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\Function_::class; + } + + /** + * @param \PhpParser\Node\Stmt\Function_ $node + * @param \PHPStan\Analyser\Scope $scope + * + * @return string[] errors + */ + public function processNode(Node $node, Scope $scope): array + { + $functionReflection = $this->broker->getCustomFunction(new Node\Name($node->name), $scope); + + $messages = []; + + foreach ($functionReflection->getParameters() as $parameterReflection) { + $message = $this->checkFunctionParameter($functionReflection, $parameterReflection); + if ($message === null) { + continue; + } + + $messages[] = $message; + } + + return $messages; + } + + private function checkFunctionParameter(FunctionReflection $functionReflection, ParameterReflection $parameterReflection): ?string + { + $parameterType = $parameterReflection->getType(); + + if ($parameterType instanceof MixedType && !$parameterType->isExplicitMixed()) { + return sprintf( + 'Function %s() has parameter $%s with no typehint specified.', + $functionReflection->getName(), + $parameterReflection->getName() + ); + } + + return null; + } + +} diff --git a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php new file mode 100644 index 00000000..d3ae248b --- /dev/null +++ b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php @@ -0,0 +1,49 @@ +broker = $broker; + } + + public function getNodeType(): string + { + return \PhpParser\Node\Stmt\Function_::class; + } + + /** + * @param \PhpParser\Node\Stmt\Function_ $node + * @param \PHPStan\Analyser\Scope $scope + * + * @return string[] errors + */ + public function processNode(Node $node, Scope $scope): array + { + $functionReflection = $this->broker->getCustomFunction(new Node\Name($node->name), $scope); + $returnType = $functionReflection->getReturnType(); + + if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { + return [ + sprintf( + 'Function %s() has no return typehint specified.', + $functionReflection->getName() + ), + ]; + } + + return []; + } + +} diff --git a/tests/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php b/tests/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php new file mode 100644 index 00000000..37321538 --- /dev/null +++ b/tests/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php @@ -0,0 +1,33 @@ +createBroker([], [])); + + return $rule; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-function-parameter-typehint.php'], [ + [ + 'Function globalFunction() has parameter $b with no typehint specified', + 9, + ], + [ + 'Function globalFunction() has parameter $c with no typehint specified', + 9, + ], + [ + 'Function MissingFunctionParameterTypehint\namespacedFunction() has parameter $d with no typehint specified', + 24, + ], + ]); + } + +} diff --git a/tests/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php b/tests/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php new file mode 100644 index 00000000..2a25e47d --- /dev/null +++ b/tests/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php @@ -0,0 +1,29 @@ +createBroker([], [])); + + return $rule; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-function-return-typehint.php'], [ + [ + 'Function globalFunction1() has no return typehint specified', + 5, + ], + [ + 'Function MissingFunctionReturnTypehint\namespacedFunction1() has no return typehint specified', + 30, + ], + ]); + } + +} diff --git a/tests/Rules/Functions/data/missing-function-parameter-typehint.php b/tests/Rules/Functions/data/missing-function-parameter-typehint.php new file mode 100644 index 00000000..4eda03c3 --- /dev/null +++ b/tests/Rules/Functions/data/missing-function-parameter-typehint.php @@ -0,0 +1,27 @@ +