Skip to content
This repository was archived by the owner on Sep 1, 2024. It is now read-only.

Commit 258aba7

Browse files
committed
Align data with phpstan/phpstan-src#3294
1 parent 1ac4e65 commit 258aba7

25 files changed

+278
-307
lines changed

src/RequireFileExistsRule.php

Lines changed: 96 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,31 @@
55
namespace Bellangelo\PHPStanRequireFileExists;
66

77
use PhpParser\Node;
8-
use PhpParser\Node\Expr\ConstFetch;
98
use PhpParser\Node\Expr\Include_;
10-
use PhpParser\Node\Expr\BinaryOp\Concat;
11-
use PhpParser\Node\Expr\ClassConstFetch;
12-
use PhpParser\Node\Identifier;
13-
use PhpParser\Node\Name;
14-
use PhpParser\Node\Scalar\MagicConst\Dir;
15-
use PhpParser\Node\Scalar\String_;
169
use PHPStan\Analyser\Scope;
17-
use PHPStan\Reflection\ReflectionProvider;
10+
use PHPStan\File\FileHelper;
11+
use PHPStan\Rules\IdentifierRuleError;
1812
use PHPStan\Rules\Rule;
1913
use PHPStan\Rules\RuleErrorBuilder;
14+
use PHPStan\ShouldNotHappenException;
15+
use function array_merge;
16+
use function dirname;
17+
use function explode;
18+
use function get_include_path;
19+
use function is_file;
20+
use function sprintf;
21+
use const PATH_SEPARATOR;
2022

