Skip to content

Commit d18007f

Browse files
authored
Merge pull request #313 from mglaman/gh303
Allow non-callable in #lazy_builder if array_intersect_key
2 parents 97af690 + 9000d42 commit d18007f

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

src/Rules/Drupal/RenderCallbackRule.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use mglaman\PHPStanDrupal\Drupal\ServiceMap;
66
use PhpParser\Node;
7+
use PhpParser\Node\Name;
78
use PHPStan\Analyser\Scope;
89
use PHPStan\Reflection\ReflectionProvider;
910
use PHPStan\Rules\Rule;
@@ -18,8 +19,11 @@
1819
use PHPStan\Type\ObjectType;
1920
use PHPStan\Type\ThisType;
2021
use PHPStan\Type\Type;
22+
use PHPStan\Type\TypeCombinator;
2123
use PHPStan\Type\UnionType;
2224
use PHPStan\Type\VerbosityLevel;
25+
use Drupal\Core\Render\PlaceholderGeneratorInterface;
26+
use Drupal\Core\Render\RendererInterface;
2327

2428
final class RenderCallbackRule implements Rule
2529
{
@@ -62,6 +66,23 @@ public function processNode(Node $node, Scope $scope): array
6266

6367
// @todo Move into its own rule.
6468
if ($keyChecked === '#lazy_builder') {
69+
// Check if being used in array_intersect_key.
70+
// NOTE: This only works against existing patterns in Drupal core where the array with boolean values is
71+
// being passed as the argument to array_intersect_key.
72+
$parent = $node->getAttribute('parent');
73+
if ($parent instanceof Node\Expr\Array_) {
74+
$parent = $parent->getAttribute('parent');
75+
if ($parent instanceof Node\Arg) {
76+
$parent = $parent->getAttribute('parent');
77+
if ($parent instanceof Node\Expr\FuncCall
78+
&& $parent->name instanceof Name
79+
&& $parent->name->toString() === 'array_intersect_key'
80+
) {
81+
return [];
82+
}
83+
}
84+
}
85+
6586
if (!$value instanceof Node\Expr\Array_) {
6687
return [
6788
RuleErrorBuilder::message(sprintf('The "%s" expects a callable array with arguments.', $keyChecked))
@@ -118,7 +139,7 @@ private function doProcessNode(Node\Expr $node, Scope $scope, string $keyChecked
118139
}
119140
// We can determine if the callback is callable through the type system. However, we cannot determine
120141
// if it is just a function or a static class call (MyClass::staticFunc).
121-
if ($this->reflectionProvider->hasFunction(new Node\Name($type->getValue()), null)) {
142+
if ($this->reflectionProvider->hasFunction(new Name($type->getValue()), null)) {
122143
return RuleErrorBuilder::message(
123144
sprintf("%s callback %s at key '%s' is not trusted.", $keyChecked, $type->describe(VerbosityLevel::value()), $pos)
124145
)->line($errorLine)

tests/src/Rules/PreRenderCallbackRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ public function fileData(): \Generator
100100
__DIR__ . '/../../fixtures/drupal/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php',
101101
[]
102102
];
103+
yield [
104+
__DIR__ . '/../../fixtures/drupal/core/lib/Drupal/Core/Render/PlaceholderGenerator.php',
105+
[]
106+
];
107+
yield [
108+
__DIR__ . '/../../fixtures/drupal/core/lib/Drupal/Core/Render/Renderer.php',
109+
[]
110+
];
103111
}
104112

105113

0 commit comments

Comments
 (0)