diff --git a/docs/DuplicateKey.md b/docs/DuplicateKey.md new file mode 100644 index 0000000..664ed3a --- /dev/null +++ b/docs/DuplicateKey.md @@ -0,0 +1,23 @@ +# DuplicateKey + +If the keys of the array duplicate, it will be overwritten with the specified value later. + +NOTE: Array keys are cast in some cases. However, it is not possible to detect duplicate keys in such cases. + +## Before + +```php + 1, "a" => 2]; // DuplicateKey: Duplicate keys found in array. +``` + +## After + +```php + 1, "b" => 2]; // OK! +``` + +## Reference + +https://secure.php.net/manual/en/language.types.array.php diff --git a/docs/README.md b/docs/README.md index f7777a6..3cd846c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,3 +35,4 @@ - [SquareBracketSyntax](SquareBracketSyntax.md) - [RedundantTernaryOperator](RedundantTernaryOperator.md) +- [DuplicateKey](DuplicateKey.md) diff --git a/src/Tool/DuplicateKey.php b/src/Tool/DuplicateKey.php new file mode 100644 index 0000000..0f768d4 --- /dev/null +++ b/src/Tool/DuplicateKey.php @@ -0,0 +1,85 @@ + 1, "a" => 2]; +* ``` +* +* ## After +* +* ``` +* 1, "b" => 2]; +* ``` +* +*/ +class DuplicateKey implements Base +{ + /** Analyze array declarations node (AST_ARRAY) */ + public const ENTRY_POINT = \ast\AST_ARRAY; + /** PHP version to enable this tool */ + public const PHP_VERSION = '0.0.0'; + public const HINT_TYPE = "DuplicateKey"; + private const HINT_MESSAGE = 'Duplicate keys found in array.'; + private const HINT_LINK = Hint::DOCUMENT_LINK."/DuplicateKey.md"; + + /** + * Detect duplicate numbers, strings, node key + * + * @param string $file File name to be analyzed. + * @param Node $node AST node to be analyzed. + * @return Hint[] List of hints obtained from results. + */ + public function run(string $file, Node $node): array + { + $hints = []; + $keys = []; + $elems = []; + + foreach ($node->children as $elem) { + if ($elem instanceof Node && !is_null($elem->children['key'])) { + $elems[] = $elem; + $key = $elem->children['key']; + $keys[] = $key instanceof Node ? $key->children : $key; + } + } + + while (count($keys) > 0) { + $elem = array_pop($elems); + $key = array_pop($keys); + + if ($key instanceof Node) { + // If the key is a node, use equal comparison operator + $strict = false; + } else { + // If the key is not a node, use identical comparison operator + $strict = true; + } + + if (in_array($key, $keys, $strict)) { + $hints[] = new Hint( + self::HINT_TYPE, + self::HINT_MESSAGE, + $file, + $elem->lineno, + self::HINT_LINK + ); + } + } + + return $hints; + } +} diff --git a/src/ToolBox.php b/src/ToolBox.php index 397283c..cf45ec6 100644 --- a/src/ToolBox.php +++ b/src/ToolBox.php @@ -24,6 +24,7 @@ class ToolBox 'EscapeShellArg', 'SquareBracketSyntax', 'RedundantTernaryOperator', + 'DuplicateKey', ]; /** diff --git a/tests/Tool/DuplicateKeyTest.php b/tests/Tool/DuplicateKeyTest.php new file mode 100644 index 0000000..620e246 --- /dev/null +++ b/tests/Tool/DuplicateKeyTest.php @@ -0,0 +1,92 @@ + 1, + "a" => 2, +]; +CODE; + $root = \ast\parse_code($code, Config::AST_VERSION); + + $tester = PahoutHelper::create(new DuplicateKey()); + $tester->test($root); + + $this->assertEquals( + [ + new Hint( + 'DuplicateKey', + 'Duplicate keys found in array.', + './test.php', + 4, + Hint::DOCUMENT_LINK.'/DuplicateKey.md' + ) + ], + $tester->hints + ); + } + + public function test_duplicate_node_key() + { + $code = <<<'CODE' + 1, + $test => 2 +]; +CODE; + $root = \ast\parse_code($code, Config::AST_VERSION); + + $tester = PahoutHelper::create(new DuplicateKey()); + $tester->test($root); + + $this->assertEquals( + [ + new Hint( + 'DuplicateKey', + 'Duplicate keys found in array.', + './test.php', + 4, + Hint::DOCUMENT_LINK.'/DuplicateKey.md' + ) + ], + $tester->hints + ); + } + + public function test_unduplicate_key() + { + $code = <<<'CODE' + 1, + "b" => 2 +]; +CODE; + $root = \ast\parse_code($code, Config::AST_VERSION); + + $tester = PahoutHelper::create(new DuplicateKey()); + $tester->test($root); + + $this->assertEmpty($tester->hints); + } +}