Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ lint:
--exclude tests/PHPStan/Rules/Properties/data/property-override-attr-missing.php \
--exclude tests/PHPStan/Rules/Properties/data/override-attr-on-property.php \
--exclude tests/PHPStan/Rules/Properties/data/property-override-attr.php \
--exclude tests/PHPStan/Rules/Classes/data/bug-14250.php \
--exclude tests/PHPStan/Rules/Classes/data/bug-14250-promoted-properties.php \
--exclude tests/PHPStan/Rules/Operators/data/bug-3585.php \
src tests

Expand Down
123 changes: 123 additions & 0 deletions src/Rules/Classes/DuplicateDeclarationHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\EnumCase;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\ShouldNotHappenException;
use function array_key_exists;
use function is_string;
use function sprintf;
use function strtolower;

final class DuplicateDeclarationHelper
{

/**
* @return list<IdentifierRuleError>
*/
public static function checkClassLike(ClassLike $classLike, string $displayName, string $identifierType): array
{
$errors = [];

$declaredClassConstantsOrEnumCases = [];
foreach ($classLike->stmts as $stmtNode) {
if ($stmtNode instanceof EnumCase) {
if (array_key_exists($stmtNode->name->name, $declaredClassConstantsOrEnumCases)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare enum case %s::%s.',
$displayName,
$stmtNode->name->name,
))->identifier(sprintf('%s.duplicateEnumCase', $identifierType))
->line($stmtNode->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredClassConstantsOrEnumCases[$stmtNode->name->name] = true;
}
} elseif ($stmtNode instanceof ClassConst) {
foreach ($stmtNode->consts as $classConstNode) {
if (array_key_exists($classConstNode->name->name, $declaredClassConstantsOrEnumCases)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare constant %s::%s.',
$displayName,
$classConstNode->name->name,
))->identifier(sprintf('%s.duplicateConstant', $identifierType))
->line($classConstNode->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredClassConstantsOrEnumCases[$classConstNode->name->name] = true;
}
}
}
}

$declaredProperties = [];
foreach ($classLike->getProperties() as $propertyDecl) {
foreach ($propertyDecl->props as $property) {
if (array_key_exists($property->name->name, $declaredProperties)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare property %s::$%s.',
$displayName,
$property->name->name,
))->identifier(sprintf('%s.duplicateProperty', $identifierType))
->line($property->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredProperties[$property->name->name] = true;
}
}
}

$declaredFunctions = [];
foreach ($classLike->getMethods() as $method) {
if ($method->name->toLowerString() === '__construct') {
foreach ($method->params as $param) {
if ($param->flags === 0) {
continue;
}

if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) {
throw new ShouldNotHappenException();
}

$propertyName = $param->var->name;

if (array_key_exists($propertyName, $declaredProperties)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare property %s::$%s.',
$displayName,
$propertyName,
))->identifier(sprintf('%s.duplicateProperty', $identifierType))
->line($param->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredProperties[$propertyName] = true;
}
}
}
if (array_key_exists(strtolower($method->name->name), $declaredFunctions)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare method %s::%s().',
$displayName,
$method->name->name,
))->identifier(sprintf('%s.duplicateMethod', $identifierType))
->line($method->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredFunctions[strtolower($method->name->name)] = true;
}
}

return $errors;
}

}
110 changes: 5 additions & 105 deletions src/Rules/Classes/DuplicateDeclarationRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,10 @@
namespace PHPStan\Rules\Classes;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\EnumCase;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Node\InClassNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\ShouldNotHappenException;
use function array_key_exists;
use function is_string;
use function sprintf;
use function strtolower;

