Skip to content

Commit 5e8127d

Browse files
committed
Bleeding edge - closure uses $this rule (level 0)
Based on slevomat/coding-standard#734
1 parent c981d89 commit 5e8127d

File tree

5 files changed

+108
-0
lines changed

5 files changed

+108
-0
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
parameters:
22
featureToggles:
3+
closureUsesThis: true
34
randomIntParameters: true

conf/config.level0.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
parameters:
22
customRulesetUsed: false
33
missingClosureNativeReturnCheckObjectTypehint: false
4+
featureToggles:
5+
closureUsesThis: false
46

57
conditionalTags:
8+
PHPStan\Rules\Functions\ClosureUsesThisRule:
9+
phpstan.rules.rule: %featureToggles.closureUsesThis%
610
PHPStan\Rules\Missing\MissingClosureNativeReturnTypehintRule:
711
phpstan.rules.rule: %checkMissingClosureNativeReturnTypehintRule%
812

@@ -55,6 +59,9 @@ services:
5559
arguments:
5660
checkFunctionNameCase: %checkFunctionNameCase%
5761

62+
-
63+
class: PHPStan\Rules\Functions\ClosureUsesThisRule
64+
5865
-
5966
class: PHPStan\Rules\Methods\CallMethodsRule
6067
tags:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Functions;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Rules\Rule;
8+
use PHPStan\Rules\RuleErrorBuilder;
9+
use PHPStan\Type\ThisType;
10+
11+
/**
12+
* @implements Rule<Node\Expr\Closure>
13+
*/
14+
class ClosureUsesThisRule implements Rule
15+
{
16+
17+
public function getNodeType(): string
18+
{
19+
return Node\Expr\Closure::class;
20+
}
21+
22+
public function processNode(Node $node, Scope $scope): array
23+
{
24+
if ($node->static) {
25+
return [];
26+
}
27+
28+
$messages = [];
29+
foreach ($node->uses as $closureUse) {
30+
$varType = $scope->getType($closureUse->var);
31+
if (!is_string($closureUse->var->name)) {
32+
continue;
33+
}
34+
if (!$varType instanceof ThisType) {
35+
continue;
36+
}
37+
38+
$messages[] = RuleErrorBuilder::message(sprintf('Anonymous function uses $this assigned to variable $%s. Use $this directly in the function body.', $closureUse->var->name))
39+
->line($closureUse->getLine())
40+
->build();
41+
}
42+
return $messages;
43+
}
44+
45+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Functions;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<ClosureUsesThisRule>
10+
*/
11+
class ClosureUsesThisRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new ClosureUsesThisRule();
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/closure-uses-this.php'], [
22+
[
23+
'Anonymous function uses $this assigned to variable $that. Use $this directly in the function body.',
24+
16,
25+
],
26+
]);
27+
}
28+
29+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace ClosureUsesThis;
4+
5+
class Foo
6+
{
7+
8+
public function doFoo()
9+
{
10+
$f = function () { // ok
11+
12+
};
13+
14+
$that = $this;
15+
$f = function () use (
16+
$that
17+
) { // report
18+
19+
};
20+
21+
$f = static function () use ($that) { // ok
22+
23+
};
24+
}
25+
26+
}

0 commit comments

Comments
 (0)