From 17e4b74335e5235d7cd6708eb687a774a0eeead4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 26 Oct 2022 12:07:32 +0200 Subject: [PATCH] Bleeding edge - detect duplicate functions --- src/PhpDoc/StubValidator.php | 9 +-- .../DuplicateFunctionDeclarationRule.php | 59 +++++++++++++++++++ stubs/arrayFunctions.stub | 8 +-- stubs/typeCheckingFunctions.stub | 9 --- .../DuplicateFunctionDeclarationRuleTest.php | 49 +++++++++++++++ .../Functions/data/duplicate-function.php | 23 ++++++++ 6 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 src/Rules/Functions/DuplicateFunctionDeclarationRule.php create mode 100644 tests/PHPStan/Rules/Functions/DuplicateFunctionDeclarationRuleTest.php create mode 100644 tests/PHPStan/Rules/Functions/data/duplicate-function.php diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 69c2babc53..578b719bb0 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -23,6 +23,7 @@ use PHPStan\Rules\Classes\ExistingClassInTraitUseRule; use PHPStan\Rules\DirectRegistry as DirectRuleRegistry; use PHPStan\Rules\FunctionDefinitionCheck; +use PHPStan\Rules\Functions\DuplicateFunctionDeclarationRule; use PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule; use PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule; use PHPStan\Rules\Generics\ClassAncestorsRule; @@ -191,10 +192,10 @@ private function getRuleRegistry(Container $container): RuleRegistry ]; if ($this->duplicateStubs) { - $rules[] = new DuplicateClassDeclarationRule( - $container->getService('stubReflector'), - $container->getService('simpleRelativePathHelper'), - ); + $reflector = $container->getService('stubReflector'); + $relativePathHelper = $container->getService('simpleRelativePathHelper'); + $rules[] = new DuplicateClassDeclarationRule($reflector, $relativePathHelper); + $rules[] = new DuplicateFunctionDeclarationRule($reflector, $relativePathHelper); } return new DirectRuleRegistry($rules); diff --git a/src/Rules/Functions/DuplicateFunctionDeclarationRule.php b/src/Rules/Functions/DuplicateFunctionDeclarationRule.php new file mode 100644 index 0000000000..ee64f2d0bf --- /dev/null +++ b/src/Rules/Functions/DuplicateFunctionDeclarationRule.php @@ -0,0 +1,59 @@ + + */ +class DuplicateFunctionDeclarationRule implements Rule +{ + + public function __construct(private Reflector $reflector, private RelativePathHelper $relativePathHelper) + { + } + + public function getNodeType(): string + { + return InFunctionNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $thisFunction = $node->getFunctionReflection(); + $allFunctions = $this->reflector->reflectAllFunctions(); + $filteredFunctions = []; + foreach ($allFunctions as $reflectionFunction) { + if ($reflectionFunction->getName() !== $thisFunction->getName()) { + continue; + } + + $filteredFunctions[] = $reflectionFunction; + } + + if (count($filteredFunctions) < 2) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + "Function %s declared multiple times:\n%s", + $thisFunction->getName(), + implode("\n", array_map(fn (ReflectionFunction $function) => sprintf('- %s:%d', $this->relativePathHelper->getRelativePath($function->getFileName() ?? 'unknown'), $function->getStartLine()), $filteredFunctions)), + ))->build(), + ]; + } + +} diff --git a/stubs/arrayFunctions.stub b/stubs/arrayFunctions.stub index 9a4c581594..d015ddfffc 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -66,9 +66,7 @@ function array_udiff( ): int {} /** - * @template K of array-key - * @template V - * @param array $array - * @return ($array is list ? true : false) + *@param array $value + * @phpstan-assert-if-true list $value */ -function array_is_list(array $array): bool {} +function array_is_list(array $value): bool {} diff --git a/stubs/typeCheckingFunctions.stub b/stubs/typeCheckingFunctions.stub index 5c3c4f5515..6d28e66ce6 100644 --- a/stubs/typeCheckingFunctions.stub +++ b/stubs/typeCheckingFunctions.stub @@ -111,12 +111,3 @@ function is_resource(mixed $value): bool { } - -/** - * @param array $value - * @phpstan-assert-if-true list $value - */ -function array_is_list(array $value): bool -{ - -} diff --git a/tests/PHPStan/Rules/Functions/DuplicateFunctionDeclarationRuleTest.php b/tests/PHPStan/Rules/Functions/DuplicateFunctionDeclarationRuleTest.php new file mode 100644 index 0000000000..784ba33c91 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/DuplicateFunctionDeclarationRuleTest.php @@ -0,0 +1,49 @@ + + */ +class DuplicateFunctionDeclarationRuleTest extends RuleTestCase +{ + + private const FILENAME = __DIR__ . '/data/duplicate-function.php'; + + protected function getRule(): Rule + { + return new DuplicateFunctionDeclarationRule( + new DefaultReflector(new OptimizedSingleFileSourceLocator( + self::getContainer()->getByType(FileNodesFetcher::class), + self::FILENAME, + )), + new SimpleRelativePathHelper(__DIR__ . '/data'), + ); + } + + public function testRule(): void + { + $this->analyse([self::FILENAME], [ + [ + "Function DuplicateFunctionDeclaration\\foo declared multiple times:\n- duplicate-function.php:10\n- duplicate-function.php:15\n- duplicate-function.php:20", + 10, + ], + [ + "Function DuplicateFunctionDeclaration\\foo declared multiple times:\n- duplicate-function.php:10\n- duplicate-function.php:15\n- duplicate-function.php:20", + 15, + ], + [ + "Function DuplicateFunctionDeclaration\\foo declared multiple times:\n- duplicate-function.php:10\n- duplicate-function.php:15\n- duplicate-function.php:20", + 20, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/duplicate-function.php b/tests/PHPStan/Rules/Functions/data/duplicate-function.php new file mode 100644 index 0000000000..35efa010bf --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/duplicate-function.php @@ -0,0 +1,23 @@ +