Skip to content

Commit 17e4b74

Browse files
committed
Bleeding edge - detect duplicate functions
1 parent 0d7db44 commit 17e4b74

File tree

6 files changed

+139
-18
lines changed

6 files changed

+139
-18
lines changed

Diff for: src/PhpDoc/StubValidator.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use PHPStan\Rules\Classes\ExistingClassInTraitUseRule;
2424
use PHPStan\Rules\DirectRegistry as DirectRuleRegistry;
2525
use PHPStan\Rules\FunctionDefinitionCheck;
26+
use PHPStan\Rules\Functions\DuplicateFunctionDeclarationRule;
2627
use PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule;
2728
use PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule;
2829
use PHPStan\Rules\Generics\ClassAncestorsRule;
@@ -191,10 +192,10 @@ private function getRuleRegistry(Container $container): RuleRegistry
191192
];
192193

193194
if ($this->duplicateStubs) {
194-
$rules[] = new DuplicateClassDeclarationRule(
195-
$container->getService('stubReflector'),
196-
$container->getService('simpleRelativePathHelper'),
197-
);
195+
$reflector = $container->getService('stubReflector');
196+
$relativePathHelper = $container->getService('simpleRelativePathHelper');
197+
$rules[] = new DuplicateClassDeclarationRule($reflector, $relativePathHelper);
198+
$rules[] = new DuplicateFunctionDeclarationRule($reflector, $relativePathHelper);
198199
}
199200

200201
return new DirectRuleRegistry($rules);
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Functions;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\BetterReflection\Reflection\ReflectionFunction;
8+
use PHPStan\BetterReflection\Reflector\Reflector;
9+
use PHPStan\File\RelativePathHelper;
10+
use PHPStan\Node\InFunctionNode;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use function array_map;
14+
use function count;
15+
use function implode;
16+
use function sprintf;
17+
18+
/**
19+
* @implements Rule<InFunctionNode>
20+
*/
21+
class DuplicateFunctionDeclarationRule implements Rule
22+
{
23+
24+
public function __construct(private Reflector $reflector, private RelativePathHelper $relativePathHelper)
25+
{
26+
}
27+
28+
public function getNodeType(): string
29+
{
30+
return InFunctionNode::class;
31+
}
32+
33+
public function processNode(Node $node, Scope $scope): array
34+
{
35+
$thisFunction = $node->getFunctionReflection();
36+
$allFunctions = $this->reflector->reflectAllFunctions();
37+
$filteredFunctions = [];
38+
foreach ($allFunctions as $reflectionFunction) {
39+
if ($reflectionFunction->getName() !== $thisFunction->getName()) {
40+
continue;
41+
}
42+
43+
$filteredFunctions[] = $reflectionFunction;
44+
}
45+
46+
if (count($filteredFunctions) < 2) {
47+
return [];
48+
}
49+
50+
return [
51+
RuleErrorBuilder::message(sprintf(
52+
"Function %s declared multiple times:\n%s",
53+
$thisFunction->getName(),
54+
implode("\n", array_map(fn (ReflectionFunction $function) => sprintf('- %s:%d', $this->relativePathHelper->getRelativePath($function->getFileName() ?? 'unknown'), $function->getStartLine()), $filteredFunctions)),
55+
))->build(),
56+
];
57+
}
58+
59+
}

Diff for: stubs/arrayFunctions.stub

+3-5
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,7 @@ function array_udiff(
6666
): int {}
6767

6868
/**
69-
* @template K of array-key
70-
* @template V
71-
* @param array<K, V> $array
72-
* @return ($array is list<V> ? true : false)
69+
*@param array<array-key, mixed> $value
70+
* @phpstan-assert-if-true list<mixed> $value
7371
*/
74-
function array_is_list(array $array): bool {}
72+
function array_is_list(array $value): bool {}

Diff for: stubs/typeCheckingFunctions.stub

-9
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,3 @@ function is_resource(mixed $value): bool
111111
{
112112

113113
}
114-
115-
/**
116-
* @param array<array-key, mixed> $value
117-
* @phpstan-assert-if-true list<mixed> $value
118-
*/
119-
function array_is_list(array $value): bool
120-
{
121-
122-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Functions;
4+
5+
use PHPStan\BetterReflection\Reflector\DefaultReflector;
6+
use PHPStan\File\SimpleRelativePathHelper;
7+
use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher;
8+
use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocator;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Testing\RuleTestCase;
11+
12+
/**
13+
* @extends RuleTestCase<DuplicateFunctionDeclarationRule>
14+
*/
15+
class DuplicateFunctionDeclarationRuleTest extends RuleTestCase
16+
{
17+
18+
private const FILENAME = __DIR__ . '/data/duplicate-function.php';
19+
20+
protected function getRule(): Rule
21+
{
22+
return new DuplicateFunctionDeclarationRule(
23+
new DefaultReflector(new OptimizedSingleFileSourceLocator(
24+
self::getContainer()->getByType(FileNodesFetcher::class),
25+
self::FILENAME,
26+
)),
27+
new SimpleRelativePathHelper(__DIR__ . '/data'),
28+
);
29+
}
30+
31+
public function testRule(): void
32+
{
33+
$this->analyse([self::FILENAME], [
34+
[
35+
"Function DuplicateFunctionDeclaration\\foo declared multiple times:\n- duplicate-function.php:10\n- duplicate-function.php:15\n- duplicate-function.php:20",
36+
10,
37+
],
38+
[
39+
"Function DuplicateFunctionDeclaration\\foo declared multiple times:\n- duplicate-function.php:10\n- duplicate-function.php:15\n- duplicate-function.php:20",
40+
15,
41+
],
42+
[
43+
"Function DuplicateFunctionDeclaration\\foo declared multiple times:\n- duplicate-function.php:10\n- duplicate-function.php:15\n- duplicate-function.php:20",
44+
20,
45+
],
46+
]);
47+
}
48+
49+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace DuplicateFunctionDeclaration;
4+
5+
function notDuplicate()
6+
{
7+
8+
}
9+
10+
function foo()
11+
{
12+
13+
}
14+
15+
function foo()
16+
{
17+
18+
}
19+
20+
function foo()
21+
{
22+
23+
}

0 commit comments

Comments
 (0)