Skip to content

Commit 87835f5

Browse files
authored
Add sniff codes for array assignment shortcut (#205)
* Move undefined variable warning to own function * Add tests for array assignment shortcut warnings * Add UndefinedArrayVariable warning for array push shortcuts * Mark array push shortcut as defined * Add UndefinedArrayVariable to README
1 parent 9a7938c commit 87835f5

File tree

5 files changed

+141
-13
lines changed

5 files changed

+141
-13
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Plugin for PHP_CodeSniffer static analysis tool that adds analysis of problemati
77
Please note that this README is for VariableAnalysis v3. For documentation about v2, [see this page](https://github.com/sirbrillig/phpcs-variable-analysis/blob/2.8.2/README.md).
88

99
- Warns if variables are used without being defined. (Sniff code: `VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable`)
10+
- Warns if variables are used for an array push shortcut without being defined. (Sniff code: `VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedArrayVariable`)
1011
- Warns if variables are set or declared but never used. (Sniff code: `VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable`)
1112
- Warns if function parameters are declared but never used. (Sniff code: `VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedParameter`)
1213
- Warns if function parameters are declared but never used before other parameters that are used. (Sniff code: `VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedParameterBeforeUsed`)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
namespace VariableAnalysis\Tests\VariableAnalysisSniff;
3+
4+
use VariableAnalysis\Tests\BaseTestCase;
5+
6+
class ArrayAssignmentShortcutTest extends BaseTestCase {
7+
public function testArrayAssignmentReportsCorrectLines() {
8+
$fixtureFile = $this->getFixture('ArrayAssignmentShortcutFixture.php');
9+
$phpcsFile = $this->prepareLocalFileForSniffs($fixtureFile);
10+
$phpcsFile->process();
11+
12+
$lines = $this->getWarningLineNumbersFromFile($phpcsFile);
13+
$expectedWarnings = [
14+
21,
15+
27,
16+
28,
17+
29,
18+
];
19+
$this->assertEquals($expectedWarnings, $lines);
20+
}
21+
22+
public function testArrayAssignmentHasCorrectSniffCodes() {
23+
$fixtureFile = $this->getFixture('ArrayAssignmentShortcutFixture.php');
24+
$phpcsFile = $this->prepareLocalFileForSniffs($fixtureFile);
25+
$phpcsFile->process();
26+
27+
$warnings = $phpcsFile->getWarnings();
28+
$this->assertEquals('VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedArrayVariable', $warnings[21][5][0]['source']);
29+
$this->assertEquals('VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedArrayVariable', $warnings[27][5][0]['source']);
30+
$this->assertEquals('VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable', $warnings[28][5][0]['source']);
31+
$this->assertEquals('VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable', $warnings[29][10][0]['source']);
32+
}
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
function arrayAssignmentWithDefine() {
4+
$ar1 = [];
5+
$ar1[]= 'hello';
6+
echo $ar1;
7+
}
8+
9+
function arrayAssignmentWithDefineWithoutRead() {
10+
$ar1 = [];
11+
$ar1[]= 'hello';
12+
}
13+
14+
function arrayAssignmentWithDefineWithSpace() {
15+
$ar1 = [];
16+
$ar1 []= 'hello';
17+
echo $ar1;
18+
}
19+
20+
function arrayAssignmentWithoutDefine() {
21+
$ar1[]= 'hello'; // should warn about undefined variable
22+
echo $ar1;
23+
$ar1[] = 'goodbye';
24+
}
25+
26+
function arrayAssignmentWithoutDefineOrRead() {
27+
$ar1[]= 'hello'; // should warn about unused variable and undefined variable
28+
$foo = 'bar'; // should warn about unused variable
29+
echo $bar; // should warn about undefined variable
30+
}

VariableAnalysis/Lib/Helpers.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,4 +793,41 @@ public static function isVariableInsideIssetOrEmpty(File $phpcsFile, $stackPtr)
793793
}
794794
return false;
795795
}
796+
797+
/**
798+
* @param File $phpcsFile
799+
* @param int $stackPtr
800+
*
801+
* @return bool
802+
*/
803+
public static function isVariableArrayPushShortcut(File $phpcsFile, $stackPtr) {
804+
$tokens = $phpcsFile->getTokens();
805+
$nonFunctionTokenTypes = array_values(Tokens::$emptyTokens);
806+
807+
$arrayPushOperatorIndex1 = self::getIntOrNull($phpcsFile->findNext($nonFunctionTokenTypes, $stackPtr + 1, null, true, null, true));
808+
if (! is_int($arrayPushOperatorIndex1)) {
809+
return false;
810+
}
811+
if (! isset($tokens[$arrayPushOperatorIndex1]['content']) || $tokens[$arrayPushOperatorIndex1]['content'] !== '[') {
812+
return false;
813+
}
814+
815+
$arrayPushOperatorIndex2 = self::getIntOrNull($phpcsFile->findNext($nonFunctionTokenTypes, $arrayPushOperatorIndex1 + 1, null, true, null, true));
816+
if (! is_int($arrayPushOperatorIndex2)) {
817+
return false;
818+
}
819+
if (! isset($tokens[$arrayPushOperatorIndex2]['content']) || $tokens[$arrayPushOperatorIndex2]['content'] !== ']') {
820+
return false;
821+
}
822+
823+
$arrayPushOperatorIndex3 = self::getIntOrNull($phpcsFile->findNext($nonFunctionTokenTypes, $arrayPushOperatorIndex2 + 1, null, true, null, true));
824+
if (! is_int($arrayPushOperatorIndex3)) {
825+
return false;
826+
}
827+
if (! isset($tokens[$arrayPushOperatorIndex3]['content']) || $tokens[$arrayPushOperatorIndex3]['content'] !== '=') {
828+
return false;
829+
}
830+
831+
return true;
832+
}
796833
}

VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -560,12 +560,13 @@ protected function markVariableReadAndWarnIfUndefined($phpcsFile, $varName, $sta
560560
$this->markVariableRead($varName, $stackPtr, $currScope);
561561
if ($this->isVariableUndefined($varName, $stackPtr, $currScope) === true) {
562562
Helpers::debug("variable $varName looks undefined");
563-
$phpcsFile->addWarning(
564-
"Variable %s is undefined.",
565-
$stackPtr,
566-
'UndefinedVariable',
567-
["\${$varName}"]
568-
);
563+
if (Helpers::isVariableArrayPushShortcut($phpcsFile, $stackPtr)) {
564+
$this->warnAboutUndefinedArrayPushShortcut($phpcsFile, $varName, $stackPtr);
565+
// Mark the variable as defined if it's of the form `$x[] = 1;`
566+
$this->markVariableAssignment($varName, $stackPtr, $currScope);
567+
return;
568+
}
569+
$this->warnAboutUndefinedVariable($phpcsFile, $varName, $stackPtr);
569570
}
570571
}
571572

@@ -654,7 +655,7 @@ protected function processVariableAsUseImportDefinition(File $phpcsFile, $stackP
654655
// If it's undefined in the enclosing scope, the use is wrong
655656
if ($this->isVariableUndefined($varName, $stackPtr, $outerScope) === true) {
656657
Helpers::debug("variable '{$varName}' in function definition looks undefined in scope", $outerScope);
657-
$phpcsFile->addWarning("Variable %s is undefined.", $stackPtr, 'UndefinedVariable', ["\${$varName}"]);
658+
$this->warnAboutUndefinedVariable($phpcsFile, $varName, $stackPtr);
658659
return;
659660
}
660661

@@ -1494,12 +1495,7 @@ protected function processVaribleInsideElse(File $phpcsFile, $stackPtr, $varName
14941495

14951496
if (count($assignmentsInsideAttachedBlocks) === count($allAssignmentIndices)) {
14961497
Helpers::debug("variable $varName inside else looks undefined");
1497-
$phpcsFile->addWarning(
1498-
"Variable %s is undefined.",
1499-
$stackPtr,
1500-
'UndefinedVariable',
1501-
["\${$varName}"]
1502-
);
1498+
$this->warnAboutUndefinedVariable($phpcsFile, $varName, $stackPtr);
15031499
return;
15041500
}
15051501

@@ -1758,4 +1754,35 @@ protected function warnAboutUnusedParameterBeforeUsed(File $phpcsFile, VariableI
17581754
);
17591755
}
17601756
}
1757+
1758+
/**
1759+
* @param File $phpcsFile
1760+
* @param string $varName
1761+
* @param int $stackPtr
1762+
*
1763+
* @return void
1764+
*/
1765+
protected function warnAboutUndefinedVariable(File $phpcsFile, $varName, $stackPtr) {
1766+
$phpcsFile->addWarning(
1767+
"Variable %s is undefined.",
1768+
$stackPtr,
1769+
'UndefinedVariable',
1770+
["\${$varName}"]
1771+
);
1772+
}
1773+
/**
1774+
* @param File $phpcsFile
1775+
* @param string $varName
1776+
* @param int $stackPtr
1777+
*
1778+
* @return void
1779+
*/
1780+
protected function warnAboutUndefinedArrayPushShortcut(File $phpcsFile, $varName, $stackPtr) {
1781+
$phpcsFile->addWarning(
1782+
"Array variable %s is undefined.",
1783+
$stackPtr,
1784+
'UndefinedArrayVariable',
1785+
["\${$varName}"]
1786+
);
1787+
}
17611788
}

0 commit comments

Comments
 (0)