Skip to content

Commit

Permalink
Add new sniff to ban the use of compact() function
Browse files Browse the repository at this point in the history
  • Loading branch information
pbogut committed Apr 6, 2023
1 parent ed8e00d commit a48958f
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 0 deletions.
@@ -0,0 +1,131 @@
<?php

/**
* Bans the use of compact() function
*
* @author Paweł Bogut <pbogut@pbogut.me>
* @copyright 2006-2023 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Standards\Generic\Sniffs\Arrays;

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

class DisallowCompactArrayBuilderSniff implements Sniff
{


/**
* Registers the tokens that this sniff wants to listen for.
*
* @return int[]
*/
public function register()
{
return [T_STRING];

}//end register()


/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

$content = $tokens[$stackPtr]['content'];

if ($content !== 'compact') {
return;
}

// Make sure this is a function call.
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($next === false || $tokens[$next]['code'] !== T_OPEN_PARENTHESIS) {
// Not a function call.
return;
}

$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);

// Make sure its not method of an object or class.
if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR || $tokens[$prev]['code'] === T_DOUBLE_COLON) {
// Not a builtin function call.
return;
}

$error = 'Array must not be created with compact() function';

$fix = true;
$toExpand = [];
$openPtr = $next;
$closePtr = null;
$validVarNameRegExp = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/';
// Find all params in compact() function call, and check if it is fixable.
while (($next = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), null, true)) !== false) {
if ($tokens[$next]['code'] === T_CONSTANT_ENCAPSED_STRING) {
$variableName = substr($tokens[$next]['content'], 1, -1);
$isValid = preg_match($validVarNameRegExp, $variableName);

if ($isValid === false || $isValid === 0) {
$fix = false;
break;
}

$toExpand[] = $next;
continue;
}

if ($tokens[$next]['code'] === T_CLOSE_PARENTHESIS) {
$closePtr = $next;
break;
}

if ($tokens[$next]['code'] !== T_COMMA) {
$fix = false;
}
}//end while

if ($closePtr === false) {
$fix = false;
}

if ($fix === false) {
$phpcsFile->addError($error, $stackPtr, 'Found');
return;
}

$fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found');

if ($fix === true) {
$phpcsFile->fixer->beginChangeset();

$phpcsFile->fixer->replaceToken($stackPtr, '');
$phpcsFile->fixer->replaceToken($openPtr, '[');
$phpcsFile->fixer->replaceToken($closePtr, ']');

foreach ($toExpand as $ptr) {
$variableName = substr($tokens[$ptr]['content'], 1, -1);
$phpcsFile->fixer->replaceToken(
$ptr,
$tokens[$ptr]['content'].' => $'.$variableName
);
}

$phpcsFile->fixer->endChangeset();
}//end if

}//end process()


}//end class
@@ -0,0 +1,33 @@
<?php
$var = compact();
$var = array(1,2,3);
$var = compact('a','b','c');
echo $var[1];
$foo = compact($var[1],$var[2]);
$foo = compact(
'a',
"b",
'c'
);
$var = compact/*comment*/('a', 'b', "c");

$var = compact(['aa', 'bb' => 'cc']);

$var = compact(array('aa', 'bb' => 'cc'));

$ver = $foo->compact('a', 'b');

function foo($compact) {}

$compact = function ($a, $b, $c) use ($foo): array {};
$compact('a', 'b', 'c');

view('some.view', compact("a", 'b', 'c'));
view('some.view', compact(
'a',
'b',
'c'
));

$var = compact('aa', 'invalid-var.name');
$var = Bazz::compact('a', 'b');
@@ -0,0 +1,33 @@
<?php
$var = [];
$var = array(1,2,3);
$var = ['a' => $a,'b' => $b,'c' => $c];
echo $var[1];
$foo = compact($var[1],$var[2]);
$foo = [
'a' => $a,
"b" => $b,
'c' => $c
];
$var = /*comment*/['a' => $a, 'b' => $b, "c" => $c];

$var = compact(['aa', 'bb' => 'cc']);

$var = compact(array('aa', 'bb' => 'cc'));

$ver = $foo->compact('a', 'b');

function foo($compact) {}

$compact = function ($a, $b, $c) use ($foo): array {};
$compact('a', 'b', 'c');

view('some.view', ["a" => $a, 'b' => $b, 'c' => $c]);
view('some.view', [
'a' => $a,
'b' => $b,
'c' => $c
]);

$var = compact('aa', 'invalid-var.name');
$var = Bazz::compact('a', 'b');
@@ -0,0 +1,22 @@
<?php
$var = compact('s', 'b');

// The following function has a simulated git conflict for testing.
// This is not a merge conflict - it is a valid test case to test handling of
// compact function call without associated closer.
// Please do not remove.
function test()
{
$a = 'a';
$b = 'b';

$arr = compact(
'a',
<<<<<<< HEAD
'b'
=======
'c'
>>>>>>> master
);
}

@@ -0,0 +1,22 @@
<?php
$var = ['s' => $s, 'b' => $b];

// The following function has a simulated git conflict for testing.
// This is not a merge conflict - it is a valid test case to test handling of
// compact function call without associated closer.
// Please do not remove.
function test()
{
$a = 'a';
$b = 'b';

$arr = compact(
'a',
<<<<<<< HEAD
'b'
=======
'c'
>>>>>>> master
);
}

@@ -0,0 +1,72 @@
<?php

/**
* Unit test class for the DisallowCompactArrayBuilder sniff.
*
* @author Paweł Bogut <pbogut@pbogut.me>
* @copyright 2006-2023 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Standards\Generic\Tests\Arrays;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

class DisallowCompactArrayBuilderUnitTest extends AbstractSniffUnitTest
{


/**
* Returns the lines where errors should occur.
*
* The key of the array should represent the line number and the value
* should represent the number of errors that should occur on that line.
*
* @param string $testFile The name of the file being tested.
*
* @return array<int, int>
*/
public function getErrorList($testFile='')
{
switch ($testFile) {
case 'DisallowCompactArrayBuilderUnitTest.1.inc':
return [
2 => 1,
4 => 1,
6 => 1,
7 => 1,
12 => 1,
14 => 1,
16 => 1,
25 => 1,
26 => 1,
32 => 1,
];
case 'DisallowCompactArrayBuilderUnitTest.2.inc':
return [
2 => 1,
13 => 1,
];
default:
return [];
}//end switch

}//end getErrorList()


/**
* Returns the lines where warnings should occur.
*
* The key of the array should represent the line number and the value
* should represent the number of warnings that should occur on that line.
*
* @return array<int, int>
*/
public function getWarningList()
{
return [];

}//end getWarningList()


}//end class

0 comments on commit a48958f

Please sign in to comment.