Skip to content

Commit fd217f2

Browse files
authored
Recognize that variable vars are not assignments (#88)
* Tests: Add tests for variable variables * Recognize that variable vars are not assignments When a variable variable is used as the left side of an assignment, there are actually two variables being checked: there is the current one, and the variable one whose name is the value of the current one. In this case, we must not treat the current one as an assignment; it is actually a read. This change updates the logic so that this is the case.
1 parent 00d3158 commit fd217f2

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,12 @@ protected function checkForAssignment(File $phpcsFile, $stackPtr, $varName, $cur
517517
return false;
518518
}
519519

520+
// Is this a variable variable? If so, it's not an assignment to the current variable.
521+
if ($this->checkForVariableVariable($phpcsFile, $stackPtr, $varName, $currScope)) {
522+
Helpers::debug('found variable variable');
523+
return false;
524+
}
525+
520526
// Plain ol' assignment. Simpl(ish).
521527
$writtenPtr = Helpers::findWhereAssignExecuted($phpcsFile, $assignPtr);
522528
if ($writtenPtr === false) {
@@ -526,6 +532,22 @@ protected function checkForAssignment(File $phpcsFile, $stackPtr, $varName, $cur
526532
return true;
527533
}
528534

535+
protected function checkForVariableVariable(File $phpcsFile, $stackPtr, $varName, $currScope) {
536+
$tokens = $phpcsFile->getTokens();
537+
$token = $tokens[$stackPtr];
538+
539+
if (!isset($tokens[$stackPtr - 1]['code'])) {
540+
return false;
541+
}
542+
if ($tokens[$stackPtr - 1]['code'] === T_DOLLAR) {
543+
return true;
544+
}
545+
if ($tokens[$stackPtr - 1]['code'] === T_OPEN_CURLY_BRACKET && isset($tokens[$stackPtr - 2]['code']) && $tokens[$stackPtr - 2]['code'] === T_DOLLAR) {
546+
return true;
547+
}
548+
return false;
549+
}
550+
529551
protected function checkForListShorthandAssignment(File $phpcsFile, $stackPtr, $varName, $currScope) {
530552
$tokens = $phpcsFile->getTokens();
531553
$token = $tokens[$stackPtr];

VariableAnalysis/Tests/CodeAnalysis/VariableAnalysisTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,30 @@ public function testVariableFunctionCallsCountAsUsage() {
540540
$this->assertEquals($expectedErrors, $lines);
541541
}
542542

543+
public function testVariableVariables() {
544+
$fixtureFile = $this->getFixture('VariableVariablesFixture.php');
545+
$phpcsFile = $this->prepareLocalFileForSniffs($this->getSniffFiles(), $fixtureFile);
546+
$phpcsFile->process();
547+
$lines = $this->getWarningLineNumbersFromFile($phpcsFile);
548+
$expectedWarnings = [
549+
4,
550+
10,
551+
16,
552+
22,
553+
29,
554+
35,
555+
41,
556+
47,
557+
48,
558+
52,
559+
53,
560+
];
561+
$this->assertEquals($expectedWarnings, $lines);
562+
$lines = $this->getErrorLineNumbersFromFile($phpcsFile);
563+
$expectedErrors = [];
564+
$this->assertEquals($expectedErrors, $lines);
565+
}
566+
543567
public function testTraitsAllowPropertyDefinitions() {
544568
$fixtureFile = $this->getFixture('TraitWithPropertiesFixture.php');
545569
$phpcsFile = $this->prepareLocalFileForSniffs($this->getSniffFiles(), $fixtureFile);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
function usedVariableVariableInReturn() {
4+
$foo = true; // this is used but it requires execution to know that
5+
$varName = 'foo';
6+
return $$varName;
7+
}
8+
9+
function usedVariableVariableInEcho() {
10+
$foo = true; // this is used but it requires execution to know that
11+
$varName = 'foo';
12+
echo $$varName;
13+
}
14+
15+
function usedVariableVariableInLeftAssignment() {
16+
$foo = true; // the below is assignment, not a read, so this should be a warning
17+
$marName = 'foo';
18+
$$marName = false;
19+
}
20+
21+
function usedVariableVariableInRightAssignment() {
22+
$foo = true; // this is used but it requires execution to know that
23+
$varName = 'foo';
24+
$bar = $$varName;
25+
echo $bar;
26+
}
27+
28+
function usedVariableVariableInEchoWithBraces() {
29+
$foo = true; // this is used but it requires execution to know that
30+
$varName = 'foo';
31+
echo ${$varName};
32+
}
33+
34+
function usedVariableVariableInLeftAssignmentWithBraces() {
35+
$foo = true; // this is used but it requires execution to know that
36+
$varName = 'foo';
37+
${$varName} = false;
38+
}
39+
40+
function usedVariableVariableInEchoWithBracesInString() {
41+
$foo = true; // this is used but it requires execution to know that
42+
$varName = 'foo';
43+
echo "${$varName}";
44+
}
45+
46+
function undefinedVariableVariableInEcho() {
47+
$foo = true; // this should be a warning
48+
echo $$varName; // this should be a warning
49+
}
50+
51+
function usedVariableVariableTwoLevels() {
52+
$foo = 'hello'; // this is used but it requires execution to know that
53+
$varNameOne = 'foo'; // this is used but it requires execution to know that
54+
$varNameTwo = 'varNameOne';
55+
return $$$varNameTwo;
56+
}

0 commit comments

Comments
 (0)