2123
/**
2224
* @implements Rule<Include_>
2325
*/
24-
class RequireFileExistsRule implements Rule
26+
final class RequireFileExistsRule implements Rule
2527
{
26-
private ReflectionProvider $reflectionProvider;
28+
private string $currentWorkingDirectory;
2729

28-
public function __construct(ReflectionProvider $reflectionProvider)
30+
public function __construct(string $currentWorkingDirectory)
2931
{
30-
$this->reflectionProvider = $reflectionProvider;
32+
$this->currentWorkingDirectory = $currentWorkingDirectory;
3133
}
3234

3335
public function getNodeType(): string
@@ -37,83 +39,104 @@ public function getNodeType(): string
3739

3840
public function processNode(Node $node, Scope $scope): array
3941
{
40-
if ($node instanceof Include_) {
41-
$filePath = $this->resolveFilePath($node->expr, $scope);
42-
if ($filePath !== null && !file_exists($filePath)) {
43-
return [
44-
RuleErrorBuilder::message(
45-
sprintf(
46-
'Included or required file "%s" does not exist.',
47-
$filePath
48-
)
49-
)->build(),
50-
];
42+
$errors = [];
43+
$paths = $this->resolveFilePaths($node, $scope);
44+
45+
foreach ($paths as $path) {
46+
if ($this->doesFileExist($path, $scope)) {
47+
continue;
5148
}
49+
50+
$errors[] = $this->getErrorMessage($node, $path);
5251
}
5352

54-
return [];
53+
return $errors;
5554
}
5655

57-
private function resolveFilePath(Node $node, Scope $scope): ?string
56+
/**
57+
* We cannot use `stream_resolve_include_path` as it works based on the calling script.
58+
* This method simulates the behavior of `stream_resolve_include_path` but for the given scope.
59+
* The priority order is the following:
60+
* 1. The current working directory.
61+
* 2. The include path.
62+
* 3. The path of the script that is being executed.
63+
*/
64+
private function doesFileExist(string $path, Scope $scope): bool
5865
{
59-
if ($node instanceof String_) {
60-
return $node->value;
61-
}
62-
63-
if ($node instanceof Dir) {
64-
return dirname($scope->getFile());
65-
}
66-
67-
if ($node instanceof ClassConstFetch) {
68-
return $this->resolveClassConstant($node);
66+
$directories = array_merge(
67+
[$this->currentWorkingDirectory],
68+
explode(PATH_SEPARATOR, get_include_path()),
69+
[dirname($scope->getFile())],
70+
);
71+
72+
foreach ($directories as $directory) {
73+
if ($this->doesFileExistForDirectory($path, $directory)) {
74+
return true;
75+
}
6976
}
7077

71-
if ($node instanceof ConstFetch) {
72-
return $this->resolveConstant($node);
73-
}
78+
return false;
79+
}
7480

75-
if ($node instanceof Concat) {
76-
$left = $this->resolveFilePath($node->left, $scope);
77-
$right = $this->resolveFilePath($node->right, $scope);
78-
if ($left !== null && $right !== null) {
79-
return $left . $right;
80-
}
81-
}
81+
private function doesFileExistForDirectory(string $path, string $workingDirectory): bool
82+
{
83+
$fileHelper = new FileHelper($workingDirectory);
84+
$normalisedPath = $fileHelper->normalizePath($path);
85+
$absolutePath = $fileHelper->absolutizePath($normalisedPath);
8286

83-
return null;
87+
return is_file($absolutePath);
8488
}
8589

86-
private function resolveClassConstant(ClassConstFetch $node): ?string
90+
private function getErrorMessage(Include_ $node, string $filePath): IdentifierRuleError
8791
{
88-
if ($node->class instanceof Name && $node->name instanceof Identifier) {
89-
$className = (string) $node->class;
90-
$constantName = $node->name->toString();
91-
92-
if ($this->reflectionProvider->hasClass($className)) {
93-
$classReflection = $this->reflectionProvider->getClass($className);
94-
if ($classReflection->hasConstant($constantName)) {
95-
$constantReflection = $classReflection->getConstant($constantName);
96-
$constantValue = $constantReflection->getValue();
97-
if (is_string($constantValue)) {
98-
return $constantValue;
99-
}
100-
}
101-
}
92+
$message = 'Path in %s() "%s" is not a file or it does not exist.';
93+
94+
switch ($node->type) {
95+
case Include_::TYPE_REQUIRE:
96+
$type = 'require';
97+
$identifierType = 'require';
98+
break;
99+
case Include_::TYPE_REQUIRE_ONCE:
100+
$type = 'require_once';
101+
$identifierType = 'requireOnce';
102+
break;
103+
case Include_::TYPE_INCLUDE:
104+
$type = 'include';
105+
$identifierType = 'include';
106+
break;
107+
case Include_::TYPE_INCLUDE_ONCE:
108+
$type = 'include_once';
109+
$identifierType = 'includeOnce';
110+
break;
111+
default:
112+
throw new ShouldNotHappenException('Rule should have already validated the node type.');
102113
}
103-
return null;
114+
115+
$identifier = sprintf('%s.fileNotFound', $identifierType);
116+
117+
return RuleErrorBuilder::message(
118+
sprintf(
119+
$message,
120+
$type,
121+
$filePath,
122+
),
123+
)->identifier($identifier)->build();
104124
}
105125

106-
private function resolveConstant(ConstFetch $node): ?string
126+
/**
127+
* @return array<string>
128+
*/
129+
private function resolveFilePaths(Include_ $node, Scope $scope): array
107130
{
108-
if ($node->name instanceof Name) {
109-
$constantName = (string) $node->name;
110-
if (defined($constantName)) {
111-
$constantValue = constant($constantName);
112-
if (is_string($constantValue)) {
113-
return $constantValue;
114-
}
115-
}
131+
$paths = [];
132+
$type = $scope->getType($node->expr);
133+
$constantStrings = $type->getConstantStrings();
134+
135+
foreach ($constantStrings as $constantString) {
136+
$paths[] = $constantString->getValue();
116137
}
117-
return null;
138+
139+
return $paths;
118140
}
119-
}
141+
142+
}

tests/ClassThatContainsConst.php

Lines changed: 0 additions & 11 deletions
This file was deleted.

tests/ClassThatContainsMethod.php

Lines changed: 0 additions & 13 deletions
This file was deleted.

tests/ClassThatContainsProperties.php

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/MainTest.php

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)