diff --git a/SlevomatCodingStandard/Sniffs/Classes/ForbiddenPublicPropertySniff.php b/SlevomatCodingStandard/Sniffs/Classes/ForbiddenPublicPropertySniff.php index 58636062b..282a4104b 100644 --- a/SlevomatCodingStandard/Sniffs/Classes/ForbiddenPublicPropertySniff.php +++ b/SlevomatCodingStandard/Sniffs/Classes/ForbiddenPublicPropertySniff.php @@ -12,6 +12,8 @@ use function array_merge; use const T_PRIVATE; use const T_PROTECTED; +use const T_READONLY; +use const T_SEMICOLON; use const T_VAR; use const T_VARIABLE; @@ -20,6 +22,9 @@ final class ForbiddenPublicPropertySniff implements Sniff public const CODE_FORBIDDEN_PUBLIC_PROPERTY = 'ForbiddenPublicProperty'; + /** @var bool */ + public $allowReadonly = false; + /** @var bool */ public $checkPromoted = false; @@ -51,6 +56,10 @@ public function process(File $file, $variablePointer): void return; } + if ($this->allowReadonly && $this->isReadonlyProperty($file, $variablePointer)) { + return; + } + $errorMessage = 'Do not use public properties. Use method access instead.'; $file->addError($errorMessage, $variablePointer, self::CODE_FORBIDDEN_PUBLIC_PROPERTY); } @@ -74,4 +83,16 @@ private function getPropertyScopeModifier(File $file, int $position): array return $file->getTokens()[$scopeModifierPosition]; } + private function isReadonlyProperty(File $file, int $position): bool + { + $readonlyPosition = TokenHelper::findPrevious($file, [T_READONLY], $position - 1); + if ($readonlyPosition === null) { + return false; + } + + $semicolonPosition = TokenHelper::findNext($file, [T_SEMICOLON], $readonlyPosition + 1, $position - 1); + + return $semicolonPosition === null; + } + } diff --git a/tests/Sniffs/Classes/ForbiddenPublicPropertySniffTest.php b/tests/Sniffs/Classes/ForbiddenPublicPropertySniffTest.php index cba0619a1..bf016bb61 100644 --- a/tests/Sniffs/Classes/ForbiddenPublicPropertySniffTest.php +++ b/tests/Sniffs/Classes/ForbiddenPublicPropertySniffTest.php @@ -7,42 +7,62 @@ class ForbiddenPublicPropertySniffTest extends TestCase { - public function testNoErrors(): void + public function testDefault(): void { - $report = self::checkFile(__DIR__ . '/data/forbiddenPublicPropertyNoErrors.php'); - self::assertNoSniffErrorInFile($report); + $report = self::checkFile(__DIR__ . '/data/forbiddenPublicProperty.php'); + + self::assertSniffError($report, 5, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 6, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); } - public function testErrors(): void + public function testReadonly(): void { - $report = self::checkFile(__DIR__ . '/data/forbiddenPublicPropertyErrors.php'); - - self::assertSame(3, $report->getErrorCount()); + $report = self::checkFile(__DIR__ . '/data/forbiddenPublicPropertyReadonly.php'); self::assertSniffError($report, 5, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); self::assertSniffError($report, 6, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); self::assertSniffError($report, 7, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 8, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 9, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); } - public function testPromotedNoErrors(): void + public function testReadonlyPromoted(): void { - $report = self::checkFile(__DIR__ . '/data/forbiddenPublicPropertyPromotedNoErrors.php', [ + $report = self::checkFile(__DIR__ . '/data/forbiddenPublicPropertyReadonly.php', [ 'checkPromoted' => true, ]); - self::assertNoSniffErrorInFile($report); + + self::assertSniffError($report, 5, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 6, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 7, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 8, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 9, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 12, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 13, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); } - public function testPromotedErrors(): void + public function testReadonlyAllowed(): void { - $report = self::checkFile(__DIR__ . '/data/forbiddenPublicPropertyPromotedErrors.php', [ - 'checkPromoted' => true, + $report = self::checkFile(__DIR__ . '/data/forbiddenPublicPropertyReadonly.php', [ + 'allowReadonly' => true, ]); - self::assertSame(4, $report->getErrorCount()); + self::assertSniffError($report, 5, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 6, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 9, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + } + + public function testReadonlyAllowedPromoted(): void + { + $report = self::checkFile(__DIR__ . '/data/forbiddenPublicPropertyReadonly.php', [ + 'allowReadonly' => true, + 'checkPromoted' => true, + ]); self::assertSniffError($report, 5, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); - self::assertSniffError($report, 13, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); - self::assertSniffError($report, 14, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 6, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 9, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); + self::assertSniffError($report, 12, ForbiddenPublicPropertySniff::CODE_FORBIDDEN_PUBLIC_PROPERTY); } } diff --git a/tests/Sniffs/Classes/data/forbiddenPublicProperty.php b/tests/Sniffs/Classes/data/forbiddenPublicProperty.php new file mode 100644 index 000000000..325c93a97 --- /dev/null +++ b/tests/Sniffs/Classes/data/forbiddenPublicProperty.php @@ -0,0 +1,15 @@ += 8.1 - -function add(int $a, int $b) -{ - return $a + $b; -} - -class Test -{ - private $one, $two; - - public function __construct(public int $promoted) - { - } -} - -class TestSniff -{ - private $one, $two; -} diff --git a/tests/Sniffs/Classes/data/forbiddenPublicPropertyPromotedErrors.php b/tests/Sniffs/Classes/data/forbiddenPublicPropertyPromotedErrors.php deleted file mode 100644 index 1160802f5..000000000 --- a/tests/Sniffs/Classes/data/forbiddenPublicPropertyPromotedErrors.php +++ /dev/null @@ -1,18 +0,0 @@ -= 8.1 - -class Test -{ - public function __construct(public int $promoted1, readonly public string $promoted2) - { - } -} - -class Test2 -{ - public function __construct( - public int $promoted3, - readonly public string $promoted4, - ) - { - } -} diff --git a/tests/Sniffs/Classes/data/forbiddenPublicPropertyPromotedNoErrors.php b/tests/Sniffs/Classes/data/forbiddenPublicPropertyPromotedNoErrors.php deleted file mode 100644 index 33e91c33b..000000000 --- a/tests/Sniffs/Classes/data/forbiddenPublicPropertyPromotedNoErrors.php +++ /dev/null @@ -1,8 +0,0 @@ -= 8.1 - -class Test -{ - public function __construct(protected int $promoted1, readonly private string $promoted2) - { - } -} diff --git a/tests/Sniffs/Classes/data/forbiddenPublicPropertyReadonly.php b/tests/Sniffs/Classes/data/forbiddenPublicPropertyReadonly.php new file mode 100644 index 000000000..0e465e4ee --- /dev/null +++ b/tests/Sniffs/Classes/data/forbiddenPublicPropertyReadonly.php @@ -0,0 +1,24 @@ += 8.1 + +class Test +{ + var $var; + public $pub; + readonly public string $rps; + public readonly string $prs; + public $pub2; + + public function __construct( + public int $promoted1, + readonly public string $promoted2 + ) + { + } +} + +class Test2 +{ + public function __construct(protected int $promoted1, readonly private string $promoted2) + { + } +}