Skip to content

Commit db24f98

Browse files
Merge pull request #21 from DaveLiddament/feature/must-use-result
Add support for #[MustUseResult]
2 parents 442df2a + 37e7d6e commit db24f98

File tree

5 files changed

+188
-0
lines changed

5 files changed

+188
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This is an extension for [PHPStan](https://phpstan.org) for adding analysis for
1414
**Language feature added:**
1515
- [Friend](https://github.com/DaveLiddament/php-language-extensions#friend)
1616
- [Injectable Version](https://github.com/DaveLiddament/php-language-extensions#injectableVersion)
17+
- [MustUseResult](https://github.com/DaveLiddament/php-language-extensions#mustUseResult)
1718
- [NamespaceVisibility](https://github.com/DaveLiddament/php-language-extensions#namespaceVisibility)
1819
- [Package](https://github.com/DaveLiddament/php-language-extensions#package)
1920
- [Test Tag](https://github.com/DaveLiddament/php-language-extensions#testtag)

src/Rules/MustUseResultRule.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
namespace DaveLiddament\PhpstanPhpLanguageExtensions\Rules;
4+
5+
use DaveLiddament\PhpLanguageExtensions\MustUseResult;
6+
use DaveLiddament\PhpstanPhpLanguageExtensions\AttributeValueReaders\AttributeFinder;
7+
use DaveLiddament\PhpstanPhpLanguageExtensions\Helpers\Cache;
8+
use PhpParser\Node;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Reflection\ReflectionProvider;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
14+
/**
15+
* @implements Rule<Node\Stmt\Expression>
16+
*/
17+
final class MustUseResultRule implements Rule
18+
{
19+
/** @var Cache<bool> */
20+
private Cache $cache;
21+
22+
public function __construct(
23+
private ReflectionProvider $reflectionProvider,
24+
) {
25+
$this->cache = new Cache();
26+
}
27+
28+
public function getNodeType(): string
29+
{
30+
return Node\Stmt\Expression::class;
31+
}
32+
33+
/** @param Node\Stmt\Expression $node */
34+
public function processNode(Node $node, Scope $scope): array
35+
{
36+
$expr = $node->expr;
37+
38+
if ($expr instanceof Node\Expr\MethodCall) {
39+
$classReflections = $scope->getType($expr->var)->getObjectClassReflections();
40+
} elseif ($expr instanceof Node\Expr\StaticCall) {
41+
$class = $expr->class;
42+
if (!$class instanceof Node\Name) {
43+
return [];
44+
}
45+
46+
$classReflections = [
47+
$this->reflectionProvider->getClass($class->toCodeString()),
48+
];
49+
} else {
50+
return [];
51+
}
52+
53+
$methodNameNode = $expr->name;
54+
if (!$methodNameNode instanceof Node\Identifier) {
55+
return [];
56+
}
57+
58+
$methodName = $methodNameNode->toLowerString();
59+
60+
foreach ($classReflections as $classReflection) {
61+
$className = $classReflection->getName();
62+
$fullMethodName = "{$className}::{$methodName}";
63+
64+
if ($this->cache->hasEntry($fullMethodName)) {
65+
$mustUseResult = $this->cache->getEntry($fullMethodName);
66+
} else {
67+
$mustUseResult = AttributeFinder::hasAttributeOnMethod(
68+
$classReflection->getNativeReflection(),
69+
$methodName,
70+
MustUseResult::class,
71+
);
72+
$this->cache->addEntry($fullMethodName, $mustUseResult);
73+
}
74+
75+
if ($mustUseResult) {
76+
return [
77+
RuleErrorBuilder::message('Result returned by method must be used')
78+
->identifier('phpExtensionLibrary.mustUseResult')
79+
->build(),
80+
];
81+
}
82+
}
83+
84+
return [];
85+
}
86+
}
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 DaveLiddament\PhpstanPhpLanguageExtensions\Tests\Rules;
6+
7+
use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\MustUseResultRule;
8+
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;
9+
use PHPStan\Rules\Rule;
10+
11+
/** @extends AbstractRuleTestCase<MustUseResultRule> */
12+
final class MustUseResultOnMethodTest extends AbstractRuleTestCase
13+
{
14+
protected function getRule(): Rule
15+
{
16+
return new MustUseResultRule($this->createReflectionProvider());
17+
}
18+
19+
public function testMustUseResultRuleOnMethod(): void
20+
{
21+
$this->assertIssuesReported(
22+
__DIR__.'/data/mustUseResult/mustUseResultOnMethod.php',
23+
);
24+
}
25+
26+
public function testMustUseResultRuleOnStaticMethod(): void
27+
{
28+
$this->assertIssuesReported(
29+
__DIR__.'/data/mustUseResult/mustUseResultOnStaticMethod.php',
30+
);
31+
}
32+
33+
protected function getErrorFormatter(): string
34+
{
35+
return 'Result returned by method must be used';
36+
}
37+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace MustUseResultOnMethod {
4+
5+
6+
use DaveLiddament\PhpLanguageExtensions\MustUseResult;
7+
8+
class AClass {
9+
10+
#[MustUseResult]
11+
public function mustUseResult(): int
12+
{
13+
return 1;
14+
}
15+
16+
public function dontNeedToUseResult(): int
17+
{
18+
return 2;
19+
}
20+
21+
}
22+
23+
24+
$class = new AClass();
25+
26+
$class->dontNeedToUseResult(); // OK
27+
28+
$class->mustUseResult(); // ERROR
29+
30+
echo $class->mustUseResult(); // OK;
31+
32+
$value = 1 + $class->mustUseResult(); // OK
33+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace MustUseResultOnMethod {
4+
5+
6+
use DaveLiddament\PhpLanguageExtensions\MustUseResult;
7+
8+
class AClass {
9+
10+
#[MustUseResult]
11+
public static function mustUseResult(): int
12+
{
13+
return 1;
14+
}
15+
16+
public static function dontNeedToUseResult(): int
17+
{
18+
return 2;
19+
}
20+
21+
}
22+
23+
24+
AClass::dontNeedToUseResult(); // OK
25+
26+
AClass::mustUseResult(); // ERROR
27+
28+
echo AClass::mustUseResult(); // OK;
29+
30+
$value = 1 + AClass::mustUseResult(); // OK
31+
}

0 commit comments

Comments
 (0)