Skip to content

Rule to check if required file exists #3294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 49 commits into from
Aug 29, 2024
Merged
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c1f3c6f
Create rule for checking paths inside the require keyword
Bellangelo Aug 5, 2024
fb165ba
Test that it cannot read constants from the analyzing file
Bellangelo Aug 5, 2024
749414a
Test that relative paths can produce problems
Bellangelo Aug 5, 2024
deefe60
Test that it can read class constants
Bellangelo Aug 5, 2024
be18ba6
Test that it can read constants
Bellangelo Aug 5, 2024
24a9650
Test that it can read __DIR__
Bellangelo Aug 5, 2024
7c3f635
Test that it cannot read class properties
Bellangelo Aug 5, 2024
4ff25c8
Test that it cannot read variables
Bellangelo Aug 5, 2024
1625225
Test that it cannot class methods
Bellangelo Aug 5, 2024
496c541
Test that it cannot read functions
Bellangelo Aug 5, 2024
03f15fa
Unify filenames
Bellangelo Aug 5, 2024
4889f9e
Const types are not released until PHP 8.3
Bellangelo Aug 5, 2024
0e581eb
Fix code style
Bellangelo Aug 5, 2024
1520ea9
Classes should be abstract or final
Bellangelo Aug 5, 2024
960c71a
getValue() is deprecated
Bellangelo Aug 5, 2024
aff4b04
Put non-important errors under the baseline
Bellangelo Aug 5, 2024
2814cf8
Test files were renamed
Bellangelo Aug 5, 2024
4acc555
Make condition more clear
Bellangelo Aug 7, 2024
4680c6e
Make error message more descriptive
Bellangelo Aug 7, 2024
67ce86b
Remove unused import
Bellangelo Aug 7, 2024
31fa5e0
getConstantStrings() can handle all the manipulation of the path
Bellangelo Aug 7, 2024
50dde41
Make condition more specific
Bellangelo Aug 7, 2024
9f353d8
Class does not accept any arguments in constructor anymore
Bellangelo Aug 7, 2024
bfa3908
Fix conflicts
Bellangelo Aug 22, 2024
2369559
Register class rule
Bellangelo Aug 7, 2024
a2f0e6e
Fix small PHPStan issues
Bellangelo Aug 7, 2024
f20f26f
Fix code style
Bellangelo Aug 7, 2024
9896884
Convert syntax to <=PHP7.4
Bellangelo Aug 7, 2024
54f7779
Handle include() and include_once()
Bellangelo Aug 13, 2024
b723cf2
Rules section is always registered regardless of the conditionalTags
Bellangelo Aug 13, 2024
9035154
Add identifier in rule
Bellangelo Aug 13, 2024
4c85ed7
Tag forces rule to be always registered
Bellangelo Aug 13, 2024
2bed45a
Constants are not readable without the usePathConstantsAsConstantStri…
Bellangelo Aug 13, 2024
76b6f4f
Validate all possible cases
Bellangelo Aug 13, 2024
8ea25b8
remove unused import
Bellangelo Aug 13, 2024
f8db0ec
Handle include paths
Bellangelo Aug 13, 2024
032965a
Simplify tests
Bellangelo Aug 13, 2024
31a86c8
Make test file names consistent
Bellangelo Aug 13, 2024
6abc7ec
Test relative paths
Bellangelo Aug 13, 2024
71ee779
Small code improvements
Bellangelo Aug 13, 2024
3df4d41
Remove unused files
Bellangelo Aug 13, 2024
95e5888
Clean-up files
Bellangelo Aug 13, 2024
4858ceb
Method that works like stream_resolve_include_path but for a specific…
Bellangelo Aug 22, 2024
67ee4d8
Use the working directory that is configured in phpstan
Bellangelo Aug 22, 2024
4e049ea
Allow rule to be ignored
Bellangelo Aug 22, 2024
71d23e4
Use php7.4 syntax
Bellangelo Aug 22, 2024
525a393
Fix return type for < PHP7.4
Bellangelo Aug 22, 2024
e861017
Allow PHPStan to statically infer the possible identifiers
Bellangelo Aug 29, 2024
0743c06
Remove unused import
Bellangelo Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Create rule for checking paths inside the require keyword
  • Loading branch information
Bellangelo committed Aug 22, 2024
commit c1f3c6f3b17b5839c6963be511a762634db5044d
125 changes: 125 additions & 0 deletions src/Rules/Keywords/RequireFileExistsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Keywords;

use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\MagicConst\Dir;
use PhpParser\Node\Scalar\String_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @implements Rule<Include_>
*/
class RequireFileExistsRule implements Rule
{
private ReflectionProvider $reflectionProvider;

public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}

public function getNodeType(): string
{
return Include_::class;
}

public function processNode(Node $node, Scope $scope): array
{
if ($this->shouldProcessNode($node)) {
$filePath = $this->resolveFilePath($node->expr, $scope);
if ($filePath !== null && !file_exists($filePath)) {
return [
RuleErrorBuilder::message(
sprintf(
'Required file "%s" does not exist.',
$filePath
)
)->build(),
];
}
}

return [];
}

private function shouldProcessNode(Node $node): bool
{
return $node instanceof Include_ && (
$node->type === Include_::TYPE_REQUIRE
|| $node->type === Include_::TYPE_REQUIRE_ONCE
);
}

private function resolveFilePath(Node $node, Scope $scope): ?string
{
if ($node instanceof String_) {
return $node->value;
}

if ($node instanceof Dir) {
return dirname($scope->getFile());
}

if ($node instanceof ClassConstFetch) {
return $this->resolveClassConstant($node);
}

if ($node instanceof ConstFetch) {
return $this->resolveConstant($node);
}

if ($node instanceof Concat) {
$left = $this->resolveFilePath($node->left, $scope);
$right = $this->resolveFilePath($node->right, $scope);
if ($left !== null && $right !== null) {
return $left . $right;
}
}

return null;
}

private function resolveClassConstant(ClassConstFetch $node): ?string
{
if ($node->class instanceof Name && $node->name instanceof Identifier) {
$className = (string) $node->class;
$constantName = $node->name->toString();

if ($this->reflectionProvider->hasClass($className)) {
$classReflection = $this->reflectionProvider->getClass($className);
if ($classReflection->hasConstant($constantName)) {
$constantReflection = $classReflection->getConstant($constantName);
$constantValue = $constantReflection->getValue();
if (is_string($constantValue)) {
return $constantValue;
}
}
}
}
return null;
}

private function resolveConstant(ConstFetch $node): ?string
{
if ($node->name instanceof Name) {
$constantName = (string) $node->name;
if (defined($constantName)) {
$constantValue = constant($constantName);
if (is_string($constantValue)) {
return $constantValue;
}
}
}
return null;
}
}