Skip to content

Commit 918c47d

Browse files
authored
Implement MagicClassConstantRule
1 parent 394064b commit 918c47d

File tree

10 files changed

+471
-1
lines changed

10 files changed

+471
-1
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ parameters:
4343
zeroFiles: true
4444
callUserFunc: true
4545
finalByPhpDoc: true
46+
magicConstantOutOfContext: true

conf/config.level0.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ conditionalTags:
2222
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
2323
PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule:
2424
phpstan.rules.rule: %featureToggles.missingMagicSerializationRule%
25+
PHPStan\Rules\Constants\MagicConstantContextRule:
26+
phpstan.rules.rule: %featureToggles.magicConstantOutOfContext%
2527

2628
rules:
2729
- PHPStan\Rules\Api\ApiInstantiationRule
@@ -278,3 +280,6 @@ services:
278280

279281
-
280282
class: PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule
283+
284+
-
285+
class: PHPStan\Rules\Constants\MagicConstantContextRule

conf/config.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ parameters:
7777
zeroFiles: false
7878
callUserFunc: false
7979
finalByPhpDoc: false
80+
magicConstantOutOfContext: false
8081
fileExtensions:
8182
- php
8283
checkAdvancedIsset: false
@@ -314,6 +315,11 @@ services:
314315
tags:
315316
- phpstan.parser.richParserNodeVisitor
316317

