diff --git a/CHANGELOG.md b/CHANGELOG.md index 611f702a..e1608818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ All notable changes to this project will be documented in this file, in reverse - [#41](https://github.com/webimpress/coding-standard/pull/41) adds `ControlStructures\DefaultAsLast` sniff which requires `default` case to be last case in a switch control structure +- [#39](https://github.com/webimpress/coding-standard/pull/39) adds `Arrays\DuplicateKey` sniff which detects duplicated keys in arrays + ### Changed - Nothing. diff --git a/src/WebimpressCodingStandard/Sniffs/Arrays/DuplicateKeySniff.php b/src/WebimpressCodingStandard/Sniffs/Arrays/DuplicateKeySniff.php new file mode 100644 index 00000000..cee1f101 --- /dev/null +++ b/src/WebimpressCodingStandard/Sniffs/Arrays/DuplicateKeySniff.php @@ -0,0 +1,129 @@ +processArray($phpcsFile, $indices); + } + + /** + * @param File $phpcsFile + * @param int $stackPtr + * @param int $arrayStart + * @param int $arrayEnd + * @param array $indices + */ + protected function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices) : void + { + $this->processArray($phpcsFile, $indices); + } + + private function processArray(File $phpcsFile, array $indices) : void + { + $tokens = $phpcsFile->getTokens(); + + $keys = []; + foreach ($indices as $element) { + if (! isset($element['index_start']) || ! isset($element['index_end'])) { + continue; + } + + $key = ''; + for ($i = $element['index_start']; $i <= $element['index_end']; ++$i) { + $token = $tokens[$i]; + + if ($token['code'] === T_DOUBLE_QUOTED_STRING + || $token['code'] === T_CONSTANT_ENCAPSED_STRING + ) { + $content = ''; + do { + $content .= $tokens[$i]['content']; + + if (! isset($tokens[$i + 1]) + || $tokens[$i + 1]['code'] !== $token['code'] + ) { + break; + } + } while (++$i); + + $key .= $this->doubleQuoted($content); + } elseif (! in_array($token['code'], Tokens::$emptyTokens, true)) { + $key .= $token['content']; + } + } + + if (isset($keys[$key])) { + $phpcsFile->addError( + 'Duplicated array key; first usage in line %d', + $element['index_start'], + 'DuplicateKey', + [$keys[$key]] + ); + } else { + $keys[$key] = $tokens[$element['index_start']]['line']; + } + } + } + + private function doubleQuoted(string $string) : string + { + if ($string[0] !== '"' || substr($string, -1) !== '"') { + return $this->preg($string); + } + + $tokens = token_get_all('preg($string, true); + } + } + + if (preg_match(self::ALLOWED_CHARS, $string)) { + return $this->preg($string, true); + } + + $string = strtr(substr($string, 1, -1), [ + '\\"' => '"', + "'" => "\\'", + ]); + + return "'" . $this->preg($string) . "'"; + } + + private function preg(string $string, bool $doubleQuotes = false) : string + { + $chars = $doubleQuotes ? '[\\\\"0-7nrftve]|x[A-Fa-f0-9]' : '[\\\\\']'; + + return preg_replace('/(? 1, 'b' => 2, 'a' => 3]; + +$multiline = [ + PHP_INT_MAX => 0, + 'ab' => 1, + 'b' => 2, + "b" => 3, + 'a'.'b' => 4, + 'a'. // comment + 'b' => 5, + 'noindex', + 'a\\b' => 6, + 'a\b' => 7, + 'a\\\\b' => 8, + 'a\\\b' => 9, + "a\b" => 10, + "a\\b" => 11, + 'a\'b' => 12, + "a'b" => 13, + 'b$a' => 14, + "b$a" => 15, + 'a + b' => 16, + "a + b" => 17, + 'PHP_INT_MAX' => 18, +]; + +$a = [ + '\a' => 1, + '\\a' => 2, + "\a" => 3, + "\\a" => 4, +]; + +$b = [ + '\b' => 1, + '\\b' => 2, + "\b" => 3, + "\\b" => 4, +]; + +$e = [ + '\e' => 1, + '\\e' => 2, + "\e" => 3, + "\\e" => 4, +]; + +$f = [ + '\f' => 1, + '\\f' => 2, + "\f" => 3, + "\\f" => 4, +]; + +$n = [ + '\n' => 1, + '\\n' => 2, + "\n" => 3, + "\\n" => 4, +]; + +$r = [ + '\r' => 1, + '\\r' => 2, + "\r" => 3, + "\\r" => 4, +]; + +$t = [ + '\t' => 1, + '\\t' => 2, + "\t" => 3, + "\\t" => 4, +]; + +$v = [ + '\v' => 1, + '\\v' => 2, + "\v" => 3, + "\\v" => 4, +]; + +$x = [ + '\x' => 1, + '\\x' => 2, + "\x" => 3, + "\\x" => 4, + + '\xa' => 1, + '\\xa' => 2, + "\xa" => 3, + "\\xa" => 4, + + '\xF' => 1, + '\\xF' => 2, + "\xF" => 3, + "\\xF" => 4, + + '\x0' => 1, + '\\x0' => 2, + "\x0" => 3, + "\\x0" => 4, + + '\x9' => 1, + '\\x9' => 2, + "\x9" => 3, + "\\x9" => 4, + + '\xZ' => 1, + '\\xZ' => 2, + "\xZ" => 3, + "\\xZ" => 4, +]; + +$quote = [ + '"' => 1, + "\"" => 2, + + "'" => 1, + '\'' => 2, +]; + +$slashes1 = [ + 'a\\b' => 1, + 'a\b' => 2, + 'a\\\b' => 3, + 'a\\\\b' => 4, +]; + +$slashes2 = [ + "a\\b" => 1, + "a\b" => 2, + "a\\\b" => 3, + "a\\\\b" => 4, +]; diff --git a/test/Sniffs/Arrays/DuplicateKeyUnitTest.php b/test/Sniffs/Arrays/DuplicateKeyUnitTest.php new file mode 100644 index 00000000..a55633ac --- /dev/null +++ b/test/Sniffs/Arrays/DuplicateKeyUnitTest.php @@ -0,0 +1,68 @@ + 1, + 9 => 1, + 11 => 1, + 15 => 1, + 17 => 1, + 18 => 1, + 19 => 1, + 21 => 1, + 26 => 1, + 33 => 1, + 34 => 1, + 35 => 1, + 40 => 1, + 41 => 1, + 42 => 1, + 47 => 1, + 49 => 1, + 54 => 1, + 56 => 1, + 61 => 1, + 63 => 1, + 68 => 1, + 70 => 1, + 75 => 1, + 77 => 1, + 82 => 1, + 84 => 1, + 89 => 1, + 90 => 1, + 91 => 1, + 94 => 1, + 96 => 1, + 99 => 1, + 101 => 1, + 104 => 1, + 106 => 1, + 109 => 1, + 111 => 1, + 114 => 1, + 115 => 1, + 116 => 1, + 121 => 1, + 124 => 1, + 129 => 1, + 131 => 1, + 136 => 1, + 138 => 1, + ]; + } + + protected function getWarningList(string $testFile = '') : array + { + return []; + } +}