diff --git a/packages/BetterPhpDocParser/PhpDocParser/ClassAnnotationMatcher.php b/packages/BetterPhpDocParser/PhpDocParser/ClassAnnotationMatcher.php index 10a3a3827e1..d7e1502be7d 100644 --- a/packages/BetterPhpDocParser/PhpDocParser/ClassAnnotationMatcher.php +++ b/packages/BetterPhpDocParser/PhpDocParser/ClassAnnotationMatcher.php @@ -57,7 +57,7 @@ private function _resolveTagFullyQualifiedName( $tag = ltrim($tag, '@'); - $uses = $this->useImportsResolver->resolveForNode($node); + $uses = $this->useImportsResolver->resolve(); $fullyQualifiedClass = $this->resolveFullyQualifiedClass($uses, $node, $tag, $returnNullOnUnknownClass); if ($fullyQualifiedClass === null) { diff --git a/packages/FileSystemRector/Parser/FileInfoParser.php b/packages/FileSystemRector/Parser/FileInfoParser.php index 6e8b9b36943..8b7cb75f947 100644 --- a/packages/FileSystemRector/Parser/FileInfoParser.php +++ b/packages/FileSystemRector/Parser/FileInfoParser.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt; use Rector\Core\PhpParser\NodeTraverser\FileWithoutNamespaceNodeTraverser; use Rector\Core\PhpParser\Parser\RectorParser; +use Rector\Core\Provider\CurrentFileProvider; use Rector\Core\ValueObject\Application\File; use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator; @@ -19,7 +20,8 @@ final class FileInfoParser public function __construct( private readonly NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator, private readonly FileWithoutNamespaceNodeTraverser $fileWithoutNamespaceNodeTraverser, - private readonly RectorParser $rectorParser + private readonly RectorParser $rectorParser, + private readonly CurrentFileProvider $currentFileProvider ) { } @@ -33,7 +35,11 @@ public function parseFileInfoToNodesAndDecorate(string $filePath): array $stmts = $this->fileWithoutNamespaceNodeTraverser->traverse($stmts); $file = new File($filePath, FileSystem::read($filePath)); + $stmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file, $stmts); - return $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file, $stmts); + $file->hydrateStmtsAndTokens($stmts, $stmts, []); + $this->currentFileProvider->setFile($file); + + return $stmts; } } diff --git a/packages/NodeTypeResolver/PhpDocNodeVisitor/ClassRenamePhpDocNodeVisitor.php b/packages/NodeTypeResolver/PhpDocNodeVisitor/ClassRenamePhpDocNodeVisitor.php index e3c6476dc6a..81278188659 100644 --- a/packages/NodeTypeResolver/PhpDocNodeVisitor/ClassRenamePhpDocNodeVisitor.php +++ b/packages/NodeTypeResolver/PhpDocNodeVisitor/ClassRenamePhpDocNodeVisitor.php @@ -130,7 +130,7 @@ private function resolveNamespacedName( return $name; } - $uses = $this->useImportsResolver->resolveForNode($phpParserNode); + $uses = $this->useImportsResolver->resolve(); $scope = $phpParserNode->getAttribute(AttributeKey::SCOPE); if (! $scope instanceof Scope) { diff --git a/packages/PostRector/Collector/UseNodesToAddCollector.php b/packages/PostRector/Collector/UseNodesToAddCollector.php index 7f1a142792a..eea9df4814e 100644 --- a/packages/PostRector/Collector/UseNodesToAddCollector.php +++ b/packages/PostRector/Collector/UseNodesToAddCollector.php @@ -61,7 +61,7 @@ public function getUseImportTypesByNode(File $file, Node $node): array $filePath = $file->getFilePath(); $objectTypes = $this->useImportTypesInFilePath[$filePath] ?? []; - $uses = $this->useImportsResolver->resolveForNode($node); + $uses = $this->useImportsResolver->resolve(); foreach ($uses as $use) { $prefix = $this->useImportsResolver->resolvePrefix($use); diff --git a/packages/PostRector/Rector/NameImportingPostRector.php b/packages/PostRector/Rector/NameImportingPostRector.php index ea945a70543..07d201e1675 100644 --- a/packages/PostRector/Rector/NameImportingPostRector.php +++ b/packages/PostRector/Rector/NameImportingPostRector.php @@ -115,7 +115,7 @@ private function processNodeName(Name $name, File $file): ?Node } /** @var Use_[]|GroupUse[] $currentUses */ - $currentUses = $this->useImportsResolver->resolveForNode($name); + $currentUses = $this->useImportsResolver->resolve(); if ($this->shouldImportName($name, $currentUses)) { $nameInUse = $this->resolveNameInUse($name, $currentUses); diff --git a/packages/PostRector/Rector/UseAddingPostRector.php b/packages/PostRector/Rector/UseAddingPostRector.php index 5f4d3fb9953..d7dab8fc61a 100644 --- a/packages/PostRector/Rector/UseAddingPostRector.php +++ b/packages/PostRector/Rector/UseAddingPostRector.php @@ -125,7 +125,7 @@ private function resolveNodesWithImportedUses( $useImportTypes = $this->filterOutNonNamespacedNames($useImportTypes); // then add, to prevent adding + removing false positive of same short use - return $this->useImportsAdder->addImportsToStmts($nodes, $useImportTypes, $functionUseImportTypes); + return $this->useImportsAdder->addImportsToStmts($namespace, $nodes, $useImportTypes, $functionUseImportTypes); } /** diff --git a/packages/StaticTypeMapper/Naming/NameScopeFactory.php b/packages/StaticTypeMapper/Naming/NameScopeFactory.php index be9b9dcab3d..58e0e145b18 100644 --- a/packages/StaticTypeMapper/Naming/NameScopeFactory.php +++ b/packages/StaticTypeMapper/Naming/NameScopeFactory.php @@ -59,7 +59,7 @@ public function createNameScopeFromNodeWithoutTemplateTypes(Node $node): NameSco $scope = $node->getAttribute(AttributeKey::SCOPE); $namespace = $scope instanceof Scope ? $scope->getNamespace() : null; - $uses = $this->useImportsResolver->resolveForNode($node); + $uses = $this->useImportsResolver->resolve(); $usesAliasesToNames = $this->resolveUseNamesByAlias($uses); if ($scope instanceof Scope && $scope->getClassReflection() instanceof ClassReflection) { diff --git a/packages/Testing/TestingParser/TestingParser.php b/packages/Testing/TestingParser/TestingParser.php index 28c1fe5a5be..53b65ae5f91 100644 --- a/packages/Testing/TestingParser/TestingParser.php +++ b/packages/Testing/TestingParser/TestingParser.php @@ -9,6 +9,7 @@ use Rector\Core\Configuration\Option; use Rector\Core\Configuration\Parameter\ParameterProvider; use Rector\Core\PhpParser\Parser\RectorParser; +use Rector\Core\Provider\CurrentFileProvider; use Rector\Core\ValueObject\Application\File; use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator; @@ -21,6 +22,7 @@ public function __construct( private readonly ParameterProvider $parameterProvider, private readonly RectorParser $rectorParser, private readonly NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator, + private readonly CurrentFileProvider $currentFileProvider ) { } @@ -29,7 +31,10 @@ public function parseFilePathToFile(string $filePath): File $file = new File($filePath, FileSystem::read($filePath)); $stmts = $this->rectorParser->parseFile($filePath); + $stmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file, $stmts); + $file->hydrateStmtsAndTokens($stmts, $stmts, []); + $this->currentFileProvider->setFile($file); return $file; } @@ -41,9 +46,15 @@ public function parseFileToDecoratedNodes(string $filePath): array { $this->parameterProvider->changeParameter(Option::SOURCE, [$filePath]); - $nodes = $this->rectorParser->parseFile($filePath); + $stmts = $this->rectorParser->parseFile($filePath); $file = new File($filePath, FileSystem::read($filePath)); - return $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file, $nodes); + + $stmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file, $stmts); + $file->hydrateStmtsAndTokens($stmts, $stmts, []); + + $this->currentFileProvider->setFile($file); + + return $stmts; } } diff --git a/phpstan.neon b/phpstan.neon index 4f199fdc207..b6b3e64332f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -683,3 +683,9 @@ parameters: # for symfony configs check - '#Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\tagged_iterator not found#' + + - + message: '#Parameters should use "PhpParser\\Node\\Expr\\Variable\|array" types as the only types passed to this method#' + path: src/PhpParser/Node/BetterNodeFinder.php + + - '#The "Rector\\Core\\ValueObject\\Application\\File" class always calls "hydrateStmtsAndTokens\(\)" setters, better move it to constructor#' diff --git a/rules-tests/Naming/Naming/UseImportsResolver/UseImportsResolverTest.php b/rules-tests/Naming/Naming/UseImportsResolver/UseImportsResolverTest.php index 5e85e3b3c5d..3f22b562999 100644 --- a/rules-tests/Naming/Naming/UseImportsResolver/UseImportsResolverTest.php +++ b/rules-tests/Naming/Naming/UseImportsResolver/UseImportsResolverTest.php @@ -40,7 +40,7 @@ public function testUsesFromProperty(string $filePath): void $firstProperty = $this->betterNodeFinder->findFirstInstanceOf($nodes, Property::class); $this->assertInstanceOf(Property::class, $firstProperty); - $resolvedUses = $this->useImportsResolver->resolveForNode($firstProperty); + $resolvedUses = $this->useImportsResolver->resolve(); $stringUses = []; diff --git a/rules/CodingStyle/Application/UseImportsAdder.php b/rules/CodingStyle/Application/UseImportsAdder.php index c63530f4c5e..e6fa15c15ea 100644 --- a/rules/CodingStyle/Application/UseImportsAdder.php +++ b/rules/CodingStyle/Application/UseImportsAdder.php @@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Use_; use PHPStan\Type\ObjectType; use Rector\CodingStyle\ClassNameImport\UsedImportsResolver; +use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType; @@ -32,7 +33,7 @@ public function __construct( * @param array $functionUseImportTypes * @return Stmt[] */ - public function addImportsToStmts(array $stmts, array $useImportTypes, array $functionUseImportTypes): array + public function addImportsToStmts(FileWithoutNamespace $fileWithoutNamespace, array $stmts, array $useImportTypes, array $functionUseImportTypes): array { $existingUseImportTypes = $this->usedImportsResolver->resolveForStmts($stmts); $existingFunctionUseImports = $this->usedImportsResolver->resolveFunctionImportsForStmts($stmts); @@ -62,14 +63,20 @@ public function addImportsToStmts(array $stmts, array $useImportTypes, array $fu array_splice($stmts, $key + 1, 0, $nodesToAdd); - return $stmts; + $fileWithoutNamespace->stmts = $stmts; + $fileWithoutNamespace->stmts = array_values($fileWithoutNamespace->stmts); + + return $fileWithoutNamespace->stmts; } } $this->mirrorUseComments($stmts, $newUses); // make use stmts first - return array_merge($newUses, $stmts); + $fileWithoutNamespace->stmts = array_merge($newUses, $stmts); + $fileWithoutNamespace->stmts = array_values($fileWithoutNamespace->stmts); + + return $fileWithoutNamespace->stmts; } /** @@ -104,6 +111,7 @@ public function addImportsToNamespace( $this->mirrorUseComments($namespace->stmts, $newUses); $namespace->stmts = array_merge($newUses, $namespace->stmts); + $namespace->stmts = array_values($namespace->stmts); } /** diff --git a/rules/Naming/Naming/AliasNameResolver.php b/rules/Naming/Naming/AliasNameResolver.php index 936a0967028..44a5b886dac 100644 --- a/rules/Naming/Naming/AliasNameResolver.php +++ b/rules/Naming/Naming/AliasNameResolver.php @@ -16,7 +16,7 @@ public function __construct( public function resolveByName(Name $name): ?string { - $uses = $this->useImportsResolver->resolveForNode($name); + $uses = $this->useImportsResolver->resolve(); $nameString = $name->toString(); foreach ($uses as $use) { diff --git a/rules/Naming/Naming/UseImportsResolver.php b/rules/Naming/Naming/UseImportsResolver.php index 0074ecf17a6..68feba2422d 100644 --- a/rules/Naming/Naming/UseImportsResolver.php +++ b/rules/Naming/Naming/UseImportsResolver.php @@ -9,25 +9,63 @@ use PhpParser\Node\Stmt\GroupUse; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Use_; -use Rector\Core\PhpParser\Node\BetterNodeFinder; use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace; +use Rector\Core\PhpParser\NodeTraverser\FileWithoutNamespaceNodeTraverser; +use Rector\Core\Provider\CurrentFileProvider; +use Rector\Core\ValueObject\Application\File; final class UseImportsResolver { public function __construct( - private readonly BetterNodeFinder $betterNodeFinder + private readonly CurrentFileProvider $currentFileProvider, + private readonly FileWithoutNamespaceNodeTraverser $fileWithoutNamespaceNodeTraverser ) { } + private function resolveNamespace(): Namespace_|FileWithoutNamespace|null + { + /** @var File|null $file */ + $file = $this->currentFileProvider->getFile(); + if (! $file instanceof File) { + return null; + } + + $newStmts = $file->getNewStmts(); + + if ($newStmts === []) { + return null; + } + + $namespaces = array_filter($newStmts, static fn(Stmt $stmt): bool => $stmt instanceof Namespace_); + + // multiple namespaces is not supported + if (count($namespaces) > 1) { + return null; + } + + $currentNamespace = current($namespaces); + if ($currentNamespace instanceof Namespace_) { + return $currentNamespace; + } + + $currentStmt = current($newStmts); + if (! $currentStmt instanceof FileWithoutNamespace) { + $newStmts = $this->fileWithoutNamespaceNodeTraverser->traverse($newStmts); + /** @var FileWithoutNamespace $currentStmt */ + $currentStmt = current($newStmts); + + return $currentStmt; + } + + return $currentStmt; + } + /** * @return Use_[]|GroupUse[] */ - public function resolveForNode(Node $node): array + public function resolve(): array { - $namespace = $this->betterNodeFinder->findParentByTypes( - $node, - [Namespace_::class, FileWithoutNamespace::class] - ); + $namespace = $this->resolveNamespace(); if (! $namespace instanceof Node) { return []; } @@ -42,12 +80,9 @@ public function resolveForNode(Node $node): array * @api * @return Use_[] */ - public function resolveBareUsesForNode(Node $node): array + public function resolveBareUses(): array { - $namespace = $this->betterNodeFinder->findParentByTypes( - $node, - [Namespace_::class, FileWithoutNamespace::class] - ); + $namespace = $this->resolveNamespace(); if (! $namespace instanceof Node) { return []; } diff --git a/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php b/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php index fc8cc0c9d13..0d5b6dd9724 100644 --- a/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php +++ b/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php @@ -121,7 +121,7 @@ public function refactor(Node $node): ?Node return null; } - $uses = $this->useImportsResolver->resolveBareUsesForNode($node); + $uses = $this->useImportsResolver->resolveBareUses(); // 1. bare tags without annotation class, e.g. "@inject" $genericAttributeGroups = $this->processGenericTags($phpDocInfo); diff --git a/rules/Php80/Rector/Property/NestedAnnotationToAttributeRector.php b/rules/Php80/Rector/Property/NestedAnnotationToAttributeRector.php index 432eeb72ce3..3afaa11f707 100644 --- a/rules/Php80/Rector/Property/NestedAnnotationToAttributeRector.php +++ b/rules/Php80/Rector/Property/NestedAnnotationToAttributeRector.php @@ -114,7 +114,7 @@ public function refactor(Node $node): ?Node return null; } - $uses = $this->useImportsResolver->resolveBareUsesForNode($node); + $uses = $this->useImportsResolver->resolveBareUses(); $attributeGroups = $this->transformDoctrineAnnotationClassesToAttributeGroups($phpDocInfo, $uses); if ($attributeGroups === []) { diff --git a/rules/Renaming/NodeManipulator/ClassRenamer.php b/rules/Renaming/NodeManipulator/ClassRenamer.php index d9112008654..fe12f8a2ad6 100644 --- a/rules/Renaming/NodeManipulator/ClassRenamer.php +++ b/rules/Renaming/NodeManipulator/ClassRenamer.php @@ -355,7 +355,7 @@ private function isValidClassNameChange(Name $name, Class_ $class, ClassReflecti private function isValidUseImportChange(string $newName, UseUse $useUse): bool { - $uses = $this->useImportsResolver->resolveForNode($useUse); + $uses = $this->useImportsResolver->resolve(); if ($uses === []) { return true; } diff --git a/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php b/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php index dc17b726026..7b8b7958aaf 100644 --- a/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php +++ b/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php @@ -57,7 +57,7 @@ public function narrowToFullyQualifiedOrAliasedObjectType( } } - $uses = $this->useImportsResolver->resolveForNode($node); + $uses = $this->useImportsResolver->resolve(); if ($uses === []) { if (! $this->reflectionProvider->hasClass($objectType->getClassName())) { return new NonExistingObjectType($objectType->getClassName());