/**
Expand All @@ -32,104 +25,11 @@ public function processNode(Node $node, Scope $scope): array
{
$classReflection = $node->getClassReflection();

$identifierType = strtolower($classReflection->getClassTypeDescription());

$errors = [];

$declaredClassConstantsOrEnumCases = [];
foreach ($node->getOriginalNode()->stmts as $stmtNode) {
if ($stmtNode instanceof EnumCase) {
if (array_key_exists($stmtNode->name->name, $declaredClassConstantsOrEnumCases)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare enum case %s::%s.',
$classReflection->getDisplayName(),
$stmtNode->name->name,
))->identifier(sprintf('%s.duplicateEnumCase', $identifierType))
->line($stmtNode->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredClassConstantsOrEnumCases[$stmtNode->name->name] = true;
}
} elseif ($stmtNode instanceof ClassConst) {
foreach ($stmtNode->consts as $classConstNode) {
if (array_key_exists($classConstNode->name->name, $declaredClassConstantsOrEnumCases)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare constant %s::%s.',
$classReflection->getDisplayName(),
$classConstNode->name->name,
))->identifier(sprintf('%s.duplicateConstant', $identifierType))
->line($classConstNode->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredClassConstantsOrEnumCases[$classConstNode->name->name] = true;
}
}
}
}

$declaredProperties = [];
foreach ($node->getOriginalNode()->getProperties() as $propertyDecl) {
foreach ($propertyDecl->props as $property) {
if (array_key_exists($property->name->name, $declaredProperties)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare property %s::$%s.',
$classReflection->getDisplayName(),
$property->name->name,
))->identifier(sprintf('%s.duplicateProperty', $identifierType))
->line($property->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredProperties[$property->name->name] = true;
}
}
}

$declaredFunctions = [];
foreach ($node->getOriginalNode()->getMethods() as $method) {
if ($method->name->toLowerString() === '__construct') {
foreach ($method->params as $param) {
if ($param->flags === 0) {
continue;
}

if (!$param->var instanceof Node\Expr\Variable || !is_string($param->var->name)) {
throw new ShouldNotHappenException();
}

$propertyName = $param->var->name;

if (array_key_exists($propertyName, $declaredProperties)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare property %s::$%s.',
$classReflection->getDisplayName(),
$propertyName,
))->identifier(sprintf('%s.duplicateProperty', $identifierType))
->line($param->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredProperties[$propertyName] = true;
}
}
}
if (array_key_exists(strtolower($method->name->name), $declaredFunctions)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Cannot redeclare method %s::%s().',
$classReflection->getDisplayName(),
$method->name->name,
))->identifier(sprintf('%s.duplicateMethod', $identifierType))
->line($method->getStartLine())
->nonIgnorable()
->build();
} else {
$declaredFunctions[strtolower($method->name->name)] = true;
}
}

return $errors;
return DuplicateDeclarationHelper::checkClassLike(
$node->getOriginalNode(),
$classReflection->getDisplayName(),
strtolower($classReflection->getClassTypeDescription()),
);
}

}
32 changes: 32 additions & 0 deletions src/Rules/Classes/DuplicateTraitDeclarationRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Node\InTraitNode;
use PHPStan\Rules\Rule;

/**
* @implements Rule<InTraitNode>
*/
#[RegisteredRule(level: 0)]
final class DuplicateTraitDeclarationRule implements Rule
{

public function getNodeType(): string
{
return InTraitNode::class;
}

public function processNode(Node $node, Scope $scope): array
{
return DuplicateDeclarationHelper::checkClassLike(
$node->getOriginalNode(),
$node->getTraitReflection()->getDisplayName(),
'trait',
);
}

}
67 changes: 67 additions & 0 deletions tests/PHPStan/Rules/Classes/DuplicateTraitDeclarationRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Classes;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<DuplicateTraitDeclarationRule>
*/
class DuplicateTraitDeclarationRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new DuplicateTraitDeclarationRule();
}

public function testBug14250(): void
{
$this->analyse([__DIR__ . '/data/bug-14250.php'], [
[
'Cannot redeclare method Bug14250\MyTrait::doSomething().',
11,
],
[
'Cannot redeclare constant Bug14250\TraitWithDuplicateConstants::CONST1.',
24,
],
[
'Cannot redeclare constant Bug14250\TraitWithDuplicateConstants::CONST2.',
26,
],
[
'Cannot redeclare property Bug14250\TraitWithDuplicateProperties::$prop1.',
41,
],
[
'Cannot redeclare property Bug14250\TraitWithDuplicateProperties::$prop2.',
44,
],
[
'Cannot redeclare method Bug14250\TraitWithDuplicateMethods::func1().',
59,
],
[
'Cannot redeclare method Bug14250\TraitWithDuplicateMethods::Func1().',
69,
],
]);
}

public function testDuplicatePromotedProperty(): void
{
$this->analyse([__DIR__ . '/data/bug-14250-promoted-properties.php'], [
[
'Cannot redeclare property Bug14250PromotedProperties\TraitWithDuplicatePromotedProperties::$foo.',
10,
],
[
'Cannot redeclare property Bug14250PromotedProperties\TraitWithDuplicatePromotedProperties::$bar.',
12,
],
]);
}

}
22 changes: 22 additions & 0 deletions tests/PHPStan/Rules/Classes/data/bug-14250-promoted-properties.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php // lint >= 8.0

namespace Bug14250PromotedProperties;

trait TraitWithDuplicatePromotedProperties
{
private $foo;

public function __construct(
private $foo,
private $bar,
private $bar
)
{

}
}

class Foo
{
use TraitWithDuplicatePromotedProperties;
}
Loading
Loading