diff --git a/README.md b/README.md index d5afb11..4af1f21 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,68 @@ final class TodoList
+### LiveListenerMethodsShouldBePublicRule + +Enforces that all methods annotated with `#[LiveListener]` in LiveComponents must be declared as public. +LiveListener methods need to be publicly accessible to be invoked when listening to events from the frontend. + +```yaml +rules: + - Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule +``` + +```php +// src/Twig/Components/Notification.php +namespace App\Twig\Components; + +use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; +use Symfony\UX\LiveComponent\Attribute\LiveListener; + +#[AsLiveComponent] +final class Notification +{ + #[LiveListener('notification:received')] + private function onNotificationReceived(): void + { + } + + #[LiveListener('notification:dismissed')] + protected function onNotificationDismissed(): void + { + } +} +``` + +:x: + +
+ +```php +// src/Twig/Components/Notification.php +namespace App\Twig\Components; + +use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; +use Symfony\UX\LiveComponent\Attribute\LiveListener; + +#[AsLiveComponent] +final class Notification +{ + #[LiveListener('notification:received')] + public function onNotificationReceived(): void + { + } + + #[LiveListener('notification:dismissed')] + public function onNotificationDismissed(): void + { + } +} +``` + +:+1: + +
+ ## TwigComponent Rules > [!NOTE] diff --git a/src/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule.php b/src/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule.php new file mode 100644 index 0000000..f545c7b --- /dev/null +++ b/src/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule.php @@ -0,0 +1,53 @@ + + */ +final class LiveListenerMethodsShouldBePublicRule implements Rule +{ + public function getNodeType(): string + { + return Class_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (! AttributeFinder::findAnyAttribute($node, [AsLiveComponent::class])) { + return []; + } + + $errors = []; + + foreach ($node->getMethods() as $method) { + if (! AttributeFinder::findAnyAttribute($method, [LiveListener::class])) { + continue; + } + + if (! $method->isPublic()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'LiveListener method "%s()" should be public.', + $method->name->toString() + )) + ->identifier('symfonyUX.liveComponent.liveListenerMethodShouldBePublic') + ->line($method->getLine()) + ->tip('Change the method visibility to public.') + ->build(); + } + } + + return $errors; + } +} diff --git a/tests/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule/Fixture/NoLiveListener.php b/tests/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule/Fixture/NoLiveListener.php new file mode 100644 index 0000000..e49dd36 --- /dev/null +++ b/tests/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule/Fixture/NoLiveListener.php @@ -0,0 +1,19 @@ + + */ +final class LiveListenerMethodsShouldBePublicRuleTest extends RuleTestCase +{ + public function testViolations(): void + { + $this->analyse( + [__DIR__ . '/Fixture/PrivateLiveListener.php'], + [ + [ + 'LiveListener method "onAnotherEvent()" should be public.', + 13, + 'Change the method visibility to public.', + ], + [ + 'LiveListener method "onSomeEvent()" should be public.', + 18, + 'Change the method visibility to public.', + ], + ] + ); + } + + public function testNoViolations(): void + { + $this->analyse( + [__DIR__ . '/Fixture/NotAComponent.php'], + [] + ); + + $this->analyse( + [__DIR__ . '/Fixture/PublicLiveListener.php'], + [] + ); + + $this->analyse( + [__DIR__ . '/Fixture/NoLiveListener.php'], + [] + ); + } + + public static function getAdditionalConfigFiles(): array + { + return [__DIR__ . '/config/configured_rule.neon']; + } + + protected function getRule(): Rule + { + return self::getContainer()->getByType(LiveListenerMethodsShouldBePublicRule::class); + } +} diff --git a/tests/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule/config/configured_rule.neon b/tests/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule/config/configured_rule.neon new file mode 100644 index 0000000..ffce0ec --- /dev/null +++ b/tests/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule/config/configured_rule.neon @@ -0,0 +1,2 @@ +rules: + - Kocal\PHPStanSymfonyUX\Rules\LiveComponent\LiveListenerMethodsShouldBePublicRule