Skip to content

Commit 49fb964

Browse files
authored
Merge pull request #5 from Kocal/ForbiddenInheritanceRule
2 parents 297f1d5 + d17e4aa commit 49fb964

File tree

8 files changed

+234
-0
lines changed

8 files changed

+234
-0
lines changed

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,76 @@ final class Alert
221221

222222
<br>
223223

224+
### ForbiddenInheritanceRule
225+
226+
Forbids the use of class inheritance in Twig Components. Composition via traits should be used instead.
227+
This promotes better code reusability and avoids tight coupling between components.
228+
229+
> [!TIP]
230+
> Another alternative is to use [Class Variant Authority](https://symfony.com/bundles/ux-twig-component/current/index.html#component-with-complex-variants-cva) to create variations of a base component without inheritance or traits,
231+
> for example `<twig:Alert variant="success"></twig:Alert>` instead of `<twig:AlertSuccess></twig:AlertSuccess>`.
232+
233+
```yaml
234+
rules:
235+
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenInheritanceRule
236+
```
237+
238+
```php
239+
// src/Twig/Components/Alert.php
240+
namespace App\Twig\Components;
241+
242+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
243+
244+
abstract class BaseComponent
245+
{
246+
public string $name;
247+
}
248+
249+
#[AsTwigComponent]
250+
final class Alert extends BaseComponent
251+
{
252+
}
253+
```
254+
255+
:x:
256+
257+
<br>
258+
259+
```php
260+
// src/Twig/Components/Alert.php
261+
namespace App\Twig\Components;
262+
263+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
264+
265+
trait CommonComponentTrait
266+
{
267+
public string $name;
268+
}
269+
270+
#[AsTwigComponent]
271+
final class Alert
272+
{
273+
use CommonComponentTrait;
274+
}
275+
```
276+
277+
```php
278+
// src/Twig/Components/Alert.php
279+
namespace App\Twig\Components;
280+
281+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
282+
283+
#[AsTwigComponent]
284+
final class Alert
285+
{
286+
public string $name;
287+
}
288+
```
289+
290+
:+1:
291+
292+
<br>
293+
224294
### PublicPropertiesShouldBeCamelCaseRule
225295

226296
Enforces that all public properties in Twig Components follow camelCase naming convention.
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\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 ForbiddenInheritanceRule 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 ($node->extends !== null) {
32+
return [
33+
RuleErrorBuilder::message('Using class inheritance in a Twig component is forbidden, use traits for composition instead.')
34+
->identifier('symfonyUX.twigComponent.forbiddenClassInheritance')
35+
->line($node->extends->getLine())
36+
->tip('Consider using traits to share common functionality between Twig components.')
37+
->build(),
38+
];
39+
}
40+
41+
return [];
42+
}
43+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ForbiddenInheritanceRule\Fixture;
6+
7+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
8+
9+
trait CommonComponentTrait
10+
{
11+
public string $name;
12+
}
13+
14+
#[AsTwigComponent]
15+
final class ComponentUsingTrait
16+
{
17+
use CommonComponentTrait;
18+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ForbiddenInheritanceRule\Fixture;
6+
7+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
8+
9+
abstract class BaseComponent
10+
{
11+
public string $name;
12+
}
13+
14+
#[AsTwigComponent]
15+
final class ComponentWithInheritance extends BaseComponent
16+
{
17+
}
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\ForbiddenInheritanceRule\Fixture;
6+
7+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
8+
9+
#[AsTwigComponent]
10+
final class ComponentWithoutInheritance
11+
{
12+
public string $name;
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ForbiddenInheritanceRule\Fixture;
6+
7+
abstract class BaseClass
8+
{
9+
public string $name;
10+
}
11+
12+
final class NotAComponent extends BaseClass
13+
{
14+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ForbiddenInheritanceRule;
6+
7+
use Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenInheritanceRule;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
11+
/**
12+
* @extends RuleTestCase<ForbiddenInheritanceRule>
13+
*/
14+
final class ForbiddenInheritanceRuleTest extends RuleTestCase
15+
{
16+
public function testViolations(): void
17+
{
18+
$this->analyse(
19+
[__DIR__ . '/Fixture/ComponentWithInheritance.php'],
20+
[
21+
[
22+
'Using class inheritance in a Twig component is forbidden, use traits for composition instead.',
23+
15,
24+
'Consider using traits to share common functionality between Twig components.',
25+
],
26+
]
27+
);
28+
}
29+
30+
public function testNoViolations(): void
31+
{
32+
$this->analyse(
33+
[__DIR__ . '/Fixture/NotAComponent.php'],
34+
[]
35+
);
36+
37+
$this->analyse(
38+
[__DIR__ . '/Fixture/ComponentWithoutInheritance.php'],
39+
[]
40+
);
41+
42+
$this->analyse(
43+
[__DIR__ . '/Fixture/ComponentUsingTrait.php'],
44+
[]
45+
);
46+
}
47+
48+
public static function getAdditionalConfigFiles(): array
49+
{
50+
return [__DIR__ . '/config/configured_rule.neon'];
51+
}
52+
53+
protected function getRule(): Rule
54+
{
55+
return self::getContainer()->getByType(ForbiddenInheritanceRule::class);
56+
}
57+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rules:
2+
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenInheritanceRule

0 commit comments

Comments
 (0)