318+
-
319+
class: PHPStan\Parser\MagicConstantParamDefaultVisitor
320+
tags:
321+
- phpstan.parser.richParserNodeVisitor
322+
317323
-
318324
class: PHPStan\Parser\NewAssignedToPropertyVisitor
319325
tags:

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ parametersSchema:
7373
zeroFiles: bool()
7474
callUserFunc: bool()
7575
finalByPhpDoc: bool()
76+
magicConstantOutOfContext: bool()
7677
])
7778
fileExtensions: listOf(string())
7879
checkAdvancedIsset: bool()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\NodeVisitorAbstract;
7+
8+
class MagicConstantParamDefaultVisitor extends NodeVisitorAbstract
9+
{
10+
11+
public const ATTRIBUTE_NAME = 'isMagicConstantParamDefault';
12+
13+
public function enterNode(Node $node): ?Node
14+
{
15+
if ($node instanceof Node\Param && $node->default instanceof Node\Scalar\MagicConst) {
16+
$node->default->setAttribute(self::ATTRIBUTE_NAME, true);
17+
}
18+
return null;
19+
}
20+
21+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Constants;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Scalar\MagicConst;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Parser\MagicConstantParamDefaultVisitor;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
use function sprintf;
12+
13+
/** @implements Rule<MagicConst> */
14+
class MagicConstantContextRule implements Rule
15+
{
16+
17+
public function getNodeType(): string
18+
{
19+
return MagicConst::class;
20+
}
21+
22+
public function processNode(Node $node, Scope $scope): array
23+
{
24+
// test cases https://3v4l.org/ZUvvr
25+
26+
if ($node instanceof MagicConst\Class_) {
27+
if ($scope->isInClass()) {
28+
return [];
29+
}
30+
31+
return [
32+
RuleErrorBuilder::message(
33+
sprintf('Magic constant %s is always empty outside a class.', $node->getName()),
34+
)->build(),
35+
];
36+
} elseif ($node instanceof MagicConst\Trait_) {
37+
if ($scope->isInTrait()) {
38+
return [];
39+
}
40+
41+
return [
42+
RuleErrorBuilder::message(
43+
sprintf('Magic constant %s is always empty outside a trait.', $node->getName()),
44+
)->build(),
45+
];
46+
} elseif ($node instanceof MagicConst\Method || $node instanceof MagicConst\Function_) {
47+
if ($scope->getFunctionName() !== null) {
48+
return [];
49+
}
50+
if ($scope->isInAnonymousFunction()) {
51+
return [];
52+
}
53+
54+
if ((bool) $node->getAttribute(MagicConstantParamDefaultVisitor::ATTRIBUTE_NAME)) {
55+
return [];
56+
}
57+
58+
return [
59+
RuleErrorBuilder::message(
60+
sprintf('Magic constant %s is always empty outside a function.', $node->getName()),
61+
)->build(),
62+
];
63+
} elseif ($node instanceof MagicConst\Namespace_) {
64+
if ($scope->getNamespace() === null) {
65+
return [
66+
RuleErrorBuilder::message(
67+
sprintf('Magic constant %s is always empty in global namespace.', $node->getName()),
68+
)->build(),
69+
];
70+
}
71+
}
72+
return [];
73+
}
74+
75+
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ public function testBug6936(): void
233233
public function testBug3405(): void
234234
{
235235
$errors = $this->runAnalyse(__DIR__ . '/data/bug-3405.php');
236-
$this->assertNoErrors($errors);
236+
$this->assertCount(1, $errors);
237+
$this->assertSame('Magic constant __TRAIT__ is always empty outside a trait.', $errors[0]->getMessage());
238+
$this->assertSame(16, $errors[0]->getLine());
237239
}
238240

239241
public function testBug3415(): void
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Constants;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<MagicConstantContextRule>
10+
*/
11+
class MagicConstantContextRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new MagicConstantContextRule();
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/magic-constant.php'], [
22+
[
23+
'Magic constant __CLASS__ is always empty outside a class.',
24+
5,
25+
],
26+
[
27+
'Magic constant __FUNCTION__ is always empty outside a function.',
28+
6,
29+
],
30+
[
31+
'Magic constant __METHOD__ is always empty outside a function.',
32+
7,
33+
],
34+
[
35+
'Magic constant __TRAIT__ is always empty outside a trait.',
36+
9,
37+
],
38+
[
39+
'Magic constant __TRAIT__ is always empty outside a trait.',
40+
17,
41+
],
42+
[
43+
'Magic constant __CLASS__ is always empty outside a class.',
44+
22,
45+
],
46+
[
47+
'Magic constant __TRAIT__ is always empty outside a trait.',
48+
26,
49+
],
50+
[
51+
'Magic constant __CLASS__ is always empty outside a class.',
52+
59,
53+
],
54+
[
55+
'Magic constant __TRAIT__ is always empty outside a trait.',
56+
64,
57+
],
58+
[
59+
'Magic constant __TRAIT__ is always empty outside a trait.',
60+
78,
61+
],
62+
[
63+
'Magic constant __METHOD__ is always empty outside a function.',
64+
91,
65+
],
66+
[
67+
'Magic constant __FUNCTION__ is always empty outside a function.',
68+
92,
69+
],
70+
[
71+
'Magic constant __TRAIT__ is always empty outside a trait.',
72+
93,
73+
],
74+
[
75+
'Magic constant __CLASS__ is always empty outside a class.',
76+
97,
77+
],
78+
[
79+
'Magic constant __TRAIT__ is always empty outside a trait.',
80+
101,
81+
],
82+
[
83+
'Magic constant __CLASS__ is always empty outside a class.',
84+
105,
85+
],
86+
[
87+
'Magic constant __TRAIT__ is always empty outside a trait.',
88+
109,
89+
],
90+
[
91+
'Magic constant __CLASS__ is always empty outside a class.',
92+
115,
93+
],
94+
[
95+
'Magic constant __TRAIT__ is always empty outside a trait.',
96+
120,
97+
],
98+
[
99+
'Magic constant __TRAIT__ is always empty outside a trait.',
100+
133,
101+
],
102+
]);
103+
}
104+
105+
public function testGlobalNamespace(): void
106+
{
107+
$this->analyse([__DIR__ . '/data/magic-constant-global-ns.php'], [
108+
[
109+
'Magic constant __CLASS__ is always empty outside a class.',
110+
5,
111+
],
112+
[
113+
'Magic constant __FUNCTION__ is always empty outside a function.',
114+
6,
115+
],
116+
[
117+
'Magic constant __METHOD__ is always empty outside a function.',
118+
7,
119+
],
120+
[
121+
'Magic constant __NAMESPACE__ is always empty in global namespace.',
122+
8,
123+
],
124+
[
125+
'Magic constant __TRAIT__ is always empty outside a trait.',
126+
9,
127+
],
128+
[
129+
'Magic constant __NAMESPACE__ is always empty in global namespace.',
130+
16,
131+
],
132+
[
133+
'Magic constant __TRAIT__ is always empty outside a trait.',
134+
17,
135+
],
136+
[
137+
'Magic constant __CLASS__ is always empty outside a class.',
138+
22,
139+
],
140+
[
141+
'Magic constant __NAMESPACE__ is always empty in global namespace.',
142+
25,
143+
],
144+
[
145+
'Magic constant __TRAIT__ is always empty outside a trait.',
146+
26,
147+
],
148+
[
149+
'Magic constant __NAMESPACE__ is always empty in global namespace.',
150+
34,
151+
],
152+
[
153+
'Magic constant __CLASS__ is always empty outside a class.',
154+
46,
155+
],
156+
[
157+
'Magic constant __NAMESPACE__ is always empty in global namespace.',
158+
48,
159+
],
160+
[
161+
'Magic constant __TRAIT__ is always empty outside a trait.',
162+
51,
163+
],
164+
]);
165+
}
166+
167+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types=1);
2+
3+
4+
5+
echo __CLASS__;
6+
echo __FUNCTION__;
7+
echo __METHOD__;
8+
echo __NAMESPACE__;
9+
echo __TRAIT__;
10+
11+
class MagicClassConstantRule {
12+
function doFoo (): void {
13+
echo __CLASS__;
14+
echo __FUNCTION__;
15+
echo __METHOD__;
16+
echo __NAMESPACE__;
17+
echo __TRAIT__;
18+
}
19+
}
20+
21+
function MagicClassConstantRuleFunc (): void {
22+
echo __CLASS__;
23+
echo __FUNCTION__;
24+
echo __METHOD__;
25+
echo __NAMESPACE__;
26+
echo __TRAIT__;
27+
}
28+
29+
trait MagicClassConstantTrait {
30+
function doFoo (): void {
31+
echo __CLASS__;
32+
echo __FUNCTION__;
33+
echo __METHOD__;
34+
echo __NAMESPACE__;
35+
echo __TRAIT__;
36+
}
37+
}
38+
39+
class MagicTraitUsingClass {
40+
use MagicClassConstantTrait ;
41+
}
42+
43+
function MagicClassConstantRuleFuncParams(
44+
string $file = __FILE__,
45+
int $line = __LINE__,
46+
string $class = __CLASS__,
47+
string $dir = __DIR__,
48+
string $namespace = __NAMESPACE__,
49+
string $method = __METHOD__,
50+
string $function = __FUNCTION__,
51+
string $trait = __TRAIT__
52+
): void
53+
{
54+
}

0 commit comments

Comments
 (0)