Skip to content

Commit 02aadff

Browse files
committed
Add new sniff to ban the use of compact() function
1 parent ed8e00d commit 02aadff

4 files changed

+326
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
/**
4+
* Bans the use of compact() function
5+
*
6+
* @author Paweł Bogut <[email protected]>
7+
* @copyright 2006-2023 Squiz Pty Ltd (ABN 77 084 670 600)
8+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
9+
*/
10+
11+
namespace PHP_CodeSniffer\Standards\Generic\Sniffs\Arrays;
12+
13+
use PHP_CodeSniffer\Files\File;
14+
use PHP_CodeSniffer\Sniffs\Sniff;
15+
use PHP_CodeSniffer\Util\Tokens;
16+
17+
class DisallowCompactArrayBuilderSniff implements Sniff
18+
{
19+
protected const VARIABLE_NAME_PATTERN = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/';
20+
21+
22+
/**
23+
* Registers the tokens that this sniff wants to listen for.
24+
*
25+
* @return int[]
26+
*/
27+
public function register()
28+
{
29+
return [T_STRING];
30+
31+
}//end register()
32+
33+
34+
/**
35+
* Processes this test, when one of its tokens is encountered.
36+
*
37+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
38+
* @param int $stackPtr The position of the current token
39+
* in the stack passed in $tokens.
40+
*
41+
* @return void
42+
*/
43+
public function process(File $phpcsFile, $stackPtr)
44+
{
45+
$tokens = $phpcsFile->getTokens();
46+
47+
$content = $tokens[$stackPtr]['content'];
48+
49+
if (strtolower($content) !== 'compact') {
50+
return;
51+
}
52+
53+
// Make sure this is a function call.
54+
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
55+
if ($next === false || $tokens[$next]['code'] !== T_OPEN_PARENTHESIS) {
56+
// Not a function call.
57+
return;
58+
}
59+
60+
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
61+
$prevPrev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true);
62+
63+
$ignorePrev = [
64+
T_BITWISE_AND,
65+
T_NS_SEPARATOR,
66+
];
67+
68+
$excludedPrev = [
69+
T_NULLSAFE_OBJECT_OPERATOR,
70+
T_OBJECT_OPERATOR,
71+
T_DOUBLE_COLON,
72+
T_NEW,
73+
T_NAMESPACE,
74+
T_STRING,
75+
T_FUNCTION,
76+
];
77+
78+
$significantPrev = $prev;
79+
if (in_array($tokens[$prev]['code'], $ignorePrev) === true) {
80+
$significantPrev = $prevPrev;
81+
}
82+
83+
// Make sure it is built-in function call.
84+
if (in_array($tokens[$significantPrev]['code'], $excludedPrev) === true) {
85+
// Not a built-in function call.
86+
return;
87+
}
88+
89+
$error = 'Array must not be created with compact() function';
90+
91+
// Make sure it is not prepended by bitwise operator.
92+
if ($tokens[$prev]['code'] === T_BITWISE_AND) {
93+
// Can not be fixed as &[] is not valid syntax.
94+
$phpcsFile->addError($error, $stackPtr, 'Found');
95+
return;
96+
}
97+
98+
$fixable = false;
99+
$toExpand = [];
100+
$openPtr = $next;
101+
$closePtr = null;
102+
// Find all params in compact() function call, and check if it is fixable.
103+
while (($next = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), null, true)) !== false) {
104+
if ($tokens[$next]['code'] === T_CONSTANT_ENCAPSED_STRING) {
105+
$variableName = substr($tokens[$next]['content'], 1, -1);
106+
$isValid = preg_match(self::VARIABLE_NAME_PATTERN, $variableName);
107+
108+
if ($isValid === false || $isValid === 0) {
109+
break;
110+
}
111+
112+
$toExpand[] = $next;
113+
continue;
114+
}
115+
116+
if ($tokens[$next]['code'] === T_CLOSE_PARENTHESIS) {
117+
$fixable = true;
118+
$closePtr = $next;
119+
break;
120+
}
121+
122+
if ($tokens[$next]['code'] !== T_COMMA) {
123+
break;
124+
}
125+
}//end while
126+
127+
if ($fixable === false) {
128+
$phpcsFile->addError($error, $stackPtr, 'Found');
129+
return;
130+
}
131+
132+
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found');
133+
134+
if ($fix === true) {
135+
$phpcsFile->fixer->beginChangeset();
136+
137+
if ($tokens[$prev]['code'] === T_NS_SEPARATOR) {
138+
$phpcsFile->fixer->replaceToken($prev, '');
139+
}
140+
141+
$phpcsFile->fixer->replaceToken($stackPtr, '');
142+
$phpcsFile->fixer->replaceToken($openPtr, '[');
143+
$phpcsFile->fixer->replaceToken($closePtr, ']');
144+
145+
foreach ($toExpand as $ptr) {
146+
$variableName = substr($tokens[$ptr]['content'], 1, -1);
147+
$phpcsFile->fixer->replaceToken(
148+
$ptr,
149+
$tokens[$ptr]['content'].' => $'.$variableName
150+
);
151+
}
152+
153+
$phpcsFile->fixer->endChangeset();
154+
}//end if
155+
156+
}//end process()
157+
158+
159+
}//end class
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
$var = compact();
3+
$var = compact('a','b','c');
4+
$foo = compact($var[1],$var[2]);
5+
$foo = compact(
6+
'a',
7+
"b",
8+
'c'
9+
);
10+
$var = compact/*comment*/('a', 'b', "c");
11+
$var = compact(['aa', 'bb' => 'cc']);
12+
$var = compact(array('aa', 'bb' => 'cc'));
13+
14+
function foo($compact) {}
15+
$compact = function ($a, $b, $c) use ($foo): array {};
16+
$compact('a', 'b', 'c');
17+
18+
view('some.view', compact("a", 'b', 'c'));
19+
view('some.view', compact(
20+
'a',
21+
'b',
22+
'c'
23+
));
24+
25+
$var = compact('aa', 'invalid-var.name');
26+
COMPACT('a');
27+
Compact('a');
28+
$var = Bazz::compact('a', 'b');
29+
$ver = $foo->compact('a', 'b');
30+
$obj?->compact('a');
31+
class compact {
32+
public function compact( $param = 'a' ) {}
33+
public function &compact( $param = 'a' ) {}
34+
}
35+
new compact('a');
36+
MyNamespace\compact('a');
37+
namespace\compact('a');
38+
\compact('a');
39+
compact(...$names);
40+
compact( 'prefix' . $name, '$name' . 'suffix', "some$name");
41+
compact(...get_names('category1', 'category2'));
42+
$bar = @compact('a', 'b');
43+
$foo = true && compact('a', 'b');
44+
$baz = &compact('a', 'b');
45+
func(compact('a', 'b'));
46+
// Live coding/parse error.
47+
compact( 'a', 'b'
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
$var = [];
3+
$var = ['a' => $a,'b' => $b,'c' => $c];
4+
$foo = compact($var[1],$var[2]);
5+
$foo = [
6+
'a' => $a,
7+
"b" => $b,
8+
'c' => $c
9+
];
10+
$var = /*comment*/['a' => $a, 'b' => $b, "c" => $c];
11+
$var = compact(['aa', 'bb' => 'cc']);
12+
$var = compact(array('aa', 'bb' => 'cc'));
13+
14+
function foo($compact) {}
15+
$compact = function ($a, $b, $c) use ($foo): array {};
16+
$compact('a', 'b', 'c');
17+
18+
view('some.view', ["a" => $a, 'b' => $b, 'c' => $c]);
19+
view('some.view', [
20+
'a' => $a,
21+
'b' => $b,
22+
'c' => $c
23+
]);
24+
25+
$var = compact('aa', 'invalid-var.name');
26+
['a' => $a];
27+
['a' => $a];
28+
$var = Bazz::compact('a', 'b');
29+
$ver = $foo->compact('a', 'b');
30+
$obj?->compact('a');
31+
class compact {
32+
public function compact( $param = 'a' ) {}
33+
public function &compact( $param = 'a' ) {}
34+
}
35+
new compact('a');
36+
MyNamespace\compact('a');
37+
namespace\compact('a');
38+
['a' => $a];
39+
compact(...$names);
40+
compact( 'prefix' . $name, '$name' . 'suffix', "some$name");
41+
compact(...get_names('category1', 'category2'));
42+
$bar = @['a' => $a, 'b' => $b];
43+
$foo = true && ['a' => $a, 'b' => $b];
44+
$baz = &compact('a', 'b');
45+
func(['a' => $a, 'b' => $b]);
46+
// Live coding/parse error.
47+
compact( 'a', 'b'
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
/**
4+
* Unit test class for the DisallowCompactArrayBuilder sniff.
5+
*
6+
* @author Paweł Bogut <[email protected]>
7+
* @copyright 2006-2023 Squiz Pty Ltd (ABN 77 084 670 600)
8+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
9+
*/
10+
11+
namespace PHP_CodeSniffer\Standards\Generic\Tests\Arrays;
12+
13+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
14+
15+
class DisallowCompactArrayBuilderUnitTest extends AbstractSniffUnitTest
16+
{
17+
18+
19+
/**
20+
* Returns the lines where errors should occur.
21+
*
22+
* The key of the array should represent the line number and the value
23+
* should represent the number of errors that should occur on that line.
24+
*
25+
* @param string $testFile The name of the file being tested.
26+
*
27+
* @return array<int, int>
28+
*/
29+
public function getErrorList($testFile='')
30+
{
31+
return [
32+
2 => 1,
33+
3 => 1,
34+
4 => 1,
35+
5 => 1,
36+
10 => 1,
37+
11 => 1,
38+
12 => 1,
39+
18 => 1,
40+
19 => 1,
41+
25 => 1,
42+
26 => 1,
43+
27 => 1,
44+
38 => 1,
45+
39 => 1,
46+
40 => 1,
47+
41 => 1,
48+
42 => 1,
49+
43 => 1,
50+
44 => 1,
51+
45 => 1,
52+
47 => 1,
53+
];
54+
55+
}//end getErrorList()
56+
57+
58+
/**
59+
* Returns the lines where warnings should occur.
60+
*
61+
* The key of the array should represent the line number and the value
62+
* should represent the number of warnings that should occur on that line.
63+
*
64+
* @return array<int, int>
65+
*/
66+
public function getWarningList()
67+
{
68+
return [];
69+
70+
}//end getWarningList()
71+
72+
73+
}//end class

0 commit comments

Comments
 (0)