Skip to content

Commit ab08481

Browse files
committed
Add AccessResultConditionRule (#737)
* AccessResultConditionTypeSpecifyingExtension * rework the rule * treatPhpDocTypesAsCertain * fix logic
1 parent 3e97210 commit ab08481

File tree

5 files changed

+141
-1
lines changed

5 files changed

+141
-1
lines changed

extension.neon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ parameters:
2222
rules:
2323
testClassSuffixNameRule: false
2424
dependencySerializationTraitPropertyRule: false
25+
accessResultConditionRule: false
2526
entityMapping:
2627
aggregator_feed:
2728
class: Drupal\aggregator\Entity\Feed
@@ -244,6 +245,7 @@ parametersSchema:
244245
rules: structure([
245246
testClassSuffixNameRule: boolean()
246247
dependencySerializationTraitPropertyRule: boolean()
248+
accessResultConditionRule: boolean()
247249
])
248250
entityMapping: arrayOf(anyOf(
249251
structure([
@@ -314,7 +316,6 @@ services:
314316
class: mglaman\PHPStanDrupal\DeprecatedScope\GroupLegacyScope
315317
tags:
316318
- phpstan.deprecations.deprecatedScopeResolver
317-
318319
-
319320
class: mglaman\PHPStanDrupal\DeprecatedScope\DeprecationHelperScope
320321
tags:

rules.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ conditionalTags:
2525
phpstan.rules.rule: %drupal.rules.testClassSuffixNameRule%
2626
mglaman\PHPStanDrupal\Rules\Drupal\DependencySerializationTraitPropertyRule:
2727
phpstan.rules.rule: %drupal.rules.dependencySerializationTraitPropertyRule%
28+
mglaman\PHPStanDrupal\Rules\Drupal\AccessResultConditionRule:
29+
phpstan.rules.rule: %drupal.rules.accessResultConditionRule%
2830

2931
services:
3032
-
3133
class: mglaman\PHPStanDrupal\Rules\Drupal\Tests\TestClassSuffixNameRule
3234
-
3335
class: mglaman\PHPStanDrupal\Rules\Drupal\DependencySerializationTraitPropertyRule
36+
-
37+
class: mglaman\PHPStanDrupal\Rules\Drupal\AccessResultConditionRule
38+
arguments:
39+
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Rules\Drupal;
6+
7+
use Drupal\Core\Access\AccessResult;
8+
use PhpParser\Node;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Rules\Rule;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
use PHPStan\Type\VerbosityLevel;
13+
14+
/**
15+
* @implements Rule<Node\Expr\StaticCall>
16+
*/
17+
final class AccessResultConditionRule implements Rule
18+
{
19+
20+
/** @var bool */
21+
private $treatPhpDocTypesAsCertain;
22+
23+
/**
24+
* @param bool $treatPhpDocTypesAsCertain
25+
*/
26+
public function __construct($treatPhpDocTypesAsCertain)
27+
{
28+
$this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain;
29+
}
30+
31+
public function getNodeType(): string
32+
{
33+
return Node\Expr\StaticCall::class;
34+
}
35+
36+
public function processNode(Node $node, Scope $scope): array
37+
{
38+
if (!$node->name instanceof Node\Identifier) {
39+
return [];
40+
}
41+
$methodName = $node->name->toString();
42+
if (!in_array($methodName, ['allowedIf', 'forbiddenIf'], true)) {
43+
return [];
44+
}
45+
if (!$node->class instanceof Node\Name) {
46+
return [];
47+
}
48+
$className = $scope->resolveName($node->class);
49+
if ($className !== AccessResult::class) {
50+
return [];
51+
}
52+
$args = $node->getArgs();
53+
if (count($args) === 0) {
54+
return [];
55+
}
56+
$condition = $args[0]->value;
57+
if (!$condition instanceof Node\Expr\BinaryOp\Identical && !$condition instanceof Node\Expr\BinaryOp\NotIdentical) {
58+
return [];
59+
}
60+
$conditionType = $this->treatPhpDocTypesAsCertain ? $scope->getType($condition) : $scope->getNativeType($condition);
61+
$bool = $conditionType->toBoolean();
62+
63+
if ($bool->isTrue()->or($bool->isFalse())->yes()) {
64+
$leftType = $this->treatPhpDocTypesAsCertain ? $scope->getType($condition->left) : $scope->getNativeType($condition->left);
65+
$rightType = $this->treatPhpDocTypesAsCertain ? $scope->getType($condition->right) : $scope->getNativeType($condition->right);
66+
67+
return [
68+
RuleErrorBuilder::message(sprintf(
69+
'Strict comparison using %s between %s and %s will always evaluate to %s.',
70+
$condition->getOperatorSigil(),
71+
$leftType->describe(VerbosityLevel::value()),
72+
$rightType->describe(VerbosityLevel::value()),
73+
$bool->describe(VerbosityLevel::value()),
74+
))->identifier(sprintf('%s.alwaysFalse', $condition instanceof Node\Expr\BinaryOp\Identical ? 'identical' : 'notIdentical'))->build(),
75+
];
76+
}
77+
return [];
78+
}
79+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Tests\Rules;
6+
7+
use mglaman\PHPStanDrupal\Rules\Drupal\AccessResultConditionRule;
8+
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
9+
use PHPStan\Rules\Rule;
10+
11+
final class AccessResultConditionRuleTest extends DrupalRuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new AccessResultConditionRule(true);
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse(
22+
[__DIR__.'/data/access-result-condition-check.php'],
23+
[
24+
[
25+
'Strict comparison using === between false and false will always evaluate to true.',
26+
14
27+
],
28+
[
29+
'Strict comparison using !== between false and false will always evaluate to false.',
30+
18
31+
],
32+
]
33+
);
34+
}
35+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace AccessResultConditionCheck;
4+
5+
use Drupal\Core\Access\AccessResult;
6+
use Drupal\Core\Access\AccessResultInterface;
7+
8+
function foo(bool $x): AccessResultInterface
9+
{
10+
return AccessResult::allowedIf($x);
11+
}
12+
function bar(): AccessResultInterface
13+
{
14+
return AccessResult::allowedIf(false === false);
15+
}
16+
function baz(): AccessResultInterface
17+
{
18+
return AccessResult::allowedIf(false !== false);
19+
}

0 commit comments

Comments
 (0)