Skip to content

Commit 404efc4

Browse files
authored
[paths] Add StringFileAbsolutePathExistsRule (#171)
1 parent 97178a0 commit 404efc4

File tree

9 files changed

+204
-0
lines changed

9 files changed

+204
-0
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,34 @@ class SomeCommand extends Command
122122

123123
:+1:
124124

125+
<br>
126+
127+
### StringFileAbsolutePathExistsRule
128+
129+
Absolute file path must exist. Checked suffixes are "yaml", "yml", "sql", "php" and "json".
130+
131+
```yaml
132+
rules:
133+
- Symplify\PHPStanRules\Rules\StringFileAbsolutePathExistsRule
134+
```
135+
136+
```php
137+
// missing file path
138+
return __DIR__ . '/some_file.yml';
139+
```
140+
141+
:x:
142+
143+
<br>
144+
145+
```php
146+
// correct file path
147+
return __DIR__ . '/../fixtures/some_file.yml';
148+
```
149+
150+
:+1:
151+
152+
125153
<br>
126154

127155
### NoConstructorOverrideRule

src/Enum/RuleIdentifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,6 @@ final class RuleIdentifier
6565
public const FORBIDDEN_NEW_INSTANCE = 'symplify.forbiddenNewInstance';
6666

6767
public const MAXIMUM_IGNORED_ERROR_COUNT = 'symplify.maximumIgnoredErrorCount';
68+
69+
public const STRING_FILE_ABSOLUTE_PATH_EXISTS = 'symplify.stringFileAbsolutePathExists';
6870
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Rules;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\BinaryOp\Concat;
7+
use PhpParser\Node\Scalar\MagicConst\Dir;
8+
use PhpParser\Node\Scalar\String_;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Rules\Rule;
11+
use PHPStan\Rules\RuleError;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use Symplify\PHPStanRules\Enum\RuleIdentifier;
14+
15+
/**
16+
* @implements Rule<Concat>
17+
*
18+
* @see \Symplify\PHPStanRules\Tests\Rules\StringFileAbsolutePathExistsRule\StringFileAbsolutePathExistsRuleTest
19+
*/
20+
final class StringFileAbsolutePathExistsRule implements Rule
21+
{
22+
/**
23+
* @var string
24+
*/
25+
public const ERROR_MESSAGE = 'File "%s" could not be found. Make sure it exists';
26+
27+
/**
28+
* @var string[]
29+
*/
30+
private const SUFFIXES_TO_CHECK = [
31+
'.sql',
32+
'.php',
33+
'.yml',
34+
'.yaml',
35+
'.json',
36+
];
37+
38+
public function getNodeType(): string
39+
{
40+
return Concat::class;
41+
}
42+
43+
/**
44+
* @param Concat $node
45+
* @return RuleError[]
46+
*/
47+
public function processNode(Node $node, Scope $scope): array
48+
{
49+
// look for __DIR__ . '/some_file.<suffix>'
50+
if (! $node->left instanceof Dir) {
51+
return [];
52+
}
53+
54+
if (! $node->right instanceof String_) {
55+
return [];
56+
}
57+
58+
$stringValue = $node->right->value;
59+
if (! $this->isDesiredFileSuffix($stringValue)) {
60+
return [];
61+
}
62+
63+
$absoluteFilePath = $this->getAbsoluteFilePath($scope, $stringValue);
64+
if (file_exists($absoluteFilePath)) {
65+
return [];
66+
}
67+
68+
$errorMessage = sprintf(self::ERROR_MESSAGE, $absoluteFilePath);
69+
70+
$identifierRuleError = RuleErrorBuilder::message($errorMessage)
71+
->identifier(RuleIdentifier::STRING_FILE_ABSOLUTE_PATH_EXISTS)
72+
->build();
73+
74+
return [$identifierRuleError];
75+
}
76+
77+
private function getAbsoluteFilePath(Scope $scope, string $stringValue): string
78+
{
79+
$directorPath = dirname($scope->getFile());
80+
return $directorPath . $stringValue;
81+
}
82+
83+
private function isDesiredFileSuffix(string $stringValue): bool
84+
{
85+
foreach (self::SUFFIXES_TO_CHECK as $suffixToCheck) {
86+
if (str_ends_with($stringValue, $suffixToCheck)) {
87+
return true;
88+
}
89+
}
90+
91+
return false;
92+
}
93+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\StringFileAbsolutePathExistsRule\Fixture;
4+
5+
final class NonExistingFile
6+
{
7+
public function go()
8+
{
9+
return __DIR__ . '/some_file.yml';
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\StringFileAbsolutePathExistsRule\Fixture;
4+
5+
final class SkipNestedConcats
6+
{
7+
public function go()
8+
{
9+
return __DIR__ . '/some_file/' . '.yml';
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\StringFileAbsolutePathExistsRule\Fixture;
4+
5+
final class SkipReferenceToExistingFile
6+
{
7+
public function go()
8+
{
9+
return __DIR__ . '/../Source/some_file.yml';
10+
}
11+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
key: value
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\StringFileAbsolutePathExistsRule;
6+
7+
use Iterator;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use Symplify\PHPStanRules\Rules\StringFileAbsolutePathExistsRule;
12+
13+
final class StringFileAbsolutePathExistsRuleTest extends RuleTestCase
14+
{
15+
/**
16+
* @param mixed[] $expectedErrorMessagesWithLines
17+
*/
18+
#[DataProvider('provideData')]
19+
public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
20+
{
21+
$this->analyse([$filePath], $expectedErrorMessagesWithLines);
22+
}
23+
24+
public static function provideData(): Iterator
25+
{
26+
$errorMessage = sprintf(StringFileAbsolutePathExistsRule::ERROR_MESSAGE, __DIR__ . '/Fixture/some_file.yml');
27+
yield [__DIR__ . '/Fixture/NonExistingFile.php', [[$errorMessage, 9]]];
28+
29+
yield [__DIR__ . '/Fixture/SkipReferenceToExistingFile.php', []];
30+
yield [__DIR__ . '/Fixture/SkipNestedConcats.php', []];
31+
}
32+
33+
protected function getRule(): Rule
34+
{
35+
return new StringFileAbsolutePathExistsRule();
36+
}
37+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
includes:
2+
- ../../../config/included_services.neon
3+
4+
services:
5+
-
6+
class: Symplify\PHPStanRules\Rules\SeeAnnotationToTestRule
7+
tags: [phpstan.rules.rule]
8+
arguments:
9+
requiredSeeTypes:
10+
- PHPStan\Rules\Rule

0 commit comments

Comments
 (0)