Skip to content

Commit

Permalink
Merge branch 'feature/39' into develop
Browse files Browse the repository at this point in the history
Close #39
  • Loading branch information
michalbundyra committed Oct 25, 2019
2 parents 67da020 + f02ca24 commit af3a167
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
129 changes: 129 additions & 0 deletions src/WebimpressCodingStandard/Sniffs/Arrays/DuplicateKeySniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

namespace WebimpressCodingStandard\Sniffs\Arrays;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\AbstractArraySniff;
use PHP_CodeSniffer\Util\Tokens;

use function in_array;
use function is_array;
use function preg_match;
use function preg_replace;
use function strtr;
use function substr;
use function token_get_all;

use const T_CONSTANT_ENCAPSED_STRING;
use const T_DOUBLE_QUOTED_STRING;
use const T_VARIABLE;

class DuplicateKeySniff extends AbstractArraySniff
{
private const ALLOWED_CHARS = '/(?<!\\\\)(?:\\\\{2})*\\\(?:[0-7nrftve]|x[A-Fa-f0-9])/';

/**
* @param File $phpcsFile
* @param int $stackPtr
* @param int $arrayStart
* @param int $arrayEnd
* @param array $indices
*/
protected function processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices) : void
{
$this->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('<?php ' . $string);
foreach ($tokens as $token) {
if (is_array($token) && $token[0] === T_VARIABLE) {
return $this->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('/(?<!\\\\)(?:\\\\{2})*\\\(?!' . $chars . ')/', '$0\\\\', $string);
}
}
139 changes: 139 additions & 0 deletions test/Sniffs/Arrays/DuplicateKeyUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

$inline = ['a' => 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,
];
68 changes: 68 additions & 0 deletions test/Sniffs/Arrays/DuplicateKeyUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace WebimpressCodingStandardTest\Sniffs\Arrays;

use WebimpressCodingStandardTest\Sniffs\AbstractTestCase;

class DuplicateKeyUnitTest extends AbstractTestCase
{
protected function getErrorList(string $testFile = '') : array
{
return [
3 => 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 [];
}
}

0 comments on commit af3a167

Please sign in to comment.