diff --git a/ecs.php b/ecs.php index 964eddb..88ffd43 100644 --- a/ecs.php +++ b/ecs.php @@ -1,13 +1,18 @@ withPaths([ +return static function (\Symplify\EasyCodingStandard\Config\ECSConfig $ecsConfig): void { + $ecsConfig->paths([ __DIR__ . '/src', __DIR__ . '/tests', - ]) - ->withSkip([ + ]); + $ecsConfig->skip([ __DIR__ . '/tests/fixtures', - ]) - ->withPhpCsFixerSets(perCS20: true); + ]); + $ecsConfig->sets([SetList::PSR_12, SetList::NAMESPACES, SetList::DOCBLOCK]); + + $ecsConfig->rules([ + ]); +}; diff --git a/src/Rules/DuplicatedArrayKeys.php b/src/Rules/DuplicatedArrayKeys.php new file mode 100644 index 0000000..278b2f9 --- /dev/null +++ b/src/Rules/DuplicatedArrayKeys.php @@ -0,0 +1,80 @@ + + */ +class DuplicatedArrayKeys implements Rule +{ + public function getNodeType(): string + { + return Array_::class; + } + + /** + * @return array + */ + public function processNode(Node $node, Scope $scope): array + { + if (!$node instanceof Array_) { + return []; + } + + $errors = []; + $existingKeys = []; + + foreach ($node->items as $item) { + if (!$item instanceof ArrayItem || $item->key === null) { + continue; + } + + $key = $this->getKey($item->key); + if ($key === null) { + continue; + } + + if (\in_array($key, $existingKeys, true)) { + $errors[] = RuleErrorBuilder::message("Array key '$key' is duplicated.") + ->identifier('extraDuplicatedArrayKeys') + ->line($item->key->getLine()) + ->build(); + continue; + } + + $existingKeys[] = $key; + } + + return $errors; + } + + private function getKey(Node $node): string|int|null + { + if ($node instanceof Node\Scalar\String_) { + $arr = [$node->value => 1]; + + return \array_key_first($arr); + } + + if ($node instanceof Node\Scalar\Int_) { + return $node->value; + } + + if ($node instanceof Node\Scalar\Float_) { + return (int) $node->value; + } + + return null; + } +} diff --git a/src/Rules/ProtectedPropertyRule.php b/src/Rules/ProtectedPropertyRule.php index fe6ae8b..bf6c637 100644 --- a/src/Rules/ProtectedPropertyRule.php +++ b/src/Rules/ProtectedPropertyRule.php @@ -6,8 +6,8 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Property; -use PHPStan\Node\ClassPropertyNode; use PHPStan\Analyser\Scope; +use PHPStan\Node\ClassPropertyNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; diff --git a/tests/DuplicatedArrayKeysTest.php b/tests/DuplicatedArrayKeysTest.php new file mode 100644 index 0000000..904b647 --- /dev/null +++ b/tests/DuplicatedArrayKeysTest.php @@ -0,0 +1,38 @@ +analyse( + [__DIR__ . '/fixtures/DuplicatedArrayKeys.php'], + [ + [ + "Array key 'key1' is duplicated.", + 6, + ], + [ + "Array key '2' is duplicated.", + 12, + ], + [ + "Array key 'nested-key2' is duplicated.", + 30, + ], + ], + ); + } + + protected function getRule(): Rule + { + return new DuplicatedArrayKeys(); + } +} diff --git a/tests/fixtures/DuplicatedArrayKeys.php b/tests/fixtures/DuplicatedArrayKeys.php new file mode 100644 index 0000000..2b476e0 --- /dev/null +++ b/tests/fixtures/DuplicatedArrayKeys.php @@ -0,0 +1,38 @@ + 'value1', + 'key2' => 'value2', + 'key1' => 'duplicate', +]; + +$array = [ + 1 => 'value1', + 2 => 'value2', + 2 => 'value3', +]; + +$array = [ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => [ + 'key1' => 'value1', + 'key2' => 'value2', + ] +]; + +$array = [ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => [ + 'nested-key1' => 'value1', + 'nested-key2' => 'value2', + 'nested-key2' => 'duplicated', + ] +]; + +$array = [ + 'value1', + 'value2', + 'value1', +];