Skip to content

Commit c922084

Browse files
authored
Merge pull request #436 from longwave/static-class-concatenation
Allow static::class . '::methodName' concatenation in lazy builders.
2 parents 7724769 + 12af3f1 commit c922084

File tree

3 files changed

+31
-39
lines changed

3 files changed

+31
-39
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"mglaman\\PHPStanDrupal\\Tests\\": "tests/src/"
4747
},
4848
"classmap": [
49-
"tests/src/Type/data"
49+
"tests/src/Type/data",
50+
"tests/src/Rules/data"
5051
]
5152
},
5253
"extra": {

src/Rules/Drupal/RenderCallbackRule.php

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use PHPStan\Type\Generic\GenericClassStringType;
2020
use PHPStan\Type\IntersectionType;
2121
use PHPStan\Type\ObjectType;
22+
use PHPStan\Type\StaticType;
2223
use PHPStan\Type\ThisType;
2324
use PHPStan\Type\Type;
2425
use PHPStan\Type\UnionType;
@@ -149,6 +150,12 @@ private function doProcessNode(Node\Expr $node, Scope $scope, string $keyChecked
149150
->tip('Change record: https://www.drupal.org/node/2966725.')
150151
->build();
151152
}
153+
154+
if (!$trustedCallbackType->isSuperTypeOf($type)->yes()) {
155+
return RuleErrorBuilder::message(
156+
sprintf("%s callback class %s at key '%s' does not implement Drupal\Core\Security\TrustedCallbackInterface.", $keyChecked, $type->describe(VerbosityLevel::value()), $pos)
157+
)->line($errorLine)->tip('Change record: https://www.drupal.org/node/2966725.')->build();
158+
}
152159
} elseif ($type instanceof ConstantArrayType) {
153160
if (!$type->isCallable()->yes()) {
154161
return RuleErrorBuilder::message(
@@ -185,25 +192,6 @@ private function doProcessNode(Node\Expr $node, Scope $scope, string $keyChecked
185192
sprintf("%s callback %s at key '%s' is not callable.", $keyChecked, $type->describe(VerbosityLevel::value()), $pos)
186193
)->line($errorLine)->build();
187194
}
188-
} elseif ($type instanceof IntersectionType) {
189-
// Try to provide a tip for this weird occurrence.
190-
$tip = '';
191-
if ($node instanceof Node\Expr\BinaryOp\Concat) {
192-
$leftStringType = $scope->getType($node->left)->toString();
193-
$rightStringType = $scope->getType($node->right)->toString();
194-
if ($leftStringType instanceof GenericClassStringType && $rightStringType instanceof ConstantStringType) {
195-
$methodName = str_replace(':', '', $rightStringType->getValue());
196-
$tip = "Refactor concatenation of `static::class` with method name to an array callback: [static::class, '$methodName']";
197-
}
198-
}
199-
200-
if ($tip === '') {
201-
$tip = 'If this error is unexpected, open an issue with the error and sample code https://github.com/mglaman/phpstan-drupal/issues/new';
202-
}
203-
204-
return RuleErrorBuilder::message(
205-
sprintf("%s value '%s' at key '%s' is invalid.", $keyChecked, $type->describe(VerbosityLevel::value()), $pos)
206-
)->line($errorLine)->tip($tip)->build();
207195
} else {
208196
return RuleErrorBuilder::message(
209197
sprintf("%s value '%s' at key '%s' is invalid.", $keyChecked, $type->describe(VerbosityLevel::value()), $pos)
@@ -217,7 +205,22 @@ private function doProcessNode(Node\Expr $node, Scope $scope, string $keyChecked
217205
private function getType(Node\Expr $node, Scope $scope): Type
218206
{
219207
$type = $scope->getType($node);
220-
if ($type instanceof ConstantStringType) {
208+
if ($type instanceof IntersectionType) {
209+
// Covers concatenation of static::class . '::methodName'.
210+
if ($node instanceof Node\Expr\BinaryOp\Concat) {
211+
$leftType = $scope->getType($node->left);
212+
$rightType = $scope->getType($node->right);
213+
if ($rightType instanceof ConstantStringType && $leftType instanceof GenericClassStringType && $leftType->getGenericType() instanceof StaticType) {
214+
return new ConstantArrayType(
215+
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
216+
[
217+
$leftType->getGenericType(),
218+
new ConstantStringType(ltrim($rightType->getValue(), ':'))
219+
]
220+
);
221+
}
222+
}
223+
} elseif ($type instanceof ConstantStringType) {
221224
if ($type->isClassString()) {
222225
return $type;
223226
}
@@ -243,7 +246,7 @@ private function getType(Node\Expr $node, Scope $scope): Type
243246
return new ConstantArrayType(
244247
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
245248
[
246-
new ConstantStringType($matches[1], true),
249+
new StaticType($this->reflectionProvider->getClass($matches[1])),
247250
new ConstantStringType($matches[2])
248251
]
249252
);

tests/src/Rules/RenderCallbackRuleTest.php

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public function fileData(): \Generator
5252
'Change record: https://www.drupal.org/node/2966725.',
5353
],
5454
[
55-
"#pre_render callback class 'Drupal\pre_render_callback_rule\NotTrustedCallback' at key '3' does not implement Drupal\Core\Security\TrustedCallbackInterface.",
55+
"#pre_render callback class 'static(Drupal\pre_render_callback_rule\NotTrustedCallback)' at key '3' does not implement Drupal\Core\Security\TrustedCallbackInterface.",
5656
29,
5757
'Change record: https://www.drupal.org/node/2966725.',
5858
],
@@ -65,27 +65,15 @@ public function fileData(): \Generator
6565
];
6666
yield [
6767
__DIR__ . '/../../fixtures/drupal/modules/pre_render_callback_rule/src/RenderArrayWithPreRenderCallback.php',
68-
[
69-
[
70-
"#pre_render value 'non-empty-string' at key '3' is invalid.",
71-
19,
72-
"Refactor concatenation of `static::class` with method name to an array callback: [static::class, 'preRenderCallback']"
73-
]
74-
]
68+
[]
7569
];
7670
yield [
7771
__DIR__ . '/../../fixtures/drupal/modules/pre_render_callback_rule/src/RenderCallbackInterfaceObject.php',
7872
[]
7973
];
8074
yield [
8175
__DIR__ . '/../../fixtures/drupal/modules/pre_render_callback_rule/src/LazyBuilderWithConstant.php',
82-
[
83-
[
84-
"#lazy_builder value 'non-empty-string' at key '0' is invalid.",
85-
25,
86-
"Refactor concatenation of `static::class` with method name to an array callback: [static::class, 'lazyBuilder']"
87-
]
88-
]
76+
[]
8977
];
9078
yield [
9179
__DIR__ . '/../../fixtures/drupal/modules/pre_render_callback_rule/src/FormWithClosure.php',
@@ -112,9 +100,9 @@ public function fileData(): \Generator
112100
__DIR__ . '/data/bug-424.php',
113101
[
114102
[
115-
"#lazy_builder value 'non-empty-string' at key '0' is invalid.",
103+
"#lazy_builder callback class 'static(Bug424\Foo)' at key '0' does not implement Drupal\Core\Security\TrustedCallbackInterface.",
116104
10,
117-
"Refactor concatenation of `static::class` with method name to an array callback: [static::class, 'contentLazyBuilder']"
105+
"Change record: https://www.drupal.org/node/2966725."
118106
],
119107
[
120108
"#lazy_builder callback class 'static(Bug424\Foo)' at key '0' does not implement Drupal\Core\Security\TrustedCallbackInterface.",

0 commit comments

Comments
 (0)