Skip to content

Commit f891bb6

Browse files
authored
Introduce FileNode to handle file-level changes (#7728)
* introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector * update use resolving * make use of FileNode in imports * add fixture to keep protected method * require rector repo updated PRs * add FileNode upgrade to UPGRADING * [ci] add job to check laravel rector with latest dev-main build * add node matching support for FileWithoutNamespace * warn about deprecated type * print stmts with false * [docs] add specific example how to iterate stmts of namespaced and non-namespaced file
1 parent d6fb9bf commit f891bb6

File tree

52 files changed

+631
-303
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+631
-303
lines changed

.github/workflows/code_analysis.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050

5151
-
5252
name: 'Finalize classes'
53-
run: vendor/bin/swiss-knife finalize-classes src tests rules --dry-run
53+
run: vendor/bin/swiss-knife finalize-classes src tests rules --dry-run --skip-file="src/PhpParser/Node/FileNode.php"
5454

5555
-
5656
name: 'Check before/after test fixture on no-changes'

.github/workflows/rector_laravel_rector_dev.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ jobs:
2828
- run: git clone https://github.com/driftingly/rector-laravel.git
2929
- run: composer require rector/rector:dev-main --working-dir rector-laravel
3030
- run: cd rector-laravel && vendor/bin/phpunit
31+

UPGRADING.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,110 @@
1+
# Upgrading from Rector 2.2.14 to 2.3
2+
3+
* `FileWithoutNamespace` is deprecated, and replaced by `FileNode` that represents both namespaced and non-namespaced files and allow changes inside
4+
* `beforeTraverse()` is now marked as `@final`, use `getNodeTypes()` with `FileNode::class` instead
5+
6+
**Before**
7+
8+
```php
9+
use Rector\PhpParser\Node\FileWithoutNamespace;
10+
use Rector\Rector\AbstractRector;
11+
12+
final class SomeRector extends AbstractRector
13+
{
14+
public function getNodeTypes(): array
15+
{
16+
return [FileWithoutNamespace::class];
17+
}
18+
19+
public function beforeTraverse(array $nodes): array
20+
{
21+
// some node hacking
22+
}
23+
24+
/**
25+
* @param FileWithoutNamespace $node
26+
*/
27+
public function refactor(Node $node): ?Node
28+
{
29+
// ...
30+
}
31+
32+
}
33+
```
34+
35+
**After**
36+
37+
```php
38+
use Rector\PhpParser\Node\FileNode;
39+
use Rector\Rector\AbstractRector;
40+
41+
final class SomeRector extends AbstractRector
42+
{
43+
public function getNodeTypes(): array
44+
{
45+
return [FileNode::class];
46+
}
47+
48+
/**
49+
* @param FileNode $node
50+
*/
51+
public function refactor(Node $node): ?Node
52+
{
53+
foreach ($node->stmts as $stmt) {
54+
// check if has declare_strict already?
55+
// ...
56+
57+
// create it
58+
$declareStrictTypes = $this->createDeclareStrictTypesNode();
59+
60+
// add it
61+
$node->stmts = array_merge([$declareStrictTypes], $node->stmts);
62+
}
63+
64+
return $node;
65+
}
66+
67+
}
68+
```
69+
70+
<br>
71+
72+
The `FileNode` handles both namespaced and non-namespaced files. To handle the first stmts inside the file, you hook into 2 nodes:
73+
74+
```php
75+
use Rector\PhpParser\Node\FileNode;
76+
use Rector\Rector\AbstractRector;
77+
use PhpParser\Node\Stmt\Namespace_;
78+
79+
final class SomeRector extends AbstractRector
80+
{
81+
public function getNodeTypes(): array
82+
{
83+
return [FileNode::class, Namespace_::class];
84+
}
85+
86+
/**
87+
* @param FileNode|Namespace_ $node
88+
*/
89+
public function refactor(Node $node): ?Node
90+
{
91+
if ($node instanceof FileNode && $node->isNamespaced()) {
92+
// handled in the Namespace_ node
93+
return null;
94+
}
95+
96+
foreach ($node->stmts as $stmt) {
97+
// modify stmts in desired way here
98+
}
99+
100+
return $node;
101+
}
102+
103+
}
104+
```
105+
106+
<br>
107+
1108
# Upgrading from Rector 1.x to 2.0
2109

3110
## PHP version requirements

composer-dependency-analyser.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
// ensure use version ^3.2.0
1818
->ignoreErrorsOnPackage('composer/pcre', [ErrorType::UNUSED_DEPENDENCY])
1919

20-
->ignoreErrorsOnPath(__DIR__ . '/src/Reporting/DeprecatedRulesReporter.php', [ErrorType::UNKNOWN_CLASS])
21-
2220
->ignoreErrorsOnPaths([
2321
__DIR__ . '/stubs',
2422
__DIR__ . '/tests',

phpstan.neon

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ parameters:
1010

1111
# see https://phpstan.org/writing-php-code/phpdoc-types#global-type-aliases
1212
typeAliases:
13-
StmtsAware: \PhpParser\Node\Stmt\Block | \PhpParser\Node\Expr\Closure | \PhpParser\Node\Stmt\Case_ | \PhpParser\Node\Stmt\Catch_ | \PhpParser\Node\Stmt\ClassMethod | \PhpParser\Node\Stmt\Do_ | \PhpParser\Node\Stmt\Else_ | \PhpParser\Node\Stmt\ElseIf_ | \PhpParser\Node\Stmt\Finally_ | \PhpParser\Node\Stmt\For_ | \PhpParser\Node\Stmt\Foreach_ | \PhpParser\Node\Stmt\Function_ | \PhpParser\Node\Stmt\If_ | \PhpParser\Node\Stmt\Namespace_ | \PhpParser\Node\Stmt\TryCatch | \PhpParser\Node\Stmt\While_ | \Rector\PhpParser\Node\CustomNode\FileWithoutNamespace
13+
StmtsAware: \PhpParser\Node\Stmt\Block | \PhpParser\Node\Expr\Closure | \PhpParser\Node\Stmt\Case_ | \PhpParser\Node\Stmt\Catch_ | \PhpParser\Node\Stmt\ClassMethod | \PhpParser\Node\Stmt\Do_ | \PhpParser\Node\Stmt\Else_ | \PhpParser\Node\Stmt\ElseIf_ | \PhpParser\Node\Stmt\Finally_ | \PhpParser\Node\Stmt\For_ | \PhpParser\Node\Stmt\Foreach_ | \PhpParser\Node\Stmt\Function_ | \PhpParser\Node\Stmt\If_ | \PhpParser\Node\Stmt\Namespace_ | \PhpParser\Node\Stmt\TryCatch | \PhpParser\Node\Stmt\While_ | \Rector\PhpParser\Node\FileNode
1414

1515
# requires exact closure types
1616
checkMissingCallableSignature: true
@@ -189,8 +189,10 @@ parameters:
189189
message: '#Method Rector\\Util\\ArrayParametersMerger\:\:mergeLeftToRightWithCallable\(\) has parameter \$mergeCallback with no signature specified for callable#'
190190
path: src/Util/ArrayParametersMerger.php
191191

192-
# fixture class
193-
- '#Class "Rector\\Tests\\Issues\\ScopeNotAvailable\\Variable\\ArrayItemForeachValueRector" is missing @see annotation with test case class reference#'
192+
# fixture Rector rules
193+
-
194+
identifier: symplify.seeAnnotationToTest
195+
path: tests/Issues/
194196

195197
# classes are part of *.php.inc fixture
196198
-
@@ -286,7 +288,6 @@ parameters:
286288
- src/Configuration/RectorConfigBuilder.php
287289
- src/Reporting/DeprecatedRulesReporter.php
288290
identifier: classConstant.deprecatedInterface
289-
- '#Class Rector\\PhpParser\\Node\\CustomNode\\FileWithoutNamespace implements deprecated interface Rector\\Contract\\PhpParser\\Node\\StmtsAwareInterface#'
290291

291292
# allowed internally only
292293
-
@@ -398,12 +399,6 @@ parameters:
398399
paths:
399400
- rules/Php70/Rector/If_/IfToSpaceshipRector.php
400401

401-
# handles full file
402-
-
403-
paths:
404-
- rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php
405-
identifier: rector.noOnlyNullReturnInRefactor
406-
407402
-
408403
identifier: rector.noIntegerRefactorReturn
409404
paths:
@@ -416,22 +411,22 @@ parameters:
416411
# condition check, just to be sure
417412
- '#Method Rector\\Rector\\AbstractRector\:\:enterNode\(\) never returns 3 so it can be removed from the return type#'
418413

419-
# special case, working on a file-level
420-
-
421-
identifier: rector.noOnlyNullReturnInRefactor
422-
path: rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php
423-
424-
# handle next with FileNode
425-
-
426-
identifier: method.parentMethodFinalByPhpDoc
427-
path: rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php
428-
429414
# deprecated
430415
-
431416
identifier: public.method.unused
432417
paths:
433418
- rules/Transform/ValueObject/ClassMethodReference.php
434419
- rules/CodeQuality/ValueObject/KeyAndExpr.php
420+
-
421+
identifier: symplify.forbiddenExtendOfNonAbstractClass
422+
path: src/PhpParser/Node/FileNode.php
423+
-
424+
identifier: method.deprecatedClass
425+
path: src/PhpParser/Node/FileNode.php
426+
427+
-
428+
identifier: class.extendsDeprecatedClass
429+
path: src/PhpParser/Node/FileNode.php
435430

436431
-
437432
identifier: public.classConstant.unused
@@ -450,8 +445,22 @@ parameters:
450445
- '#Register "Rector\\Php81\\Rector\\Array_\\FirstClassCallableRector" service to "php81\.php" config set#'
451446
- '#Class "Rector\\CodingStyle\\Rector\\String_\\SymplifyQuoteEscapeRector" is missing @see annotation with test case class reference#'
452447
- '#Access to constant on deprecated class Rector\\Php81\\Rector\\Array_\\FirstClassCallableRector#'
448+
449+
# BC layer for FileWithoutNamespace node
450+
- message: '#Use @see \\Rector\\PhpParser\\Node\\FileNode instead#'
451+
- '#BC layer for FileNode and FileWithoutNamespace compat, use FileNode instead#'
452+
-
453+
path: src/PhpParser/Node/CustomNode/FileWithoutNamespace.php
454+
identifier: symplify.forbiddenExtendOfNonAbstractClass
453455
-
454456
message: '#Only abstract classes can be extended#'
455457
path: rules/Php81/Rector/Array_/FirstClassCallableRector.php
456458

459+
- '#Method Rector\\Tests\\Issues\\FileWithoutNamespaceCompat\\Rector\\SubscribedToFileWithoutNamespaceRector\:\:refactor\(\) should return Rector\\PhpParser\\Node\\FileNode but returns Rector\\PhpParser\\Node\\CustomNode\\FileWithoutNamespace#'
457460

461+
# BC layer for FileWithoutNamespace node
462+
- message: '#Use @see \\Rector\\PhpParser\\Node\\FileNode instead#'
463+
- '#Class Rector\\PhpParser\\Node\\CustomNode\\FileWithoutNamespace extends final class Rector\\PhpParser\\Node\\FileNode#'
464+
-
465+
path: src/PhpParser/Node/CustomNode/FileWithoutNamespace.php
466+
identifier: symplify.forbiddenExtendOfNonAbstractClass

rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/Fixture/keep_magic_parent_called_method.php.inc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
namespace Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Fixture;
44

55
use PhpParser\PrettyPrinter\Standard;
6+
use Rector\PhpParser\Node\FileNode;
67

78
final class KeepMagicParentCalledMethod extends Standard
89
{
910
// called from parent by $this->{'p'} method
10-
protected function pFileNode($fileNode)
11+
protected function pFileNode(FileNode $fileNode)
1112
{
1213
}
1314
}

rules/CodingStyle/Application/UseImportsAdder.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use Rector\CodingStyle\ClassNameImport\UsedImportsResolver;
1515
use Rector\NodeTypeResolver\Node\AttributeKey;
1616
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
17-
use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
17+
use Rector\PhpParser\Node\FileNode;
1818
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
1919
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
2020

@@ -33,7 +33,7 @@ public function __construct(
3333
* @param array<FullyQualifiedObjectType|AliasedObjectType> $functionUseImportTypes
3434
*/
3535
public function addImportsToStmts(
36-
FileWithoutNamespace $fileWithoutNamespace,
36+
FileNode $fileNode,
3737
array $stmts,
3838
array $useImportTypes,
3939
array $constantUseImportTypes,
@@ -45,10 +45,12 @@ public function addImportsToStmts(
4545
$existingFunctionUseImports = $usedImports->getFunctionImports();
4646

4747
$useImportTypes = $this->diffFullyQualifiedObjectTypes($useImportTypes, $existingUseImportTypes);
48+
4849
$constantUseImportTypes = $this->diffFullyQualifiedObjectTypes(
4950
$constantUseImportTypes,
5051
$existingConstantUseImports
5152
);
53+
5254
$functionUseImportTypes = $this->diffFullyQualifiedObjectTypes(
5355
$functionUseImportTypes,
5456
$existingFunctionUseImports
@@ -90,17 +92,17 @@ public function addImportsToStmts(
9092

9193
array_splice($stmts, $key + 1, 0, $nodesToAdd);
9294

93-
$fileWithoutNamespace->stmts = $stmts;
94-
$fileWithoutNamespace->stmts = array_values($fileWithoutNamespace->stmts);
95+
$fileNode->stmts = $stmts;
96+
$fileNode->stmts = array_values($fileNode->stmts);
9597

9698
return true;
9799
}
98100

99101
$this->mirrorUseComments($stmts, $newUses);
100102

101103
// make use stmts first
102-
$fileWithoutNamespace->stmts = array_merge($newUses, $this->resolveInsertNop($fileWithoutNamespace), $stmts);
103-
$fileWithoutNamespace->stmts = array_values($fileWithoutNamespace->stmts);
104+
$fileNode->stmts = array_merge($newUses, $this->resolveInsertNop($fileNode), $stmts);
105+
$fileNode->stmts = array_values($fileNode->stmts);
104106

105107
return true;
106108
}
@@ -154,7 +156,7 @@ public function addImportsToNamespace(
154156
/**
155157
* @return Nop[]
156158
*/
157-
private function resolveInsertNop(FileWithoutNamespace|Namespace_ $namespace): array
159+
private function resolveInsertNop(FileNode|Namespace_ $namespace): array
158160
{
159161
$currentStmt = $namespace->stmts[0] ?? null;
160162
if (! $currentStmt instanceof Stmt || $currentStmt instanceof Use_ || $currentStmt instanceof GroupUse) {

rules/CodingStyle/Application/UseImportsRemover.php

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

77
use PhpParser\Node\Stmt\Namespace_;
88
use PhpParser\Node\Stmt\Use_;
9-
use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
9+
use Rector\PhpParser\Node\FileNode;
1010
use Rector\Renaming\Collector\RenamedNameCollector;
1111

1212
final readonly class UseImportsRemover
@@ -19,7 +19,7 @@ public function __construct(
1919
/**
2020
* @param string[] $removedUses
2121
*/
22-
public function removeImportsFromStmts(FileWithoutNamespace|Namespace_ $node, array $removedUses): bool
22+
public function removeImportsFromStmts(FileNode|Namespace_ $node, array $removedUses): bool
2323
{
2424
$hasRemoved = false;
2525
foreach ($node->stmts as $key => $stmt) {

rules/CodingStyle/ClassNameImport/AliasUsesResolver.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use PhpParser\Node\Stmt\Namespace_;
1111
use PhpParser\Node\Stmt\Use_;
1212
use PhpParser\Node\UseItem;
13-
use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
13+
use Rector\PhpParser\Node\FileNode;
1414

1515
final readonly class AliasUsesResolver
1616
{
@@ -25,11 +25,11 @@ public function __construct(
2525
*/
2626
public function resolveFromNode(Node $node, array $stmts): array
2727
{
28-
if (! $node instanceof Namespace_ && ! $node instanceof FileWithoutNamespace) {
29-
/** @var Namespace_[]|FileWithoutNamespace[] $namespaces */
28+
if (! $node instanceof Namespace_ && ! $node instanceof FileNode) {
29+
/** @var Namespace_[]|FileNode[] $namespaces */
3030
$namespaces = array_filter(
3131
$stmts,
32-
static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileWithoutNamespace
32+
static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileNode
3333
);
3434
if (count($namespaces) !== 1) {
3535
return [];

rules/CodingStyle/ClassNameImport/ShortNameResolver.php

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use PhpParser\Node\Name;
1010
use PhpParser\Node\Stmt;
1111
use PhpParser\Node\Stmt\ClassLike;
12-
use PhpParser\Node\Stmt\Namespace_;
1312
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
1413
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
1514
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
@@ -20,7 +19,6 @@
2019
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
2120
use Rector\PhpDocParser\PhpDocParser\PhpDocNodeTraverser;
2221
use Rector\PhpParser\Node\BetterNodeFinder;
23-
use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
2422
use Rector\ValueObject\Application\File;
2523

2624
/**
@@ -65,22 +63,15 @@ public function resolveFromFile(File $file): array
6563
*/
6664
public function resolveShortClassLikeNames(File $file): array
6765
{
68-
$newStmts = $file->getNewStmts();
69-
70-
/** @var Namespace_[]|FileWithoutNamespace[] $namespaces */
71-
$namespaces = array_filter(
72-
$newStmts,
73-
static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileWithoutNamespace
74-
);
75-
if (count($namespaces) !== 1) {
76-
// only handle single namespace nodes
66+
$rootNode = $file->getUseImportsRootNode();
67+
68+
// nothing to resolve
69+
if (! $rootNode instanceof Node) {
7770
return [];
7871
}
7972

80-
$namespace = current($namespaces);
81-
8273
/** @var ClassLike[] $classLikes */
83-
$classLikes = $this->betterNodeFinder->findInstanceOf($namespace->stmts, ClassLike::class);
74+
$classLikes = $this->betterNodeFinder->findInstanceOf($rootNode->stmts, ClassLike::class);
8475

8576
$shortClassLikeNames = [];
8677
foreach ($classLikes as $classLike) {

0 commit comments

Comments
 (0)