Skip to content

Commit 09dcbc7

Browse files
committed
Add TwigComponent\ForbiddenClassPropertyRule
1 parent 9316af0 commit 09dcbc7

File tree

6 files changed

+165
-0
lines changed

6 files changed

+165
-0
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,46 @@ final class Alert
7979
:+1:
8080

8181
<br>
82+
83+
### ForbiddenClassPropertyRule
84+
85+
Forbid the use of the `$class` property in Twig Components, as it is considered a bad practice to manipulate CSS classes directly in components.
86+
Use `{{ attributes }}` or `{{ attributes.defaults({ class: '...' }) }}` in your Twig templates instead.
87+
88+
```yaml
89+
rules:
90+
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenClassPropertyRule
91+
```
92+
93+
```php
94+
// src/Twig/Components/Alert.php
95+
namespace App\Twig\Components;
96+
97+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
98+
99+
#[AsTwigComponent]
100+
final class Alert
101+
{
102+
public $class;
103+
}
104+
```
105+
106+
:x:
107+
108+
<br>
109+
110+
```php
111+
// src/Twig/Components/Alert.php
112+
namespace App\Twig\Components;
113+
114+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
115+
116+
#[AsTwigComponent]
117+
final class Alert
118+
{
119+
}
120+
```
121+
122+
:+1:
123+
124+
<br>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Rules\TwigComponent;
6+
7+
use Kocal\PHPStanSymfonyUX\NodeAnalyzer\AttributeFinder;
8+
use PhpParser\Node;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
14+
15+
/**
16+
* @implements Rule<Class_>
17+
*/
18+
final class ForbiddenClassPropertyRule implements Rule
19+
{
20+
public function getNodeType(): string
21+
{
22+
return Class_::class;
23+
}
24+
25+
public function processNode(Node $node, Scope $scope): array
26+
{
27+
if (! AttributeFinder::findAttribute($node, AsTwigComponent::class)) {
28+
return [];
29+
}
30+
31+
if ($propertyClass = $node->getProperty('class')) {
32+
return [
33+
RuleErrorBuilder::message('Using a "class" property in a Twig component is forbidden, it is considered as an anti-pattern.')
34+
->identifier('symfonyUX.twigComponent.forbiddenClassProperty')
35+
->line($propertyClass->getLine())
36+
->tip('Consider using {{ attributes }} to automatically render unknown properties as HTML attributes, such as "class". Learn more at https://symfony.com/bundles/ux-twig-component/current/index.html#component-attributes.')
37+
->build(),
38+
39+
];
40+
}
41+
42+
return [];
43+
}
44+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ForbiddenClassPropertyRule\Fixture;
6+
7+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
8+
9+
#[AsTwigComponent]
10+
final class ComponentWithClassProperty
11+
{
12+
public $class;
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ForbiddenClassPropertyRule\Fixture;
6+
7+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
8+
9+
#[AsTwigComponent]
10+
final class ComponentWithNoClassProperty
11+
{
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ForbiddenClassPropertyRule\Fixture;
6+
7+
final class NotAComponent
8+
{
9+
public $attributes;
10+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ForbiddenClassPropertyRule;
6+
7+
use Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenClassPropertyRule;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
11+
final class ForbiddenClassPropertyRuleTest extends RuleTestCase
12+
{
13+
public function testViolations(): void
14+
{
15+
$this->analyse(
16+
[__DIR__ . '/Fixture/ComponentWithClassProperty.php'],
17+
[
18+
[
19+
'Using a "class" property in a Twig component is forbidden, it is considered as an anti-pattern.',
20+
12,
21+
'Consider using {{ attributes }} to automatically render unknown properties as HTML attributes, such as "class". Learn more at https://symfony.com/bundles/ux-twig-component/current/index.html#component-attributes.',
22+
],
23+
]
24+
);
25+
}
26+
27+
public function testNoViolations(): void
28+
{
29+
$this->analyse(
30+
[__DIR__ . '/Fixture/NotAComponent.php'],
31+
[]
32+
);
33+
$this->analyse(
34+
[__DIR__ . '/Fixture/ComponentWithNoClassProperty.php'],
35+
[]
36+
);
37+
}
38+
39+
protected function getRule(): Rule
40+
{
41+
return new ForbiddenClassPropertyRule();
42+
}
43+
}

0 commit comments

Comments
 (0)