From e38ca48968c018e283c7de123c7292768e6fbd9b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 14 Oct 2025 14:54:12 +0200 Subject: [PATCH 1/2] Implement CompositeRule Allows testing of rules which delegate work to NodeCallbackInvoker --- src/Testing/CompositeRule.php | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/Testing/CompositeRule.php diff --git a/src/Testing/CompositeRule.php b/src/Testing/CompositeRule.php new file mode 100644 index 0000000000..848f5706a4 --- /dev/null +++ b/src/Testing/CompositeRule.php @@ -0,0 +1,48 @@ + + * + * @api + */ +final class CompositeRule implements Rule +{ + + private DirectRegistry $registry; + + public function __construct(DirectRegistry $ruleRegistry) + { + $this->registry = $ruleRegistry; + } + + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array + { + $errors = []; + + $nodeType = get_class($node); + foreach ($this->registry->getRules($nodeType) as $rule) { + foreach ($rule->processNode($node, $scope) as $error) { + $errors[] = $error; + } + } + + return $errors; + } + +} From 3b313e466ff1d766a03f184851a25a281773d781 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 14 Oct 2025 15:39:15 +0200 Subject: [PATCH 2/2] added test --- src/Testing/CompositeRule.php | 9 +- tests/PHPStan/Testing/CompositeRuleTest.php | 98 +++++++++++++++++++ tests/PHPStan/Testing/data/composite-rule.php | 14 +++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Testing/CompositeRuleTest.php create mode 100644 tests/PHPStan/Testing/data/composite-rule.php diff --git a/src/Testing/CompositeRule.php b/src/Testing/CompositeRule.php index 848f5706a4..b1403d53bd 100644 --- a/src/Testing/CompositeRule.php +++ b/src/Testing/CompositeRule.php @@ -21,9 +21,14 @@ final class CompositeRule implements Rule private DirectRegistry $registry; - public function __construct(DirectRegistry $ruleRegistry) + /** + * @param array> $rules + * + * @api + */ + public function __construct(array $rules) { - $this->registry = $ruleRegistry; + $this->registry = new DirectRegistry($rules); } public function getNodeType(): string diff --git a/tests/PHPStan/Testing/CompositeRuleTest.php b/tests/PHPStan/Testing/CompositeRuleTest.php new file mode 100644 index 0000000000..2539a53c6b --- /dev/null +++ b/tests/PHPStan/Testing/CompositeRuleTest.php @@ -0,0 +1,98 @@ + + */ +final class CompositeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new CompositeRule([ + /** + * @implements Rule + */ + new class implements Rule { + + public function getNodeType(): string + { + return String_::class; + } + + /** + * @param String_ $node + */ + public function processNode(Node $node, Scope&NodeCallbackInvoker $scope): array + { + if (ctype_digit($node->value)) { + $scope->invokeNodeCallback( + new Int_((int) $node->value, ['startLine' => $node->getStartLine()]), + ); + + return []; + } + + return [ + RuleErrorBuilder::message('Error from String_ Rule.')->identifier('CompositeRuleString')->build(), + ]; + } + + }, + /** + * @implements Rule + */ + new class implements Rule { + + public function getNodeType(): string + { + return Int_::class; + } + + /** + * @param Int_ $node + */ + public function processNode(Node $node, Scope $scope): array + { + return [ + RuleErrorBuilder::message('Error from Int_ Rule.')->identifier('CompositeRuleInt')->build(), + ]; + } + + }, + ]); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/composite-rule.php'], [ + [ + 'Error from String_ Rule.', + 6, + ], + [ + 'Error from Int_ Rule.', + 7, + ], + [ + 'Error from Int_ Rule.', + 9, + ], + [ + 'Error from Int_ Rule.', + 13, + ], + ]); + } + +} diff --git a/tests/PHPStan/Testing/data/composite-rule.php b/tests/PHPStan/Testing/data/composite-rule.php new file mode 100644 index 0000000000..b7adcb348b --- /dev/null +++ b/tests/PHPStan/Testing/data/composite-rule.php @@ -0,0 +1,14 @@ +