Skip to content

Commit 4f672c2

Browse files
MaartenStaafelixfbecker
authored andcommitted
feat(diagnostics): report error when $this is used in a static method or outside a class method (#528)
1 parent 80ef8ff commit 4f672c2

File tree

6 files changed

+164
-0
lines changed

6 files changed

+164
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
class Foo
4+
{
5+
public function bar()
6+
{
7+
return $this;
8+
}
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
function foo()
4+
{
5+
return $this;
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
echo $this;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
class Foo
4+
{
5+
public static function bar()
6+
{
7+
return $this;
8+
}
9+
}

src/TreeAnalyzer.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public function __construct(PhpParser\Parser $parser, string $content, DocBlockF
6868
*/
6969
private function collectDiagnostics($node)
7070
{
71+
// Get errors from the parser.
7172
if (($error = PhpParser\DiagnosticsProvider::checkDiagnostics($node)) !== null) {
7273
$range = PhpParser\PositionUtilities::getRangeFromPosition($error->start, $error->length, $this->sourceFileNode->fileContents);
7374

@@ -92,6 +93,24 @@ private function collectDiagnostics($node)
9293
'php'
9394
);
9495
}
96+
97+
// Check for invalid usage of $this.
98+
if ($node instanceof Node\Expression\Variable && $node->getName() === 'this') {
99+
// Find the first ancestor that's a class method. Return an error
100+
// if there is none, or if the method is static.
101+
$method = $node->getFirstAncestor(Node\MethodDeclaration::class);
102+
if ($method === null || $method->isStatic()) {
103+
$this->diagnostics[] = new Diagnostic(
104+
$method === null
105+
? "\$this can only be used in an object context."
106+
: "\$this can not be used in static methods.",
107+
Range::fromNode($node),
108+
null,
109+
DiagnosticSeverity::ERROR,
110+
'php'
111+
);
112+
}
113+
}
95114
}
96115

97116
/**
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace LanguageServer\Tests\Diagnostics;
5+
6+
use PHPUnit\Framework\TestCase;
7+
use phpDocumentor\Reflection\DocBlockFactory;
8+
use LanguageServer\{
9+
DefinitionResolver, TreeAnalyzer
10+
};
11+
use LanguageServer\Index\{Index};
12+
use LanguageServer\Protocol\{
13+
Diagnostic, DiagnosticSeverity, Position, Range
14+
};
15+
use function LanguageServer\pathToUri;
16+
use Microsoft\PhpParser\Parser;
17+
18+
class InvalidThisUsageTest extends TestCase
19+
{
20+
/**
21+
* Parse the given file and return diagnostics.
22+
*
23+
* @param string $path
24+
* @return Diagnostic[]
25+
*/
26+
private function collectDiagnostics(string $path): array
27+
{
28+
$uri = pathToUri($path);
29+
$parser = new Parser();
30+
31+
$docBlockFactory = DocBlockFactory::createInstance();
32+
$index = new Index;
33+
$definitionResolver = new DefinitionResolver($index);
34+
$content = file_get_contents($path);
35+
36+
$treeAnalyzer = new TreeAnalyzer($parser, $content, $docBlockFactory, $definitionResolver, $uri);
37+
return $treeAnalyzer->getDiagnostics();
38+
}
39+
40+
/**
41+
* Assertions about a diagnostic.
42+
*
43+
* @param Diagnostic|null $diagnostic
44+
* @param int $message
45+
* @param string $severity
46+
* @param Range $range
47+
*/
48+
private function assertDiagnostic($diagnostic, $message, $severity, $range)
49+
{
50+
$this->assertInstanceOf(Diagnostic::class, $diagnostic);
51+
$this->assertEquals($message, $diagnostic->message);
52+
$this->assertEquals($severity, $diagnostic->severity);
53+
$this->assertEquals($range, $diagnostic->range);
54+
}
55+
56+
public function testThisInStaticMethodProducesError()
57+
{
58+
$diagnostics = $this->collectDiagnostics(
59+
__DIR__ . '/../../fixtures/diagnostics/errors/this_in_static_method.php'
60+
);
61+
62+
$this->assertCount(1, $diagnostics);
63+
$this->assertDiagnostic(
64+
$diagnostics[0],
65+
'$this can not be used in static methods.',
66+
DiagnosticSeverity::ERROR,
67+
new Range(
68+
new Position(6, 15),
69+
new Position(6, 20)
70+
)
71+
);
72+
}
73+
74+
public function testThisInFunctionProducesError()
75+
{
76+
$diagnostics = $this->collectDiagnostics(
77+
__DIR__ . '/../../fixtures/diagnostics/errors/this_in_function.php'
78+
);
79+
80+
$this->assertCount(1, $diagnostics);
81+
$this->assertDiagnostic(
82+
$diagnostics[0],
83+
'$this can only be used in an object context.',
84+
DiagnosticSeverity::ERROR,
85+
new Range(
86+
new Position(4, 11),
87+
new Position(4, 16)
88+
)
89+
);
90+
}
91+
92+
public function testThisInRoot()
93+
{
94+
$diagnostics = $this->collectDiagnostics(
95+
__DIR__ . '/../../fixtures/diagnostics/errors/this_in_root.php'
96+
);
97+
98+
$this->assertCount(1, $diagnostics);
99+
$this->assertDiagnostic(
100+
$diagnostics[0],
101+
'$this can only be used in an object context.',
102+
DiagnosticSeverity::ERROR,
103+
new Range(
104+
new Position(2, 5),
105+
new Position(2, 10)
106+
)
107+
);
108+
}
109+
110+
public function testThisInMethodProducesNoError()
111+
{
112+
$diagnostics = $this->collectDiagnostics(
113+
__DIR__ . '/../../fixtures/diagnostics/baselines/this_in_method.php'
114+
);
115+
116+
$this->assertCount(0, $diagnostics);
117+
}
118+
}

0 commit comments

Comments
 (0)