Skip to content

Commit

Permalink
Bleeding edge - detect duplicate functions
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Oct 26, 2022
1 parent 0d7db44 commit 17e4b74
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 18 deletions.
9 changes: 5 additions & 4 deletions src/PhpDoc/StubValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
59 changes: 59 additions & 0 deletions src/Rules/Functions/DuplicateFunctionDeclarationRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\BetterReflection\Reflection\ReflectionFunction;
use PHPStan\BetterReflection\Reflector\Reflector;
use PHPStan\File\RelativePathHelper;
use PHPStan\Node\InFunctionNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function array_map;
use function count;
use function implode;
use function sprintf;

/**
* @implements Rule<InFunctionNode>
*/
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(),
];
}

}
8 changes: 3 additions & 5 deletions stubs/arrayFunctions.stub
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ function array_udiff(
): int {}

/**
* @template K of array-key
* @template V
* @param array<K, V> $array
* @return ($array is list<V> ? true : false)
*@param array<array-key, mixed> $value
* @phpstan-assert-if-true list<mixed> $value
*/
function array_is_list(array $array): bool {}
function array_is_list(array $value): bool {}
9 changes: 0 additions & 9 deletions stubs/typeCheckingFunctions.stub
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,3 @@ function is_resource(mixed $value): bool
{

}

/**
* @param array<array-key, mixed> $value
* @phpstan-assert-if-true list<mixed> $value
*/
function array_is_list(array $value): bool
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PHPStan\BetterReflection\Reflector\DefaultReflector;
use PHPStan\File\SimpleRelativePathHelper;
use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher;
use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<DuplicateFunctionDeclarationRule>
*/
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,
],
]);
}

}
23 changes: 23 additions & 0 deletions tests/PHPStan/Rules/Functions/data/duplicate-function.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace DuplicateFunctionDeclaration;

function notDuplicate()
{

}

function foo()
{

}

function foo()
{

}

function foo()
{

}

0 comments on commit 17e4b74

Please sign in to comment.