Skip to content

Commit e2df0d8

Browse files
committed
feat(Rector): Add PrivateToProtectedVisibilityForTraitRector
- Introduce a new rector to change private method visibility to protected for traits. - Enhance method visibility handling within trait reflections. - Improve code maintainability and adherence to best practices for trait usage.
1 parent 7d80f74 commit e2df0d8

File tree

11 files changed

+230
-27
lines changed

11 files changed

+230
-27
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@
378378
"rector:process-clear-cache": "@rector:process --clear-cache",
379379
"rector:process-clear-cache-dry-run": "@rector:process-clear-cache --dry-run",
380380
"rector:process-dry-run": "@rector:process --dry-run",
381-
"rector:process-only": "@rector:process-clear-cache tests.php --only=Guanguans\\RectorRules\\Rector\\Name\\RenameToConventionalCaseNameRector",
381+
"rector:process-only": "@rector:process-clear-cache tests.php --only=Guanguans\\RectorRules\\Rector\\ClassMethod\\PrivateToProtectedVisibilityForTraitRector",
382382
"rector:process-only-dry-run": "@rector:process-only --dry-run",
383383
"rule-doc-generator": [
384384
"@putenv:php",

config/set/common.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use Guanguans\RectorRules\Rector\Array_\SimplifyListIndexRector;
1717
use Guanguans\RectorRules\Rector\Array_\SortListItemOfSameScalarTypeRector;
18+
use Guanguans\RectorRules\Rector\ClassMethod\PrivateToProtectedVisibilityForTraitRector;
1819
use Guanguans\RectorRules\Rector\File\SortFileFirstStmtDocblockRector;
1920
use Guanguans\RectorRules\Rector\File\SortFileFunctionStmtRector;
2021
use Guanguans\RectorRules\Rector\FunctionLike\RenameGarbageParamNameRector;
@@ -25,6 +26,7 @@
2526
return static function (RectorConfig $rectorConfig): void {
2627
$rectorConfig->import(__DIR__.'/../config.php');
2728
$rectorConfig->rules([
29+
PrivateToProtectedVisibilityForTraitRector::class,
2830
RemoveNamespaceRector::class,
2931
RenameGarbageParamNameRector::class,
3032
SimplifyListIndexRector::class,

docs/rules-overview.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 12 Rules Overview
1+
# 13 Rules Overview
22

33
<br>
44

@@ -8,6 +8,8 @@
88

99
- [Class](#class) (2)
1010

11+
- [ClassMethod](#classmethod) (1)
12+
1113
- [File](#file) (3)
1214

1315
- [FuncCall](#funccall) (1)
@@ -177,6 +179,27 @@ Update rector method node param docblock from node types
177179

178180
<br>
179181

182+
## ClassMethod
183+
184+
### PrivateToProtectedVisibilityForTraitRector
185+
186+
Private to protected visibility for trait
187+
188+
- class: [`Guanguans\RectorRules\Rector\ClassMethod\PrivateToProtectedVisibilityForTraitRector`](../src/Rector/ClassMethod/PrivateToProtectedVisibilityForTraitRector.php)
189+
190+
```diff
191+
/** @noinspection ALL */
192+
trait Foo
193+
{
194+
- private function run(): void
195+
+ protected function run(): void
196+
{
197+
}
198+
}
199+
```
200+
201+
<br>
202+
180203
## File
181204

182205
### AddNoinspectionDocblockToFileFirstStmtRector

rector.php

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@
5050
use Rector\Transform\Rector\String_\StringToClassConstantRector;
5151
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\SafeDeclareStrictTypesRector;
5252
use Rector\ValueObject\PhpVersion;
53-
use Rector\ValueObject\Visibility;
54-
use Rector\Visibility\Rector\ClassMethod\ChangeMethodVisibilityRector;
55-
use Rector\Visibility\ValueObject\ChangeMethodVisibility;
56-
use function Guanguans\RectorRules\Support\classes;
5753

5854
return RectorConfig::configure()
5955
->withPaths([
@@ -153,24 +149,6 @@
153149
'phpstan-ignore-next-line',
154150
'psalm-suppress',
155151
])
156-
->withConfiguredRule(
157-
ChangeMethodVisibilityRector::class,
158-
classes(static fn (string $class): bool => str_starts_with($class, 'Guanguans\RectorRules'))
159-
->filter(static fn (ReflectionClass $reflectionClass): bool => $reflectionClass->isTrait())
160-
->map(
161-
static fn (ReflectionClass $reflectionClass): array => collect($reflectionClass->getMethods(ReflectionMethod::IS_PRIVATE))
162-
->reject(static fn (ReflectionMethod $reflectionMethod): bool => $reflectionMethod->isFinal() || $reflectionMethod->isInternal())
163-
->map(static fn (ReflectionMethod $reflectionMethod): ChangeMethodVisibility => new ChangeMethodVisibility(
164-
$reflectionClass->getName(),
165-
$reflectionMethod->getName(),
166-
Visibility::PROTECTED
167-
))
168-
->all()
169-
)
170-
->flatten()
171-
// ->dd()
172-
->all(),
173-
)
174152
->withSkip([
175153
DowngradeArrayFirstLastRector::class,
176154
DowngradeArrayIsListRector::class,
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
/** @noinspection PhpMultipleClassDeclarationsInspection */
4+
5+
declare(strict_types=1);
6+
7+
/**
8+
* Copyright (c) 2025-2026 guanguans<ityaozm@gmail.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*
13+
* @see https://github.com/guanguans/rector-rules
14+
*/
15+
16+
namespace Guanguans\RectorRules\Rector\ClassMethod;
17+
18+
use Guanguans\RectorRules\Rector\AbstractRector;
19+
use PhpParser\Node;
20+
use PhpParser\Node\Stmt\ClassMethod;
21+
use PHPStan\Reflection\ClassReflection;
22+
use Rector\PHPStan\ScopeFetcher;
23+
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
24+
use Rector\ValueObject\Visibility;
25+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
26+
27+
/**
28+
* @see \Guanguans\RectorRulesTests\Rector\ClassMethod\PrivateToProtectedVisibilityForTraitRector\PrivateToProtectedVisibilityForTraitRectorTest
29+
*/
30+
final class PrivateToProtectedVisibilityForTraitRector extends AbstractRector
31+
{
32+
private VisibilityManipulator $visibilityManipulator;
33+
34+
public function __construct(VisibilityManipulator $visibilityManipulator)
35+
{
36+
$this->visibilityManipulator = $visibilityManipulator;
37+
}
38+
39+
public function getNodeTypes(): array
40+
{
41+
return [
42+
ClassMethod::class,
43+
];
44+
}
45+
46+
/**
47+
* @see \Rector\Visibility\Rector\ClassMethod\ChangeMethodVisibilityRector
48+
*
49+
* @param \PhpParser\Node\Stmt\ClassMethod $node
50+
*
51+
* @throws \PHPStan\Reflection\MissingMethodFromReflectionException
52+
* @throws \Rector\Exception\ShouldNotHappenException
53+
*/
54+
public function refactor(Node $node): ?Node
55+
{
56+
// $classReflection = ScopeFetcher::fetch($node)->getTraitReflection();
57+
$classReflection = ScopeFetcher::fetch($node)->getClassReflection();
58+
59+
if (
60+
!$classReflection instanceof ClassReflection
61+
|| !$classReflection->isTrait()
62+
// || !$classReflection->getMethod($this->getName($node), ScopeFetcher::fetch($node))->isPrivate()
63+
|| !$classReflection->getNativeMethod($this->getName($node))->isPrivate()
64+
) {
65+
return null;
66+
}
67+
68+
$this->visibilityManipulator->changeNodeVisibility($node, Visibility::PROTECTED);
69+
70+
return $node;
71+
}
72+
73+
/**
74+
* @return list<\Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample>
75+
*/
76+
protected function codeSamples(): array
77+
{
78+
return [
79+
new CodeSample(
80+
<<<'PHP'
81+
/** @noinspection ALL */
82+
trait Foo
83+
{
84+
private function run(): void
85+
{
86+
}
87+
}
88+
PHP,
89+
<<<'PHP'
90+
/** @noinspection ALL */
91+
trait Foo
92+
{
93+
protected function run(): void
94+
{
95+
}
96+
}
97+
PHP,
98+
),
99+
];
100+
}
101+
}

src/Rector/FunctionLike/RenameGarbageParamNameRector.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,13 @@ private function hasPrototypeMethod(FunctionLike $functionLikeNode): bool
255255
$classReflection = ScopeFetcher::fetch($functionLikeNode)->getClassReflection();
256256

257257
if (!$classReflection instanceof ClassReflection) {
258-
return false;
258+
return false; // @codeCoverageIgnore
259259
}
260260

261261
try {
262262
return $classReflection->getNativeReflection()->getMethod($this->getName($functionLikeNode))->hasPrototype();
263-
} catch (\ReflectionException $reflectionException) {
264-
return false;
263+
} catch (\ReflectionException $reflectionException) { // @codeCoverageIgnore
264+
return false; // @codeCoverageIgnore
265265
}
266266
}
267267

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/** @noinspection ALL */
4+
trait Foo
5+
{
6+
private function run(): void
7+
{
8+
}
9+
}
10+
?>
11+
-----
12+
<?php
13+
14+
/** @noinspection ALL */
15+
trait Foo
16+
{
17+
protected function run(): void
18+
{
19+
}
20+
}
21+
?>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
/** @noinspection ALL */
4+
trait Foo
5+
{
6+
protected function run(): void
7+
{
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
/** @noinspection ALL */
4+
class Foo
5+
{
6+
private function run(): void
7+
{
8+
}
9+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/** @noinspection AnonymousFunctionStaticInspection */
4+
/** @noinspection NullPointerExceptionInspection */
5+
/** @noinspection PhpPossiblePolymorphicInvocationInspection */
6+
/** @noinspection PhpUndefinedClassInspection */
7+
/** @noinspection PhpUnhandledExceptionInspection */
8+
/** @noinspection PhpVoidFunctionResultUsedInspection */
9+
/** @noinspection StaticClosureCanBeUsedInspection */
10+
declare(strict_types=1);
11+
12+
/**
13+
* Copyright (c) 2025-2026 guanguans<ityaozm@gmail.com>
14+
*
15+
* For the full copyright and license information, please view
16+
* the LICENSE file that was distributed with this source code.
17+
*
18+
* @see https://github.com/guanguans/rector-rules
19+
*/
20+
21+
namespace Guanguans\RectorRulesTests\Rector\ClassMethod\PrivateToProtectedVisibilityForTraitRector;
22+
23+
use Guanguans\RectorRulesTests\Rector\AbstractRectorTestCase;
24+
25+
/**
26+
* @covers \Guanguans\RectorRules\Rector\ClassMethod\PrivateToProtectedVisibilityForTraitRector
27+
*/
28+
final class PrivateToProtectedVisibilityForTraitRectorTest extends AbstractRectorTestCase
29+
{
30+
protected static function directory(): string
31+
{
32+
return __DIR__;
33+
}
34+
}

0 commit comments

Comments
 (0)