Skip to content

Commit 8e5c168

Browse files
authored
Add NoUselessWriteVisibilityFixer (#1014)
1 parent d30d363 commit 8e5c168

File tree

4 files changed

+246
-1
lines changed

4 files changed

+246
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# CHANGELOG for PHP CS Fixer: custom fixers
22

33
## v3.25.0
4+
- Add NoUselessWriteVisibilityFixer
45
- ReadonlyPromotedPropertiesFixer - support asymmetric visibility
56

67
## v3.24.0

README.md

Lines changed: 14 additions & 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-3607-brightgreen.svg)
8+
![Tests](https://img.shields.io/badge/tests-3632-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)
@@ -399,6 +399,19 @@ Functions `strlen` and `mb_strlen` must not be compared to 0.
399399
+$isNotEmpty = $string !== '';
400400
```
401401

402+
#### NoUselessWriteVisibilityFixer
403+
There must be no useless write visibility.
404+
```diff
405+
<?php class Foo {
406+
- public public(set) $x;
407+
- public(set) $y;
408+
- protected protected(set) $z;
409+
+ public $x;
410+
+ public $y;
411+
+ protected $z;
412+
}
413+
```
414+
402415
#### NumericLiteralSeparatorFixer
403416
Numeric literals must have configured separators.
404417
DEPRECATED: use `numeric_literal_separator` instead.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
* This file is part of PHP CS Fixer: custom fixers.
5+
*
6+
* (c) 2018 Kuba Werłos
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace PhpCsFixerCustomFixers\Fixer;
13+
14+
use PhpCsFixer\FixerDefinition\CodeSample;
15+
use PhpCsFixer\FixerDefinition\FixerDefinition;
16+
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
17+
use PhpCsFixer\Tokenizer\CT;
18+
use PhpCsFixer\Tokenizer\Token;
19+
use PhpCsFixer\Tokenizer\Tokens;
20+
21+
final class NoUselessWriteVisibilityFixer extends AbstractFixer
22+
{
23+
/** @var non-empty-array<int, list<int>> */
24+
private array $predecessorKindMap;
25+
26+
public function __construct()
27+
{
28+
if (\defined('T_PUBLIC_SET')) {
29+
$this->predecessorKindMap = [
30+
\T_PUBLIC_SET => [\T_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC],
31+
\T_PROTECTED_SET => [\T_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED],
32+
\T_PRIVATE_SET => [\T_PRIVATE, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE],
33+
];
34+
}
35+
}
36+
37+
public function getDefinition(): FixerDefinitionInterface
38+
{
39+
return new FixerDefinition(
40+
'There must be no useless write visibility.',
41+
[new CodeSample(
42+
<<<'PHP'
43+
<?php class Foo {
44+
public public(set) $x;
45+
public(set) $y;
46+
protected protected(set) $z;
47+
}
48+
49+
PHP,
50+
)],
51+
);
52+
}
53+
54+
public function getPriority(): int
55+
{
56+
return 0;
57+
}
58+
59+
public function isCandidate(Tokens $tokens): bool
60+
{
61+
return \defined('T_PUBLIC_SET') && $tokens->isAnyTokenKindsFound(\array_keys($this->predecessorKindMap));
62+
}
63+
64+
public function isRisky(): bool
65+
{
66+
return false;
67+
}
68+
69+
public function fix(\SplFileInfo $file, Tokens $tokens): void
70+
{
71+
foreach ($tokens->findGivenKind(\array_keys($this->predecessorKindMap)) as $kind => $elements) {
72+
foreach (\array_keys($elements) as $index) {
73+
$this->fixVisibility($tokens, $index, $kind, $kind === \T_PUBLIC_SET);
74+
}
75+
}
76+
}
77+
78+
private function fixVisibility(Tokens $tokens, int $index, int $kind, bool $makePublicIfNone): void
79+
{
80+
$prevIndex = $tokens->getPrevMeaningfulToken($index);
81+
\assert(\is_int($prevIndex));
82+
if ($tokens[$prevIndex]->isGivenKind(\T_ABSTRACT)) {
83+
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
84+
\assert(\is_int($prevIndex));
85+
}
86+
87+
if (!$tokens[$prevIndex]->isGivenKind($this->predecessorKindMap[$kind])) {
88+
if ($makePublicIfNone) {
89+
$prevDeciderIndex = $tokens->getPrevTokenOfKind($index, ['(', ';', '{']);
90+
\assert(\is_int($prevDeciderIndex));
91+
$kind = $tokens[$prevDeciderIndex]->equals('(') ? CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC : \T_PUBLIC;
92+
$tokens[$index] = new Token([$kind, 'public']);
93+
}
94+
95+
return;
96+
}
97+
98+
$tokens->clearAt($index);
99+
100+
if ($tokens[$index + 1]->isWhitespace()) {
101+
$tokens->clearAt($index + 1);
102+
}
103+
}
104+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
* This file is part of PHP CS Fixer: custom fixers.
5+
*
6+
* (c) 2018 Kuba Werłos
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace Tests\Fixer;
13+
14+
/**
15+
* @internal
16+
*
17+
* @covers \PhpCsFixerCustomFixers\Fixer\NoUselessWriteVisibilityFixer
18+
*
19+
* @requires PHP >= 8.4
20+
*/
21+
final class NoUselessWriteVisibilityFixerTest extends AbstractFixerTestCase
22+
{
23+
public function testIsRisky(): void
24+
{
25+
self::assertRiskiness(false);
26+
}
27+
28+
/**
29+
* @dataProvider provideFixCases
30+
*/
31+
public function testFix(string $expected, ?string $input = null): void
32+
{
33+
$this->doTest($expected, $input);
34+
}
35+
36+
/**
37+
* @return iterable<string, array{0: string, 1?: string}>
38+
*/
39+
public static function provideFixCases(): iterable
40+
{
41+
yield 'class properties' => [
42+
<<<'PHP'
43+
<?php class Foo {
44+
public int $x;
45+
protected int $y;
46+
private int $z;
47+
}
48+
PHP,
49+
<<<'PHP'
50+
<?php class Foo {
51+
public public(set) int $x;
52+
protected protected(set) int $y;
53+
private private(set) int $z;
54+
}
55+
PHP,
56+
];
57+
58+
yield 'only write visibility' => [
59+
<<<'PHP'
60+
<?php class Foo {
61+
public string $a;
62+
public string $b;
63+
public function __construct(
64+
public string $x,
65+
public string $y,
66+
) {}
67+
public string $c;
68+
public string $d;
69+
}
70+
abstract class Bar {
71+
abstract public function __construct();
72+
public string $a;
73+
}
74+
PHP,
75+
<<<'PHP'
76+
<?php class Foo {
77+
public(set) string $a;
78+
public(set) string $b;
79+
public function __construct(
80+
public(set) string $x,
81+
public(set) string $y,
82+
) {}
83+
public(set) string $c;
84+
public(set) string $d;
85+
}
86+
abstract class Bar {
87+
abstract public function __construct();
88+
public(set) string $a;
89+
}
90+
PHP,
91+
];
92+
93+
yield 'promoted properties' => [
94+
<<<'PHP'
95+
<?php class Foo {
96+
public function __construct(
97+
public string $x,
98+
protected string $y,
99+
private string $z,
100+
) {}
101+
}
102+
PHP,
103+
<<<'PHP'
104+
<?php class Foo {
105+
public function __construct(
106+
public public(set) string $x,
107+
protected protected(set) string $y,
108+
private private(set) string $z,
109+
) {}
110+
}
111+
PHP,
112+
];
113+
114+
yield 'abstract property' => [
115+
<<<'PHP'
116+
<?php abstract class Foo {
117+
public abstract int $x { get; }
118+
}
119+
PHP,
120+
<<<'PHP'
121+
<?php abstract class Foo {
122+
public abstract public(set) int $x { get; }
123+
}
124+
PHP,
125+
];
126+
}
127+
}

0 commit comments

Comments
 (0)