diff --git a/.github/workflows/rector_laravel_rector_dev.yaml b/.github/workflows/rector_laravel_rector_dev.yaml
index ad80c419076..2a4a4181fa9 100644
--- a/.github/workflows/rector_laravel_rector_dev.yaml
+++ b/.github/workflows/rector_laravel_rector_dev.yaml
@@ -28,3 +28,4 @@ jobs:
- run: git clone https://github.com/driftingly/rector-laravel.git
- run: composer require rector/rector:dev-main --working-dir rector-laravel
- run: cd rector-laravel && vendor/bin/phpunit
+
diff --git a/UPGRADING.md b/UPGRADING.md
index 70ed3fd5e3e..21a791c0b00 100644
--- a/UPGRADING.md
+++ b/UPGRADING.md
@@ -1,3 +1,74 @@
+# Upgrading from Rector 2.2.11 to 2.2.12
+
+* `FileWithoutNamespace` is deprecated, and replaced by `FileNode` that represents both namespaced and non-namespaced files and allow changes inside
+* `beforeTraverse()` is now soft marked as `@final`, use `getNodeTypes()` with `FileNode::class` instead
+
+**Before**
+
+```php
+use Rector\PhpParser\Node\FileWithoutNamespace;
+use Rector\Rector\AbstractRector;
+
+final class SomeRector extends AbstractRector
+{
+ public function getNodeTypes(): array
+ {
+ return [FileWithoutNamespace::class];
+ }
+
+ public function beforeTraverse(array $nodes): array
+ {
+ // some node hacking
+ }
+
+ /**
+ * @param FileWithoutNamespace $node
+ */
+ public function refactor(Node $node): ?Node
+ {
+ // ...
+ }
+
+}
+```
+
+**After**
+
+```php
+use Rector\PhpParser\Node\FileNode;
+use Rector\Rector\AbstractRector;
+
+final class SomeRector extends AbstractRector
+{
+ public function getNodeTypes(): array
+ {
+ return [FileNode::class];
+ }
+
+ /**
+ * @param FileNode $node
+ */
+ public function refactor(Node $node): ?Node
+ {
+ foreach ($node->stmts as $stmt) {
+ // check if has declare_strict already?
+ // ...
+
+ // create it
+ $declareStrictTypes = $this->createDeclareStrictTypesNode();
+
+ // add it
+ $node->stmts = array_merge([$declareStrictTypes], $node->stmts);
+ }
+
+ return $node;
+ }
+
+}
+```
+
+
+
# Upgrading from Rector 1.x to 2.0
## PHP version requirements
diff --git a/composer.json b/composer.json
index 8fca0284522..454e7d126cf 100644
--- a/composer.json
+++ b/composer.json
@@ -28,9 +28,9 @@
"react/promise": "^3.3",
"react/socket": "^1.17",
"rector/extension-installer": "^0.11.2",
- "rector/rector-doctrine": "dev-main",
- "rector/rector-downgrade-php": "dev-main",
- "rector/rector-phpunit": "dev-main",
+ "rector/rector-doctrine": "dev-tv-file-node",
+ "rector/rector-downgrade-php": "dev-tv-file-node",
+ "rector/rector-phpunit": "dev-tv-file-node",
"rector/rector-symfony": "dev-main",
"sebastian/diff": "^6.0",
"symfony/console": "^6.4.24",
diff --git a/phpstan.neon b/phpstan.neon
index c718ab070bf..5f28e1582eb 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -10,7 +10,7 @@ parameters:
# see https://phpstan.org/writing-php-code/phpdoc-types#global-type-aliases
typeAliases:
- 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
+ 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
# requires exact closure types
checkMissingCallableSignature: true
@@ -284,7 +284,6 @@ parameters:
- src/Configuration/RectorConfigBuilder.php
- src/Reporting/DeprecatedRulesReporter.php
identifier: classConstant.deprecatedInterface
- - '#Class Rector\\PhpParser\\Node\\CustomNode\\FileWithoutNamespace implements deprecated interface Rector\\Contract\\PhpParser\\Node\\StmtsAwareInterface#'
# allowed internally only
-
@@ -391,12 +390,6 @@ parameters:
paths:
- rules/Php70/Rector/If_/IfToSpaceshipRector.php
- # handles full file
- -
- paths:
- - rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php
- identifier: rector.noOnlyNullReturnInRefactor
-
-
identifier: rector.noIntegerRefactorReturn
paths:
@@ -409,16 +402,6 @@ parameters:
# condition check, just to be sure
- '#Method Rector\\Rector\\AbstractRector\:\:enterNode\(\) never returns 3 so it can be removed from the return type#'
- # special case, working on a file-level
- -
- identifier: rector.noOnlyNullReturnInRefactor
- path: rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php
-
- # handle next with FileNode
- -
- identifier: method.parentMethodFinalByPhpDoc
- path: rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php
-
# deprecated
-
identifier: public.method.unused
@@ -432,4 +415,3 @@ parameters:
- rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php
- rules/Php55/Rector/String_/StringClassNameToClassConstantRector.php
- rules/Php81/Enum/AttributeName.php
-
diff --git a/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/Fixture/keep_magic_parent_called_method.php.inc b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/Fixture/keep_magic_parent_called_method.php.inc
index 07c14c62fa7..03414a5a4cf 100644
--- a/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/Fixture/keep_magic_parent_called_method.php.inc
+++ b/rules-tests/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector/Fixture/keep_magic_parent_called_method.php.inc
@@ -3,11 +3,12 @@
namespace Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Fixture;
use PhpParser\PrettyPrinter\Standard;
+use Rector\PhpParser\Node\FileNode;
final class KeepMagicParentCalledMethod extends Standard
{
// called from parent by $this->{'p'} method
- protected function pFileNode($fileNode)
+ protected function pFileNode(FileNode $fileNode)
{
}
}
diff --git a/rules/CodingStyle/Application/UseImportsAdder.php b/rules/CodingStyle/Application/UseImportsAdder.php
index 7396ac4f327..1e1cc7c6529 100644
--- a/rules/CodingStyle/Application/UseImportsAdder.php
+++ b/rules/CodingStyle/Application/UseImportsAdder.php
@@ -14,7 +14,7 @@
use Rector\CodingStyle\ClassNameImport\UsedImportsResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
@@ -33,7 +33,7 @@ public function __construct(
* @param array $functionUseImportTypes
*/
public function addImportsToStmts(
- FileWithoutNamespace $fileWithoutNamespace,
+ FileNode $fileNode,
array $stmts,
array $useImportTypes,
array $constantUseImportTypes,
@@ -45,10 +45,12 @@ public function addImportsToStmts(
$existingFunctionUseImports = $usedImports->getFunctionImports();
$useImportTypes = $this->diffFullyQualifiedObjectTypes($useImportTypes, $existingUseImportTypes);
+
$constantUseImportTypes = $this->diffFullyQualifiedObjectTypes(
$constantUseImportTypes,
$existingConstantUseImports
);
+
$functionUseImportTypes = $this->diffFullyQualifiedObjectTypes(
$functionUseImportTypes,
$existingFunctionUseImports
@@ -90,8 +92,8 @@ public function addImportsToStmts(
array_splice($stmts, $key + 1, 0, $nodesToAdd);
- $fileWithoutNamespace->stmts = $stmts;
- $fileWithoutNamespace->stmts = array_values($fileWithoutNamespace->stmts);
+ $fileNode->stmts = $stmts;
+ $fileNode->stmts = array_values($fileNode->stmts);
return true;
}
@@ -99,8 +101,8 @@ public function addImportsToStmts(
$this->mirrorUseComments($stmts, $newUses);
// make use stmts first
- $fileWithoutNamespace->stmts = array_merge($newUses, $this->resolveInsertNop($fileWithoutNamespace), $stmts);
- $fileWithoutNamespace->stmts = array_values($fileWithoutNamespace->stmts);
+ $fileNode->stmts = array_merge($newUses, $this->resolveInsertNop($fileNode), $stmts);
+ $fileNode->stmts = array_values($fileNode->stmts);
return true;
}
@@ -154,7 +156,7 @@ public function addImportsToNamespace(
/**
* @return Nop[]
*/
- private function resolveInsertNop(FileWithoutNamespace|Namespace_ $namespace): array
+ private function resolveInsertNop(FileNode|Namespace_ $namespace): array
{
$currentStmt = $namespace->stmts[0] ?? null;
if (! $currentStmt instanceof Stmt || $currentStmt instanceof Use_ || $currentStmt instanceof GroupUse) {
diff --git a/rules/CodingStyle/Application/UseImportsRemover.php b/rules/CodingStyle/Application/UseImportsRemover.php
index d20c6bf1eb4..904cf358e3a 100644
--- a/rules/CodingStyle/Application/UseImportsRemover.php
+++ b/rules/CodingStyle/Application/UseImportsRemover.php
@@ -6,7 +6,7 @@
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\Renaming\Collector\RenamedNameCollector;
final readonly class UseImportsRemover
@@ -19,7 +19,7 @@ public function __construct(
/**
* @param string[] $removedUses
*/
- public function removeImportsFromStmts(FileWithoutNamespace|Namespace_ $node, array $removedUses): bool
+ public function removeImportsFromStmts(FileNode|Namespace_ $node, array $removedUses): bool
{
$hasRemoved = false;
foreach ($node->stmts as $key => $stmt) {
diff --git a/rules/CodingStyle/ClassNameImport/AliasUsesResolver.php b/rules/CodingStyle/ClassNameImport/AliasUsesResolver.php
index 65f5e26ae47..2419c381f58 100644
--- a/rules/CodingStyle/ClassNameImport/AliasUsesResolver.php
+++ b/rules/CodingStyle/ClassNameImport/AliasUsesResolver.php
@@ -10,7 +10,7 @@
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\UseItem;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
final readonly class AliasUsesResolver
{
@@ -25,11 +25,11 @@ public function __construct(
*/
public function resolveFromNode(Node $node, array $stmts): array
{
- if (! $node instanceof Namespace_ && ! $node instanceof FileWithoutNamespace) {
- /** @var Namespace_[]|FileWithoutNamespace[] $namespaces */
+ if (! $node instanceof Namespace_ && ! $node instanceof FileNode) {
+ /** @var Namespace_[]|FileNode[] $namespaces */
$namespaces = array_filter(
$stmts,
- static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileWithoutNamespace
+ static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileNode
);
if (count($namespaces) !== 1) {
return [];
diff --git a/rules/CodingStyle/ClassNameImport/ShortNameResolver.php b/rules/CodingStyle/ClassNameImport/ShortNameResolver.php
index 2829a5a0c78..7b53fab2602 100644
--- a/rules/CodingStyle/ClassNameImport/ShortNameResolver.php
+++ b/rules/CodingStyle/ClassNameImport/ShortNameResolver.php
@@ -9,7 +9,6 @@
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\ClassLike;
-use PhpParser\Node\Stmt\Namespace_;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
@@ -20,7 +19,6 @@
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
use Rector\PhpDocParser\PhpDocParser\PhpDocNodeTraverser;
use Rector\PhpParser\Node\BetterNodeFinder;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\ValueObject\Application\File;
/**
@@ -65,22 +63,15 @@ public function resolveFromFile(File $file): array
*/
public function resolveShortClassLikeNames(File $file): array
{
- $newStmts = $file->getNewStmts();
-
- /** @var Namespace_[]|FileWithoutNamespace[] $namespaces */
- $namespaces = array_filter(
- $newStmts,
- static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileWithoutNamespace
- );
- if (count($namespaces) !== 1) {
- // only handle single namespace nodes
+ $rootNode = $file->getUseImportsRootNode();
+
+ // nothing to resolve
+ if (! $rootNode instanceof Node) {
return [];
}
- $namespace = current($namespaces);
-
/** @var ClassLike[] $classLikes */
- $classLikes = $this->betterNodeFinder->findInstanceOf($namespace->stmts, ClassLike::class);
+ $classLikes = $this->betterNodeFinder->findInstanceOf($rootNode->stmts, ClassLike::class);
$shortClassLikeNames = [];
foreach ($classLikes as $classLike) {
diff --git a/rules/CodingStyle/ClassNameImport/UseImportsTraverser.php b/rules/CodingStyle/ClassNameImport/UseImportsTraverser.php
index 679d8977192..0c12c361003 100644
--- a/rules/CodingStyle/ClassNameImport/UseImportsTraverser.php
+++ b/rules/CodingStyle/ClassNameImport/UseImportsTraverser.php
@@ -10,7 +10,7 @@
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\UseItem;
use Rector\NodeNameResolver\NodeNameResolver;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
final readonly class UseImportsTraverser
{
@@ -26,7 +26,7 @@ public function __construct(
public function traverserStmts(array $stmts, callable $callable): void
{
foreach ($stmts as $stmt) {
- if ($stmt instanceof Namespace_ || $stmt instanceof FileWithoutNamespace) {
+ if ($stmt instanceof Namespace_ || $stmt instanceof FileNode) {
$this->traverserStmts($stmt->stmts, $callable);
continue;
}
diff --git a/rules/CodingStyle/Node/NameImporter.php b/rules/CodingStyle/Node/NameImporter.php
index ed8848c4bcf..404bec35ef3 100644
--- a/rules/CodingStyle/Node/NameImporter.php
+++ b/rules/CodingStyle/Node/NameImporter.php
@@ -32,6 +32,7 @@ public function __construct(
*/
public function importName(FullyQualified $fullyQualified, File $file, array $currentUses): ?Name
{
+
if ($this->classNameImportSkipper->shouldSkipName($fullyQualified, $currentUses)) {
return null;
}
diff --git a/rules/CodingStyle/Rector/Catch_/CatchExceptionNameMatchingTypeRector.php b/rules/CodingStyle/Rector/Catch_/CatchExceptionNameMatchingTypeRector.php
index 2eeb231a608..3048f8cba55 100644
--- a/rules/CodingStyle/Rector/Catch_/CatchExceptionNameMatchingTypeRector.php
+++ b/rules/CodingStyle/Rector/Catch_/CatchExceptionNameMatchingTypeRector.php
@@ -21,7 +21,7 @@
use PHPStan\Type\ObjectType;
use Rector\Naming\Naming\PropertyNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -73,17 +73,11 @@ public function getRuleDefinition(): RuleDefinition
*/
public function getNodeTypes(): array
{
- return [
- ClassMethod::class,
- Function_::class,
- Closure::class,
- FileWithoutNamespace::class,
- Namespace_::class,
- ];
+ return [ClassMethod::class, Function_::class, Closure::class, FileNode::class, Namespace_::class];
}
/**
- * @param ClassMethod|Function_|Closure|FileWithoutNamespace|Namespace_ $node
+ * @param ClassMethod|Function_|Closure|FileNode|Namespace_ $node
*/
public function refactor(Node $node): ?Node
{
@@ -91,6 +85,11 @@ public function refactor(Node $node): ?Node
return null;
}
+ if ($node instanceof FileNode && $node->isNamespaced()) {
+ // handled in Namespace_ node
+ return null;
+ }
+
$hasChanged = false;
foreach ($node->stmts as $key => $stmt) {
diff --git a/rules/CodingStyle/Rector/Stmt/RemoveUselessAliasInUseStatementRector.php b/rules/CodingStyle/Rector/Stmt/RemoveUselessAliasInUseStatementRector.php
index c81af62584a..fbd2df030d4 100644
--- a/rules/CodingStyle/Rector/Stmt/RemoveUselessAliasInUseStatementRector.php
+++ b/rules/CodingStyle/Rector/Stmt/RemoveUselessAliasInUseStatementRector.php
@@ -9,7 +9,7 @@
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -42,15 +42,21 @@ public function getRuleDefinition(): RuleDefinition
*/
public function getNodeTypes(): array
{
- return [FileWithoutNamespace::class, Namespace_::class];
+ return [FileNode::class, Namespace_::class];
}
/**
- * @param FileWithoutNamespace|Namespace_ $node
+ * @param Namespace_|FileNode $node
*/
- public function refactor(Node $node): null|FileWithoutNamespace|Namespace_
+ public function refactor(Node $node): null|FileNode|Namespace_
{
+ if ($node instanceof FileNode && $node->isNamespaced()) {
+ // handle in Namespace_ node
+ return null;
+ }
+
$hasChanged = false;
+
foreach ($node->stmts as $stmt) {
if (! $stmt instanceof Use_) {
continue;
diff --git a/rules/CodingStyle/Rector/Use_/SeparateMultiUseImportsRector.php b/rules/CodingStyle/Rector/Use_/SeparateMultiUseImportsRector.php
index 8e381f1833a..06b5d7d3cac 100644
--- a/rules/CodingStyle/Rector/Use_/SeparateMultiUseImportsRector.php
+++ b/rules/CodingStyle/Rector/Use_/SeparateMultiUseImportsRector.php
@@ -11,7 +11,7 @@
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\Node\Stmt\TraitUseAdaptation\Alias;
use PhpParser\Node\Stmt\Use_;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -56,14 +56,19 @@ class SomeClass
*/
public function getNodeTypes(): array
{
- return [FileWithoutNamespace::class, Namespace_::class, Class_::class];
+ return [FileNode::class, Namespace_::class, Class_::class];
}
/**
- * @param FileWithoutNamespace|Namespace_|Class_ $node
+ * @param FileNode|Namespace_|Class_ $node
*/
- public function refactor(Node $node): FileWithoutNamespace|Namespace_|Class_|null
+ public function refactor(Node $node): FileNode|Namespace_|Class_|null
{
+ if ($node instanceof FileNode && $node->isNamespaced()) {
+ // handled in Namespace_
+ return null;
+ }
+
$hasChanged = false;
foreach ($node->stmts as $key => $stmt) {
if ($stmt instanceof Use_) {
diff --git a/rules/DeadCode/Rector/Stmt/RemoveUnreachableStatementRector.php b/rules/DeadCode/Rector/Stmt/RemoveUnreachableStatementRector.php
index 54d78d29d42..05d0e944390 100644
--- a/rules/DeadCode/Rector/Stmt/RemoveUnreachableStatementRector.php
+++ b/rules/DeadCode/Rector/Stmt/RemoveUnreachableStatementRector.php
@@ -8,6 +8,7 @@
use PhpParser\Node\Stmt;
use Rector\NodeAnalyzer\TerminatedNodeAnalyzer;
use Rector\PhpParser\Enum\NodeGroup;
+use Rector\PhpParser\Node\FileNode;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -64,6 +65,11 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
+ if ($node instanceof FileNode && $node->isNamespaced()) {
+ // handled in Namespace_ node
+ return null;
+ }
+
if ($node->stmts === null) {
return null;
}
diff --git a/rules/Naming/Naming/UseImportsResolver.php b/rules/Naming/Naming/UseImportsResolver.php
index 001c22665af..f4fcb24ea7d 100644
--- a/rules/Naming/Naming/UseImportsResolver.php
+++ b/rules/Naming/Naming/UseImportsResolver.php
@@ -4,13 +4,10 @@
namespace Rector\Naming\Naming;
-use PhpParser\Node;
-use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\GroupUse;
-use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use Rector\Application\Provider\CurrentFileProvider;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\ValueObject\Application\File;
final readonly class UseImportsResolver
@@ -25,15 +22,17 @@ public function __construct(
*/
public function resolve(): array
{
- $namespace = $this->resolveNamespace();
- if (! $namespace instanceof Node) {
+ $file = $this->currentFileProvider->getFile();
+ if (! $file instanceof File) {
+ return [];
+ }
+
+ $rootNode = $file->getFileNode();
+ if (! $rootNode instanceof FileNode) {
return [];
}
- return array_filter(
- $namespace->stmts,
- static fn (Stmt $stmt): bool => $stmt instanceof Use_ || $stmt instanceof GroupUse
- );
+ return $rootNode->getUsesAndGroupUses();
}
/**
@@ -42,12 +41,18 @@ public function resolve(): array
*/
public function resolveBareUses(): array
{
- $namespace = $this->resolveNamespace();
- if (! $namespace instanceof Node) {
+ $file = $this->currentFileProvider->getFile();
+
+ if (! $file instanceof File) {
+ return [];
+ }
+
+ $fileNode = $file->getFileNode();
+ if (! $fileNode instanceof FileNode) {
return [];
}
- return array_filter($namespace->stmts, static fn (Stmt $stmt): bool => $stmt instanceof Use_);
+ return $fileNode->getUses();
}
public function resolvePrefix(Use_|GroupUse $use): string
@@ -56,32 +61,4 @@ public function resolvePrefix(Use_|GroupUse $use): string
? $use->prefix . '\\'
: '';
}
-
- private function resolveNamespace(): Namespace_|FileWithoutNamespace|null
- {
- /** @var File|null $file */
- $file = $this->currentFileProvider->getFile();
- if (! $file instanceof File) {
- return null;
- }
-
- $newStmts = $file->getNewStmts();
-
- if ($newStmts === []) {
- return null;
- }
-
- /** @var Namespace_[]|FileWithoutNamespace[] $namespaces */
- $namespaces = array_filter(
- $newStmts,
- static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileWithoutNamespace
- );
-
- // multiple namespaces is not supported
- if (count($namespaces) !== 1) {
- return null;
- }
-
- return current($namespaces);
- }
}
diff --git a/rules/Php71/Rector/Assign/AssignArrayToStringRector.php b/rules/Php71/Rector/Assign/AssignArrayToStringRector.php
index 532c30c0e55..7e69acbbb2d 100644
--- a/rules/Php71/Rector/Assign/AssignArrayToStringRector.php
+++ b/rules/Php71/Rector/Assign/AssignArrayToStringRector.php
@@ -19,7 +19,7 @@
use PhpParser\Node\Stmt\Property;
use PhpParser\NodeVisitor;
use PHPStan\Type\UnionType;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\PhpParser\NodeFinder\PropertyFetchFinder;
use Rector\Rector\AbstractRector;
use Rector\ValueObject\PhpVersionFeature;
@@ -67,7 +67,7 @@ public function getNodeTypes(): array
{
return [
Namespace_::class,
- FileWithoutNamespace::class,
+ FileNode::class,
Class_::class,
ClassMethod::class,
Function_::class,
@@ -76,7 +76,7 @@ public function getNodeTypes(): array
}
/**
- * @param Namespace_|FileWithoutNamespace|Class_|ClassMethod|Function_|Closure $node
+ * @param Namespace_|FileNode|Class_|ClassMethod|Function_|Closure $node
*/
public function refactor(Node $node): ?Node
{
@@ -88,6 +88,11 @@ public function refactor(Node $node): ?Node
return null;
}
+ if ($node instanceof FileNode && $node->isNamespaced()) {
+ // handled in Namespace_
+ return null;
+ }
+
$hasChanged = false;
$this->traverseNodesWithCallable(
$node->stmts,
@@ -171,7 +176,7 @@ private function hasPropertyDefaultEmptyString(Property $property): bool
*/
private function findSameNamedVariableAssigns(
Variable $variable,
- Namespace_|FileWithoutNamespace|ClassMethod|Function_|Closure $node
+ Namespace_|FileNode|ClassMethod|Function_|Closure $node
): array {
if ($node->stmts === null) {
return [];
@@ -234,7 +239,7 @@ private function isReAssignedAsArray(Assign $assign, string $variableName, Varia
private function refactorAssign(
Assign $assign,
- Namespace_|FileWithoutNamespace|ClassMethod|Function_|Closure $node
+ Namespace_|FileNode|ClassMethod|Function_|Closure $node
): ?Assign {
if (! $assign->var instanceof Variable) {
return null;
diff --git a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php
index 2bd4a13b4b5..4b8061775e6 100644
--- a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php
+++ b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php
@@ -85,6 +85,7 @@ public function refactor(Node $node): ?Node
}
$scope = ScopeFetcher::fetch($node);
+
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return null;
diff --git a/rules/TypeDeclaration/NodeAnalyzer/DeclareStrictTypeFinder.php b/rules/TypeDeclaration/NodeAnalyzer/DeclareStrictTypeFinder.php
index 281d70dafd2..2d03733e272 100644
--- a/rules/TypeDeclaration/NodeAnalyzer/DeclareStrictTypeFinder.php
+++ b/rules/TypeDeclaration/NodeAnalyzer/DeclareStrictTypeFinder.php
@@ -4,22 +4,24 @@
namespace Rector\TypeDeclaration\NodeAnalyzer;
-use PhpParser\Node;
use PhpParser\Node\Stmt\Declare_;
+use Rector\PhpParser\Node\FileNode;
final readonly class DeclareStrictTypeFinder
{
- public function hasDeclareStrictTypes(Node $node): bool
+ public function hasDeclareStrictTypes(FileNode $fileNode): bool
{
- // when first node is Declare_, verify if there is strict_types definition already,
- // as multiple declare is allowed, with declare(strict_types=1) only allowed on very first node
- if (! $node instanceof Declare_) {
- return false;
- }
+ // when first fileNode is Declare_, verify if there is strict_types definition already,
+ // as multiple declare is allowed, with declare(strict_types=1) only allowed on very first fileNode
+ foreach ($fileNode->stmts as $stmt) {
+ if (! $stmt instanceof Declare_) {
+ continue;
+ }
- foreach ($node->declares as $declare) {
- if ($declare->key->toString() === 'strict_types') {
- return true;
+ foreach ($stmt->declares as $declare) {
+ if ($declare->key->toString() === 'strict_types') {
+ return true;
+ }
}
}
diff --git a/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php b/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php
index aa643275adf..012345a8744 100644
--- a/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php
+++ b/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php
@@ -88,7 +88,6 @@ public function narrowToFullyQualifiedOrAliasedObjectType(
}
$currentTemplateTag = $templateTags[$className] ?? null;
-
if ($currentTemplateTag === null) {
// invalid type
return $this->resolveNamespacedNonExistingObjectType($namespaceName, $className, $withPreslash);
diff --git a/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php b/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php
index 49b99186763..d221c74aa2e 100644
--- a/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php
+++ b/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php
@@ -7,10 +7,8 @@
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Nop;
-use Rector\ChangesReporting\ValueObject\RectorWithLineChange;
use Rector\Contract\Rector\HTMLAverseRectorInterface;
-use Rector\PhpParser\Enum\NodeGroup;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\Rector\AbstractRector;
use Rector\TypeDeclaration\NodeAnalyzer\DeclareStrictTypeFinder;
use Rector\ValueObject\Application\File;
@@ -62,33 +60,35 @@ function someFunction(int $number)
}
/**
- * @param Stmt[] $nodes
- * @return Stmt[]|null
+ * @param FileNode $node
*/
- public function beforeTraverse(array $nodes): ?array
+ public function refactor(Node $node): ?FileNode
{
- parent::beforeTraverse($nodes);
+ // parent::beforeTraverse($nodes);
- if ($this->shouldSkipNodes($nodes, $this->file)) {
+ if ($this->shouldSkipNodes($node->stmts, $this->file)) {
return null;
}
- /** @var Node $rootStmt */
- $rootStmt = current($nodes);
- if ($rootStmt instanceof FileWithoutNamespace) {
+ // /** @var Node $rootStmt */
+ // $rootStmt = current();
+ if (! $node->isNamespaced()) {
return null;
}
// when first stmt is Declare_, verify if there is strict_types definition already,
// as multiple declare is allowed, with declare(strict_types=1) only allowed on very first stmt
- if ($this->declareStrictTypeFinder->hasDeclareStrictTypes($rootStmt)) {
+ if ($this->declareStrictTypeFinder->hasDeclareStrictTypes($node)) {
return null;
}
- $rectorWithLineChange = new RectorWithLineChange(self::class, $rootStmt->getStartLine());
- $this->file->addRectorClassWithLine($rectorWithLineChange);
+ // $rectorWithLineChange = new RectorWithLineChange(self::class, $rootStmt->getStartLine());
+ // $this->file->addRectorClassWithLine($rectorWithLineChange);
+
+ $node->stmts = array_merge([$this->nodeFactory->createDeclaresStrictType(), new Nop()], $node->stmts);
- return [$this->nodeFactory->createDeclaresStrictType(), new Nop(), ...$nodes];
+ return $node;
+ // return [$this->nodeFactory->createDeclaresStrictType(), new Nop(), ...$nodes];
}
/**
@@ -96,17 +96,7 @@ public function beforeTraverse(array $nodes): ?array
*/
public function getNodeTypes(): array
{
- return NodeGroup::STMTS_AWARE;
- }
-
- /**
- * @param StmtsAware $node
- */
- public function refactor(Node $node): null
- {
- // workaround, as Rector now only hooks to specific nodes, not arrays
- // avoid traversing, as we already handled in beforeTraverse()
- return null;
+ return [FileNode::class];
}
public function provideMinPhpVersion(): int
@@ -123,6 +113,7 @@ private function shouldSkipNodes(array $nodes, File $file): bool
return true;
}
+ // shebang files cannot have declare strict types
if (str_starts_with($file->getFileContent(), '#!')) {
return true;
}
diff --git a/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php b/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php
index b6584cf6fa2..95fa9b18b95 100644
--- a/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php
+++ b/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php
@@ -62,7 +62,7 @@ public function getNodeTypes(): array
public function refactor(Node $node): ?Node
{
throw new ShouldNotHappenException(sprintf(
- '"%s" is deprecated as changes strict types randomly on each run.. Use "%s" Rector on specific paths instead.',
+ '"%s" is deprecated as changes strict types randomly on each run. Use "%s" Rector on specific paths instead.',
self::class,
DeclareStrictTypesRector::class
));
diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php
index 9c8c87b5413..0cd2f51f166 100644
--- a/src/Application/FileProcessor.php
+++ b/src/Application/FileProcessor.php
@@ -13,6 +13,7 @@
use Rector\Exception\ShouldNotHappenException;
use Rector\FileSystem\FilePathHelper;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
+use Rector\PhpParser\Node\FileNode;
use Rector\PhpParser\NodeTraverser\RectorNodeTraverser;
use Rector\PhpParser\Parser\ParserErrors;
use Rector\PhpParser\Parser\RectorParser;
@@ -57,7 +58,6 @@ public function processFile(File $file, Configuration $configuration): FileProce
do {
$file->changeHasChanged(false);
- // 1. change nodes with Rector Rules
$newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts());
// 2. apply post rectors
@@ -140,14 +140,14 @@ private function parseFileAndDecorateNodes(File $file): ?SystemError
private function printFile(File $file, Configuration $configuration, string $filePath): void
{
// only save to string first, no need to print to file when not needed
- $newContent = $this->betterStandardPrinter->printFormatPreserving(
+ $newFileContent = $this->betterStandardPrinter->printFormatPreserving(
$file->getNewStmts(),
$file->getOldStmts(),
$file->getOldTokens()
);
// change file content early to make $file->hasChanged() based on new content
- $file->changeFileContent($newContent);
+ $file->changeFileContent($newFileContent);
if ($configuration->isDryRun()) {
return;
}
@@ -156,7 +156,7 @@ private function printFile(File $file, Configuration $configuration, string $fil
return;
}
- FileSystem::write($filePath, $newContent, null);
+ FileSystem::write($filePath, $newFileContent, null);
}
private function parseFileNodes(File $file, bool $forNewestSupportedVersion = true): void
@@ -168,9 +168,14 @@ private function parseFileNodes(File $file, bool $forNewestSupportedVersion = tr
);
$oldStmts = $stmtsAndTokens->getStmts();
+
+ // wrap in FileNode to allow file-level rules
+ $oldStmts = [new FileNode($oldStmts)];
+
$oldTokens = $stmtsAndTokens->getTokens();
$newStmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file->getFilePath(), $oldStmts);
+
$file->hydrateStmtsAndTokens($newStmts, $oldStmts, $oldTokens);
}
}
diff --git a/src/NodeAnalyzer/TerminatedNodeAnalyzer.php b/src/NodeAnalyzer/TerminatedNodeAnalyzer.php
index 5ba3aab93ff..a37f829f3c1 100644
--- a/src/NodeAnalyzer/TerminatedNodeAnalyzer.php
+++ b/src/NodeAnalyzer/TerminatedNodeAnalyzer.php
@@ -25,7 +25,7 @@
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\Switch_;
use PhpParser\Node\Stmt\TryCatch;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
final class TerminatedNodeAnalyzer
{
@@ -53,7 +53,7 @@ public function isAlwaysTerminated(Node $stmtsAware, Stmt $node, Stmt $currentSt
return false;
}
- if (($stmtsAware instanceof FileWithoutNamespace || $stmtsAware instanceof Namespace_) && ($currentStmt instanceof ClassLike || $currentStmt instanceof Function_)) {
+ if (($stmtsAware instanceof FileNode || $stmtsAware instanceof Namespace_) && ($currentStmt instanceof ClassLike || $currentStmt instanceof Function_)) {
return false;
}
diff --git a/src/NodeTypeResolver/NodeScopeAndMetadataDecorator.php b/src/NodeTypeResolver/NodeScopeAndMetadataDecorator.php
index 77dc42edea5..8231490ada2 100644
--- a/src/NodeTypeResolver/NodeScopeAndMetadataDecorator.php
+++ b/src/NodeTypeResolver/NodeScopeAndMetadataDecorator.php
@@ -8,7 +8,6 @@
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\CloningVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\PHPStanNodeScopeResolver;
-use Rector\PhpParser\NodeTraverser\FileWithoutNamespaceNodeTraverser;
final readonly class NodeScopeAndMetadataDecorator
{
@@ -17,7 +16,6 @@
public function __construct(
CloningVisitor $cloningVisitor,
private PHPStanNodeScopeResolver $phpStanNodeScopeResolver,
- private FileWithoutNamespaceNodeTraverser $fileWithoutNamespaceNodeTraverser
) {
// needed for format preserving printing
$this->nodeTraverser = new NodeTraverser($cloningVisitor);
@@ -29,9 +27,7 @@ public function __construct(
*/
public function decorateNodesFromFile(string $filePath, array $stmts): array
{
- $stmts = $this->fileWithoutNamespaceNodeTraverser->traverse($stmts);
$stmts = $this->phpStanNodeScopeResolver->processNodes($stmts, $filePath);
-
return $this->nodeTraverser->traverse($stmts);
}
}
diff --git a/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php b/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php
index eb629aeeab8..67bf6a55b93 100644
--- a/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php
+++ b/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php
@@ -106,7 +106,7 @@
use Rector\NodeAnalyzer\ClassAnalyzer;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\Util\Reflection\PrivatesAccessor;
use Webmozart\Assert\Assert;
@@ -202,7 +202,8 @@ public function processNodes(
$node->setAttribute(AttributeKey::SCOPE, $mutatingScope);
}
- if ($node instanceof FileWithoutNamespace) {
+ // handle unwrapped stmts
+ if ($node instanceof FileNode) {
$this->nodeScopeResolverProcessNodes($node->stmts, $mutatingScope, $nodeCallback);
return;
}
diff --git a/src/NodeTypeResolver/PhpDocNodeVisitor/NameImportingPhpDocNodeVisitor.php b/src/NodeTypeResolver/PhpDocNodeVisitor/NameImportingPhpDocNodeVisitor.php
index 152070d60e3..dc1006472ee 100644
--- a/src/NodeTypeResolver/PhpDocNodeVisitor/NameImportingPhpDocNodeVisitor.php
+++ b/src/NodeTypeResolver/PhpDocNodeVisitor/NameImportingPhpDocNodeVisitor.php
@@ -73,7 +73,6 @@ public function enterNode(Node $node): ?Node
$staticType = $this->identifierPhpDocTypeMapper->mapIdentifierTypeNode($node, $this->currentPhpParserNode);
$staticType = $this->resolveFullyQualified($staticType);
-
if (! $staticType instanceof FullyQualifiedObjectType) {
return null;
}
diff --git a/src/PhpParser/Enum/NodeGroup.php b/src/PhpParser/Enum/NodeGroup.php
index 871e8de9d5c..c3ab3264de8 100644
--- a/src/PhpParser/Enum/NodeGroup.php
+++ b/src/PhpParser/Enum/NodeGroup.php
@@ -27,7 +27,7 @@
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\While_;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
final class NodeGroup
{
@@ -55,7 +55,7 @@ final class NodeGroup
Namespace_::class,
TryCatch::class,
While_::class,
- FileWithoutNamespace::class,
+ FileNode::class,
];
/**
diff --git a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php
index d4e68fd6392..00dd425de3a 100644
--- a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php
+++ b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php
@@ -8,6 +8,9 @@
use Rector\Contract\PhpParser\Node\StmtsAwareInterface;
/**
+ * @deprecated Use @see \Rector\PhpParser\Node\FileNode instead
+ * @api
+ *
* Inspired by https://github.com/phpstan/phpstan-src/commit/ed81c3ad0b9877e6122c79b4afda9d10f3994092
*/
final class FileWithoutNamespace extends Stmt implements StmtsAwareInterface
diff --git a/src/PhpParser/Node/FileNode.php b/src/PhpParser/Node/FileNode.php
new file mode 100644
index 00000000000..8b558ff60fd
--- /dev/null
+++ b/src/PhpParser/Node/FileNode.php
@@ -0,0 +1,99 @@
+getAttributes() : []);
+
+ parent::__construct();
+
+ }
+
+ /**
+ * This triggers Printed method with "pFileNode" name
+ * @see \Rector\PhpParser\Printer\BetterStandardPrinter::pStmt_FileNode()
+ */
+ public function getType(): string
+ {
+ return 'Stmt_FileNode';
+ }
+
+ /**
+ * @return array
+ */
+ public function getSubNodeNames(): array
+ {
+ return ['stmts'];
+ }
+
+ public function isNamespaced(): bool
+ {
+ foreach ($this->stmts as $stmt) {
+ if ($stmt instanceof Namespace_) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function getNamespace(): ?Namespace_
+ {
+ /** @var Namespace_[] $namespaces */
+ $namespaces = array_filter($this->stmts, static fn (Stmt $stmt): bool => $stmt instanceof Namespace_);
+
+ if (count($namespaces) === 1) {
+ return current($namespaces);
+ }
+
+ return null;
+ }
+
+ /**
+ * @return array
+ */
+ public function getUsesAndGroupUses(): array
+ {
+ $rootNode = $this->getNamespace();
+ if (! $rootNode instanceof Namespace_) {
+ $rootNode = $this;
+ }
+
+ return array_filter(
+ $rootNode->stmts,
+ static fn (Stmt $stmt): bool => $stmt instanceof Use_ || $stmt instanceof GroupUse
+ );
+ }
+
+ /**
+ * @return Use_[]
+ */
+ public function getUses(): array
+ {
+ $rootNode = $this->getNamespace();
+ if (! $rootNode instanceof Namespace_) {
+ $rootNode = $this;
+ }
+
+ return array_filter($rootNode->stmts, static fn (Stmt $stmt): bool => $stmt instanceof Use_);
+ }
+}
diff --git a/src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php b/src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php
index 8d9329bdbe6..874676a5148 100644
--- a/src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php
+++ b/src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php
@@ -101,6 +101,7 @@ protected function traverseNode(Node $node): void
$traverseChildren = true;
$visitorIndex = -1;
$currentNodeVisitors = $this->getVisitorsForNode($subNode);
+
foreach ($currentNodeVisitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if ($return !== null) {
@@ -177,6 +178,7 @@ protected function traverseArray(array $nodes): array
$traverseChildren = true;
$visitorIndex = -1;
$currentNodeVisitors = $this->getVisitorsForNode($node);
+
foreach ($currentNodeVisitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if ($return !== null) {
diff --git a/src/PhpParser/NodeTraverser/FileWithoutNamespaceNodeTraverser.php b/src/PhpParser/NodeTraverser/FileWithoutNamespaceNodeTraverser.php
deleted file mode 100644
index e6594eda8fb..00000000000
--- a/src/PhpParser/NodeTraverser/FileWithoutNamespaceNodeTraverser.php
+++ /dev/null
@@ -1,31 +0,0 @@
-resolveNewStmts($stmts);
+ $newStmts = $this->unwrapFileNode($stmts);
+ $origStmts = $this->unwrapFileNode($origStmts);
$content = parent::printFormatPreserving($newStmts, $origStmts, $origTokens);
@@ -82,6 +83,7 @@ public function printFormatPreserving(array $stmts, array $origStmts, array $ori
*/
public function print(Node | array | null $node): string
{
+
if ($node === null) {
$node = [];
}
@@ -90,6 +92,8 @@ public function print(Node | array | null $node): string
$node = [$node];
}
+ $node = $this->unwrapFileNode($node);
+
return $this->prettyPrint($node);
}
@@ -104,14 +108,6 @@ public function prettyPrintFile(array $stmts): string
return parent::prettyPrintFile($stmts) . PHP_EOL;
}
- /**
- * @api magic method in parent
- */
- public function pFileWithoutNamespace(FileWithoutNamespace $fileWithoutNamespace): string
- {
- return $this->pStmts($fileWithoutNamespace->stmts);
- }
-
protected function p(
Node $node,
int $precedence = self::MAX_PRECEDENCE,
@@ -151,6 +147,11 @@ protected function p(
return $content;
}
+ protected function pStmt_FileNode(FileNode $fileNode): string
+ {
+ return $this->pStmts($fileNode->stmts, true);
+ }
+
protected function pExpr_ArrowFunction(ArrowFunction $arrowFunction, int $precedence, int $lhsPrecedence): string
{
if (! $arrowFunction->hasAttribute(AttributeKey::COMMENTS)) {
@@ -511,11 +512,10 @@ private function getIndentCharacter(): string
* @param Node[] $stmts
* @return Node[]|mixed[]
*/
- private function resolveNewStmts(array $stmts): array
+ private function unwrapFileNode(array $stmts): array
{
- $stmts = array_values($stmts);
-
- if (count($stmts) === 1 && $stmts[0] instanceof FileWithoutNamespace) {
+ // $stmts = array_values($stmts);
+ if (count($stmts) === 1 && $stmts[0] instanceof FileNode) {
return array_values($stmts[0]->stmts);
}
diff --git a/src/PostRector/Application/PostFileProcessor.php b/src/PostRector/Application/PostFileProcessor.php
index 2a309fc9a6f..670d9acf55e 100644
--- a/src/PostRector/Application/PostFileProcessor.php
+++ b/src/PostRector/Application/PostFileProcessor.php
@@ -86,7 +86,7 @@ private function shouldSkipPostRector(PostRectorInterface $postRector, string $f
}
/**
- * Load on the fly, to allow test reset with different configuration
+ * Lazy load, to enable test reset with different configuration
* @return PostRectorInterface[]
*/
private function getPostRectors(): array
@@ -124,7 +124,6 @@ private function getPostRectors(): array
}
$this->postRectors = $postRectors;
-
return $this->postRectors;
}
}
diff --git a/src/PostRector/Collector/UseNodesToAddCollector.php b/src/PostRector/Collector/UseNodesToAddCollector.php
index d9d792a756b..ebc27a8357e 100644
--- a/src/PostRector/Collector/UseNodesToAddCollector.php
+++ b/src/PostRector/Collector/UseNodesToAddCollector.php
@@ -36,6 +36,7 @@ public function __construct(
public function addUseImport(FullyQualifiedObjectType $fullyQualifiedObjectType): void
{
+ // @todo consider using FileNode directly
/** @var File $file */
$file = $this->currentFileProvider->getFile();
diff --git a/src/PostRector/Guard/AddUseStatementGuard.php b/src/PostRector/Guard/AddUseStatementGuard.php
index 76bb559b093..0247857cd48 100644
--- a/src/PostRector/Guard/AddUseStatementGuard.php
+++ b/src/PostRector/Guard/AddUseStatementGuard.php
@@ -8,6 +8,7 @@
use PhpParser\Node\Stmt\InlineHTML;
use PhpParser\Node\Stmt\Namespace_;
use Rector\PhpParser\Node\BetterNodeFinder;
+use Rector\PhpParser\Node\FileNode;
final class AddUseStatementGuard
{
@@ -34,6 +35,10 @@ public function shouldTraverse(array $stmts, string $filePath): bool
// just loop the first level stmts to locate namespace to improve performance
// as namespace is always on first level
+ if (isset($stmts[0]) && $stmts[0] instanceof FileNode) {
+ $stmts = $stmts[0]->stmts;
+ }
+
foreach ($stmts as $stmt) {
if ($stmt instanceof Namespace_) {
++$totalNamespaces;
diff --git a/src/PostRector/Rector/ClassRenamingPostRector.php b/src/PostRector/Rector/ClassRenamingPostRector.php
index 50f91212e44..32e8251e528 100644
--- a/src/PostRector/Rector/ClassRenamingPostRector.php
+++ b/src/PostRector/Rector/ClassRenamingPostRector.php
@@ -5,14 +5,11 @@
namespace Rector\PostRector\Rector;
use PhpParser\Node;
-use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\NodeVisitor;
use Rector\CodingStyle\Application\UseImportsRemover;
-use Rector\Configuration\Option;
-use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\Configuration\RenamedClassesDataCollector;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\PostRector\Guard\AddUseStatementGuard;
use Rector\Renaming\Collector\RenamedNameCollector;
@@ -31,41 +28,37 @@ public function __construct(
) {
}
- /**
- * @param Stmt[] $nodes
- * @return Stmt[]
- */
- public function beforeTraverse(array $nodes): array
+ public function enterNode(Node $node): Namespace_|FileNode|int|null
{
- if (! SimpleParameterProvider::provideBoolParameter(Option::AUTO_IMPORT_NAMES)) {
- return $nodes;
- }
-
- foreach ($nodes as $node) {
- if ($node instanceof FileWithoutNamespace || $node instanceof Namespace_) {
- $removedUses = $this->renamedClassesDataCollector->getOldClasses();
- if ($this->useImportsRemover->removeImportsFromStmts($node, $removedUses)) {
- $this->addRectorClassWithLine($node);
- }
+ if ($node instanceof FileNode) {
+ // handle in Namespace_ node
+ if ($node->isNamespaced()) {
+ return null;
+ }
- break;
+ // handle here
+ $removedUses = $this->renamedClassesDataCollector->getOldClasses();
+ if ($this->useImportsRemover->removeImportsFromStmts($node, $removedUses)) {
+ $this->addRectorClassWithLine($node);
}
+
+ $this->renamedNameCollector->reset();
+
+ return $node;
}
- $this->renamedNameCollector->reset();
- return $nodes;
- }
+ if ($node instanceof Namespace_) {
+ $removedUses = $this->renamedClassesDataCollector->getOldClasses();
+ if ($this->useImportsRemover->removeImportsFromStmts($node, $removedUses)) {
+ $this->addRectorClassWithLine($node);
+ }
- public function enterNode(Node $node): int
- {
- /**
- * We stop the traversal because all the work has already been done in the beforeTraverse() function
- *
- * Using STOP_TRAVERSAL is usually dangerous as it will stop the processing of all your nodes for all visitors
- * but since the PostFileProcessor is using direct new NodeTraverser() and traverse() for only a single
- * visitor per execution, using stop traversal here is safe,
- * ref https://github.com/rectorphp/rector-src/blob/fc1e742fa4d9861ccdc5933f3b53613b8223438d/src/PostRector/Application/PostFileProcessor.php#L59-L61
- */
+ $this->renamedNameCollector->reset();
+
+ return $node;
+ }
+
+ // nothing else to handle here, as first 2 nodes we'll hit are handled above
return NodeVisitor::STOP_TRAVERSAL;
}
diff --git a/src/PostRector/Rector/NameImportingPostRector.php b/src/PostRector/Rector/NameImportingPostRector.php
index 66ada94a3ba..7c63e54b217 100644
--- a/src/PostRector/Rector/NameImportingPostRector.php
+++ b/src/PostRector/Rector/NameImportingPostRector.php
@@ -49,6 +49,7 @@ public function enterNode(Node $node): Name|null
}
$this->addRectorClassWithLine($node);
+
return $name;
}
diff --git a/src/PostRector/Rector/UnusedImportRemovingPostRector.php b/src/PostRector/Rector/UnusedImportRemovingPostRector.php
index b06f2ad7191..0d933319891 100644
--- a/src/PostRector/Rector/UnusedImportRemovingPostRector.php
+++ b/src/PostRector/Rector/UnusedImportRemovingPostRector.php
@@ -20,7 +20,7 @@
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
final class UnusedImportRemovingPostRector extends AbstractPostRector
{
@@ -32,7 +32,7 @@ public function __construct(
public function enterNode(Node $node): ?Node
{
- if (! $node instanceof Namespace_ && ! $node instanceof FileWithoutNamespace) {
+ if (! $node instanceof Namespace_ && ! $node instanceof FileNode) {
return null;
}
@@ -101,11 +101,11 @@ public function enterNode(Node $node): ?Node
/**
* @return string[]
*/
- private function findNonUseImportNames(Namespace_|FileWithoutNamespace $namespace): array
+ private function findNonUseImportNames(Namespace_|FileNode $fileNode): array
{
$names = [];
- $this->simpleCallableNodeTraverser->traverseNodesWithCallable($namespace->stmts, static function (Node $node) use (
+ $this->simpleCallableNodeTraverser->traverseNodesWithCallable($fileNode->stmts, static function (Node $node) use (
&$names
): int|null|Name {
if ($node instanceof Use_) {
@@ -136,11 +136,11 @@ private function findNonUseImportNames(Namespace_|FileWithoutNamespace $namespac
/**
* @return string[]
*/
- private function findNamesInDocBlocks(Namespace_|FileWithoutNamespace $namespace): array
+ private function findNamesInDocBlocks(Namespace_|FileNode $rootNode): array
{
$names = [];
- $this->simpleCallableNodeTraverser->traverseNodesWithCallable($namespace, function (Node $node) use (
+ $this->simpleCallableNodeTraverser->traverseNodesWithCallable($rootNode, function (Node $node) use (
&$names
) {
$comments = $node->getComments();
@@ -180,10 +180,10 @@ private function findNamesInDocBlocks(Namespace_|FileWithoutNamespace $namespace
/**
* @return string[]
*/
- private function resolveUsedPhpAndDocNames(Namespace_|FileWithoutNamespace $namespace): array
+ private function resolveUsedPhpAndDocNames(Namespace_|FileNode $rootNode): array
{
- $phpNames = $this->findNonUseImportNames($namespace);
- $docBlockNames = $this->findNamesInDocBlocks($namespace);
+ $phpNames = $this->findNonUseImportNames($rootNode);
+ $docBlockNames = $this->findNamesInDocBlocks($rootNode);
$names = [...$phpNames, ...$docBlockNames];
return array_unique($names);
diff --git a/src/PostRector/Rector/UseAddingPostRector.php b/src/PostRector/Rector/UseAddingPostRector.php
index 2927abba72b..7b9e70784f0 100644
--- a/src/PostRector/Rector/UseAddingPostRector.php
+++ b/src/PostRector/Rector/UseAddingPostRector.php
@@ -10,7 +10,7 @@
use PhpParser\NodeVisitor;
use Rector\CodingStyle\Application\UseImportsAdder;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
-use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
+use Rector\PhpParser\Node\FileNode;
use Rector\PostRector\Collector\UseNodesToAddCollector;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
@@ -35,7 +35,7 @@ public function beforeTraverse(array $nodes): array
}
$rootNode = $this->resolveRootNode($nodes);
- if (! $rootNode instanceof FileWithoutNamespace && ! $rootNode instanceof Namespace_) {
+ if (! $rootNode instanceof FileNode && ! $rootNode instanceof Namespace_) {
return $nodes;
}
@@ -44,6 +44,7 @@ public function beforeTraverse(array $nodes): array
$this->getFile()
->getFilePath()
);
+
$functionUseImportTypes = $this->useNodesToAddCollector->getFunctionImportsByFilePath(
$this->getFile()
->getFilePath()
@@ -55,7 +56,7 @@ public function beforeTraverse(array $nodes): array
/** @var FullyQualifiedObjectType[] $useImportTypes */
$useImportTypes = $this->typeFactory->uniquateTypes($useImportTypes);
- $stmts = $rootNode instanceof FileWithoutNamespace ? $rootNode->stmts : $nodes;
+ $stmts = $rootNode instanceof FileNode ? $rootNode->stmts : $nodes;
if ($this->processStmtsWithImportedUses(
$stmts,
@@ -94,7 +95,7 @@ private function processStmtsWithImportedUses(
array $useImportTypes,
array $constantUseImportTypes,
array $functionUseImportTypes,
- FileWithoutNamespace|Namespace_ $namespace
+ FileNode|Namespace_ $namespace
): bool {
// A. has namespace? add under it
if ($namespace instanceof Namespace_) {
@@ -143,14 +144,23 @@ private function filterOutNonNamespacedNames(array $useImportTypes): array
/**
* @param Stmt[] $nodes
*/
- private function resolveRootNode(array $nodes): Namespace_|FileWithoutNamespace|null
+ private function resolveRootNode(array $nodes): Namespace_|FileNode|null
{
- foreach ($nodes as $node) {
- if ($node instanceof FileWithoutNamespace || $node instanceof Namespace_) {
- return $node;
+ if ($nodes === []) {
+ return null;
+ }
+
+ $firstStmt = $nodes[0];
+ if (! $firstStmt instanceof FileNode) {
+ return null;
+ }
+
+ foreach ($firstStmt->stmts as $stmt) {
+ if ($stmt instanceof Namespace_) {
+ return $stmt;
}
}
- return null;
+ return $firstStmt;
}
}
diff --git a/src/Testing/PHPUnit/AbstractRectorTestCase.php b/src/Testing/PHPUnit/AbstractRectorTestCase.php
index 490b0401d37..f0477faa32e 100644
--- a/src/Testing/PHPUnit/AbstractRectorTestCase.php
+++ b/src/Testing/PHPUnit/AbstractRectorTestCase.php
@@ -149,6 +149,7 @@ protected function doTestFile(string $fixtureFilePath, bool $includeFixtureDirec
}
$inputFilePath = $this->createInputFilePath($fixtureFilePath);
+
// to remove later in tearDown()
$this->inputFilePath = $inputFilePath;
diff --git a/src/Testing/TestingParser/TestingParser.php b/src/Testing/TestingParser/TestingParser.php
index 01f7d8d1650..5bace90461a 100644
--- a/src/Testing/TestingParser/TestingParser.php
+++ b/src/Testing/TestingParser/TestingParser.php
@@ -9,6 +9,7 @@
use Rector\Application\Provider\CurrentFileProvider;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
+use Rector\PhpParser\Node\FileNode;
use Rector\PhpParser\Parser\RectorParser;
use Rector\ValueObject\Application\File;
@@ -28,7 +29,6 @@ public function __construct(
public function parseFilePathToFile(string $filePath): File
{
[$file, $stmts] = $this->parseToFileAndStmts($filePath);
-
return $file;
}
@@ -38,7 +38,6 @@ public function parseFilePathToFile(string $filePath): File
public function parseFileToDecoratedNodes(string $filePath): array
{
[$file, $stmts] = $this->parseToFileAndStmts($filePath);
-
return $stmts;
}
@@ -52,8 +51,10 @@ private function parseToFileAndStmts(string $filePath): array
$fileContent = FileSystem::read($filePath);
$file = new File($filePath, $fileContent);
-
$stmts = $this->rectorParser->parseString($fileContent);
+
+ // wrap in FileNode to enable file-level rules
+ $stmts = [new FileNode($stmts)];
$stmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($filePath, $stmts);
$file->hydrateStmtsAndTokens($stmts, $stmts, []);
diff --git a/src/ValueObject/Application/File.php b/src/ValueObject/Application/File.php
index 89c163db666..9296b6ddd6e 100644
--- a/src/ValueObject/Application/File.php
+++ b/src/ValueObject/Application/File.php
@@ -7,10 +7,12 @@
use PhpParser\Node;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\InlineHTML;
+use PhpParser\Node\Stmt\Namespace_;
use PhpParser\NodeFinder;
use PhpParser\Token;
use Rector\ChangesReporting\ValueObject\RectorWithLineChange;
use Rector\Exception\ShouldNotHappenException;
+use Rector\PhpParser\Node\FileNode;
use Rector\ValueObject\Reporting\FileDiff;
final class File
@@ -151,6 +153,39 @@ public function addRectorClassWithLine(RectorWithLineChange $rectorWithLineChang
$this->rectorWithLineChanges[] = $rectorWithLineChange;
}
+ /**
+ * This node returns top most node,
+ * that includes use imports
+ */
+ public function getUseImportsRootNode(): Namespace_|FileNode|null
+ {
+ if ($this->newStmts === []) {
+ return null;
+ }
+
+ $firstStmt = $this->newStmts[0];
+ if ($firstStmt instanceof FileNode) {
+ if (! $firstStmt->isNamespaced()) {
+ return $firstStmt;
+ }
+
+ // return sole Namespace, or none
+ $namespaces = [];
+ foreach ($firstStmt->stmts as $stmt) {
+ if ($stmt instanceof Namespace_) {
+ $namespaces[] = $stmt;
+ }
+ }
+
+ if (count($namespaces) === 1) {
+ return $namespaces[0];
+ }
+ }
+
+ return null;
+
+ }
+
/**
* @return RectorWithLineChange[]
*/
@@ -170,4 +205,17 @@ public function containsHTML(): bool
$this->containsHtml = (bool) $nodeFinder->findFirstInstanceOf($this->oldStmts, InlineHTML::class);
return $this->containsHtml;
}
+
+ public function getFileNode(): ?FileNode
+ {
+ if ($this->newStmts === []) {
+ return null;
+ }
+
+ if ($this->newStmts[0] instanceof FileNode) {
+ return $this->newStmts[0];
+ }
+
+ return null;
+ }
}
diff --git a/src/functions/node_helper.php b/src/functions/node_helper.php
index 1c572018586..0c0b58dba5e 100644
--- a/src/functions/node_helper.php
+++ b/src/functions/node_helper.php
@@ -6,6 +6,7 @@
use PhpParser\Node;
use PhpParser\PrettyPrinter\Standard;
use Rector\Console\Style\SymfonyStyleFactory;
+use Rector\PhpParser\Node\FileNode;
use Rector\Util\NodePrinter;
use Symfony\Component\Console\Output\OutputInterface;
@@ -18,6 +19,9 @@ function print_node(Node | array $node): void
$standard = new Standard();
$nodes = is_array($node) ? $node : [$node];
+ if ($nodes[0] instanceof FileNode) {
+ $nodes = $nodes[0]->stmts;
+ }
foreach ($nodes as $node) {
$printedContent = $standard->prettyPrint([$node]);
diff --git a/tests/Comments/CommentRemover/CommentRemoverTest.php b/tests/Comments/CommentRemover/CommentRemoverTest.php
index 55bb072bbf1..eb5e857bbec 100644
--- a/tests/Comments/CommentRemover/CommentRemoverTest.php
+++ b/tests/Comments/CommentRemover/CommentRemoverTest.php
@@ -29,6 +29,7 @@ protected function setUp(): void
$this->commentRemover = $this->make(CommentRemover::class);
$this->testingParser = $this->make(TestingParser::class);
+
$this->betterStandardPrinter = $this->make(BetterStandardPrinter::class);
}
@@ -44,11 +45,11 @@ public function test(string $filePath): void
$nodesWithoutComments = $this->commentRemover->removeFromNode($nodes);
- $fileContent = $this->betterStandardPrinter->print($nodesWithoutComments);
- $fileContent = trim($fileContent);
+ $printedFileContent = $this->betterStandardPrinter->print($nodesWithoutComments);
+ $printedFileContent = trim($printedFileContent);
$expectedContent = trim($expectedOutputContents);
- $this->assertSame($fileContent, $expectedContent);
+ $this->assertSame($printedFileContent, $expectedContent);
// original nodes are not touched
$originalContent = $this->betterStandardPrinter->print($nodes);