From 90e7a01d451cc3dd5246e8123bcfc1608f45c521 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 6 Dec 2025 21:38:08 +0100 Subject: [PATCH 01/11] introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector --- rules/Naming/Naming/UseImportsResolver.php | 19 +++---- .../NodeAnalyzer/DeclareStrictTypeFinder.php | 22 ++++---- .../PHPStan/ObjectTypeSpecifier.php | 1 - .../DeclareStrictTypesRector.php | 53 ++++++++++--------- .../IncreaseDeclareStrictTypesRector.php | 15 ++++++ src/Application/FileProcessor.php | 23 ++++++-- .../ArrayItemClassNameDecorator.php | 2 +- .../ConstExprClassNameDecorator.php | 2 +- .../NodeScopeAndMetadataDecorator.php | 4 -- .../Scope/PHPStanNodeScopeResolver.php | 5 +- src/PhpParser/Node/FileNode.php | 53 +++++++++++++++++++ .../Printer/BetterStandardPrinter.php | 6 +++ .../PHPUnit/AbstractRectorTestCase.php | 1 + src/Testing/TestingParser/TestingParser.php | 24 +++++++++ src/functions/node_helper.php | 4 ++ 15 files changed, 173 insertions(+), 61 deletions(-) create mode 100644 src/PhpParser/Node/FileNode.php diff --git a/rules/Naming/Naming/UseImportsResolver.php b/rules/Naming/Naming/UseImportsResolver.php index 001c22665af..ea3e437873d 100644 --- a/rules/Naming/Naming/UseImportsResolver.php +++ b/rules/Naming/Naming/UseImportsResolver.php @@ -10,7 +10,7 @@ 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 @@ -57,7 +57,7 @@ public function resolvePrefix(Use_|GroupUse $use): string : ''; } - private function resolveNamespace(): Namespace_|FileWithoutNamespace|null + private function resolveNamespace(): Namespace_|FileNode|null { /** @var File|null $file */ $file = $this->currentFileProvider->getFile(); @@ -66,22 +66,15 @@ private function resolveNamespace(): Namespace_|FileWithoutNamespace|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; + if ($newStmts[0] instanceof FileNode) { + $fileNode = $newStmts[0]; + return $fileNode->getNamespace(); } - return current($namespaces); + 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..9133a46c1f7 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,18 +96,18 @@ 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]; } + // + // /** + // * @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; + // } public function provideMinPhpVersion(): int { @@ -123,6 +123,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..82a13ffdf19 100644 --- a/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php +++ b/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php @@ -14,7 +14,12 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** +<<<<<<< HEAD * @deprecated As keeps changing files randomly on every run. Not deterministic. Use more reliable @see \Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector instead on specific paths. +======= + * @deprecated This rule is deprecated as behaves very randomly and keeps adding strict types on new run Cannot be automated. + * Use @see DeclareStrictTypesRector on specific paths instead narrow control. +>>>>>>> 595b685f3e (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) */ final class IncreaseDeclareStrictTypesRector extends AbstractRector implements ConfigurableRectorInterface, DeprecatedInterface { @@ -62,7 +67,12 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { throw new ShouldNotHappenException(sprintf( +<<<<<<< HEAD '"%s" is deprecated as changes strict types randomly on each run.. Use "%s" Rector on specific paths instead.', +======= + 'This rule is deprecated as behaves very randomly and keeps adding strict types on new run Cannot be automated. + * Use %s on specific paths instead narrow control', +>>>>>>> 595b685f3e (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) self::class, DeclareStrictTypesRector::class )); @@ -73,5 +83,10 @@ public function refactor(Node $node): ?Node */ public function configure(array $configuration): void { +<<<<<<< HEAD +======= + Assert::keyExists($configuration, self::LIMIT); + $this->limit = (int) $configuration[self::LIMIT]; +>>>>>>> 595b685f3e (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) } } diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index 9c8c87b5413..88c60f41ba4 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,9 +140,22 @@ 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 + $newStmts = $file->getNewStmts(); + + $oldStmts = $file->getOldStmts(); + + // unwrap FileNode stmts to allow printing + if ($newStmts[0] instanceof FileNode) { + $newStmts = $newStmts[0]->stmts; + } + + if ($oldStmts[0] instanceof FileNode) { + $oldStmts = $oldStmts[0]->stmts; + } + $newContent = $this->betterStandardPrinter->printFormatPreserving( - $file->getNewStmts(), - $file->getOldStmts(), + $newStmts, + $oldStmts, $file->getOldTokens() ); @@ -168,9 +181,13 @@ 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/BetterPhpDocParser/PhpDocParser/ArrayItemClassNameDecorator.php b/src/BetterPhpDocParser/PhpDocParser/ArrayItemClassNameDecorator.php index 66ad52c98ff..30da71e27d9 100644 --- a/src/BetterPhpDocParser/PhpDocParser/ArrayItemClassNameDecorator.php +++ b/src/BetterPhpDocParser/PhpDocParser/ArrayItemClassNameDecorator.php @@ -49,7 +49,7 @@ public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void } $className = $this->resolveFullyQualifiedClass($splitScopeResolution[0], $phpNode); - $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); + // $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); return $node; }); diff --git a/src/BetterPhpDocParser/PhpDocParser/ConstExprClassNameDecorator.php b/src/BetterPhpDocParser/PhpDocParser/ConstExprClassNameDecorator.php index d3a67f243b6..97278b05b6c 100644 --- a/src/BetterPhpDocParser/PhpDocParser/ConstExprClassNameDecorator.php +++ b/src/BetterPhpDocParser/PhpDocParser/ConstExprClassNameDecorator.php @@ -40,7 +40,7 @@ public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void } $className = $this->resolveFullyQualifiedClass($node, $phpNode); - $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); + // $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); return $node; }); 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/PhpParser/Node/FileNode.php b/src/PhpParser/Node/FileNode.php new file mode 100644 index 00000000000..23f7b4f46bb --- /dev/null +++ b/src/PhpParser/Node/FileNode.php @@ -0,0 +1,53 @@ +stmts as $stmt) { + if ($stmt instanceof Stmt\Namespace_) { + return true; + } + } + + return false; + } + + public function getNamespace(): ?Stmt\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; + } +} diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index 8f9d94f63b5..6bacb4a5085 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -35,6 +35,7 @@ use Rector\NodeAnalyzer\ExprAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace; +use Rector\PhpParser\Node\FileNode; use Rector\Util\NewLineSplitter; use Rector\Util\Reflection\PrivatesAccessor; @@ -161,6 +162,11 @@ protected function p( return $content; } + protected function pCustomNode_File(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)) { 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..cc30d466467 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; @@ -27,7 +28,24 @@ public function __construct( public function parseFilePathToFile(string $filePath): File { +<<<<<<< HEAD [$file, $stmts] = $this->parseToFileAndStmts($filePath); +======= + // needed for PHPStan reflection, as it caches the last processed file + $this->dynamicSourceLocatorProvider->setFilePath($filePath); + + $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, []); + $this->currentFileProvider->setFile($file); +>>>>>>> 2751658832 (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) return $file; } @@ -53,7 +71,13 @@ private function parseToFileAndStmts(string $filePath): array $fileContent = FileSystem::read($filePath); $file = new File($filePath, $fileContent); +<<<<<<< HEAD $stmts = $this->rectorParser->parseString($fileContent); +======= + // wrap in FileNode to enable file-level rules + $stmts = [new FileNode($stmts)]; + +>>>>>>> 2751658832 (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) $stmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($filePath, $stmts); $file->hydrateStmtsAndTokens($stmts, $stmts, []); 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]); From e5f0f102e1bbdceaeb652ba49593f24217c4c302 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 6 Dec 2025 22:52:05 +0100 Subject: [PATCH 02/11] update use resolving --- rules/Naming/Naming/UseImportsResolver.php | 62 +++++++++---------- .../IncreaseDeclareStrictTypesRector.php | 7 +++ .../Node/CustomNode/FileWithoutNamespace.php | 2 + src/PhpParser/Node/FileNode.php | 38 ++++++++++++ .../Rector/UnusedImportRemovingPostRector.php | 18 +++--- src/ValueObject/Application/File.php | 14 +++++ 6 files changed, 99 insertions(+), 42 deletions(-) diff --git a/rules/Naming/Naming/UseImportsResolver.php b/rules/Naming/Naming/UseImportsResolver.php index ea3e437873d..b6fdb1ec012 100644 --- a/rules/Naming/Naming/UseImportsResolver.php +++ b/rules/Naming/Naming/UseImportsResolver.php @@ -4,13 +4,9 @@ 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\FileNode; use Rector\ValueObject\Application\File; final readonly class UseImportsResolver @@ -25,15 +21,13 @@ public function __construct( */ public function resolve(): array { - $namespace = $this->resolveNamespace(); - if (! $namespace instanceof Node) { + $file = $this->currentFileProvider->getFile(); + if (! $file instanceof File) { return []; } - return array_filter( - $namespace->stmts, - static fn (Stmt $stmt): bool => $stmt instanceof Use_ || $stmt instanceof GroupUse - ); + $fileNode = $file->getFileNode(); + return $fileNode->getUsesAndGroupUses(); } /** @@ -42,12 +36,14 @@ public function resolve(): array */ public function resolveBareUses(): array { - $namespace = $this->resolveNamespace(); - if (! $namespace instanceof Node) { + $file = $this->currentFileProvider->getFile(); + + if (! $file instanceof File) { return []; } - return array_filter($namespace->stmts, static fn (Stmt $stmt): bool => $stmt instanceof Use_); + $fileNode = $file->getFileNode(); + return $fileNode->getUses(); } public function resolvePrefix(Use_|GroupUse $use): string @@ -57,24 +53,24 @@ public function resolvePrefix(Use_|GroupUse $use): string : ''; } - private function resolveNamespace(): Namespace_|FileNode|null - { - /** @var File|null $file */ - $file = $this->currentFileProvider->getFile(); - if (! $file instanceof File) { - return null; - } - - $newStmts = $file->getNewStmts(); - if ($newStmts === []) { - return null; - } - - if ($newStmts[0] instanceof FileNode) { - $fileNode = $newStmts[0]; - return $fileNode->getNamespace(); - } - - return null; - } + // private function resolveNamespace(): Namespace_|null + // { + // /** @var File|null $file */ + // $file = $this->currentFileProvider->getFile(); + // if (! $file instanceof File) { + // return null; + // } + // + // $newStmts = $file->getNewStmts(); + // if ($newStmts === []) { + // return null; + // } + // + // if ($newStmts[0] instanceof FileNode) { + // $fileNode = $newStmts[0]; + // return $fileNode->getNamespace(); + // } + // + // return null; + // } } diff --git a/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php b/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php index 82a13ffdf19..7b1ec45a500 100644 --- a/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php +++ b/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php @@ -67,10 +67,14 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { throw new ShouldNotHappenException(sprintf( +<<<<<<< HEAD <<<<<<< HEAD '"%s" is deprecated as changes strict types randomly on each run.. Use "%s" Rector on specific paths instead.', ======= 'This rule is deprecated as behaves very randomly and keeps adding strict types on new run Cannot be automated. +======= + 'The "%s" rule is deprecated as behaves very randomly and keeps adding strict types on new run Cannot be automated. +>>>>>>> a0bb7b3a1a (update use resolving) * Use %s on specific paths instead narrow control', >>>>>>> 595b685f3e (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) self::class, @@ -84,9 +88,12 @@ public function refactor(Node $node): ?Node public function configure(array $configuration): void { <<<<<<< HEAD +<<<<<<< HEAD ======= Assert::keyExists($configuration, self::LIMIT); $this->limit = (int) $configuration[self::LIMIT]; >>>>>>> 595b685f3e (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) +======= +>>>>>>> a0bb7b3a1a (update use resolving) } } diff --git a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php index d4e68fd6392..3220b691207 100644 --- a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php +++ b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php @@ -8,6 +8,8 @@ use Rector\Contract\PhpParser\Node\StmtsAwareInterface; /** + * @deprecated Use @see \Rector\PhpParser\Node\FileNode instead + * * 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 index 23f7b4f46bb..dd4da11c36d 100644 --- a/src/PhpParser/Node/FileNode.php +++ b/src/PhpParser/Node/FileNode.php @@ -5,8 +5,13 @@ namespace Rector\PhpParser\Node; use PhpParser\Node\Stmt; +use PhpParser\Node\Stmt\GroupUse; use PhpParser\Node\Stmt\Namespace_; +use PhpParser\Node\Stmt\Use_; +/** + * Inspired by https://github.com/phpstan/phpstan-src/commit/ed81c3ad0b9877e6122c79b4afda9d10f3994092 + */ final class FileNode extends Stmt { /** @@ -15,7 +20,11 @@ final class FileNode extends Stmt public function __construct( public array $stmts ) { + $firstStmt = $stmts[0] ?? null; + parent::__construct($firstStmt instanceof \PhpParser\Node ? $firstStmt->getAttributes() : []); + parent::__construct(); + } public function getType(): string @@ -50,4 +59,33 @@ public function getNamespace(): ?Stmt\Namespace_ 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/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/ValueObject/Application/File.php b/src/ValueObject/Application/File.php index 89c163db666..22d50a31d64 100644 --- a/src/ValueObject/Application/File.php +++ b/src/ValueObject/Application/File.php @@ -11,6 +11,7 @@ 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 @@ -170,4 +171,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; + } } From b0e6e0f3f1e04907b56ed09c808e02b156083832 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 6 Dec 2025 23:44:53 +0100 Subject: [PATCH 03/11] make use of FileNode in imports --- UPGRADING.md | 18 +++++ phpstan.neon | 15 ++-- .../Application/UseImportsAdder.php | 16 +++-- .../Application/UseImportsRemover.php | 4 +- .../ClassNameImport/AliasUsesResolver.php | 8 +-- .../ClassNameImport/ShortNameResolver.php | 19 ++--- .../ClassNameImport/UseImportsTraverser.php | 4 +- rules/CodingStyle/Node/NameImporter.php | 1 + .../CatchExceptionNameMatchingTypeRector.php | 17 +++-- ...RemoveUselessAliasInUseStatementRector.php | 14 ++-- .../Use_/SeparateMultiUseImportsRector.php | 13 ++-- .../Stmt/RemoveUnreachableStatementRector.php | 6 ++ rules/Naming/Naming/UseImportsResolver.php | 13 +++- .../Assign/AssignArrayToStringRector.php | 15 ++-- .../DeclareStrictTypesRector.php | 10 --- .../IncreaseDeclareStrictTypesRector.php | 24 +------ src/Application/FileProcessor.php | 24 ++----- .../ArrayItemClassNameDecorator.php | 2 +- .../ConstExprClassNameDecorator.php | 2 +- src/NodeAnalyzer/TerminatedNodeAnalyzer.php | 4 +- .../NameImportingPhpDocNodeVisitor.php | 1 - src/PhpParser/Enum/NodeGroup.php | 4 +- src/PhpParser/Node/FileNode.php | 10 ++- .../FileWithoutNamespaceNodeTraverser.php | 31 --------- .../Printer/BetterStandardPrinter.php | 34 +++++---- .../Application/PostFileProcessor.php | 3 +- .../Collector/UseNodesToAddCollector.php | 1 + src/PostRector/Guard/AddUseStatementGuard.php | 5 ++ .../Rector/ClassRenamingPostRector.php | 69 ++++++++++--------- .../Rector/NameImportingPostRector.php | 1 + src/PostRector/Rector/UseAddingPostRector.php | 28 +++++--- src/Testing/TestingParser/TestingParser.php | 29 ++++++++ src/ValueObject/Application/File.php | 34 +++++++++ .../CommentRemover/CommentRemoverTest.php | 7 +- 34 files changed, 273 insertions(+), 213 deletions(-) delete mode 100644 src/PhpParser/NodeTraverser/FileWithoutNamespaceNodeTraverser.php diff --git a/UPGRADING.md b/UPGRADING.md index 70ed3fd5e3e..f5da97f986e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,3 +1,21 @@ +# Upgrading from Rector 2.2.11 to 2.2.12 + + +@todo + +* FileWithoutNamespace is deprecated, and replaced by `FileNode` +* `beforeTraverse()` is @final, use getNodeTypes() with `FileNode::class` instead + +**Before** + +@todo + + +**After** + +@todo + + # Upgrading from Rector 1.x to 2.0 ## PHP version requirements diff --git a/phpstan.neon b/phpstan.neon index 9720808bdc2..f4c8694f547 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 @@ -286,7 +286,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 - @@ -398,12 +397,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: @@ -415,6 +408,7 @@ parameters: # condition check, just to be sure - '#Method Rector\\Rector\\AbstractRector\:\:enterNode\(\) never returns 3 so it can be removed from the return type#' +<<<<<<< HEAD # special case, working on a file-level - @@ -440,6 +434,7 @@ parameters: - rules/Php55/Rector/String_/StringClassNameToClassConstantRector.php - rules/Php81/Enum/AttributeName.php +<<<<<<< HEAD - identifier: symplify.seeAnnotationToTest paths: @@ -455,3 +450,7 @@ parameters: path: rules/Php81/Rector/Array_/FirstClassCallableRector.php +======= +======= +>>>>>>> ddfef52951 (cleanup phpstan errors) +>>>>>>> d71688c507 (make use of FileNode in imports) 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 b6fdb1ec012..6d63f45c854 100644 --- a/rules/Naming/Naming/UseImportsResolver.php +++ b/rules/Naming/Naming/UseImportsResolver.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\GroupUse; use PhpParser\Node\Stmt\Use_; use Rector\Application\Provider\CurrentFileProvider; +use Rector\PhpParser\Node\FileNode; use Rector\ValueObject\Application\File; final readonly class UseImportsResolver @@ -26,8 +27,12 @@ public function resolve(): array return []; } - $fileNode = $file->getFileNode(); - return $fileNode->getUsesAndGroupUses(); + $rootNode = $file->getFileNode(); + if (! $rootNode instanceof FileNode) { + return []; + } + + return $rootNode->getUsesAndGroupUses(); } /** @@ -43,6 +48,10 @@ public function resolveBareUses(): array } $fileNode = $file->getFileNode(); + if (! $fileNode instanceof FileNode) { + return []; + } + return $fileNode->getUses(); } 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/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php b/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php index 9133a46c1f7..d221c74aa2e 100644 --- a/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php +++ b/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php @@ -98,16 +98,6 @@ public function getNodeTypes(): array { return [FileNode::class]; } - // - // /** - // * @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; - // } public function provideMinPhpVersion(): int { diff --git a/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php b/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php index 7b1ec45a500..95fa9b18b95 100644 --- a/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php +++ b/rules/TypeDeclaration/Rector/StmtsAwareInterface/IncreaseDeclareStrictTypesRector.php @@ -14,12 +14,7 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** -<<<<<<< HEAD * @deprecated As keeps changing files randomly on every run. Not deterministic. Use more reliable @see \Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector instead on specific paths. -======= - * @deprecated This rule is deprecated as behaves very randomly and keeps adding strict types on new run Cannot be automated. - * Use @see DeclareStrictTypesRector on specific paths instead narrow control. ->>>>>>> 595b685f3e (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) */ final class IncreaseDeclareStrictTypesRector extends AbstractRector implements ConfigurableRectorInterface, DeprecatedInterface { @@ -67,16 +62,7 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { throw new ShouldNotHappenException(sprintf( -<<<<<<< HEAD -<<<<<<< HEAD - '"%s" is deprecated as changes strict types randomly on each run.. Use "%s" Rector on specific paths instead.', -======= - 'This rule is deprecated as behaves very randomly and keeps adding strict types on new run Cannot be automated. -======= - 'The "%s" rule is deprecated as behaves very randomly and keeps adding strict types on new run Cannot be automated. ->>>>>>> a0bb7b3a1a (update use resolving) - * Use %s on specific paths instead narrow control', ->>>>>>> 595b685f3e (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) + '"%s" is deprecated as changes strict types randomly on each run. Use "%s" Rector on specific paths instead.', self::class, DeclareStrictTypesRector::class )); @@ -87,13 +73,5 @@ public function refactor(Node $node): ?Node */ public function configure(array $configuration): void { -<<<<<<< HEAD -<<<<<<< HEAD -======= - Assert::keyExists($configuration, self::LIMIT); - $this->limit = (int) $configuration[self::LIMIT]; ->>>>>>> 595b685f3e (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) -======= ->>>>>>> a0bb7b3a1a (update use resolving) } } diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index 88c60f41ba4..0cd2f51f166 100644 --- a/src/Application/FileProcessor.php +++ b/src/Application/FileProcessor.php @@ -140,27 +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 - $newStmts = $file->getNewStmts(); - - $oldStmts = $file->getOldStmts(); - - // unwrap FileNode stmts to allow printing - if ($newStmts[0] instanceof FileNode) { - $newStmts = $newStmts[0]->stmts; - } - - if ($oldStmts[0] instanceof FileNode) { - $oldStmts = $oldStmts[0]->stmts; - } - - $newContent = $this->betterStandardPrinter->printFormatPreserving( - $newStmts, - $oldStmts, + $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; } @@ -169,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 @@ -184,6 +171,7 @@ private function parseFileNodes(File $file, bool $forNewestSupportedVersion = tr // wrap in FileNode to allow file-level rules $oldStmts = [new FileNode($oldStmts)]; + $oldTokens = $stmtsAndTokens->getTokens(); $newStmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file->getFilePath(), $oldStmts); diff --git a/src/BetterPhpDocParser/PhpDocParser/ArrayItemClassNameDecorator.php b/src/BetterPhpDocParser/PhpDocParser/ArrayItemClassNameDecorator.php index 30da71e27d9..66ad52c98ff 100644 --- a/src/BetterPhpDocParser/PhpDocParser/ArrayItemClassNameDecorator.php +++ b/src/BetterPhpDocParser/PhpDocParser/ArrayItemClassNameDecorator.php @@ -49,7 +49,7 @@ public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void } $className = $this->resolveFullyQualifiedClass($splitScopeResolution[0], $phpNode); - // $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); + $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); return $node; }); diff --git a/src/BetterPhpDocParser/PhpDocParser/ConstExprClassNameDecorator.php b/src/BetterPhpDocParser/PhpDocParser/ConstExprClassNameDecorator.php index 97278b05b6c..d3a67f243b6 100644 --- a/src/BetterPhpDocParser/PhpDocParser/ConstExprClassNameDecorator.php +++ b/src/BetterPhpDocParser/PhpDocParser/ConstExprClassNameDecorator.php @@ -40,7 +40,7 @@ public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void } $className = $this->resolveFullyQualifiedClass($node, $phpNode); - // $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); + $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); return $node; }); 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/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/FileNode.php b/src/PhpParser/Node/FileNode.php index dd4da11c36d..f20f31bee4c 100644 --- a/src/PhpParser/Node/FileNode.php +++ b/src/PhpParser/Node/FileNode.php @@ -4,6 +4,7 @@ namespace Rector\PhpParser\Node; +use PhpParser\Node; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\GroupUse; use PhpParser\Node\Stmt\Namespace_; @@ -21,7 +22,7 @@ public function __construct( public array $stmts ) { $firstStmt = $stmts[0] ?? null; - parent::__construct($firstStmt instanceof \PhpParser\Node ? $firstStmt->getAttributes() : []); + parent::__construct($firstStmt instanceof Node ? $firstStmt->getAttributes() : []); parent::__construct(); @@ -32,6 +33,9 @@ public function getType(): string return 'CustomNode_File'; } + /** + * @return array + */ public function getSubNodeNames(): array { return ['stmts']; @@ -40,7 +44,7 @@ public function getSubNodeNames(): array public function isNamespaced(): bool { foreach ($this->stmts as $stmt) { - if ($stmt instanceof Stmt\Namespace_) { + if ($stmt instanceof Namespace_) { return true; } } @@ -48,7 +52,7 @@ public function isNamespaced(): bool return false; } - public function getNamespace(): ?Stmt\Namespace_ + public function getNamespace(): ?Namespace_ { /** @var Namespace_[] $namespaces */ $namespaces = array_filter($this->stmts, static fn (Stmt $stmt): bool => $stmt instanceof Namespace_); 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); @@ -84,6 +84,7 @@ public function printFormatPreserving(array $stmts, array $origStmts, array $ori */ public function print(Node | array | null $node): string { + if ($node === null) { $node = []; } @@ -92,9 +93,12 @@ public function print(Node | array | null $node): string $node = [$node]; } + $node = $this->unwrapFileNode($node); + return $this->prettyPrint($node); } +<<<<<<< HEAD /** * @param Node[] $stmts */ @@ -106,14 +110,15 @@ 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); - } + // /** + // * @api magic method in parent + // */ + // public function pFileWithoutNamespace(FileWithoutNamespace $fileWithoutNamespace): string + // { + // return $this->pStmts($fileWithoutNamespace->stmts); + // } +<<<<<<< HEAD /** * Use for standalone InterpolatedStringPart printing, that is not support by php-parser natively. * Used e.g. in \Rector\PhpParser\Comparing\NodeComparator::printWithoutComments @@ -123,6 +128,10 @@ protected function pInterpolatedStringPart(InterpolatedStringPart $interpolatedS return $interpolatedStringPart->value; } +======= +======= +>>>>>>> ddfef52951 (cleanup phpstan errors) +>>>>>>> d71688c507 (make use of FileNode in imports) protected function p( Node $node, int $precedence = self::MAX_PRECEDENCE, @@ -527,11 +536,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..89a52109ccb 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,42 +28,48 @@ 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 return NodeVisitor::STOP_TRAVERSAL; + // + // /** + // * 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 + // */ + // return NodeVisitor::STOP_TRAVERSAL; } public function shouldTraverse(array $stmts): bool 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/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/TestingParser/TestingParser.php b/src/Testing/TestingParser/TestingParser.php index cc30d466467..59e3cfb6614 100644 --- a/src/Testing/TestingParser/TestingParser.php +++ b/src/Testing/TestingParser/TestingParser.php @@ -28,6 +28,7 @@ public function __construct( public function parseFilePathToFile(string $filePath): File { +<<<<<<< HEAD <<<<<<< HEAD [$file, $stmts] = $this->parseToFileAndStmts($filePath); ======= @@ -46,6 +47,9 @@ public function parseFilePathToFile(string $filePath): File $file->hydrateStmtsAndTokens($stmts, $stmts, []); $this->currentFileProvider->setFile($file); >>>>>>> 2751658832 (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) +======= + [$file, $stmts] = $this->parseToFileAndStmts($filePath); +>>>>>>> 8e51776f69 (cleanup phpstan errors) return $file; } @@ -58,10 +62,34 @@ public function parseFileToDecoratedNodes(string $filePath): array [$file, $stmts] = $this->parseToFileAndStmts($filePath); return $stmts; +<<<<<<< HEAD } /** * @return array{0: File, 1: Node[]} +======= + // + // // needed for PHPStan reflection, as it caches the last processed file + // $this->dynamicSourceLocatorProvider->setFilePath($filePath); + // + // $fileContent = FileSystem::read($filePath); + // $stmts = $this->rectorParser->parseString($fileContent); + // $file = new File($filePath, $fileContent); + // + // // wrap in FileNode to enable file-level rules + // $stmts = [new FileNode($stmts)]; + // + // $stmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($filePath, $stmts); + // $file->hydrateStmtsAndTokens($stmts, $stmts, []); + // + // $this->currentFileProvider->setFile($file); + // + // return $stmts; + } + + /** + * @return array{0: File, 1: Node\Stmt[]} +>>>>>>> 8e51776f69 (cleanup phpstan errors) */ private function parseToFileAndStmts(string $filePath): array { @@ -70,6 +98,7 @@ private function parseToFileAndStmts(string $filePath): array $fileContent = FileSystem::read($filePath); $file = new File($filePath, $fileContent); + $stmts = $this->rectorParser->parseString($fileContent); <<<<<<< HEAD $stmts = $this->rectorParser->parseString($fileContent); diff --git a/src/ValueObject/Application/File.php b/src/ValueObject/Application/File.php index 22d50a31d64..150242fa7e5 100644 --- a/src/ValueObject/Application/File.php +++ b/src/ValueObject/Application/File.php @@ -4,6 +4,7 @@ namespace Rector\ValueObject\Application; +use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\InlineHTML; @@ -152,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[] */ 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); From 0ac86aa17015a5dac9935b8dbcc20baed61e333a Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 7 Dec 2025 12:27:06 +0100 Subject: [PATCH 04/11] add fixture to keep protected method --- .../keep_magic_parent_called_method.php.inc | 8 +++ ...FunctionLikeToFirstClassCallableRector.php | 54 ++++++++++++++ rules/Naming/Naming/UseImportsResolver.php | 21 ------ .../PrivatizeFinalClassMethodRector.php | 9 +++ .../ParentClassMagicCallGuard.php | 72 +++++++++++++++++++ .../Node/CustomNode/FileWithoutNamespace.php | 1 + src/PhpParser/Node/FileNode.php | 6 +- .../Printer/BetterStandardPrinter.php | 2 +- .../Rector/ClassRenamingPostRector.php | 12 +--- src/Testing/TestingParser/TestingParser.php | 10 ++- src/ValueObject/Application/File.php | 2 +- 11 files changed, 161 insertions(+), 36 deletions(-) create mode 100644 rules/Privatization/VisibilityGuard/ParentClassMagicCallGuard.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..aca70839a84 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,19 @@ namespace Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Fixture; use PhpParser\PrettyPrinter\Standard; +<<<<<<< HEAD +======= +use Rector\PhpParser\Node\FileNode; +>>>>>>> fb58990e39 (extract LaravelClassName) final class KeepMagicParentCalledMethod extends Standard { // called from parent by $this->{'p'} method +<<<<<<< HEAD protected function pFileNode($fileNode) +======= + protected function pFileNode(FileNode $fileNode) +>>>>>>> fb58990e39 (extract LaravelClassName) { } } diff --git a/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php b/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php index b54925dbbfd..abec37d630c 100644 --- a/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php +++ b/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php @@ -54,12 +54,66 @@ public function getNodeTypes(): array */ public function refactor(Node $node): null|CallLike { +<<<<<<< HEAD throw new ShouldNotHappenException(sprintf( '"%s" rule is deprecated. It was split into "%s" and "%s" rules.', self::class, ClosureDelegatingCallToFirstClassCallableRector::class, ArrowFunctionDelegatingCallToFirstClassCallableRector::class )); +======= + if ($node instanceof Assign) { + // @todo handle by existing attribute already + if ($node->expr instanceof Closure || $node->expr instanceof ArrowFunction) { + $node->expr->setAttribute(self::IS_IN_ASSIGN, true); + } + + return null; + } + + if ($node instanceof CallLike) { + if ($node->isFirstClassCallable()) { + return null; + } + + $methodReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node); + foreach ($node->getArgs() as $arg) { + if (! $arg->value instanceof Closure && ! $arg->value instanceof ArrowFunction) { + continue; + } + + if ($methodReflection instanceof NativeFunctionReflection) { + $parametersAcceptors = ParametersAcceptorSelector::combineAcceptors( + $methodReflection->getVariants() + ); + foreach ($parametersAcceptors->getParameters() as $extendedParameterReflection) { + if ($extendedParameterReflection->getType() instanceof CallableType && $extendedParameterReflection->getType()->isVariadic()) { + $arg->value->setAttribute(self::HAS_CALLBACK_SIGNATURE_MULTI_PARAMS, true); + } + } + + return null; + } + + $arg->value->setAttribute(self::HAS_CALLBACK_SIGNATURE_MULTI_PARAMS, true); + } + + return null; + } + + $callLike = $this->extractCallLike($node); + if ($callLike === null) { + return null; + } + + if ($this->shouldSkip($node, $callLike, ScopeFetcher::fetch($node))) { + return null; + } + + $callLike->args = [new VariadicPlaceholder()]; + + return $callLike; +>>>>>>> 24ed2fa7ac (add fixture to keep protected method) } public function provideMinPhpVersion(): int diff --git a/rules/Naming/Naming/UseImportsResolver.php b/rules/Naming/Naming/UseImportsResolver.php index 6d63f45c854..f4fcb24ea7d 100644 --- a/rules/Naming/Naming/UseImportsResolver.php +++ b/rules/Naming/Naming/UseImportsResolver.php @@ -61,25 +61,4 @@ public function resolvePrefix(Use_|GroupUse $use): string ? $use->prefix . '\\' : ''; } - - // private function resolveNamespace(): Namespace_|null - // { - // /** @var File|null $file */ - // $file = $this->currentFileProvider->getFile(); - // if (! $file instanceof File) { - // return null; - // } - // - // $newStmts = $file->getNewStmts(); - // if ($newStmts === []) { - // return null; - // } - // - // if ($newStmts[0] instanceof FileNode) { - // $fileNode = $newStmts[0]; - // return $fileNode->getNamespace(); - // } - // - // return null; - // } } diff --git a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php index 2bd4a13b4b5..3a4bd23c2e6 100644 --- a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php +++ b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php @@ -31,7 +31,11 @@ public function __construct( private readonly OverrideByParentClassGuard $overrideByParentClassGuard, private readonly BetterNodeFinder $betterNodeFinder, private readonly LaravelModelGuard $laravelModelGuard, +<<<<<<< HEAD private readonly ParentClassMagicCallGuard $parentClassMagicCallGuard, +======= + private readonly \Rector\Privatization\VisibilityGuard\ParentClassMagicCallGuard $parentClassMagicCallGuard +>>>>>>> 4263eff61e (skip parent magic called method in PrivatizeFinalClassMethodRector) ) { } @@ -85,6 +89,7 @@ public function refactor(Node $node): ?Node } $scope = ScopeFetcher::fetch($node); + $classReflection = $scope->getClassReflection(); if (! $classReflection instanceof ClassReflection) { return null; @@ -108,6 +113,10 @@ public function refactor(Node $node): ?Node continue; } + if ($this->parentClassMagicCallGuard->containsParentClassMagicCall($node)) { + continue; + } + if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByTrait( $classMethod, $classReflection diff --git a/rules/Privatization/VisibilityGuard/ParentClassMagicCallGuard.php b/rules/Privatization/VisibilityGuard/ParentClassMagicCallGuard.php new file mode 100644 index 00000000000..1f2b1ba38c7 --- /dev/null +++ b/rules/Privatization/VisibilityGuard/ParentClassMagicCallGuard.php @@ -0,0 +1,72 @@ + + */ + private array $cachedContainsByClassName = []; + + public function __construct( + private readonly NodeNameResolver $nodeNameResolver, + private readonly AstResolver $astResolver, + private readonly BetterNodeFinder $betterNodeFinder + ) { + } + + /** + * E.g. parent class has $this->{$magicName} call that might call the protected method + * If we make it private, it will break the code + */ + public function containsParentClassMagicCall(Class_ $class): bool + { + // cache as heavy AST parsing here + $className = $this->nodeNameResolver->getName($class); + if (isset($this->cachedContainsByClassName[$className])) { + return $this->cachedContainsByClassName[$className]; + } + + if ($class->extends === null) { + return false; + } + + $parentClassName = $this->nodeNameResolver->getName($class->extends); + + $parentClass = $this->astResolver->resolveClassFromName($parentClassName); + if (! $parentClass instanceof Class_) { + $this->cachedContainsByClassName[$className] = false; + return false; + } + + foreach ($parentClass->getMethods() as $classMethod) { + if ($classMethod->isAbstract()) { + continue; + } + + /** @var MethodCall[] $methodCalls */ + $methodCalls = $this->betterNodeFinder->findInstancesOfScoped( + (array) $classMethod->stmts, + MethodCall::class + ); + foreach ($methodCalls as $methodCall) { + if ($methodCall->name instanceof Expr) { + $this->cachedContainsByClassName[$className] = true; + return true; + } + } + } + + return $this->containsParentClassMagicCall($parentClass); + } +} diff --git a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php index 3220b691207..00dd425de3a 100644 --- a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php +++ b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php @@ -9,6 +9,7 @@ /** * @deprecated Use @see \Rector\PhpParser\Node\FileNode instead + * @api * * Inspired by https://github.com/phpstan/phpstan-src/commit/ed81c3ad0b9877e6122c79b4afda9d10f3994092 */ diff --git a/src/PhpParser/Node/FileNode.php b/src/PhpParser/Node/FileNode.php index f20f31bee4c..8b558ff60fd 100644 --- a/src/PhpParser/Node/FileNode.php +++ b/src/PhpParser/Node/FileNode.php @@ -28,9 +28,13 @@ public function __construct( } + /** + * This triggers Printed method with "pFileNode" name + * @see \Rector\PhpParser\Printer\BetterStandardPrinter::pStmt_FileNode() + */ public function getType(): string { - return 'CustomNode_File'; + return 'Stmt_FileNode'; } /** diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index 0b906849341..34de2323040 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -171,7 +171,7 @@ protected function p( return $content; } - protected function pCustomNode_File(FileNode $fileNode): string + protected function pStmt_FileNode(FileNode $fileNode): string { return $this->pStmts($fileNode->stmts, true); } diff --git a/src/PostRector/Rector/ClassRenamingPostRector.php b/src/PostRector/Rector/ClassRenamingPostRector.php index 89a52109ccb..32e8251e528 100644 --- a/src/PostRector/Rector/ClassRenamingPostRector.php +++ b/src/PostRector/Rector/ClassRenamingPostRector.php @@ -58,18 +58,8 @@ public function enterNode(Node $node): Namespace_|FileNode|int|null return $node; } - // nothing else to handle here + // nothing else to handle here, as first 2 nodes we'll hit are handled above return NodeVisitor::STOP_TRAVERSAL; - // - // /** - // * 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 - // */ - // return NodeVisitor::STOP_TRAVERSAL; } public function shouldTraverse(array $stmts): bool diff --git a/src/Testing/TestingParser/TestingParser.php b/src/Testing/TestingParser/TestingParser.php index 59e3cfb6614..ecc972fc3f3 100644 --- a/src/Testing/TestingParser/TestingParser.php +++ b/src/Testing/TestingParser/TestingParser.php @@ -31,6 +31,7 @@ public function parseFilePathToFile(string $filePath): File <<<<<<< HEAD <<<<<<< HEAD [$file, $stmts] = $this->parseToFileAndStmts($filePath); +<<<<<<< HEAD ======= // needed for PHPStan reflection, as it caches the last processed file $this->dynamicSourceLocatorProvider->setFilePath($filePath); @@ -51,6 +52,8 @@ public function parseFilePathToFile(string $filePath): File [$file, $stmts] = $this->parseToFileAndStmts($filePath); >>>>>>> 8e51776f69 (cleanup phpstan errors) +======= +>>>>>>> 90002f8aee (extract LaravelClassName) return $file; } @@ -60,8 +63,8 @@ public function parseFilePathToFile(string $filePath): File public function parseFileToDecoratedNodes(string $filePath): array { [$file, $stmts] = $this->parseToFileAndStmts($filePath); - return $stmts; +<<<<<<< HEAD <<<<<<< HEAD } @@ -85,6 +88,8 @@ public function parseFileToDecoratedNodes(string $filePath): array // $this->currentFileProvider->setFile($file); // // return $stmts; +======= +>>>>>>> 90002f8aee (extract LaravelClassName) } /** @@ -105,8 +110,11 @@ private function parseToFileAndStmts(string $filePath): array ======= // wrap in FileNode to enable file-level rules $stmts = [new FileNode($stmts)]; +<<<<<<< HEAD >>>>>>> 2751658832 (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) +======= +>>>>>>> 90002f8aee (extract LaravelClassName) $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 150242fa7e5..9296b6ddd6e 100644 --- a/src/ValueObject/Application/File.php +++ b/src/ValueObject/Application/File.php @@ -4,10 +4,10 @@ namespace Rector\ValueObject\Application; -use PhpParser\Node\Stmt\Namespace_; 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; From c87b73a141947840f8c6118f1eca763283fccd5e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 7 Dec 2025 13:05:07 +0100 Subject: [PATCH 05/11] require rector repo updated PRs --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index ce878e83d21..5ce6f1a07d1 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", From a9a7b2e9c00f35f1e1599e2a2f0496916ef12905 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 7 Dec 2025 19:40:37 +0100 Subject: [PATCH 06/11] add FileNode upgrade to UPGRADING --- UPGRADING.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index f5da97f986e..21a791c0b00 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,20 +1,73 @@ # 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 -@todo +**Before** -* FileWithoutNamespace is deprecated, and replaced by `FileNode` -* `beforeTraverse()` is @final, use getNodeTypes() with `FileNode::class` instead +```php +use Rector\PhpParser\Node\FileWithoutNamespace; +use Rector\Rector\AbstractRector; -**Before** +final class SomeRector extends AbstractRector +{ + public function getNodeTypes(): array + { + return [FileWithoutNamespace::class]; + } -@todo + public function beforeTraverse(array $nodes): array + { + // some node hacking + } + /** + * @param FileWithoutNamespace $node + */ + public function refactor(Node $node): ?Node + { + // ... + } + +} +``` **After** -@todo +```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 From 2dde27edd12254b7987cde4f48be36371667bd0a Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 7 Dec 2025 19:52:58 +0100 Subject: [PATCH 07/11] [ci] add job to check laravel rector with latest dev-main build --- .../workflows/rector_laravel_rector_dev.yaml | 1 + UPGRADING.md | 6 ++ phpstan.neon | 18 ----- .../keep_magic_parent_called_method.php.inc | 7 -- ...FunctionLikeToFirstClassCallableRector.php | 54 -------------- .../PrivatizeFinalClassMethodRector.php | 8 --- .../ParentClassMagicCallGuard.php | 72 ------------------- .../Printer/BetterStandardPrinter.php | 14 ---- src/Testing/TestingParser/TestingParser.php | 60 ---------------- 9 files changed, 7 insertions(+), 233 deletions(-) delete mode 100644 rules/Privatization/VisibilityGuard/ParentClassMagicCallGuard.php 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 21a791c0b00..fd11354925a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -67,6 +67,12 @@ final class SomeRector extends AbstractRector } ``` +The `FileNode` handles both namespaced and non-namespaced files. To check if the file is namespaced, use: + +```php +$fileNode->isNamespaced(); +``` +
# Upgrading from Rector 1.x to 2.0 diff --git a/phpstan.neon b/phpstan.neon index f4c8694f547..47ee954c6b2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -408,17 +408,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#' -<<<<<<< HEAD - - # 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 - @@ -434,7 +423,6 @@ parameters: - rules/Php55/Rector/String_/StringClassNameToClassConstantRector.php - rules/Php81/Enum/AttributeName.php -<<<<<<< HEAD - identifier: symplify.seeAnnotationToTest paths: @@ -448,9 +436,3 @@ parameters: - message: '#Only abstract classes can be extended#' path: rules/Php81/Rector/Array_/FirstClassCallableRector.php - - -======= -======= ->>>>>>> ddfef52951 (cleanup phpstan errors) ->>>>>>> d71688c507 (make use of FileNode in imports) 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 aca70839a84..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,19 +3,12 @@ namespace Rector\Tests\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector\Fixture; use PhpParser\PrettyPrinter\Standard; -<<<<<<< HEAD -======= use Rector\PhpParser\Node\FileNode; ->>>>>>> fb58990e39 (extract LaravelClassName) final class KeepMagicParentCalledMethod extends Standard { // called from parent by $this->{'p'} method -<<<<<<< HEAD - protected function pFileNode($fileNode) -======= protected function pFileNode(FileNode $fileNode) ->>>>>>> fb58990e39 (extract LaravelClassName) { } } diff --git a/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php b/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php index abec37d630c..b54925dbbfd 100644 --- a/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php +++ b/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php @@ -54,66 +54,12 @@ public function getNodeTypes(): array */ public function refactor(Node $node): null|CallLike { -<<<<<<< HEAD throw new ShouldNotHappenException(sprintf( '"%s" rule is deprecated. It was split into "%s" and "%s" rules.', self::class, ClosureDelegatingCallToFirstClassCallableRector::class, ArrowFunctionDelegatingCallToFirstClassCallableRector::class )); -======= - if ($node instanceof Assign) { - // @todo handle by existing attribute already - if ($node->expr instanceof Closure || $node->expr instanceof ArrowFunction) { - $node->expr->setAttribute(self::IS_IN_ASSIGN, true); - } - - return null; - } - - if ($node instanceof CallLike) { - if ($node->isFirstClassCallable()) { - return null; - } - - $methodReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node); - foreach ($node->getArgs() as $arg) { - if (! $arg->value instanceof Closure && ! $arg->value instanceof ArrowFunction) { - continue; - } - - if ($methodReflection instanceof NativeFunctionReflection) { - $parametersAcceptors = ParametersAcceptorSelector::combineAcceptors( - $methodReflection->getVariants() - ); - foreach ($parametersAcceptors->getParameters() as $extendedParameterReflection) { - if ($extendedParameterReflection->getType() instanceof CallableType && $extendedParameterReflection->getType()->isVariadic()) { - $arg->value->setAttribute(self::HAS_CALLBACK_SIGNATURE_MULTI_PARAMS, true); - } - } - - return null; - } - - $arg->value->setAttribute(self::HAS_CALLBACK_SIGNATURE_MULTI_PARAMS, true); - } - - return null; - } - - $callLike = $this->extractCallLike($node); - if ($callLike === null) { - return null; - } - - if ($this->shouldSkip($node, $callLike, ScopeFetcher::fetch($node))) { - return null; - } - - $callLike->args = [new VariadicPlaceholder()]; - - return $callLike; ->>>>>>> 24ed2fa7ac (add fixture to keep protected method) } public function provideMinPhpVersion(): int diff --git a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php index 3a4bd23c2e6..4b8061775e6 100644 --- a/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php +++ b/rules/Privatization/Rector/ClassMethod/PrivatizeFinalClassMethodRector.php @@ -31,11 +31,7 @@ public function __construct( private readonly OverrideByParentClassGuard $overrideByParentClassGuard, private readonly BetterNodeFinder $betterNodeFinder, private readonly LaravelModelGuard $laravelModelGuard, -<<<<<<< HEAD private readonly ParentClassMagicCallGuard $parentClassMagicCallGuard, -======= - private readonly \Rector\Privatization\VisibilityGuard\ParentClassMagicCallGuard $parentClassMagicCallGuard ->>>>>>> 4263eff61e (skip parent magic called method in PrivatizeFinalClassMethodRector) ) { } @@ -113,10 +109,6 @@ public function refactor(Node $node): ?Node continue; } - if ($this->parentClassMagicCallGuard->containsParentClassMagicCall($node)) { - continue; - } - if ($this->classMethodVisibilityGuard->isClassMethodVisibilityGuardedByTrait( $classMethod, $classReflection diff --git a/rules/Privatization/VisibilityGuard/ParentClassMagicCallGuard.php b/rules/Privatization/VisibilityGuard/ParentClassMagicCallGuard.php deleted file mode 100644 index 1f2b1ba38c7..00000000000 --- a/rules/Privatization/VisibilityGuard/ParentClassMagicCallGuard.php +++ /dev/null @@ -1,72 +0,0 @@ - - */ - private array $cachedContainsByClassName = []; - - public function __construct( - private readonly NodeNameResolver $nodeNameResolver, - private readonly AstResolver $astResolver, - private readonly BetterNodeFinder $betterNodeFinder - ) { - } - - /** - * E.g. parent class has $this->{$magicName} call that might call the protected method - * If we make it private, it will break the code - */ - public function containsParentClassMagicCall(Class_ $class): bool - { - // cache as heavy AST parsing here - $className = $this->nodeNameResolver->getName($class); - if (isset($this->cachedContainsByClassName[$className])) { - return $this->cachedContainsByClassName[$className]; - } - - if ($class->extends === null) { - return false; - } - - $parentClassName = $this->nodeNameResolver->getName($class->extends); - - $parentClass = $this->astResolver->resolveClassFromName($parentClassName); - if (! $parentClass instanceof Class_) { - $this->cachedContainsByClassName[$className] = false; - return false; - } - - foreach ($parentClass->getMethods() as $classMethod) { - if ($classMethod->isAbstract()) { - continue; - } - - /** @var MethodCall[] $methodCalls */ - $methodCalls = $this->betterNodeFinder->findInstancesOfScoped( - (array) $classMethod->stmts, - MethodCall::class - ); - foreach ($methodCalls as $methodCall) { - if ($methodCall->name instanceof Expr) { - $this->cachedContainsByClassName[$className] = true; - return true; - } - } - } - - return $this->containsParentClassMagicCall($parentClass); - } -} diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index 34de2323040..46ad6524b8d 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -98,7 +98,6 @@ public function print(Node | array | null $node): string return $this->prettyPrint($node); } -<<<<<<< HEAD /** * @param Node[] $stmts */ @@ -110,15 +109,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); - // } - -<<<<<<< HEAD /** * Use for standalone InterpolatedStringPart printing, that is not support by php-parser natively. * Used e.g. in \Rector\PhpParser\Comparing\NodeComparator::printWithoutComments @@ -128,10 +118,6 @@ protected function pInterpolatedStringPart(InterpolatedStringPart $interpolatedS return $interpolatedStringPart->value; } -======= -======= ->>>>>>> ddfef52951 (cleanup phpstan errors) ->>>>>>> d71688c507 (make use of FileNode in imports) protected function p( Node $node, int $precedence = self::MAX_PRECEDENCE, diff --git a/src/Testing/TestingParser/TestingParser.php b/src/Testing/TestingParser/TestingParser.php index ecc972fc3f3..5bace90461a 100644 --- a/src/Testing/TestingParser/TestingParser.php +++ b/src/Testing/TestingParser/TestingParser.php @@ -28,32 +28,7 @@ public function __construct( public function parseFilePathToFile(string $filePath): File { -<<<<<<< HEAD -<<<<<<< HEAD [$file, $stmts] = $this->parseToFileAndStmts($filePath); -<<<<<<< HEAD -======= - // needed for PHPStan reflection, as it caches the last processed file - $this->dynamicSourceLocatorProvider->setFilePath($filePath); - - $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, []); - $this->currentFileProvider->setFile($file); ->>>>>>> 2751658832 (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) -======= - [$file, $stmts] = $this->parseToFileAndStmts($filePath); ->>>>>>> 8e51776f69 (cleanup phpstan errors) - -======= ->>>>>>> 90002f8aee (extract LaravelClassName) return $file; } @@ -64,37 +39,10 @@ public function parseFileToDecoratedNodes(string $filePath): array { [$file, $stmts] = $this->parseToFileAndStmts($filePath); return $stmts; -<<<<<<< HEAD -<<<<<<< HEAD } /** * @return array{0: File, 1: Node[]} -======= - // - // // needed for PHPStan reflection, as it caches the last processed file - // $this->dynamicSourceLocatorProvider->setFilePath($filePath); - // - // $fileContent = FileSystem::read($filePath); - // $stmts = $this->rectorParser->parseString($fileContent); - // $file = new File($filePath, $fileContent); - // - // // wrap in FileNode to enable file-level rules - // $stmts = [new FileNode($stmts)]; - // - // $stmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($filePath, $stmts); - // $file->hydrateStmtsAndTokens($stmts, $stmts, []); - // - // $this->currentFileProvider->setFile($file); - // - // return $stmts; -======= ->>>>>>> 90002f8aee (extract LaravelClassName) - } - - /** - * @return array{0: File, 1: Node\Stmt[]} ->>>>>>> 8e51776f69 (cleanup phpstan errors) */ private function parseToFileAndStmts(string $filePath): array { @@ -105,16 +53,8 @@ private function parseToFileAndStmts(string $filePath): array $file = new File($filePath, $fileContent); $stmts = $this->rectorParser->parseString($fileContent); -<<<<<<< HEAD - $stmts = $this->rectorParser->parseString($fileContent); -======= // wrap in FileNode to enable file-level rules $stmts = [new FileNode($stmts)]; -<<<<<<< HEAD - ->>>>>>> 2751658832 (introduce FileNode to handle file-level changes; deprecate IncreaseDeclareStrictTypesRector) -======= ->>>>>>> 90002f8aee (extract LaravelClassName) $stmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($filePath, $stmts); $file->hydrateStmtsAndTokens($stmts, $stmts, []); From da7fdb59c54d360ae0c346e34e7f41cb3f1e4c0d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 22 Dec 2025 10:56:39 +0100 Subject: [PATCH 08/11] add node matching support for FileWithoutNamespace --- composer.json | 6 +-- phpstan.neon | 31 ++++++++++++++- src/Application/FileProcessor.php | 1 + .../Node/CustomNode/FileWithoutNamespace.php | 9 +++-- src/PhpParser/Node/FileNode.php | 10 ++--- .../NodeTraverser/RectorNodeTraverser.php | 10 +++++ .../Printer/BetterStandardPrinter.php | 1 - .../FileWithoutNamespaceCompatTest.php | 28 +++++++++++++ .../Fixture/namespace_less_file.php.inc | 16 ++++++++ ...SubscribedToFileWithoutNamespaceRector.php | 39 +++++++++++++++++++ .../config/configured_rule.php | 8 ++++ 11 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 tests/Issues/FileWithoutNamespaceCompat/FileWithoutNamespaceCompatTest.php create mode 100644 tests/Issues/FileWithoutNamespaceCompat/Fixture/namespace_less_file.php.inc create mode 100644 tests/Issues/FileWithoutNamespaceCompat/Rector/SubscribedToFileWithoutNamespaceRector.php create mode 100644 tests/Issues/FileWithoutNamespaceCompat/config/configured_rule.php diff --git a/composer.json b/composer.json index 5ce6f1a07d1..ce878e83d21 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-tv-file-node", - "rector/rector-downgrade-php": "dev-tv-file-node", - "rector/rector-phpunit": "dev-tv-file-node", + "rector/rector-doctrine": "dev-main", + "rector/rector-downgrade-php": "dev-main", + "rector/rector-phpunit": "dev-main", "rector/rector-symfony": "dev-main", "sebastian/diff": "^6.0", "symfony/console": "^6.4.24", diff --git a/phpstan.neon b/phpstan.neon index 47ee954c6b2..cb8722bf283 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -189,8 +189,10 @@ parameters: message: '#Method Rector\\Util\\ArrayParametersMerger\:\:mergeLeftToRightWithCallable\(\) has parameter \$mergeCallback with no signature specified for callable#' path: src/Util/ArrayParametersMerger.php - # fixture class - - '#Class "Rector\\Tests\\Issues\\ScopeNotAvailable\\Variable\\ArrayItemForeachValueRector" is missing @see annotation with test case class reference#' + # fixture Rector rules + - + identifier: symplify.seeAnnotationToTest + path: tests/Issues/ # classes are part of *.php.inc fixture - @@ -415,6 +417,16 @@ parameters: paths: - rules/Transform/ValueObject/ClassMethodReference.php - rules/CodeQuality/ValueObject/KeyAndExpr.php + - + identifier: symplify.forbiddenExtendOfNonAbstractClass + path: src/PhpParser/Node/FileNode.php + - + identifier: method.deprecatedClass + path: src/PhpParser/Node/FileNode.php + + - + identifier: class.extendsDeprecatedClass + path: src/PhpParser/Node/FileNode.php - identifier: public.classConstant.unused @@ -433,6 +445,21 @@ parameters: - '#Register "Rector\\Php81\\Rector\\Array_\\FirstClassCallableRector" service to "php81\.php" config set#' - '#Class "Rector\\CodingStyle\\Rector\\String_\\SymplifyQuoteEscapeRector" is missing @see annotation with test case class reference#' - '#Access to constant on deprecated class Rector\\Php81\\Rector\\Array_\\FirstClassCallableRector#' + + # BC layer for FileWithoutNamespace node + - message: '#Use @see \\Rector\\PhpParser\\Node\\FileNode instead#' + - '#BC layer for FileNode and FileWithoutNamespace compat, use FileNode instead#' + - + path: src/PhpParser/Node/CustomNode/FileWithoutNamespace.php + identifier: symplify.forbiddenExtendOfNonAbstractClass - message: '#Only abstract classes can be extended#' path: rules/Php81/Rector/Array_/FirstClassCallableRector.php + + # BC layer for FileWithoutNamespace node + - message: '#Use @see \\Rector\\PhpParser\\Node\\FileNode instead#' + + + - + path: src/PhpParser/Node/CustomNode/FileWithoutNamespace.php + identifier: symplify.forbiddenExtendOfNonAbstractClass diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index 0cd2f51f166..fa9e863e4d4 100644 --- a/src/Application/FileProcessor.php +++ b/src/Application/FileProcessor.php @@ -58,6 +58,7 @@ 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 diff --git a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php index 00dd425de3a..b087ab1a604 100644 --- a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php +++ b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php @@ -5,7 +5,7 @@ namespace Rector\PhpParser\Node\CustomNode; use PhpParser\Node\Stmt; -use Rector\Contract\PhpParser\Node\StmtsAwareInterface; +use Rector\PhpParser\Node\FileNode; /** * @deprecated Use @see \Rector\PhpParser\Node\FileNode instead @@ -13,15 +13,16 @@ * * Inspired by https://github.com/phpstan/phpstan-src/commit/ed81c3ad0b9877e6122c79b4afda9d10f3994092 */ -final class FileWithoutNamespace extends Stmt implements StmtsAwareInterface +final class FileWithoutNamespace extends FileNode { /** * @param Stmt[] $stmts */ public function __construct( - public array $stmts + public array $stmts, + public array $attributes = [] ) { - parent::__construct(); + parent::__construct($stmts, $attributes); } public function getType(): string diff --git a/src/PhpParser/Node/FileNode.php b/src/PhpParser/Node/FileNode.php index 8b558ff60fd..149da180adc 100644 --- a/src/PhpParser/Node/FileNode.php +++ b/src/PhpParser/Node/FileNode.php @@ -13,19 +13,19 @@ /** * Inspired by https://github.com/phpstan/phpstan-src/commit/ed81c3ad0b9877e6122c79b4afda9d10f3994092 */ -final class FileNode extends Stmt +class FileNode extends Stmt { /** * @param Stmt[] $stmts */ public function __construct( - public array $stmts + public array $stmts, + array $attributes = [] ) { $firstStmt = $stmts[0] ?? null; - parent::__construct($firstStmt instanceof Node ? $firstStmt->getAttributes() : []); - - parent::__construct(); + $attributes = $firstStmt instanceof Node ? $firstStmt->getAttributes() : []; + parent::__construct($attributes); } /** diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index b91a82bc225..c9918795e61 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -9,6 +9,8 @@ use PhpParser\NodeVisitor; use Rector\Configuration\ConfigurationRuleFilter; use Rector\Contract\Rector\RectorInterface; +use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace; +use Rector\PhpParser\Node\FileNode; use Rector\VersionBonding\PhpVersionedFilter; /** @@ -71,6 +73,14 @@ public function getVisitorsForNode(Node $node): array /** @var RectorInterface $visitor */ foreach ($this->visitors as $visitor) { foreach ($visitor->getNodeTypes() as $nodeType) { + // BC layer matching + if ($nodeType === FileWithoutNamespace::class) { + if ($nodeClass === FileNode::class) { + $this->visitorsPerNodeClass[$nodeClass][] = $visitor; + continue; + } + } + if (is_a($nodeClass, $nodeType, true)) { $this->visitorsPerNodeClass[$nodeClass][] = $visitor; continue 2; diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index 46ad6524b8d..d2085750d62 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -524,7 +524,6 @@ private function getIndentCharacter(): string */ private function unwrapFileNode(array $stmts): array { - // $stmts = array_values($stmts); if (count($stmts) === 1 && $stmts[0] instanceof FileNode) { return array_values($stmts[0]->stmts); } diff --git a/tests/Issues/FileWithoutNamespaceCompat/FileWithoutNamespaceCompatTest.php b/tests/Issues/FileWithoutNamespaceCompat/FileWithoutNamespaceCompatTest.php new file mode 100644 index 00000000000..f4f57be3b9e --- /dev/null +++ b/tests/Issues/FileWithoutNamespaceCompat/FileWithoutNamespaceCompatTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Issues/FileWithoutNamespaceCompat/Fixture/namespace_less_file.php.inc b/tests/Issues/FileWithoutNamespaceCompat/Fixture/namespace_less_file.php.inc new file mode 100644 index 00000000000..c6a2f6dbc4d --- /dev/null +++ b/tests/Issues/FileWithoutNamespaceCompat/Fixture/namespace_less_file.php.inc @@ -0,0 +1,16 @@ + +----- +namespacedName = new Node\Name('someFunction'); + + $node->stmts[] = $function; + + return $node; + } +} diff --git a/tests/Issues/FileWithoutNamespaceCompat/config/configured_rule.php b/tests/Issues/FileWithoutNamespaceCompat/config/configured_rule.php new file mode 100644 index 00000000000..840287d6a04 --- /dev/null +++ b/tests/Issues/FileWithoutNamespaceCompat/config/configured_rule.php @@ -0,0 +1,8 @@ +withRules([\Rector\Tests\Issues\FileWithoutNamespaceCompat\Rector\SubscribedToFileWithoutNamespaceRector::class]); From 65c921ee1b96b58b6780031c7a4b6ed0a3d7dd42 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 22 Dec 2025 11:02:12 +0100 Subject: [PATCH 09/11] warn about deprecated type --- .github/workflows/code_analysis.yaml | 2 +- UPGRADING.md | 2 +- phpstan.neon | 5 +-- .../DeclareStrictTypesRector.php | 33 +++---------------- src/Console/Command/ProcessCommand.php | 1 + .../Node/CustomNode/FileWithoutNamespace.php | 8 ++--- src/PhpParser/Node/FileNode.php | 1 - .../NodeTraverser/RectorNodeTraverser.php | 8 ++--- src/Reporting/DeprecatedRulesReporter.php | 25 ++++++++++++++ src/ValueObject/Application/File.php | 5 +++ ...SubscribedToFileWithoutNamespaceRector.php | 3 +- .../config/configured_rule.php | 3 +- 12 files changed, 51 insertions(+), 45 deletions(-) diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml index 5784b9bb521..830d8e23d3e 100644 --- a/.github/workflows/code_analysis.yaml +++ b/.github/workflows/code_analysis.yaml @@ -50,7 +50,7 @@ jobs: - name: 'Finalize classes' - run: vendor/bin/swiss-knife finalize-classes src tests rules --dry-run + run: vendor/bin/swiss-knife finalize-classes src tests rules --dry-run --skip-file="src/PhpParser/Node/FileNode.php" - name: 'Check before/after test fixture on no-changes' diff --git a/UPGRADING.md b/UPGRADING.md index fd11354925a..0ba64a115dc 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,4 +1,4 @@ -# Upgrading from Rector 2.2.11 to 2.2.12 +# Upgrading from Rector 2.2.14 to 2.3 * `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 diff --git a/phpstan.neon b/phpstan.neon index cb8722bf283..be9ef0658dc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -456,10 +456,11 @@ parameters: message: '#Only abstract classes can be extended#' path: rules/Php81/Rector/Array_/FirstClassCallableRector.php + - '#Method Rector\\Tests\\Issues\\FileWithoutNamespaceCompat\\Rector\\SubscribedToFileWithoutNamespaceRector\:\:refactor\(\) should return Rector\\PhpParser\\Node\\FileNode but returns Rector\\PhpParser\\Node\\CustomNode\\FileWithoutNamespace#' + # BC layer for FileWithoutNamespace node - message: '#Use @see \\Rector\\PhpParser\\Node\\FileNode instead#' - - + - '#Class Rector\\PhpParser\\Node\\CustomNode\\FileWithoutNamespace extends final class Rector\\PhpParser\\Node\\FileNode#' - path: src/PhpParser/Node/CustomNode/FileWithoutNamespace.php identifier: symplify.forbiddenExtendOfNonAbstractClass diff --git a/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php b/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php index d221c74aa2e..87467db79e9 100644 --- a/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php +++ b/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php @@ -11,7 +11,6 @@ use Rector\PhpParser\Node\FileNode; use Rector\Rector\AbstractRector; use Rector\TypeDeclaration\NodeAnalyzer\DeclareStrictTypeFinder; -use Rector\ValueObject\Application\File; use Rector\ValueObject\PhpVersion; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -64,14 +63,12 @@ function someFunction(int $number) */ public function refactor(Node $node): ?FileNode { - // parent::beforeTraverse($nodes); - - if ($this->shouldSkipNodes($node->stmts, $this->file)) { + // shebang files cannot have declare strict types + if ($this->file->hasShebang()) { return null; } - // /** @var Node $rootStmt */ - // $rootStmt = current(); + // only add to namespaced files, as global namespace files are often included in other files if (! $node->isNamespaced()) { return null; } @@ -82,13 +79,10 @@ public function refactor(Node $node): ?FileNode return null; } - // $rectorWithLineChange = new RectorWithLineChange(self::class, $rootStmt->getStartLine()); - // $this->file->addRectorClassWithLine($rectorWithLineChange); - - $node->stmts = array_merge([$this->nodeFactory->createDeclaresStrictType(), new Nop()], $node->stmts); + $declaresStrictType = $this->nodeFactory->createDeclaresStrictType(); + $node->stmts = array_merge([$declaresStrictType, new Nop()], $node->stmts); return $node; - // return [$this->nodeFactory->createDeclaresStrictType(), new Nop(), ...$nodes]; } /** @@ -103,21 +97,4 @@ public function provideMinPhpVersion(): int { return PhpVersion::PHP_70; } - - /** - * @param Stmt[] $nodes - */ - private function shouldSkipNodes(array $nodes, File $file): bool - { - if ($this->skipper->shouldSkipElementAndFilePath(self::class, $file->getFilePath())) { - return true; - } - - // shebang files cannot have declare strict types - if (str_starts_with($file->getFileContent(), '#!')) { - return true; - } - - return $nodes === []; - } } diff --git a/src/Console/Command/ProcessCommand.php b/src/Console/Command/ProcessCommand.php index 03907c86892..6f6edea58ef 100644 --- a/src/Console/Command/ProcessCommand.php +++ b/src/Console/Command/ProcessCommand.php @@ -179,6 +179,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $outputFormatter->report($processResult, $configuration); + // 4. Deprecations reporter $this->deprecatedRulesReporter->reportDeprecatedRules(); $this->deprecatedRulesReporter->reportDeprecatedSkippedRules(); $this->deprecatedRulesReporter->reportDeprecatedNodeTypes(); diff --git a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php index b087ab1a604..1ce633055d9 100644 --- a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php +++ b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php @@ -18,11 +18,9 @@ final class FileWithoutNamespace extends FileNode /** * @param Stmt[] $stmts */ - public function __construct( - public array $stmts, - public array $attributes = [] - ) { - parent::__construct($stmts, $attributes); + public function __construct(array $stmts) + { + parent::__construct($stmts); } public function getType(): string diff --git a/src/PhpParser/Node/FileNode.php b/src/PhpParser/Node/FileNode.php index 149da180adc..be23ed4a3b5 100644 --- a/src/PhpParser/Node/FileNode.php +++ b/src/PhpParser/Node/FileNode.php @@ -20,7 +20,6 @@ class FileNode extends Stmt */ public function __construct( public array $stmts, - array $attributes = [] ) { $firstStmt = $stmts[0] ?? null; $attributes = $firstStmt instanceof Node ? $firstStmt->getAttributes() : []; diff --git a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php index c9918795e61..fe61e018022 100644 --- a/src/PhpParser/NodeTraverser/RectorNodeTraverser.php +++ b/src/PhpParser/NodeTraverser/RectorNodeTraverser.php @@ -74,11 +74,9 @@ public function getVisitorsForNode(Node $node): array foreach ($this->visitors as $visitor) { foreach ($visitor->getNodeTypes() as $nodeType) { // BC layer matching - if ($nodeType === FileWithoutNamespace::class) { - if ($nodeClass === FileNode::class) { - $this->visitorsPerNodeClass[$nodeClass][] = $visitor; - continue; - } + if ($nodeType === FileWithoutNamespace::class && $nodeClass === FileNode::class) { + $this->visitorsPerNodeClass[$nodeClass][] = $visitor; + continue; } if (is_a($nodeClass, $nodeType, true)) { diff --git a/src/Reporting/DeprecatedRulesReporter.php b/src/Reporting/DeprecatedRulesReporter.php index aa5ef694428..b5d1fe82d4f 100644 --- a/src/Reporting/DeprecatedRulesReporter.php +++ b/src/Reporting/DeprecatedRulesReporter.php @@ -10,8 +10,16 @@ use Rector\Contract\PhpParser\Node\StmtsAwareInterface; use Rector\Contract\Rector\RectorInterface; use Rector\PhpParser\Enum\NodeGroup; +<<<<<<< HEAD use Rector\PhpParserNode\FileNode; +======= +<<<<<<< HEAD +>>>>>>> 6bcee47e76 (warn about deprecated type) use ReflectionMethod; +======= +use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace; +use Rector\PhpParser\Node\FileNode; +>>>>>>> 77056cdc84 (warn about deprecated type) use Symfony\Component\Console\Style\SymfonyStyle; final readonly class DeprecatedRulesReporter @@ -83,6 +91,11 @@ public function reportDeprecatedNodeTypes(): void static $reportedClasses = []; foreach ($this->rectors as $rector) { + if (in_array(FileWithoutNamespace::class, $rector->getNodeTypes(), true)) { + $this->reportDeprecatedFileWithoutNamespace($rector); + continue; + } + if (! in_array(StmtsAwareInterface::class, $rector->getNodeTypes())) { continue; } @@ -105,4 +118,16 @@ public function reportDeprecatedNodeTypes(): void )); } } + + private function reportDeprecatedFileWithoutNamespace(RectorInterface $rector): void + { + $this->symfonyStyle->warning(sprintf( + 'Node type "%s" is deprecated and will be removed. Use "%s" in the "%s" rule instead instead.%sSee %s for upgrade path', + FileWithoutNamespace::class, + FileNode::class, + $rector::class, + PHP_EOL . PHP_EOL, + 'https://github.com/rectorphp/rector-src/blob/main/UPGRADING.md' + )); + } } diff --git a/src/ValueObject/Application/File.php b/src/ValueObject/Application/File.php index 9296b6ddd6e..0dfcd462bee 100644 --- a/src/ValueObject/Application/File.php +++ b/src/ValueObject/Application/File.php @@ -218,4 +218,9 @@ public function getFileNode(): ?FileNode return null; } + + public function hasShebang(): bool + { + return str_starts_with($this->getFileContent(), '#!'); + } } diff --git a/tests/Issues/FileWithoutNamespaceCompat/Rector/SubscribedToFileWithoutNamespaceRector.php b/tests/Issues/FileWithoutNamespaceCompat/Rector/SubscribedToFileWithoutNamespaceRector.php index 1bce4932453..6dc90169a31 100644 --- a/tests/Issues/FileWithoutNamespaceCompat/Rector/SubscribedToFileWithoutNamespaceRector.php +++ b/tests/Issues/FileWithoutNamespaceCompat/Rector/SubscribedToFileWithoutNamespaceRector.php @@ -5,6 +5,7 @@ namespace Rector\Tests\Issues\FileWithoutNamespaceCompat\Rector; use PhpParser\Node; +use PhpParser\Node\Name; use PhpParser\Node\Stmt\Function_; use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace; use Rector\PhpParser\Node\FileNode; @@ -30,7 +31,7 @@ public function refactor(Node $node): FileNode { $function = new Function_('someFunction'); // required for PHPStan scope resolver refresh - $function->namespacedName = new Node\Name('someFunction'); + $function->namespacedName = new Name('someFunction'); $node->stmts[] = $function; diff --git a/tests/Issues/FileWithoutNamespaceCompat/config/configured_rule.php b/tests/Issues/FileWithoutNamespaceCompat/config/configured_rule.php index 840287d6a04..ddceb900caf 100644 --- a/tests/Issues/FileWithoutNamespaceCompat/config/configured_rule.php +++ b/tests/Issues/FileWithoutNamespaceCompat/config/configured_rule.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Rector\Config\RectorConfig; +use Rector\Tests\Issues\FileWithoutNamespaceCompat\Rector\SubscribedToFileWithoutNamespaceRector; return RectorConfig::configure() - ->withRules([\Rector\Tests\Issues\FileWithoutNamespaceCompat\Rector\SubscribedToFileWithoutNamespaceRector::class]); + ->withRules([SubscribedToFileWithoutNamespaceRector::class]); From fbd4e4cfac6894735109e45f01455cd5ecb7822b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 22 Dec 2025 11:28:33 +0100 Subject: [PATCH 10/11] print stmts with false --- .../Rector/StmtsAwareInterface/DeclareStrictTypesRector.php | 1 - src/PhpParser/Node/CustomNode/FileWithoutNamespace.php | 3 ++- src/PhpParser/Printer/BetterStandardPrinter.php | 2 +- src/Reporting/DeprecatedRulesReporter.php | 5 ++++- src/ValueObject/Application/File.php | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php b/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php index 87467db79e9..7cb04dfe14e 100644 --- a/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php +++ b/rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php @@ -5,7 +5,6 @@ namespace Rector\TypeDeclaration\Rector\StmtsAwareInterface; use PhpParser\Node; -use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Nop; use Rector\Contract\Rector\HTMLAverseRectorInterface; use Rector\PhpParser\Node\FileNode; diff --git a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php index 1ce633055d9..86706c48994 100644 --- a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php +++ b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php @@ -5,6 +5,7 @@ namespace Rector\PhpParser\Node\CustomNode; use PhpParser\Node\Stmt; +use Rector\Contract\PhpParser\Node\StmtsAwareInterface; use Rector\PhpParser\Node\FileNode; /** @@ -13,7 +14,7 @@ * * Inspired by https://github.com/phpstan/phpstan-src/commit/ed81c3ad0b9877e6122c79b4afda9d10f3994092 */ -final class FileWithoutNamespace extends FileNode +final class FileWithoutNamespace extends FileNode implements StmtsAwareInterface { /** * @param Stmt[] $stmts diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index d2085750d62..97543ea602d 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -159,7 +159,7 @@ protected function p( protected function pStmt_FileNode(FileNode $fileNode): string { - return $this->pStmts($fileNode->stmts, true); + return $this->pStmts($fileNode->stmts); } protected function pExpr_ArrowFunction(ArrowFunction $arrowFunction, int $precedence, int $lhsPrecedence): string diff --git a/src/Reporting/DeprecatedRulesReporter.php b/src/Reporting/DeprecatedRulesReporter.php index b5d1fe82d4f..7987fefa1f4 100644 --- a/src/Reporting/DeprecatedRulesReporter.php +++ b/src/Reporting/DeprecatedRulesReporter.php @@ -11,15 +11,18 @@ use Rector\Contract\Rector\RectorInterface; use Rector\PhpParser\Enum\NodeGroup; <<<<<<< HEAD +<<<<<<< HEAD use Rector\PhpParserNode\FileNode; ======= <<<<<<< HEAD >>>>>>> 6bcee47e76 (warn about deprecated type) use ReflectionMethod; ======= +======= +>>>>>>> 00c8276150 (print stmts with false) use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace; use Rector\PhpParser\Node\FileNode; ->>>>>>> 77056cdc84 (warn about deprecated type) +use ReflectionMethod; use Symfony\Component\Console\Style\SymfonyStyle; final readonly class DeprecatedRulesReporter diff --git a/src/ValueObject/Application/File.php b/src/ValueObject/Application/File.php index 0dfcd462bee..33c354d4a05 100644 --- a/src/ValueObject/Application/File.php +++ b/src/ValueObject/Application/File.php @@ -221,6 +221,6 @@ public function getFileNode(): ?FileNode public function hasShebang(): bool { - return str_starts_with($this->getFileContent(), '#!'); + return str_starts_with($this->fileContent, '#!'); } } From ff771b0adcec70f445f5a0f52bc74fbfb621c0dd Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 22 Dec 2025 14:31:41 +0100 Subject: [PATCH 11/11] [docs] add specific example how to iterate stmts of namespaced and non-namespaced file --- UPGRADING.md | 36 +++++++++++++++++-- composer-dependency-analyser.php | 2 -- .../Node/CustomNode/FileWithoutNamespace.php | 9 ----- src/Reporting/DeprecatedRulesReporter.php | 10 ------ 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 0ba64a115dc..f2089bac57d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,7 +1,7 @@ # Upgrading from Rector 2.2.14 to 2.3 * `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 +* `beforeTraverse()` is now marked as `@final`, use `getNodeTypes()` with `FileNode::class` instead **Before** @@ -67,10 +67,40 @@ final class SomeRector extends AbstractRector } ``` -The `FileNode` handles both namespaced and non-namespaced files. To check if the file is namespaced, use: +
+ +The `FileNode` handles both namespaced and non-namespaced files. To handle the first stmts inside the file, you hook into 2 nodes: ```php -$fileNode->isNamespaced(); +use Rector\PhpParser\Node\FileNode; +use Rector\Rector\AbstractRector; +use PhpParser\Node\Stmt\Namespace_; + +final class SomeRector extends AbstractRector +{ + public function getNodeTypes(): array + { + return [FileNode::class, Namespace_::class]; + } + + /** + * @param FileNode|Namespace_ $node + */ + public function refactor(Node $node): ?Node + { + if ($node instanceof FileNode && $node->isNamespaced()) { + // handled in the Namespace_ node + return null; + } + + foreach ($node->stmts as $stmt) { + // modify stmts in desired way here + } + + return $node; + } + +} ```
diff --git a/composer-dependency-analyser.php b/composer-dependency-analyser.php index 8ee1533e5eb..9b360269026 100644 --- a/composer-dependency-analyser.php +++ b/composer-dependency-analyser.php @@ -17,8 +17,6 @@ // ensure use version ^3.2.0 ->ignoreErrorsOnPackage('composer/pcre', [ErrorType::UNUSED_DEPENDENCY]) - ->ignoreErrorsOnPath(__DIR__ . '/src/Reporting/DeprecatedRulesReporter.php', [ErrorType::UNKNOWN_CLASS]) - ->ignoreErrorsOnPaths([ __DIR__ . '/stubs', __DIR__ . '/tests', diff --git a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php index 86706c48994..5bb6093891d 100644 --- a/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php +++ b/src/PhpParser/Node/CustomNode/FileWithoutNamespace.php @@ -4,7 +4,6 @@ namespace Rector\PhpParser\Node\CustomNode; -use PhpParser\Node\Stmt; use Rector\Contract\PhpParser\Node\StmtsAwareInterface; use Rector\PhpParser\Node\FileNode; @@ -16,14 +15,6 @@ */ final class FileWithoutNamespace extends FileNode implements StmtsAwareInterface { - /** - * @param Stmt[] $stmts - */ - public function __construct(array $stmts) - { - parent::__construct($stmts); - } - public function getType(): string { return 'FileWithoutNamespace'; diff --git a/src/Reporting/DeprecatedRulesReporter.php b/src/Reporting/DeprecatedRulesReporter.php index 7987fefa1f4..092301f7022 100644 --- a/src/Reporting/DeprecatedRulesReporter.php +++ b/src/Reporting/DeprecatedRulesReporter.php @@ -10,16 +10,6 @@ use Rector\Contract\PhpParser\Node\StmtsAwareInterface; use Rector\Contract\Rector\RectorInterface; use Rector\PhpParser\Enum\NodeGroup; -<<<<<<< HEAD -<<<<<<< HEAD -use Rector\PhpParserNode\FileNode; -======= -<<<<<<< HEAD ->>>>>>> 6bcee47e76 (warn about deprecated type) -use ReflectionMethod; -======= -======= ->>>>>>> 00c8276150 (print stmts with false) use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace; use Rector\PhpParser\Node\FileNode; use ReflectionMethod;