Skip to content

Commit 8dad96c

Browse files
committed
Add ExposePublicPropsShouldBeFalseRule
1 parent 1553924 commit 8dad96c

File tree

8 files changed

+235
-0
lines changed

8 files changed

+235
-0
lines changed

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,63 @@ final class Alert
5353

5454
<br>
5555

56+
### ExposePublicPropsShouldBeFalseRule
57+
58+
Enforces that the `#[AsTwigComponent]` attribute has its `exposePublicProps` parameter explicitly set to `false`.
59+
This prevents public properties from being automatically exposed to templates, promoting explicit control over what data is accessible in your Twig components.
60+
61+
```yaml
62+
rules:
63+
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule
64+
```
65+
66+
```php
67+
// src/Twig/Components/Alert.php
68+
namespace App\Twig\Components;
69+
70+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
71+
72+
#[AsTwigComponent]
73+
final class Alert
74+
{
75+
public string $message;
76+
}
77+
```
78+
79+
```php
80+
// src/Twig/Components/Alert.php
81+
namespace App\Twig\Components;
82+
83+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
84+
85+
#[AsTwigComponent(exposePublicProps: true)]
86+
final class Alert
87+
{
88+
public string $message;
89+
}
90+
```
91+
92+
:x:
93+
94+
<br>
95+
96+
```php
97+
// src/Twig/Components/Alert.php
98+
namespace App\Twig\Components;
99+
100+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
101+
102+
#[AsTwigComponent(exposePublicProps: false)]
103+
final class Alert
104+
{
105+
public string $message;
106+
}
107+
```
108+
109+
:+1:
110+
111+
<br>
112+
56113
### ForbiddenAttributesPropertyRule
57114

58115
Forbid the use of the `$attributes` property in Twig Components, which can lead to confusion when using `{{ attributes }}` (an instance of `ComponentAttributes` that is automatically injected) in Twig templates.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 ExposePublicPropsShouldBeFalseRule 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 (! $asTwigComponent = AttributeFinder::findAttribute($node, AsTwigComponent::class)) {
28+
return [];
29+
}
30+
31+
$exposePublicPropsValue = $this->getExposePublicPropsValue($asTwigComponent);
32+
33+
if ($exposePublicPropsValue !== false) {
34+
return [
35+
RuleErrorBuilder::message('The #[AsTwigComponent] attribute must have its "exposePublicProps" parameter set to false.')
36+
->identifier('symfonyUX.twigComponent.exposePublicPropsShouldBeFalse')
37+
->line($asTwigComponent->getLine())
38+
->tip('Set "exposePublicProps" to false in the #[AsTwigComponent] attribute.')
39+
->build(),
40+
];
41+
}
42+
43+
return [];
44+
}
45+
46+
private function getExposePublicPropsValue(Node\Attribute $attribute): ?bool
47+
{
48+
foreach ($attribute->args as $arg) {
49+
if ($arg->name && $arg->name->toString() === 'exposePublicProps') {
50+
if ($arg->value instanceof Node\Expr\ConstFetch) {
51+
$constantName = $arg->value->name->toString();
52+
53+
return match (strtolower($constantName)) {
54+
'true' => true,
55+
'false' => false,
56+
default => null,
57+
};
58+
}
59+
}
60+
}
61+
62+
return null;
63+
}
64+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule;
6+
7+
use Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
11+
/**
12+
* @extends RuleTestCase<ExposePublicPropsShouldBeFalseRule>
13+
*/
14+
final class ExposePublicPropsShouldBeFalseRuleTest extends RuleTestCase
15+
{
16+
public function testViolations(): void
17+
{
18+
$this->analyse(
19+
[__DIR__ . '/Fixture/ComponentWithoutExposePublicProps.php'],
20+
[
21+
[
22+
'The #[AsTwigComponent] attribute must have its "exposePublicProps" parameter set to false.',
23+
9,
24+
'Set "exposePublicProps" to false in the #[AsTwigComponent] attribute.',
25+
],
26+
]
27+
);
28+
29+
$this->analyse(
30+
[__DIR__ . '/Fixture/ComponentWithExposePublicPropsTrue.php'],
31+
[
32+
[
33+
'The #[AsTwigComponent] attribute must have its "exposePublicProps" parameter set to false.',
34+
9,
35+
'Set "exposePublicProps" to false in the #[AsTwigComponent] attribute.',
36+
],
37+
]
38+
);
39+
}
40+
41+
public function testNoViolations(): void
42+
{
43+
$this->analyse(
44+
[__DIR__ . '/Fixture/NotAComponent.php'],
45+
[]
46+
);
47+
48+
$this->analyse(
49+
[__DIR__ . '/Fixture/ComponentWithExposePublicPropsFalse.php'],
50+
[]
51+
);
52+
}
53+
54+
public static function getAdditionalConfigFiles(): array
55+
{
56+
return [__DIR__ . '/config/configured_rule.neon'];
57+
}
58+
59+
protected function getRule(): Rule
60+
{
61+
return self::getContainer()->getByType(ExposePublicPropsShouldBeFalseRule::class);
62+
}
63+
}
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\ExposePublicPropsShouldBeFalseRule\Fixture;
6+
7+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
8+
9+
#[AsTwigComponent(exposePublicProps: false)]
10+
final class ComponentWithExposePublicPropsFalse
11+
{
12+
public string $name;
13+
}
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\ExposePublicPropsShouldBeFalseRule\Fixture;
6+
7+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
8+
9+
#[AsTwigComponent(exposePublicProps: true)]
10+
final class ComponentWithExposePublicPropsTrue
11+
{
12+
public string $name;
13+
}
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\ExposePublicPropsShouldBeFalseRule\Fixture;
6+
7+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
8+
9+
#[AsTwigComponent]
10+
final class ComponentWithoutExposePublicProps
11+
{
12+
public string $name;
13+
}
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\ExposePublicPropsShouldBeFalseRule\Fixture;
6+
7+
final class NotAComponent
8+
{
9+
public string $name;
10+
}
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\ExposePublicPropsShouldBeFalseRule

0 commit comments

Comments
 (0)