Skip to content

Commit

Permalink
Merge pull request #15 from PHPCSStandards/feature/new-arrays-mixedar…
Browse files Browse the repository at this point in the history
…raykeytypes-sniff

New `Universal.Arrays.MixedArrayKeyTypes` sniff
  • Loading branch information
jrfnl committed Jan 23, 2020
2 parents ec3d9ea + badd720 commit 183e887
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 0 deletions.
36 changes: 36 additions & 0 deletions Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<documentation title="Mixed Array Key Types">
<standard>
<![CDATA[
In an array where the items have keys, all items should either have a numeric key assigned or a string key. A mix of numeric and string keys is not allowed.
]]>
</standard>
<code_comparison>
<code title="Valid: Arrays with either numeric keys or string keys.">
<![CDATA[
$args = array(
<em>'foo'</em> => 22,
<em>'bar'</em> => 25,
);
$args = array(
<em>0</em> => 22,
<em>1</em> => 25,
);
]]>
</code>
<code title="Invalid: Arrays with a mix of numeric and string keys.">
<![CDATA[
$args = array(
'foo' => 22,
25,
);
$args = array(
'foo' => 22,
12 => 25,
);
]]>
</code>
</code_comparison>
</documentation>
170 changes: 170 additions & 0 deletions Universal/Sniffs/Arrays/MixedArrayKeyTypesSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php
/**
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
*
* @package PHPCSExtra
* @copyright 2020 PHPCSExtra Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSExtra
*/

namespace PHPCSExtra\Universal\Sniffs\Arrays;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\AbstractSniffs\AbstractArrayDeclarationSniff;

/**
* Forbid arrays which contain both array items with numeric keys as well as array items with string keys.
*
* @since 1.0.0
*/
class MixedArrayKeyTypesSniff extends AbstractArrayDeclarationSniff
{

/**
* Whether a string key was encountered.
*
* @var bool
*/
private $seenStringKey = false;

/**
* Whether a numeric key was encountered.
*
* @var bool
*/
private $seenNumericKey = false;

/**
* Process the array declaration.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
*
* @return void
*/
public function processArray(File $phpcsFile)
{
// Reset properties before processing this array.
$this->seenStringKey = false;
$this->seenNumericKey = false;

parent::processArray($phpcsFile);
}

/**
* Process the tokens in an array key.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $startPtr The stack pointer to the first token in the "key" part of
* an array item.
* @param int $endPtr The stack pointer to the last token in the "key" part of
* an array item.
* @param int $itemNr Which item in the array is being handled.
*
* @return void
*/
public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr)
{
$key = $this->getActualArrayKey($phpcsFile, $startPtr, $endPtr);
if (isset($key) === false) {
// Key could not be determined.
return;
}

$integerKey = \is_int($key);

// Handle integer key.
if ($integerKey === true) {
if ($this->seenStringKey === false) {
if ($this->seenNumericKey !== false) {
// Already seen a numeric key before.
return;
}

$this->seenNumericKey = true;
return;
}

// Ok, so we've seen a string key before and now see an explicit numeric key.
$firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true);
$phpcsFile->addError(
'Arrays should have either numeric keys or string keys. Explicit numeric key detected,'
. ' while all previous keys in this array were string keys.',
$firstNonEmpty,
'ExplicitNumericKey'
);

// Stop the loop.
return true;
}

// Handle string key.
if ($this->seenNumericKey === false) {
if ($this->seenStringKey !== false) {
// Already seen a string key before.
return;
}

$this->seenStringKey = true;
return;
}

// Ok, so we've seen a numeric key before and now see a string key.
$firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true);
$phpcsFile->addError(
'Arrays should have either numeric keys or string keys. String key detected,'
. ' while all previous keys in this array were integer based keys.',
$firstNonEmpty,
'StringKey'
);

// Stop the loop.
return true;
}

/**
* Process an array item without an array key.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $startPtr The stack pointer to the first token in the array item,
* which in this case will be the first token of the array
* value part of the array item.
* @param int $itemNr Which item in the array is being handled.
*
* @return void
*/
public function processNoKey(File $phpcsFile, $startPtr, $itemNr)
{
if ($this->seenStringKey === false) {
if ($this->seenNumericKey !== false) {
// Already seen a numeric key before.
return;
}

$this->seenNumericKey = true;
return;
}

// Ok, so we've seen a string key before and now see an implicit numeric key.
$firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true);
$phpcsFile->addError(
'Arrays should have either numeric keys or string keys. Implicit numeric key detected,'
. ' while all previous keys in this array were string keys.',
$firstNonEmpty,
'ImplicitNumericKey'
);

// Stop the loop.
return true;
}
}
49 changes: 49 additions & 0 deletions Universal/Tests/Arrays/MixedArrayKeyTypesUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

// OK: Array without keys.
$array = [1, 2];

// OK: All items have numeric keys.
$array = array(
1 => 'a',
2 => 'b',
3 => 'c',
4 => 'd',
);

// OK: All items have numeric keys.
$array = array(
'a',
2 => 'b',
'3' => 'c',
4 => 'd',
);

// OK: All items have string keys.
$array = array(
'a' => 'a',
'b' => 'b',
'c' => 'c',
'd' => 'd',
);

// Mixed numeric first.
$array = [
12 => 'numeric key',
'value',
'string' => 'string key', // Error.
];

// Mixed string first.
$array = [
'stringA' => 'string key',
'stringB' => 'string key',
12 => 'numeric key', // Error.
];

// Mixed string first, implicit numeric.
$array = [
'stringA' => 'string key',
'numeric key', // Error.
'stringB' => 'string key',
];
48 changes: 48 additions & 0 deletions Universal/Tests/Arrays/MixedArrayKeyTypesUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
*
* @package PHPCSExtra
* @copyright 2020 PHPCSExtra Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSExtra
*/

namespace PHPCSExtra\Universal\Tests\Arrays;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Unit test class for the MixedArrayKeyTypes sniff.
*
* @covers PHPCSExtra\Universal\Sniffs\Arrays\MixedArrayKeyTypesSniff
*
* @since 1.0.0
*/
class MixedArrayKeyTypesUnitTest extends AbstractSniffUnitTest
{

/**
* Returns the lines where errors should occur.
*
* @return array <int line number> => <int number of errors>
*/
public function getErrorList()
{
return [
34 => 1,
41 => 1,
47 => 1,
];
}

/**
* Returns the lines where warnings should occur.
*
* @return array <int line number> => <int number of warnings>
*/
public function getWarningList()
{
return [];
}
}

0 comments on commit 183e887

Please sign in to comment.