Skip to content

Commit 37cbe36

Browse files
authored
Fix keywords used as constant names resetting parser state (#257)
1 parent b159fac commit 37cbe36

File tree

4 files changed

+117
-5
lines changed

4 files changed

+117
-5
lines changed

src/UsedSymbolExtractor.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ public function parseUsedSymbols(
9191
case T_INTERFACE:
9292
case T_TRAIT:
9393
case T_ENUM:
94-
$inClassLevel = $level + 1;
94+
if (!$this->isKeywordUsedAsConstantName()) {
95+
$inClassLevel = $level + 1;
96+
}
97+
9598
break;
9699

97100
case T_USE:
@@ -109,10 +112,12 @@ public function parseUsedSymbols(
109112
break;
110113

111114
case T_NAMESPACE:
112-
// namespace change
113-
$inGlobalScope = false;
114-
$useStatements = [];
115-
$useStatementKinds = [];
115+
if (!$this->isKeywordUsedAsConstantName()) {
116+
$inGlobalScope = false;
117+
$useStatements = [];
118+
$useStatementKinds = [];
119+
}
120+
116121
break;
117122

118123
case T_NAME_FULLY_QUALIFIED:
@@ -361,6 +366,18 @@ private function canBeSymbolName(
361366
return true;
362367
}
363368

369+
/**
370+
* Checks if the current keyword token is used as a constant name.
371+
* E.g., SomeClass::NAMESPACE (access) or public const INTERFACE = 'value' (definition)
372+
*/
373+
private function isKeywordUsedAsConstantName(): bool
374+
{
375+
$tokenBefore = $this->getTokenBefore($this->pointer - 2);
376+
377+
return $tokenBefore->id === T_DOUBLE_COLON
378+
|| $tokenBefore->id === T_CONST;
379+
}
380+
364381
private function getTokenBefore(int $pointer): PhpToken
365382
{
366383
while ($pointer >= 0) {

tests/UsedSymbolExtractorTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,30 @@ public static function provideVariants(): iterable
213213
],
214214
];
215215

216+
// https://github.com/shipmonk-rnd/composer-dependency-analyser/issues/256
217+
yield 'namespace keyword as constant' => [
218+
__DIR__ . '/data/not-autoloaded/used-symbols/namespace-keyword-constant.php',
219+
[
220+
SymbolKind::CLASSLIKE => [
221+
// Line 15: after "public const NAMESPACE = ..." (T_CONST case)
222+
// Line 27: after "ConstClass::NAMESPACE" (T_DOUBLE_COLON case)
223+
'Carbon\Carbon' => [15, 27],
224+
],
225+
],
226+
];
227+
228+
// Related to #256: CLASS/INTERFACE/TRAIT/ENUM keywords can also be constant names
229+
yield 'class keyword as constant' => [
230+
__DIR__ . '/data/not-autoloaded/used-symbols/class-keyword-constant.php',
231+
[
232+
SymbolKind::CLASSLIKE => [
233+
// Line 16: after "public const TRAIT = ..." (T_CONST case)
234+
// Line 37: after "Other::CLASS" and closure (T_DOUBLE_COLON case)
235+
'Carbon\Carbon' => [16, 37],
236+
],
237+
],
238+
];
239+
216240
if (PHP_VERSION_ID >= 80_400) {
217241
yield 'property hooks' => [
218242
__DIR__ . '/data/not-autoloaded/used-symbols/property-hooks.php',
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace App;
4+
5+
use Carbon\Carbon;
6+
7+
class ConstHolder
8+
{
9+
// T_TRAIT after T_CONST - should not corrupt $inClassLevel
10+
// (CLASS cannot be used as constant name - it's reserved for ::class)
11+
public const TRAIT = 'some-trait';
12+
13+
public function test(): void
14+
{
15+
// Carbon should be detected here (after const TRAIT definition)
16+
Carbon::now();
17+
}
18+
}
19+
20+
class Foo
21+
{
22+
public function method(): void
23+
{
24+
// T_CLASS after T_DOUBLE_COLON - should not corrupt $inClassLevel
25+
$x = Other::CLASS;
26+
27+
$fn = function () {
28+
// closure that could cause spurious $inClassLevel reset
29+
};
30+
}
31+
32+
use App\SomeTrait; // trait use, NOT an import - should be ignored
33+
34+
public function bar(): void
35+
{
36+
// Carbon should be detected here (after ::CLASS and closure)
37+
Carbon::now();
38+
}
39+
}
40+
41+
// SomeTrait should NOT be in use statements (trait use is not import)
42+
new SomeTrait();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace App;
4+
5+
use Carbon\Carbon;
6+
7+
class ConstClass
8+
{
9+
// T_NAMESPACE after T_CONST - should not reset use statements
10+
public const NAMESPACE = 'some-namespace';
11+
12+
public function test(): void
13+
{
14+
// Carbon should be detected here (after const NAMESPACE definition)
15+
Carbon::now();
16+
}
17+
}
18+
19+
class IssueHere
20+
{
21+
public function init(): void
22+
{
23+
// T_NAMESPACE after T_DOUBLE_COLON - should not reset use statements
24+
$namespace = ConstClass::NAMESPACE;
25+
26+
// Carbon should still be detected here (after ::NAMESPACE access)
27+
Carbon::now();
28+
}
29+
}

0 commit comments

Comments
 (0)