Skip to content

Commit 49b2d8b

Browse files
jrfnlsirbrillig
authored andcommitted
Bug fix: $this in nested function declaration (#119)
While `$this` can be used freely within classes, traits and closures, if a nested closed scope is defined within any of these, `$this` is not inherited and should be considered undefined. As things were, the sniff would not throw a warning for this (false negative). This commit fixes this. Includes unit tests. Includes removing an outdated comment. `T_CLOSURE` works perfectly fine with the conditions array (and has for quite a few years). Fixes 109
1 parent 2798661 commit 49b2d8b

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,15 +605,24 @@ protected function checkForThisWithinClass(File $phpcsFile, $stackPtr, $varName)
605605
return false;
606606
}
607607

608+
$inFunction = false;
608609
foreach (array_reverse($token['conditions'], true) as $scopePtr => $scopeCode) {
609610
// $this within a closure is valid
610-
// Note: have to fetch code from $tokens, T_CLOSURE isn't set for conditions codes.
611-
if ($tokens[$scopePtr]['code'] === T_CLOSURE) {
611+
if ($scopeCode === T_CLOSURE && $inFunction === false) {
612612
return true;
613613
}
614614
if ($scopeCode === T_CLASS || $scopeCode === T_ANON_CLASS || $scopeCode === T_TRAIT) {
615615
return true;
616616
}
617+
618+
// Handle nested function declarations.
619+
if ($scopeCode === T_FUNCTION) {
620+
if ($inFunction === true) {
621+
break;
622+
}
623+
624+
$inFunction = true;
625+
}
617626
}
618627

619628
return false;

VariableAnalysis/Tests/CodeAnalysis/VariableAnalysisTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,4 +907,20 @@ public function testGetDefinedVarsCountsAsRead() {
907907
];
908908
$this->assertEquals($expectedWarnings, $lines);
909909
}
910+
911+
public function testThisWithinNestedClosedScope() {
912+
$fixtureFile = $this->getFixture('ThisWithinNestedClosedScopeFixture.php');
913+
$phpcsFile = $this->prepareLocalFileForSniffs($fixtureFile);
914+
$phpcsFile->process();
915+
$lines = $this->getWarningLineNumbersFromFile($phpcsFile);
916+
$expectedWarnings = [
917+
5,
918+
8,
919+
20,
920+
33,
921+
47,
922+
61,
923+
];
924+
$this->assertEquals($expectedWarnings, $lines);
925+
}
910926
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
function foo() {
4+
// Using $this will not work.
5+
if ($this->something) {
6+
function nestedFunctionDeclaration() {
7+
// Using $this here will also not work as the nested function is a closed scope in the global namespace.
8+
if ($this->something) {
9+
// Do something.
10+
}
11+
}
12+
}
13+
};
14+
15+
$closure = function() {
16+
// Using $this here is fine.
17+
if ($this->something) {
18+
function nestedFunctionDeclaration() {
19+
// Using $this here is not ok as the nested function is a closed scope in the global namespace.
20+
if ($this->something) {
21+
// Do something.
22+
}
23+
}
24+
}
25+
};
26+
27+
class Foo {
28+
public function bar() {
29+
// Using $this here is fine.
30+
if ($this->something) {
31+
function nestedFunctionDeclaration() {
32+
// Using $this here is not ok as the nested function is a closed scope in the global namespace.
33+
if ($this->something) {
34+
// Do something.
35+
}
36+
}
37+
}
38+
}
39+
}
40+
41+
$anonClass = class() {
42+
public function bar() {
43+
// Using $this here is fine.
44+
if ($this->something) {
45+
function nestedFunctionDeclaration() {
46+
// Using $this here is not ok as the nested function is a closed scope in the global namespace.
47+
if ($this->something) {
48+
// Do something.
49+
}
50+
}
51+
}
52+
}
53+
}
54+
55+
trait FooTrait {
56+
public function bar() {
57+
// Using $this here is fine.
58+
if ($this->something) {
59+
function nestedFunctionDeclaration() {
60+
// Using $this here is not ok as the nested function is a closed scope in the global namespace.
61+
if ($this->something) {
62+
// Do something.
63+
}
64+
}
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)