Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,63 @@ final class Alert

<br>

### ExposePublicPropsShouldBeFalseRule

Enforces that the `#[AsTwigComponent]` attribute has its `exposePublicProps` parameter explicitly set to `false`.
This prevents public properties from being automatically exposed to templates, promoting explicit control over what data is accessible in your Twig components.

```yaml
rules:
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule
```

```php
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
public string $message;
}
```

```php
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent(exposePublicProps: true)]
final class Alert
{
public string $message;
}
```

:x:

<br>

```php
// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent(exposePublicProps: false)]
final class Alert
{
public string $message;
}
```

:+1:

<br>

### ForbiddenAttributesPropertyRule

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.
Expand Down
64 changes: 64 additions & 0 deletions src/Rules/TwigComponent/ExposePublicPropsShouldBeFalseRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Rules\TwigComponent;

use Kocal\PHPStanSymfonyUX\NodeAnalyzer\AttributeFinder;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

/**
* @implements Rule<Class_>
*/
final class ExposePublicPropsShouldBeFalseRule implements Rule
{
public function getNodeType(): string
{
return Class_::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (! $asTwigComponent = AttributeFinder::findAttribute($node, AsTwigComponent::class)) {
return [];
}

$exposePublicPropsValue = $this->getExposePublicPropsValue($asTwigComponent);

if ($exposePublicPropsValue !== false) {
return [
RuleErrorBuilder::message('The #[AsTwigComponent] attribute must have its "exposePublicProps" parameter set to false.')
->identifier('symfonyUX.twigComponent.exposePublicPropsShouldBeFalse')
->line($asTwigComponent->getLine())
->tip('Set "exposePublicProps" to false in the #[AsTwigComponent] attribute.')
->build(),
];
}

return [];
}

private function getExposePublicPropsValue(Node\Attribute $attribute): ?bool
{
foreach ($attribute->args as $arg) {
if ($arg->name && $arg->name->toString() === 'exposePublicProps') {
if ($arg->value instanceof Node\Expr\ConstFetch) {
$constantName = $arg->value->name->toString();

return match (strtolower($constantName)) {
'true' => true,
'false' => false,
default => null,
};
}
}
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule;

use Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<ExposePublicPropsShouldBeFalseRule>
*/
final class ExposePublicPropsShouldBeFalseRuleTest extends RuleTestCase
{
public function testViolations(): void
{
$this->analyse(
[__DIR__ . '/Fixture/ComponentWithoutExposePublicProps.php'],
[
[
'The #[AsTwigComponent] attribute must have its "exposePublicProps" parameter set to false.',
9,
'Set "exposePublicProps" to false in the #[AsTwigComponent] attribute.',
],
]
);

$this->analyse(
[__DIR__ . '/Fixture/ComponentWithExposePublicPropsTrue.php'],
[
[
'The #[AsTwigComponent] attribute must have its "exposePublicProps" parameter set to false.',
9,
'Set "exposePublicProps" to false in the #[AsTwigComponent] attribute.',
],
]
);
}

public function testNoViolations(): void
{
$this->analyse(
[__DIR__ . '/Fixture/NotAComponent.php'],
[]
);

$this->analyse(
[__DIR__ . '/Fixture/ComponentWithExposePublicPropsFalse.php'],
[]
);
}

public static function getAdditionalConfigFiles(): array
{
return [__DIR__ . '/config/configured_rule.neon'];
}

protected function getRule(): Rule
{
return self::getContainer()->getByType(ExposePublicPropsShouldBeFalseRule::class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule\Fixture;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent(exposePublicProps: false)]
final class ComponentWithExposePublicPropsFalse
{
public string $name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule\Fixture;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent(exposePublicProps: true)]
final class ComponentWithExposePublicPropsTrue
{
public string $name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule\Fixture;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class ComponentWithoutExposePublicProps
{
public string $name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Kocal\PHPStanSymfonyUX\Tests\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule\Fixture;

final class NotAComponent
{
public string $name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rules:
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ExposePublicPropsShouldBeFalseRule