diff --git a/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/namespace_less_class.php.inc b/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/namespace_less_class.php.inc index 894383437e84..cf8e890bd14c 100644 --- a/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/namespace_less_class.php.inc +++ b/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/namespace_less_class.php.inc @@ -6,6 +6,8 @@ class NamespaceLessClass { public function someMethod() { + $parentClass = parent::someMethod(); + $someClass = new Rector\Tests\PSR4\Rector\FileWithoutNamespace\NormalizeNamespaceByPSR4ComposerAutoloadRector\Source\SomeClass(); } } @@ -22,6 +24,8 @@ class NamespaceLessClass { public function someMethod() { + $parentClass = parent::someMethod(); + $someClass = new \Rector\Tests\PSR4\Rector\FileWithoutNamespace\NormalizeNamespaceByPSR4ComposerAutoloadRector\Source\SomeClass(); } } diff --git a/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/no_prefix_consts.php.inc b/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/no_prefix_consts.php.inc new file mode 100644 index 000000000000..c0514e3b3cd0 --- /dev/null +++ b/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/no_prefix_consts.php.inc @@ -0,0 +1,45 @@ + +----- + diff --git a/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/skip_class_consts.php.inc b/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/skip_class_consts.php.inc new file mode 100644 index 000000000000..e37a7498ae54 --- /dev/null +++ b/rules-tests/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector/Fixture/skip_class_consts.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/rules/DeadCode/NodeAnalyzer/UsedVariableNameAnalyzer.php b/rules/DeadCode/NodeAnalyzer/UsedVariableNameAnalyzer.php new file mode 100644 index 000000000000..d823e08db31a --- /dev/null +++ b/rules/DeadCode/NodeAnalyzer/UsedVariableNameAnalyzer.php @@ -0,0 +1,39 @@ +nodeNameResolver = $nodeNameResolver; + } + + public function isVariableNamed(Node $node, Variable $variable): bool + { + if (($node instanceof MethodCall || $node instanceof PropertyFetch) && ($node->name instanceof Variable && is_string( + $node->name->name + ))) { + return $this->nodeNameResolver->isName($variable, $node->name->name); + } + + if (! $node instanceof Variable) { + return false; + } + + return $this->nodeNameResolver->areNamesEqual($variable, $node); + } +} diff --git a/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php b/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php index f0266a51e6b2..2d74076e1bff 100644 --- a/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php +++ b/rules/DeadCode/Rector/Assign/RemoveUnusedVariableAssignRector.php @@ -12,7 +12,6 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\NullsafeMethodCall; -use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\FunctionLike; @@ -22,6 +21,7 @@ use Rector\Core\Php\ReservedKeywordAnalyzer; use Rector\Core\PhpParser\Comparing\ConditionSearcher; use Rector\Core\Rector\AbstractRector; +use Rector\DeadCode\NodeAnalyzer\UsedVariableNameAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -46,13 +46,20 @@ final class RemoveUnusedVariableAssignRector extends AbstractRector */ private $conditionSearcher; + /** + * @var UsedVariableNameAnalyzer + */ + private $usedVariableNameAnalyzer; + public function __construct( ReservedKeywordAnalyzer $reservedKeywordAnalyzer, - CompactFuncCallAnalyzer $compactFuncCallAnalyzer + CompactFuncCallAnalyzer $compactFuncCallAnalyzer, + UsedVariableNameAnalyzer $usedVariableNameAnalyzer ) { $this->reservedKeywordAnalyzer = $reservedKeywordAnalyzer; $this->compactFuncCallAnalyzer = $compactFuncCallAnalyzer; $this->conditionSearcher = new ConditionSearcher(); + $this->usedVariableNameAnalyzer = $usedVariableNameAnalyzer; } public function getRuleDefinition(): RuleDefinition @@ -98,29 +105,19 @@ public function refactor(Node $node): ?Node return null; } - /** @var Variable $variable */ $variable = $node->var; - if (is_string($variable->name) && $this->reservedKeywordAnalyzer->isNativeVariable($variable->name)) { + if (! $variable instanceof Variable) { + return null; + } + + $variableName = $this->getName($variable); + if ($variableName !== null && $this->reservedKeywordAnalyzer->isNativeVariable($variableName)) { return null; } // variable is used if ($this->isUsed($node, $variable)) { - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - $ifNode = $parentNode->getAttribute(AttributeKey::NEXT_NODE); - - // check if next node is if - if (! $ifNode instanceof If_) { - return null; - } - - $nodeFound = $this->conditionSearcher->searchIfAndElseForVariableRedeclaration($node, $ifNode); - if ($nodeFound) { - $this->removeNode($node); - return $node; - } - - return null; + return $this->refactorUsedVariable($node); } $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); @@ -149,9 +146,12 @@ private function shouldSkip(Assign $assign): bool return true; } - return $variable->name instanceof Variable && (bool) $this->betterNodeFinder->findFirstNext($assign, function (Node $node): bool { - return $node instanceof Variable; - }); + return $variable->name instanceof Variable && $this->betterNodeFinder->findFirstNext( + $assign, + function (Node $node): bool { + return $node instanceof Variable; + } + ); } private function isUsed(Assign $assign, Variable $variable): bool @@ -159,7 +159,7 @@ private function isUsed(Assign $assign, Variable $variable): bool $isUsedPrev = (bool) $this->betterNodeFinder->findFirstPreviousOfNode($variable, function (Node $node) use ( $variable ): bool { - return $this->isVariableNamed($node, $variable); + return $this->usedVariableNameAnalyzer->isVariableNamed($node, $variable); }); if ($isUsedPrev) { @@ -184,7 +184,7 @@ private function isUsedNext(Variable $variable): bool return (bool) $this->betterNodeFinder->findFirstNext($variable, function (Node $node) use ( $variable ): bool { - if ($this->isVariableNamed($node, $variable)) { + if ($this->usedVariableNameAnalyzer->isVariableNamed($node, $variable)) { return true; } @@ -201,8 +201,7 @@ private function isUsedNext(Variable $variable): bool */ private function isUsedInAssignExpr(Expr $expr, Assign $assign): bool { - $args = $expr->args; - foreach ($args as $arg) { + foreach ($expr->args as $arg) { $variable = $arg->value; if (! $variable instanceof Variable) { continue; @@ -211,8 +210,12 @@ private function isUsedInAssignExpr(Expr $expr, Assign $assign): bool $previousAssign = $this->betterNodeFinder->findFirstPreviousOfNode($assign, function (Node $node) use ( $variable ): bool { - return $node instanceof Assign && $this->isVariableNamed($node->var, $variable); + return $node instanceof Assign && $this->usedVariableNameAnalyzer->isVariableNamed( + $node->var, + $variable + ); }); + if ($previousAssign instanceof Assign) { return $this->isUsed($assign, $variable); } @@ -226,20 +229,22 @@ private function isCall(Expr $expr): bool return $expr instanceof FuncCall || $expr instanceof MethodCall || $expr instanceof New_ || $expr instanceof NullsafeMethodCall || $expr instanceof StaticCall; } - private function isVariableNamed(Node $node, Variable $variable): bool + private function refactorUsedVariable(Assign $assign): ?Assign { - if ($node instanceof MethodCall && $node->name instanceof Variable && is_string($node->name->name)) { - return $this->isName($variable, $node->name->name); - } + $parentNode = $assign->getAttribute(AttributeKey::PARENT_NODE); + + $if = $parentNode->getAttribute(AttributeKey::NEXT_NODE); - if ($node instanceof PropertyFetch && $node->name instanceof Variable && is_string($node->name->name)) { - return $this->isName($variable, $node->name->name); + // check if next node is if + if (! $if instanceof If_) { + return null; } - if (! $node instanceof Variable) { - return false; + if ($this->conditionSearcher->searchIfAndElseForVariableRedeclaration($assign, $if)) { + $this->removeNode($assign); + return $assign; } - return $this->isName($variable, (string) $this->getName($node)); + return null; } } diff --git a/rules/PSR4/NodeManipulator/FullyQualifyStmtsAnalyzer.php b/rules/PSR4/NodeManipulator/FullyQualifyStmtsAnalyzer.php new file mode 100644 index 000000000000..86a17b6c269c --- /dev/null +++ b/rules/PSR4/NodeManipulator/FullyQualifyStmtsAnalyzer.php @@ -0,0 +1,98 @@ +parameterProvider = $parameterProvider; + $this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser; + $this->nodeNameResolver = $nodeNameResolver; + $this->reflectionProvider = $reflectionProvider; + } + + /** + * @param Stmt[] $nodes + */ + public function process(array $nodes): void + { + // no need to + if ($this->parameterProvider->provideBoolParameter(Option::AUTO_IMPORT_NAMES)) { + return; + } + + // FQNize all class names + $this->simpleCallableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node): ?FullyQualified { + if (! $node instanceof Name) { + return null; + } + + $fullyQualifiedName = $this->nodeNameResolver->getName($node); + if (in_array($fullyQualifiedName, ['self', 'parent', 'static'], true)) { + return null; + } + + if ($this->isNativeConstant($node)) { + return null; + } + + return new FullyQualified($fullyQualifiedName); + }); + } + + private function isNativeConstant(Name $name): bool + { + $parent = $name->getAttribute(AttributeKey::PARENT_NODE); + if (! $parent instanceof ConstFetch) { + return false; + } + + $scope = $name->getAttribute(AttributeKey::SCOPE); + if (! $this->reflectionProvider->hasConstant($name, $scope)) { + return false; + } + + $constantReflection = $this->reflectionProvider->getConstant($name, $scope); + return $constantReflection instanceof RuntimeConstantReflection; + } +} diff --git a/rules/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector.php b/rules/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector.php index 644a8262a028..7e19f98cb062 100644 --- a/rules/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector.php +++ b/rules/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector.php @@ -6,14 +6,12 @@ use PhpParser\Node; use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Declare_; use PhpParser\Node\Stmt\Namespace_; -use Rector\Core\Configuration\Option; use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace; use Rector\Core\Rector\AbstractRector; use Rector\PSR4\Contract\PSR4AutoloadNamespaceMatcherInterface; +use Rector\PSR4\NodeManipulator\FullyQualifyStmtsAnalyzer; use Rector\PSR4\Rector\Namespace_\MultipleClassFileToPsr4ClassesRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ComposerJsonAwareCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -28,9 +26,17 @@ final class NormalizeNamespaceByPSR4ComposerAutoloadRector extends AbstractRecto */ private $psr4AutoloadNamespaceMatcher; - public function __construct(PSR4AutoloadNamespaceMatcherInterface $psr4AutoloadNamespaceMatcher) - { + /** + * @var FullyQualifyStmtsAnalyzer + */ + private $fullyQualifyStmtsAnalyzer; + + public function __construct( + PSR4AutoloadNamespaceMatcherInterface $psr4AutoloadNamespaceMatcher, + FullyQualifyStmtsAnalyzer $fullyQualifyStmtsAnalyzer + ) { $this->psr4AutoloadNamespaceMatcher = $psr4AutoloadNamespaceMatcher; + $this->fullyQualifyStmtsAnalyzer = $fullyQualifyStmtsAnalyzer; } public function getRuleDefinition(): RuleDefinition @@ -102,7 +108,7 @@ public function refactor(Node $node): ?Node } $node->name = new Name($expectedNamespace); - $this->makeNamesFullyQualified($node->stmts); + $this->fullyQualifyStmtsAnalyzer->process($node->stmts); return $node; } @@ -124,29 +130,9 @@ private function refactorFileWithoutNamespace( $namespace = new Namespace_(new Name($expectedNamespace), $nodes); $nodesWithStrictTypesThenNamespace[] = $namespace; - $this->makeNamesFullyQualified($nodes); + $this->fullyQualifyStmtsAnalyzer->process($nodes); // @todo update to a new class node, like FileWithNamespace return new FileWithoutNamespace($nodesWithStrictTypesThenNamespace); } - - /** - * @param Stmt[] $nodes - */ - private function makeNamesFullyQualified(array $nodes): void - { - // no need to - if ($this->parameterProvider->provideBoolParameter(Option::AUTO_IMPORT_NAMES)) { - return; - } - - // FQNize all class names - $this->traverseNodesWithCallable($nodes, function (Node $node): ?FullyQualified { - if (! $node instanceof Name) { - return null; - } - - return new FullyQualified($this->getName($node)); - }); - } } diff --git a/src/Bootstrap/ExtensionConfigResolver.php b/src/Bootstrap/ExtensionConfigResolver.php index c187f4644aed..a62ae60cdb63 100644 --- a/src/Bootstrap/ExtensionConfigResolver.php +++ b/src/Bootstrap/ExtensionConfigResolver.php @@ -1,8 +1,11 @@ getFileName() === false) { + $generatedConfigReflectionClass = new ReflectionClass('Rector\RectorInstaller\GeneratedConfig'); + if ($generatedConfigReflectionClass->getFileName() === false) { return $configFileInfos; } - $generatedConfigDirectory = dirname($generatedConfigReflection->getFileName()); - foreach (\Rector\RectorInstaller\GeneratedConfig::EXTENSIONS as $name => $extensionConfig) { + $generatedConfigDirectory = dirname($generatedConfigReflectionClass->getFileName()); + foreach (GeneratedConfig::EXTENSIONS as $extensionConfig) { foreach ($extensionConfig['extra']['includes'] ?? [] as $includedFile) { - $includedFilePath = null; - if (isset($extensionConfig['relative_install_path'])) { - $includedFilePath = sprintf( - '%s/%s/%s', - $generatedConfigDirectory, - $extensionConfig['relative_install_path'], - $includedFile - ); - if (! file_exists($includedFilePath) || ! is_readable($includedFilePath)) { - $includedFilePath = null; - } - } + $includedFilePath = $this->resolveIncludeFilePath( + $extensionConfig, + $generatedConfigDirectory, + $includedFile + ); if ($includedFilePath === null) { $includedFilePath = sprintf('%s/%s', $extensionConfig['install_path'], $includedFile); } + $configFileInfos[] = new SmartFileInfo($includedFilePath); } } return $configFileInfos; } + + /** + * @param array $extensionConfig + */ + private function resolveIncludeFilePath( + array $extensionConfig, + string $generatedConfigDirectory, + string $includedFile + ): ?string { + if (! isset($extensionConfig['relative_install_path'])) { + return null; + } + + $includedFilePath = sprintf( + '%s/%s/%s', + $generatedConfigDirectory, + $extensionConfig['relative_install_path'], + $includedFile + ); + if (! file_exists($includedFilePath)) { + return null; + } + if (! is_readable($includedFilePath)) { + return null; + } + return $includedFilePath; + } }