-
Notifications
You must be signed in to change notification settings - Fork 486
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
348 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Names; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\ClassLike; | ||
use PhpParser\Node\Stmt\Enum_; | ||
use PhpParser\Node\Stmt\GroupUse; | ||
use PhpParser\Node\Stmt\Interface_; | ||
use PhpParser\Node\Stmt\Namespace_; | ||
use PhpParser\Node\Stmt\Trait_; | ||
use PhpParser\Node\Stmt\Use_; | ||
use PhpParser\Node\Stmt\UseUse; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\FileNode; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleError; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use function in_array; | ||
use function sprintf; | ||
use function strtolower; | ||
|
||
/** | ||
* @implements Rule<FileNode> | ||
*/ | ||
final class UsedNamesRule implements Rule | ||
{ | ||
|
||
public function getNodeType(): string | ||
{ | ||
return FileNode::class; | ||
} | ||
|
||
/** | ||
* @param FileNode $node | ||
*/ | ||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$usedNames = []; | ||
$errors = []; | ||
foreach ($node->getNodes() as $oneNode) { | ||
if ($oneNode instanceof Namespace_) { | ||
$namespaceName = $oneNode->name !== null ? $oneNode->name->toString() : ''; | ||
foreach ($oneNode->stmts as $stmt) { | ||
foreach ($this->findErrorsForNode($stmt, $namespaceName, $usedNames) as $error) { | ||
$errors[] = $error; | ||
} | ||
} | ||
continue; | ||
} | ||
|
||
foreach ($this->findErrorsForNode($oneNode, '', $usedNames) as $error) { | ||
$errors[] = $error; | ||
} | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
/** | ||
* @param array<string, string[]> $usedNames | ||
* @return RuleError[] | ||
*/ | ||
private function findErrorsForNode(Node $node, string $namespace, array &$usedNames): array | ||
{ | ||
$lowerNamespace = strtolower($namespace); | ||
if ($node instanceof Use_) { | ||
if ($this->shouldBeIgnored($node)) { | ||
return []; | ||
} | ||
return $this->findErrorsInUses($node->uses, '', $lowerNamespace, $usedNames); | ||
} | ||
|
||
if ($node instanceof GroupUse) { | ||
if ($this->shouldBeIgnored($node)) { | ||
return []; | ||
} | ||
$useGroupPrefix = $node->prefix->toString(); | ||
return $this->findErrorsInUses($node->uses, $useGroupPrefix, $lowerNamespace, $usedNames); | ||
} | ||
|
||
if ($node instanceof ClassLike) { | ||
if ($node->name === null) { | ||
return []; | ||
} | ||
$type = 'class'; | ||
if ($node instanceof Interface_) { | ||
$type = 'interface'; | ||
} elseif ($node instanceof Trait_) { | ||
$type = 'trait'; | ||
} elseif ($node instanceof Enum_) { | ||
$type = 'enum'; | ||
} | ||
$name = $node->name->toLowerString(); | ||
if (in_array($name, $usedNames[$lowerNamespace] ?? [], true)) { | ||
return [ | ||
RuleErrorBuilder::message(sprintf( | ||
'Cannot declare %s %s because the name is already in use.', | ||
$type, | ||
$namespace !== '' ? $namespace . '\\' . $node->name->toString() : $node->name->toString(), | ||
)) | ||
->line($node->getLine()) | ||
->nonIgnorable() | ||
->build(), | ||
]; | ||
} | ||
$usedNames[$lowerNamespace][] = $name; | ||
return []; | ||
} | ||
|
||
return []; | ||
} | ||
|
||
/** | ||
* @param UseUse[] $uses | ||
* @param array<string, string[]> $usedNames | ||
* @return RuleError[] | ||
*/ | ||
private function findErrorsInUses(array $uses, string $useGroupPrefix, string $lowerNamespace, array &$usedNames): array | ||
{ | ||
$errors = []; | ||
foreach ($uses as $use) { | ||
if ($this->shouldBeIgnored($use)) { | ||
continue; | ||
} | ||
$useAlias = $use->getAlias()->toLowerString(); | ||
if (in_array($useAlias, $usedNames[$lowerNamespace] ?? [], true)) { | ||
$errors[] = RuleErrorBuilder::message(sprintf( | ||
'Cannot use %s as %s because the name is already in use.', | ||
$useGroupPrefix !== '' ? $useGroupPrefix . '\\' . $use->name->toString() : $use->name->toString(), | ||
$use->getAlias()->toString(), | ||
)) | ||
->line($use->getLine()) | ||
->nonIgnorable() | ||
->build(); | ||
continue; | ||
} | ||
$usedNames[$lowerNamespace][] = $useAlias; | ||
} | ||
return $errors; | ||
} | ||
|
||
private function shouldBeIgnored(Use_|GroupUse|UseUse $use): bool | ||
{ | ||
return in_array($use->type, [Use_::TYPE_FUNCTION, Use_::TYPE_CONSTANT], true); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Names; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<UsedNamesRule> | ||
*/ | ||
final class UsedNamesRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new UsedNamesRule(); | ||
} | ||
|
||
public function testSimpleUses(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/simple-uses.php'], [ | ||
[ | ||
'Cannot declare class SomeNamespace\SimpleUses because the name is already in use.', | ||
7, | ||
], | ||
]); | ||
} | ||
|
||
public function testGroupedUses(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/grouped-uses.php'], [ | ||
[ | ||
'Cannot declare interface SomeNamespace\GroupedUses because the name is already in use.', | ||
10, | ||
], | ||
]); | ||
} | ||
|
||
public function testSimpleUsesUnderClass(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/simple-uses-under-class.php'], [ | ||
[ | ||
'Cannot use SomeOtherNamespace\UsesUnderClass as SimpleUsesUnderClass because the name is already in use.', | ||
9, | ||
], | ||
]); | ||
} | ||
|
||
public function testGroupedUsesUnderClass(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/grouped-uses-under-class.php'], [ | ||
[ | ||
'Cannot use SomeOtherNamespace\FooBar as FooBar because the name is already in use.', | ||
14, | ||
], | ||
[ | ||
'Cannot use SomeOtherNamespace\UsesUnderClass as GroupedUsesUnderClass because the name is already in use.', | ||
15, | ||
], | ||
]); | ||
} | ||
|
||
public function testNoNamespace(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/no-namespace.php'], [ | ||
[ | ||
'Cannot declare class NoNamespace because the name is already in use.', | ||
5, | ||
], | ||
[ | ||
'Cannot declare class NoNamespace because the name is already in use.', | ||
9, | ||
], | ||
]); | ||
} | ||
|
||
public function testMultipleNamespaces(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/multiple-namespaces.php'], [ | ||
[ | ||
'Cannot declare trait FirstNamespace\MultipleNamespaces because the name is already in use.', | ||
24, | ||
], | ||
]); | ||
} | ||
|
||
public function testIgnoreUseFunctionAndConstant(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/ignore-use-function-and-constant.php'], []); | ||
} | ||
|
||
} |
16 changes: 16 additions & 0 deletions
16
tests/PHPStan/Rules/Names/data/grouped-uses-under-class.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
namespace SomeNamespace; | ||
|
||
final class GroupedUsesUnderClass | ||
{ | ||
} | ||
|
||
final class FooBar | ||
{ | ||
} | ||
|
||
use SomeOtherNamespace\{ | ||
FooBar, | ||
UsesUnderClass as GroupedUsesUnderClass, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
namespace SomeNamespace; | ||
|
||
use SomeOtherNamespace\{ | ||
SimpleUses as GroupedUses, | ||
UsesUnderClass, | ||
}; | ||
|
||
interface GroupedUses | ||
{ | ||
} |
14 changes: 14 additions & 0 deletions
14
tests/PHPStan/Rules/Names/data/ignore-use-function-and-constant.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
|
||
namespace SomeNamespace; | ||
|
||
use function count; | ||
use const CaSeInSeNsItIvE; | ||
|
||
final class Count | ||
{ | ||
} | ||
|
||
final class CaSeInSeNsItIvE | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
namespace FirstNamespace | ||
{ | ||
use SomeOtherNamespace\SimpleUses; | ||
|
||
final class MultipleNamespaces | ||
{ | ||
} | ||
} | ||
|
||
namespace SecondNamespace | ||
{ | ||
use SomeOtherNamespace\SimpleUses; | ||
|
||
final class MultipleNamespaces | ||
{ | ||
} | ||
} | ||
|
||
|
||
namespace FirstNamespace | ||
{ | ||
trait MultipleNamespaces | ||
{ | ||
} | ||
} | ||
|
||
namespace { | ||
final class MultipleNamespaces | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
use SomeNamespace\NoNamespace; | ||
|
||
abstract class NoNamespace | ||
{ | ||
} | ||
|
||
final class NoNamespace | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
namespace SomeNamespace; | ||
|
||
final class SimpleUsesUnderClass | ||
{ | ||
} | ||
|
||
use SomeOtherNamespace\UsesUnderClass as SimpleUsesUnderClass; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
namespace SomeNamespace; | ||
|
||
use SomeOtherNamespace\SimpleUses; | ||
|
||
final class SimpleUses | ||
{ | ||
} |