Skip to content

Commit 79b9d56

Browse files
authored
ReadonlyPromotedPropertiesFixer - support asymmetric visibility (#1012)
1 parent edb1034 commit 79b9d56

File tree

6 files changed

+134
-26
lines changed

6 files changed

+134
-26
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# CHANGELOG for PHP CS Fixer: custom fixers
22

3+
## v3.25.0
4+
- ReadonlyPromotedPropertiesFixer - support asymmetric visibility
5+
36
## v3.24.0
47
- Add PhpUnitRequiresConstraintFixer
58

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
66
[![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net)
77
[![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE)
8-
![Tests](https://img.shields.io/badge/tests-3610-brightgreen.svg)
8+
![Tests](https://img.shields.io/badge/tests-3613-brightgreen.svg)
99
[![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
1010

1111
[![CI status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml/badge.svg)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml)

src/Fixer/ReadonlyPromotedPropertiesFixer.php

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@
2222

2323
final class ReadonlyPromotedPropertiesFixer extends AbstractFixer
2424
{
25+
/** @var list<int> */
26+
private array $promotedPropertyVisibilityKinds;
27+
28+
public function __construct()
29+
{
30+
$this->promotedPropertyVisibilityKinds = [
31+
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
32+
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
33+
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
34+
];
35+
if (\defined('T_PUBLIC_SET')) {
36+
$this->promotedPropertyVisibilityKinds[] = \T_PUBLIC_SET;
37+
$this->promotedPropertyVisibilityKinds[] = \T_PROTECTED_SET;
38+
$this->promotedPropertyVisibilityKinds[] = \T_PRIVATE_SET;
39+
}
40+
}
41+
2542
public function getDefinition(): FixerDefinitionInterface
2643
{
2744
return new FixerDefinition(
@@ -53,11 +70,7 @@ public function getPriority(): int
5370

5471
public function isCandidate(Tokens $tokens): bool
5572
{
56-
return \defined('T_READONLY') && $tokens->isAnyTokenKindsFound([
57-
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
58-
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
59-
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
60-
]);
73+
return \defined('T_READONLY') && $tokens->isAnyTokenKindsFound($this->promotedPropertyVisibilityKinds);
6174
}
6275

6376
public function isRisky(): bool
@@ -83,6 +96,8 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void
8396
continue;
8497
}
8598

99+
$constructorNameIndex = $tokens->getNextMeaningfulToken($constructorAnalysis->getConstructorIndex());
100+
86101
$classOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['{']);
87102
\assert(\is_int($classOpenBraceIndex));
88103
$classCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpenBraceIndex);
@@ -119,34 +134,20 @@ private function fixParameters(
119134
int $constructorCloseParenthesisIndex
120135
): void {
121136
for ($index = $constructorCloseParenthesisIndex; $index > $constructorOpenParenthesisIndex; $index--) {
122-
if (
123-
!$tokens[$index]->isGivenKind([
124-
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
125-
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
126-
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
127-
])
128-
) {
129-
continue;
130-
}
131-
132-
$nextIndex = $tokens->getNextMeaningfulToken($index);
133-
if ($tokens[$nextIndex]->isGivenKind(\T_READONLY)) {
137+
if (!$tokens[$index]->isGivenKind(\T_VARIABLE)) {
134138
continue;
135139
}
136140

137-
$prevIndex = $tokens->getPrevMeaningfulToken($index);
138-
if ($tokens[$prevIndex]->isGivenKind(\T_READONLY)) {
141+
$insertIndex = $this->getInsertIndex($tokens, $index);
142+
if ($insertIndex === null) {
139143
continue;
140144
}
141145

142-
$propertyIndex = $tokens->getNextTokenOfKind($index, [[\T_VARIABLE]]);
143-
\assert(\is_int($propertyIndex));
144-
145146
$propertyAssignment = $tokens->findSequence(
146147
[
147148
[\T_VARIABLE, '$this'],
148149
[\T_OBJECT_OPERATOR],
149-
[\T_STRING, \substr($tokens[$propertyIndex]->getContent(), 1)],
150+
[\T_STRING, \substr($tokens[$index]->getContent(), 1)],
150151
],
151152
$classOpenBraceIndex,
152153
$classCloseBraceIndex,
@@ -156,12 +157,32 @@ private function fixParameters(
156157
}
157158

158159
$tokens->insertAt(
159-
$index + 1,
160+
$insertIndex + 1,
160161
[
161162
new Token([\T_WHITESPACE, ' ']),
162163
new Token([\T_READONLY, 'readonly']),
163164
],
164165
);
165166
}
166167
}
168+
169+
private function getInsertIndex(Tokens $tokens, int $index): ?int
170+
{
171+
$insertIndex = null;
172+
173+
$index = $tokens->getPrevMeaningfulToken($index);
174+
\assert(\is_int($index));
175+
while (!$tokens[$index]->equalsAny([',', '('])) {
176+
$index = $tokens->getPrevMeaningfulToken($index);
177+
\assert(\is_int($index));
178+
if ($tokens[$index]->isGivenKind(\T_READONLY)) {
179+
return null;
180+
}
181+
if ($insertIndex === null && $tokens[$index]->isGivenKind($this->promotedPropertyVisibilityKinds)) {
182+
$insertIndex = $index;
183+
}
184+
}
185+
186+
return $insertIndex;
187+
}
167188
}

tests/Fixer/AbstractFixerTestCase.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ abstract class AbstractFixerTestCase extends TestCase
4040
'testFix80',
4141
'testFix81',
4242
'testFix82',
43+
'testFix84',
4344
'testIsRisky',
4445
'testReversingCodeSample',
4546
'testStringIsTheSame',

tests/Fixer/ReadonlyPromotedPropertiesFixerTest.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,87 @@ class Baz { public function __construct(public int $x) {} }
173173
',
174174
];
175175
}
176+
177+
/**
178+
* @dataProvider provideFix84Cases
179+
*
180+
* @requires PHP >= 8.4
181+
*/
182+
public function testFix84(string $expected, ?string $input = null): void
183+
{
184+
$this->doTest($expected, $input);
185+
}
186+
187+
/**
188+
* @return iterable<string, array{0: string, 1?: string}>
189+
*/
190+
public static function provideFix84Cases(): iterable
191+
{
192+
yield 'asymmetric visibility with both visibilities' => [
193+
<<<'PHP'
194+
<?php
195+
final class Foo {
196+
public function __construct(
197+
public public(set) readonly int $a,
198+
public protected(set) readonly int $b,
199+
public private(set) readonly int $c,
200+
protected protected(set) readonly int $d,
201+
protected private(set) readonly int $e,
202+
private private(set) readonly int $f,
203+
) {}
204+
}
205+
PHP,
206+
<<<'PHP'
207+
<?php
208+
final class Foo {
209+
public function __construct(
210+
public public(set) int $a,
211+
public protected(set) int $b,
212+
public private(set) int $c,
213+
protected protected(set) int $d,
214+
protected private(set) int $e,
215+
private private(set) int $f,
216+
) {}
217+
}
218+
PHP,
219+
];
220+
221+
yield 'asymmetric visibility with only write visibility' => [
222+
<<<'PHP'
223+
<?php
224+
final class Foo {
225+
public function __construct(
226+
public(set) readonly int $a,
227+
protected(set) readonly int $b,
228+
private(set) readonly int $c,
229+
) {}
230+
}
231+
PHP,
232+
<<<'PHP'
233+
<?php
234+
final class Foo {
235+
public function __construct(
236+
public(set) int $a,
237+
protected(set) int $b,
238+
private(set) int $c,
239+
) {}
240+
}
241+
PHP,
242+
];
243+
244+
yield 'readonly asymmetric visibility' => [
245+
<<<'PHP'
246+
<?php
247+
final class Foo {
248+
public function __construct(
249+
readonly public public(set) int $a,
250+
public readonly protected(set) int $b,
251+
public private(set) readonly int $c,
252+
readonly private(set) int $e,
253+
private(set) readonly int $f,
254+
) {}
255+
}
256+
PHP,
257+
];
258+
}
176259
}

tests/Readme/ReadmeCommandTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
/**
1919
* @internal
2020
*
21-
* @requires PHP >= 8.2
21+
* @requires PHP >= 8.4
2222
*
2323
* @covers \PhpCsFixerCustomFixersDev\Readme\ReadmeCommand
2424
*/

0 commit comments

Comments
 (0)