Skip to content

Commit c1f3c6f

Browse files
committed
Create rule for checking paths inside the require keyword
1 parent 30c4b9e commit c1f3c6f

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Keywords;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\ConstFetch;
7+
use PhpParser\Node\Expr\Include_;
8+
use PhpParser\Node\Expr\BinaryOp\Concat;
9+
use PhpParser\Node\Expr\ClassConstFetch;
10+
use PhpParser\Node\Identifier;
11+
use PhpParser\Node\Name;
12+
use PhpParser\Node\Scalar\MagicConst\Dir;
13+
use PhpParser\Node\Scalar\String_;
14+
use PHPStan\Analyser\Scope;
15+
use PHPStan\Reflection\ReflectionProvider;
16+
use PHPStan\Rules\Rule;
17+
use PHPStan\Rules\RuleErrorBuilder;
18+
19+
/**
20+
* @implements Rule<Include_>
21+
*/
22+
class RequireFileExistsRule implements Rule
23+
{
24+
private ReflectionProvider $reflectionProvider;
25+
26+
public function __construct(ReflectionProvider $reflectionProvider)
27+
{
28+
$this->reflectionProvider = $reflectionProvider;
29+
}
30+
31+
public function getNodeType(): string
32+
{
33+
return Include_::class;
34+
}
35+
36+
public function processNode(Node $node, Scope $scope): array
37+
{
38+
if ($this->shouldProcessNode($node)) {
39+
$filePath = $this->resolveFilePath($node->expr, $scope);
40+
if ($filePath !== null && !file_exists($filePath)) {
41+
return [
42+
RuleErrorBuilder::message(
43+
sprintf(
44+
'Required file "%s" does not exist.',
45+
$filePath
46+
)
47+
)->build(),
48+
];
49+
}
50+
}
51+
52+
return [];
53+
}
54+
55+
private function shouldProcessNode(Node $node): bool
56+
{
57+
return $node instanceof Include_ && (
58+
$node->type === Include_::TYPE_REQUIRE
59+
|| $node->type === Include_::TYPE_REQUIRE_ONCE
60+
);
61+
}
62+
63+
private function resolveFilePath(Node $node, Scope $scope): ?string
64+
{
65+
if ($node instanceof String_) {
66+
return $node->value;
67+
}
68+
69+
if ($node instanceof Dir) {
70+
return dirname($scope->getFile());
71+
}
72+
73+
if ($node instanceof ClassConstFetch) {
74+
return $this->resolveClassConstant($node);
75+
}
76+
77+
if ($node instanceof ConstFetch) {
78+
return $this->resolveConstant($node);
79+
}
80+
81+
if ($node instanceof Concat) {
82+
$left = $this->resolveFilePath($node->left, $scope);
83+
$right = $this->resolveFilePath($node->right, $scope);
84+
if ($left !== null && $right !== null) {
85+
return $left . $right;
86+
}
87+
}
88+
89+
return null;
90+
}
91+
92+
private function resolveClassConstant(ClassConstFetch $node): ?string
93+
{
94+
if ($node->class instanceof Name && $node->name instanceof Identifier) {
95+
$className = (string) $node->class;
96+
$constantName = $node->name->toString();
97+
98+
if ($this->reflectionProvider->hasClass($className)) {
99+
$classReflection = $this->reflectionProvider->getClass($className);
100+
if ($classReflection->hasConstant($constantName)) {
101+
$constantReflection = $classReflection->getConstant($constantName);
102+
$constantValue = $constantReflection->getValue();
103+
if (is_string($constantValue)) {
104+
return $constantValue;
105+
}
106+
}
107+
}
108+
}
109+
return null;
110+
}
111+
112+
private function resolveConstant(ConstFetch $node): ?string
113+
{
114+
if ($node->name instanceof Name) {
115+
$constantName = (string) $node->name;
116+
if (defined($constantName)) {
117+
$constantValue = constant($constantName);
118+
if (is_string($constantValue)) {
119+
return $constantValue;
120+
}
121+
}
122+
}
123+
return null;
124+
}
125+
}

0 commit comments

Comments
 (0)