Skip to content

Commit 09fec28

Browse files
committed
Add excludePropertyWithDefaultValue option
1 parent 55a2d1d commit 09fec28

File tree

7 files changed

+70
-8
lines changed

7 files changed

+70
-8
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,19 @@ class NoNativeReturnTypehint {
219219
- Ensures immutability of all public properties by enforcing `readonly` modifier
220220
- No modifier needed for readonly classes in PHP 8.2
221221
- Does nothing if PHP version does not support readonly properties (PHP 8.0 and below)
222+
- Can be configured to exclude properties with a default value
222223
```php
223224
class EnforceReadonlyPublicPropertyRule {
224225
public int $foo; // fails, no readonly modifier
225226
public readonly int $bar;
226227
}
227228
```
229+
```neon
230+
parameters:
231+
shipmonkRules:
232+
enforceReadonlyPublicProperty:
233+
excludePropertyWithDefaultValue: true # defaults to false
234+
```
228235

229236
### forbidArithmeticOperationOnNonNumber
230237
- Disallows using [arithmetic operators](https://www.php.net/manual/en/language.operators.arithmetic.php) with non-numeric types (only `float`, `int` and `BcMath\Number` is allowed)

rules.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ parameters:
2222
enabled: %shipmonkRules.enableAllRules%
2323
enforceReadonlyPublicProperty:
2424
enabled: %shipmonkRules.enableAllRules%
25+
excludePropertyWithDefaultValue: false
2526
forbidArithmeticOperationOnNonNumber:
2627
enabled: %shipmonkRules.enableAllRules%
2728
allowNumericString: false
@@ -121,6 +122,7 @@ parametersSchema:
121122
])
122123
enforceReadonlyPublicProperty: structure([
123124
enabled: bool()
125+
excludePropertyWithDefaultValue: bool()
124126
])
125127
forbidArithmeticOperationOnNonNumber: structure([
126128
enabled: bool()
@@ -325,6 +327,8 @@ services:
325327
treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain%
326328
-
327329
class: ShipMonk\PHPStan\Rule\EnforceReadonlyPublicPropertyRule
330+
arguments:
331+
excludePropertyWithDefaultValue: %shipmonkRules.enforceReadonlyPublicProperty.excludePropertyWithDefaultValue%
328332
-
329333
class: ShipMonk\PHPStan\Rule\ForbidArithmeticOperationOnNonNumberRule
330334
arguments:

src/Rule/EnforceReadonlyPublicPropertyRule.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616
class EnforceReadonlyPublicPropertyRule implements Rule
1717
{
1818

19+
private bool $excludePropertyWithDefaultValue;
20+
1921
private PhpVersion $phpVersion;
2022

21-
public function __construct(PhpVersion $phpVersion)
23+
public function __construct(
24+
bool $excludePropertyWithDefaultValue,
25+
PhpVersion $phpVersion
26+
)
2227
{
28+
$this->excludePropertyWithDefaultValue = $excludePropertyWithDefaultValue;
2329
$this->phpVersion = $phpVersion;
2430
}
2531

@@ -48,8 +54,8 @@ public function processNode(
4854
|| $node->isPrivateSet()
4955
|| $node->isProtectedSet()
5056
|| $node->isStatic()
51-
|| $node->getDefault() !== null
5257
|| $node->getNativeType() === null
58+
|| ($this->excludePropertyWithDefaultValue && $node->getDefault() !== null)
5359
) {
5460
return [];
5561
}

tests/Rule/EnforceReadonlyPublicPropertyRuleTest.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace ShipMonk\PHPStan\Rule;
44

5+
use LogicException;
56
use PHPStan\Php\PhpVersion;
67
use PHPStan\Rules\Rule;
78
use ShipMonk\PHPStan\RuleTestCase;
@@ -13,12 +14,24 @@
1314
class EnforceReadonlyPublicPropertyRuleTest extends RuleTestCase
1415
{
1516

17+
private ?bool $excludePropertyWithDefaultValue = null;
18+
1619
private ?PhpVersion $phpVersion = null;
1720

1821
protected function getRule(): Rule
1922
{
20-
self::assertNotNull($this->phpVersion);
21-
return new EnforceReadonlyPublicPropertyRule($this->phpVersion);
23+
if ($this->excludePropertyWithDefaultValue === null) {
24+
throw new LogicException('excludePropertyWithDefaultValue must be set');
25+
}
26+
27+
if ($this->phpVersion === null) {
28+
throw new LogicException('phpVersion must be set');
29+
}
30+
31+
return new EnforceReadonlyPublicPropertyRule(
32+
$this->excludePropertyWithDefaultValue,
33+
$this->phpVersion,
34+
);
2235
}
2336

2437
public function testPhp84(): void
@@ -27,22 +40,32 @@ public function testPhp84(): void
2740
self::markTestSkipped('PHP7 parser fails with property hooks');
2841
}
2942

43+
$this->excludePropertyWithDefaultValue = false;
3044
$this->phpVersion = $this->createPhpVersion(80_400);
3145
$this->analyseFile(__DIR__ . '/data/EnforceReadonlyPublicPropertyRule/code-84.php');
3246
}
3347

3448
public function testPhp81(): void
3549
{
50+
$this->excludePropertyWithDefaultValue = false;
3651
$this->phpVersion = $this->createPhpVersion(80_100);
3752
$this->analyseFile(__DIR__ . '/data/EnforceReadonlyPublicPropertyRule/code-81.php');
3853
}
3954

4055
public function testPhp80(): void
4156
{
57+
$this->excludePropertyWithDefaultValue = false;
4258
$this->phpVersion = $this->createPhpVersion(80_000);
4359
$this->analyseFile(__DIR__ . '/data/EnforceReadonlyPublicPropertyRule/code-80.php');
4460
}
4561

62+
public function testExcludePropertyWithDefaultValue(): void
63+
{
64+
$this->excludePropertyWithDefaultValue = true;
65+
$this->phpVersion = $this->createPhpVersion(80_100);
66+
$this->analyseFile(__DIR__ . '/data/EnforceReadonlyPublicPropertyRule/exclude-property-with-default-value.php');
67+
}
68+
4669
private function createPhpVersion(int $version): PhpVersion
4770
{
4871
return new PhpVersion($version);

tests/Rule/data/EnforceReadonlyPublicPropertyRule/code-81.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ trait MyTrait {
1414

1515
public static string $static;
1616

17-
public int $default = 42;
17+
public int $default = 42; // error: Public property `default` not marked as readonly.
1818

1919
public $untyped;
2020

@@ -34,7 +34,7 @@ class MyClass {
3434

3535
public static string $static;
3636

37-
public int $quux = 7;
37+
public int $quux = 7; // error: Public property `quux` not marked as readonly.
3838

3939
public $quuz;
4040

tests/Rule/data/EnforceReadonlyPublicPropertyRule/code-84.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ trait MyTrait {
1616

1717
public static string $static;
1818

19-
public int $default = 42;
19+
public int $default = 42; // error: Public property `default` not marked as readonly.
2020

2121
public $untyped;
2222

@@ -38,7 +38,7 @@ class MyClass {
3838

3939
public static string $static;
4040

41-
public int $quux = 7;
41+
public int $quux = 7; // error: Public property `quux` not marked as readonly.
4242

4343
public $quuz;
4444

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace EnforceReadonlyPublicPropertyRuleExcludePropertyWithDefaultValue;
4+
5+
trait MyTrait {
6+
7+
public ?string $public; // error: Public property `public` not marked as readonly.
8+
9+
public int $default = 42;
10+
11+
}
12+
13+
class MyClass {
14+
15+
use MyTrait;
16+
17+
public ?int $foo; // error: Public property `foo` not marked as readonly.
18+
19+
public int $quux = 7;
20+
21+
}
22+

0 commit comments

Comments
 (0)