From 7d79a2984df32a4852804d3e7c91cc2bdc053c3d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 25 Aug 2026 19:30:12 +0100 Subject: [PATCH] [PhpParser] merge decorating node visitors from 14 to 8 for faster traversal Each DecoratingNodeVisitor runs enterNode() on every node during a single traversal, so fewer visitors means fewer per-node dispatches. Merge visitors that key on the same node type, and share the expensive work three pairs duplicated (reflection resolve, function-body/stmt sub-traversal): - CallLikeReflectionNodeVisitor = ClosureWithVariadicParameters + ArgNotAcceptingClosure (resolve reflection once) - ByRefNodeVisitor = ByRefReturn + ByRefVariable (one FunctionLike entry) - LocalVariableScopeNodeVisitor = GlobalVariable + StaticVariable (one shared stmts scan) - NameAndArgNodeVisitor = Name + Arg + ClassConstFetch (single FuncCall branch) - DefaultValueNodeVisitor = ParamDefault + PropertyOrClassConstDefault AssignedTo, PhpVersionCondition and Context stay standalone: AssignedTo is reused bare by SimplePhpParser, the other two carry distinct deps. Decorators only write attributes (never read a sibling's), so merge order is behavior-preserving. --- .../LazyContainerFactory.php | 32 ++--- src/PhpParser/NodeVisitor/ArgNodeVisitor.php | 38 ------ ...leNodeVisitor.php => ByRefNodeVisitor.php} | 58 +++++++- .../NodeVisitor/ByRefReturnNodeVisitor.php | 61 --------- ....php => CallLikeReflectionNodeVisitor.php} | 74 ++++++++-- .../ClassConstFetchNodeVisitor.php | 31 ----- ...osureWithVariadicParametersNodeVisitor.php | 70 ---------- ...isitor.php => DefaultValueNodeVisitor.php} | 17 ++- .../NodeVisitor/GlobalVariableNodeVisitor.php | 95 ------------- .../LocalVariableScopeNodeVisitor.php | 127 ++++++++++++++++++ ...eVisitor.php => NameAndArgNodeVisitor.php} | 25 +++- .../NodeVisitor/ParamDefaultNodeVisitor.php | 31 ----- .../NodeVisitor/StaticVariableNodeVisitor.php | 96 ------------- 13 files changed, 288 insertions(+), 467 deletions(-) delete mode 100644 src/PhpParser/NodeVisitor/ArgNodeVisitor.php rename src/PhpParser/NodeVisitor/{ByRefVariableNodeVisitor.php => ByRefNodeVisitor.php} (66%) delete mode 100644 src/PhpParser/NodeVisitor/ByRefReturnNodeVisitor.php rename src/PhpParser/NodeVisitor/{ArgNotAcceptingClosureNodeVisitor.php => CallLikeReflectionNodeVisitor.php} (56%) delete mode 100644 src/PhpParser/NodeVisitor/ClassConstFetchNodeVisitor.php delete mode 100644 src/PhpParser/NodeVisitor/ClosureWithVariadicParametersNodeVisitor.php rename src/PhpParser/NodeVisitor/{PropertyOrClassConstDefaultNodeVisitor.php => DefaultValueNodeVisitor.php} (66%) delete mode 100644 src/PhpParser/NodeVisitor/GlobalVariableNodeVisitor.php create mode 100644 src/PhpParser/NodeVisitor/LocalVariableScopeNodeVisitor.php rename src/PhpParser/NodeVisitor/{NameNodeVisitor.php => NameAndArgNodeVisitor.php} (50%) delete mode 100644 src/PhpParser/NodeVisitor/ParamDefaultNodeVisitor.php delete mode 100644 src/PhpParser/NodeVisitor/StaticVariableNodeVisitor.php diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index 5c2ecbd775a..d420e687fad 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -59,20 +59,14 @@ use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser; use Rector\PhpParser\Comparing\NodeComparator; use Rector\PhpParser\Node\NodeFactory; -use Rector\PhpParser\NodeVisitor\ArgNodeVisitor; -use Rector\PhpParser\NodeVisitor\ArgNotAcceptingClosureNodeVisitor; use Rector\PhpParser\NodeVisitor\AssignedToNodeVisitor; -use Rector\PhpParser\NodeVisitor\ByRefReturnNodeVisitor; -use Rector\PhpParser\NodeVisitor\ByRefVariableNodeVisitor; -use Rector\PhpParser\NodeVisitor\ClassConstFetchNodeVisitor; -use Rector\PhpParser\NodeVisitor\ClosureWithVariadicParametersNodeVisitor; +use Rector\PhpParser\NodeVisitor\ByRefNodeVisitor; +use Rector\PhpParser\NodeVisitor\CallLikeReflectionNodeVisitor; use Rector\PhpParser\NodeVisitor\ContextNodeVisitor; -use Rector\PhpParser\NodeVisitor\GlobalVariableNodeVisitor; -use Rector\PhpParser\NodeVisitor\NameNodeVisitor; -use Rector\PhpParser\NodeVisitor\ParamDefaultNodeVisitor; +use Rector\PhpParser\NodeVisitor\DefaultValueNodeVisitor; +use Rector\PhpParser\NodeVisitor\LocalVariableScopeNodeVisitor; +use Rector\PhpParser\NodeVisitor\NameAndArgNodeVisitor; use Rector\PhpParser\NodeVisitor\PhpVersionConditionNodeVisitor; -use Rector\PhpParser\NodeVisitor\PropertyOrClassConstDefaultNodeVisitor; -use Rector\PhpParser\NodeVisitor\StaticVariableNodeVisitor; use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper; use Rector\PHPStanStaticTypeMapper\TypeMapper\ArrayTypeMapper; use Rector\PHPStanStaticTypeMapper\TypeMapper\ConditionalTypeForParameterMapper; @@ -103,20 +97,14 @@ final class LazyContainerFactory * @var array> */ private const array DECORATING_NODE_VISITOR_CLASSES = [ - ArgNodeVisitor::class, - ClosureWithVariadicParametersNodeVisitor::class, + CallLikeReflectionNodeVisitor::class, PhpVersionConditionNodeVisitor::class, AssignedToNodeVisitor::class, - ByRefReturnNodeVisitor::class, - ByRefVariableNodeVisitor::class, + ByRefNodeVisitor::class, ContextNodeVisitor::class, - GlobalVariableNodeVisitor::class, - NameNodeVisitor::class, - StaticVariableNodeVisitor::class, - PropertyOrClassConstDefaultNodeVisitor::class, - ParamDefaultNodeVisitor::class, - ClassConstFetchNodeVisitor::class, - ArgNotAcceptingClosureNodeVisitor::class, + LocalVariableScopeNodeVisitor::class, + NameAndArgNodeVisitor::class, + DefaultValueNodeVisitor::class, ]; /** diff --git a/src/PhpParser/NodeVisitor/ArgNodeVisitor.php b/src/PhpParser/NodeVisitor/ArgNodeVisitor.php deleted file mode 100644 index c539f208310..00000000000 --- a/src/PhpParser/NodeVisitor/ArgNodeVisitor.php +++ /dev/null @@ -1,38 +0,0 @@ -name instanceof Name) { - return null; - } - - // has no args - if ($node->isFirstClassCallable()) { - return null; - } - - $funcCallName = $node->name->toString(); - foreach ($node->getArgs() as $arg) { - $arg->value->setAttribute(AttributeKey::FROM_FUNC_CALL_NAME, $funcCallName); - } - - return null; - } -} diff --git a/src/PhpParser/NodeVisitor/ByRefVariableNodeVisitor.php b/src/PhpParser/NodeVisitor/ByRefNodeVisitor.php similarity index 66% rename from src/PhpParser/NodeVisitor/ByRefVariableNodeVisitor.php rename to src/PhpParser/NodeVisitor/ByRefNodeVisitor.php index 562ba48f8ad..dcec8419060 100644 --- a/src/PhpParser/NodeVisitor/ByRefVariableNodeVisitor.php +++ b/src/PhpParser/NodeVisitor/ByRefNodeVisitor.php @@ -10,12 +10,20 @@ use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\Variable; use PhpParser\Node\FunctionLike; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Return_; +use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser; +use Rector\PhpParser\NodeTraverser\SimpleNodeTraverser; -final class ByRefVariableNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface +/** + * Marks by-ref returns (IS_BYREF_RETURN / IS_INSIDE_BYREF_FUNCTION_LIKE) and by-ref variables + * (IS_BYREF_VAR) from a single FunctionLike entry, sharing the one node subscription. + */ +final class ByRefNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface { public function __construct( private readonly SimpleCallableNodeTraverser $simpleCallableNodeTraverser @@ -33,14 +41,54 @@ public function enterNode(Node $node): ?Node return null; } - $byRefVariableNames = $this->resolveClosureUseIsByRefAttribute($node, []); - $byRefVariableNames = $this->resolveParamIsByRefAttribute($node, $byRefVariableNames); - $stmts = $node->getStmts(); if ($stmts === null) { return null; } + $this->decorateByRefReturn($node, $stmts); + $this->decorateByRefVariables($node, $stmts); + + return null; + } + + /** + * @param Node\Stmt[] $stmts + */ + private function decorateByRefReturn(FunctionLike $functionLike, array $stmts): void + { + if (! $functionLike->returnsByRef()) { + return; + } + + SimpleNodeTraverser::decorateWithAttributeValue($stmts, AttributeKey::IS_INSIDE_BYREF_FUNCTION_LIKE, true); + + $this->simpleCallableNodeTraverser->traverseNodesWithCallable( + $stmts, + static function (Node $node): int|null|Node { + // avoid nested functions or classes + if ($node instanceof Class_ || $node instanceof FunctionLike) { + return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } + + if (! $node instanceof Return_) { + return null; + } + + $node->setAttribute(AttributeKey::IS_BYREF_RETURN, true); + return $node; + } + ); + } + + /** + * @param Node\Stmt[] $stmts + */ + private function decorateByRefVariables(FunctionLike $functionLike, array $stmts): void + { + $byRefVariableNames = $this->resolveClosureUseIsByRefAttribute($functionLike, []); + $byRefVariableNames = $this->resolveParamIsByRefAttribute($functionLike, $byRefVariableNames); + $this->simpleCallableNodeTraverser->traverseNodesWithCallable( $stmts, function (Node $subNode) use (&$byRefVariableNames): null|Variable { @@ -61,8 +109,6 @@ function (Node $subNode) use (&$byRefVariableNames): null|Variable { return $subNode; } ); - - return null; } /** diff --git a/src/PhpParser/NodeVisitor/ByRefReturnNodeVisitor.php b/src/PhpParser/NodeVisitor/ByRefReturnNodeVisitor.php deleted file mode 100644 index 158874a2075..00000000000 --- a/src/PhpParser/NodeVisitor/ByRefReturnNodeVisitor.php +++ /dev/null @@ -1,61 +0,0 @@ -returnsByRef()) { - return null; - } - - $stmts = $node->getStmts(); - if ($stmts === null) { - return null; - } - - SimpleNodeTraverser::decorateWithAttributeValue($stmts, AttributeKey::IS_INSIDE_BYREF_FUNCTION_LIKE, true); - - $this->simpleCallableNodeTraverser->traverseNodesWithCallable( - $stmts, - static function (Node $node): int|null|Node { - // avoid nested functions or classes - if ($node instanceof Class_ || $node instanceof FunctionLike) { - return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; - } - - if (! $node instanceof Return_) { - return null; - } - - $node->setAttribute(AttributeKey::IS_BYREF_RETURN, true); - return $node; - } - ); - - return null; - } -} diff --git a/src/PhpParser/NodeVisitor/ArgNotAcceptingClosureNodeVisitor.php b/src/PhpParser/NodeVisitor/CallLikeReflectionNodeVisitor.php similarity index 56% rename from src/PhpParser/NodeVisitor/ArgNotAcceptingClosureNodeVisitor.php rename to src/PhpParser/NodeVisitor/CallLikeReflectionNodeVisitor.php index 155923c4ec1..d7782ca763f 100644 --- a/src/PhpParser/NodeVisitor/ArgNotAcceptingClosureNodeVisitor.php +++ b/src/PhpParser/NodeVisitor/CallLikeReflectionNodeVisitor.php @@ -7,12 +7,17 @@ use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr\Array_; +use PhpParser\Node\Expr\ArrowFunction; use PhpParser\Node\Expr\CallLike; +use PhpParser\Node\Expr\Closure; use PhpParser\Node\Identifier; use PhpParser\NodeVisitorAbstract; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\Native\NativeFunctionReflection; use PHPStan\Reflection\ParameterReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\CallableType; use PHPStan\Type\ObjectType; use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface; use Rector\NodeTypeResolver\Node\AttributeKey; @@ -21,12 +26,12 @@ use Rector\Reflection\ReflectionResolver; /** - * Mark array arguments passed to a parameter that cannot hold a Closure, - * e.g. "array" or "string|array|null", so an array callable is kept as is - * - * @see https://github.com/rectorphp/rector/issues/9563 + * Decorates call args using the resolved function-like reflection, resolved once per call: + * - Closure/arrow-fn args passed to a variadic callable parameter (HAS_CLOSURE_WITH_VARIADIC_ARGS) + * - array args passed to a parameter that cannot hold a Closure, so an array callable is kept as is + * (IS_ARG_NOT_ACCEPTING_CLOSURE), see https://github.com/rectorphp/rector/issues/9563 */ -final class ArgNotAcceptingClosureNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface +final class CallLikeReflectionNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface { public function __construct( private readonly ReflectionResolver $reflectionResolver @@ -44,19 +49,68 @@ public function enterNode(Node $node): ?Node } $args = $node->getArgs(); - if (! array_any($args, static fn (Arg $arg): bool => $arg->value instanceof Array_)) { + if ($args === []) { return null; } $functionLikeReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node); + + $this->decorateClosureWithVariadicArgs($args, $functionLikeReflection); + $this->decorateArgNotAcceptingClosure($node, $args, $functionLikeReflection); + + return null; + } + + /** + * @param Arg[] $args + */ + private function decorateClosureWithVariadicArgs( + array $args, + MethodReflection|FunctionReflection|null $functionLikeReflection + ): void { + foreach ($args as $arg) { + if (! $arg->value instanceof Closure && ! $arg->value instanceof ArrowFunction) { + continue; + } + + if ($functionLikeReflection instanceof NativeFunctionReflection) { + $parametersAcceptors = ParametersAcceptorSelector::combineAcceptors( + $functionLikeReflection->getVariants() + ); + + foreach ($parametersAcceptors->getParameters() as $extendedParameterReflection) { + if ($extendedParameterReflection->getType() instanceof CallableType && $extendedParameterReflection->getType()->isVariadic()) { + $arg->value->setAttribute(AttributeKey::HAS_CLOSURE_WITH_VARIADIC_ARGS, true); + } + } + + return; + } + + $arg->value->setAttribute(AttributeKey::HAS_CLOSURE_WITH_VARIADIC_ARGS, true); + } + } + + /** + * @param Arg[] $args + */ + private function decorateArgNotAcceptingClosure( + CallLike $callLike, + array $args, + MethodReflection|FunctionReflection|null $functionLikeReflection + ): void { if (! $functionLikeReflection instanceof FunctionReflection && ! $functionLikeReflection instanceof MethodReflection) { - return null; + return; + } + + if (! array_any($args, static fn (Arg $arg): bool => $arg->value instanceof Array_)) { + return; } $parameterReflections = ParametersAcceptorSelectorVariantsWrapper::select( $functionLikeReflection, - $node, - ScopeFetcher::fetch($node) + $callLike, + ScopeFetcher::fetch($callLike) )->getParameters(); $closureObjectType = new ObjectType('Closure'); @@ -77,8 +131,6 @@ public function enterNode(Node $node): ?Node $arg->value->setAttribute(AttributeKey::IS_ARG_NOT_ACCEPTING_CLOSURE, true); } - - return $node; } /** diff --git a/src/PhpParser/NodeVisitor/ClassConstFetchNodeVisitor.php b/src/PhpParser/NodeVisitor/ClassConstFetchNodeVisitor.php deleted file mode 100644 index 3d9e66587dc..00000000000 --- a/src/PhpParser/NodeVisitor/ClassConstFetchNodeVisitor.php +++ /dev/null @@ -1,31 +0,0 @@ -name instanceof Identifier) { - return null; - } - - $node->class->setAttribute(AttributeKey::CLASS_CONST_FETCH_NAME, $node->name->toString()); - - return null; - } -} diff --git a/src/PhpParser/NodeVisitor/ClosureWithVariadicParametersNodeVisitor.php b/src/PhpParser/NodeVisitor/ClosureWithVariadicParametersNodeVisitor.php deleted file mode 100644 index e87a3d46ac9..00000000000 --- a/src/PhpParser/NodeVisitor/ClosureWithVariadicParametersNodeVisitor.php +++ /dev/null @@ -1,70 +0,0 @@ -isFirstClassCallable()) { - return null; - } - - if ($node->getArgs() === []) { - 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(AttributeKey::HAS_CLOSURE_WITH_VARIADIC_ARGS, true); - } - } - - return null; - } - - $arg->value->setAttribute(AttributeKey::HAS_CLOSURE_WITH_VARIADIC_ARGS, true); - } - - return null; - } -} diff --git a/src/PhpParser/NodeVisitor/PropertyOrClassConstDefaultNodeVisitor.php b/src/PhpParser/NodeVisitor/DefaultValueNodeVisitor.php similarity index 66% rename from src/PhpParser/NodeVisitor/PropertyOrClassConstDefaultNodeVisitor.php rename to src/PhpParser/NodeVisitor/DefaultValueNodeVisitor.php index e4ca8759d47..579b205bbf6 100644 --- a/src/PhpParser/NodeVisitor/PropertyOrClassConstDefaultNodeVisitor.php +++ b/src/PhpParser/NodeVisitor/DefaultValueNodeVisitor.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Expr; +use PhpParser\Node\Param; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\Property; use PhpParser\NodeVisitorAbstract; @@ -13,10 +14,22 @@ use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\NodeTraverser\SimpleNodeTraverser; -final class PropertyOrClassConstDefaultNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface +/** + * Marks default-value subtrees of params, properties and class constants, so later rules can tell + * a value node stands in a default position (IS_PARAM_DEFAULT / IS_DEFAULT_PROPERTY_VALUE / IS_CLASS_CONST_VALUE). + */ +final class DefaultValueNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface { public function enterNode(Node $node): ?Node { + if ($node instanceof Param) { + if ($node->default instanceof Expr) { + SimpleNodeTraverser::decorateWithAttributeValue($node->default, AttributeKey::IS_PARAM_DEFAULT, true); + } + + return null; + } + if ($node instanceof Property) { foreach ($node->props as $propertyItem) { $default = $propertyItem->default; @@ -30,6 +43,8 @@ public function enterNode(Node $node): ?Node true ); } + + return null; } if ($node instanceof ClassConst) { diff --git a/src/PhpParser/NodeVisitor/GlobalVariableNodeVisitor.php b/src/PhpParser/NodeVisitor/GlobalVariableNodeVisitor.php deleted file mode 100644 index f872d0c4279..00000000000 --- a/src/PhpParser/NodeVisitor/GlobalVariableNodeVisitor.php +++ /dev/null @@ -1,95 +0,0 @@ -stmts === null) { - return null; - } - - /** @var string[] $globalVariableNames */ - $globalVariableNames = []; - - foreach ($node->stmts as $stmt) { - if (! $stmt instanceof Global_) { - $this->setIsGlobalVarAttribute($stmt, $globalVariableNames); - continue; - } - - foreach ($stmt->vars as $variable) { - if ($variable instanceof Variable && ! $variable->name instanceof Expr) { - $variable->setAttribute(AttributeKey::IS_GLOBAL_VAR, true); - - /** @var string $variableName */ - $variableName = $variable->name; - $globalVariableNames[] = $variableName; - } - } - } - - return null; - } - - /** - * @param string[] $globalVariableNames - */ - private function setIsGlobalVarAttribute(Stmt $stmt, array $globalVariableNames): void - { - if ($globalVariableNames === []) { - return; - } - - $this->simpleCallableNodeTraverser->traverseNodesWithCallable( - $stmt, - static function (Node $subNode) use ($globalVariableNames): int|null|Variable { - if ($subNode instanceof Class_) { - return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; - } - - if (! $subNode instanceof Variable) { - return null; - } - - if ($subNode->name instanceof Expr) { - return null; - } - - if (! in_array($subNode->name, $globalVariableNames, true)) { - return null; - } - - $subNode->setAttribute(AttributeKey::IS_GLOBAL_VAR, true); - return $subNode; - } - ); - } -} diff --git a/src/PhpParser/NodeVisitor/LocalVariableScopeNodeVisitor.php b/src/PhpParser/NodeVisitor/LocalVariableScopeNodeVisitor.php new file mode 100644 index 00000000000..75343f60320 --- /dev/null +++ b/src/PhpParser/NodeVisitor/LocalVariableScopeNodeVisitor.php @@ -0,0 +1,127 @@ +stmts === null) { + return null; + } + + /** @var string[] $globalVariableNames */ + $globalVariableNames = []; + + /** @var string[] $staticVariableNames */ + $staticVariableNames = []; + + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Global_) { + foreach ($stmt->vars as $variable) { + if ($variable instanceof Variable && ! $variable->name instanceof Expr) { + $variable->setAttribute(AttributeKey::IS_GLOBAL_VAR, true); + + /** @var string $variableName */ + $variableName = $variable->name; + $globalVariableNames[] = $variableName; + } + } + + continue; + } + + if ($stmt instanceof Static_) { + foreach ($stmt->vars as $staticVar) { + $staticVariableName = $staticVar->var->name; + + if (! is_string($staticVariableName)) { + continue; + } + + $staticVar->var->setAttribute(AttributeKey::IS_STATIC_VAR, true); + $staticVariableNames[] = $staticVariableName; + } + + continue; + } + + $this->decorateScopedVariableUses($stmt, $globalVariableNames, $staticVariableNames); + } + + return null; + } + + /** + * @param string[] $globalVariableNames + * @param string[] $staticVariableNames + */ + private function decorateScopedVariableUses(Stmt $stmt, array $globalVariableNames, array $staticVariableNames): void + { + if ($globalVariableNames === [] && $staticVariableNames === []) { + return; + } + + $this->simpleCallableNodeTraverser->traverseNodesWithCallable( + $stmt, + static function (Node $subNode) use ($globalVariableNames, $staticVariableNames): int|null|Variable { + if ($subNode instanceof Class_) { + return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } + + if (! $subNode instanceof Variable) { + return null; + } + + if ($subNode->name instanceof Expr) { + return null; + } + + $isDecorated = false; + + if (in_array($subNode->name, $globalVariableNames, true)) { + $subNode->setAttribute(AttributeKey::IS_GLOBAL_VAR, true); + $isDecorated = true; + } + + if (in_array($subNode->name, $staticVariableNames, true)) { + $subNode->setAttribute(AttributeKey::IS_STATIC_VAR, true); + $isDecorated = true; + } + + return $isDecorated ? $subNode : null; + } + ); + } +} diff --git a/src/PhpParser/NodeVisitor/NameNodeVisitor.php b/src/PhpParser/NodeVisitor/NameAndArgNodeVisitor.php similarity index 50% rename from src/PhpParser/NodeVisitor/NameNodeVisitor.php rename to src/PhpParser/NodeVisitor/NameAndArgNodeVisitor.php index 5fbd56bb35f..7e646c0417f 100644 --- a/src/PhpParser/NodeVisitor/NameNodeVisitor.php +++ b/src/PhpParser/NodeVisitor/NameAndArgNodeVisitor.php @@ -5,21 +5,35 @@ namespace Rector\PhpParser\NodeVisitor; use PhpParser\Node; +use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\NodeVisitorAbstract; use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface; use Rector\NodeTypeResolver\Node\AttributeKey; -final class NameNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface +/** + * Marks call/name context attributes on the name or class node, plus the func-call name on its + * arg values, so later rules can tell how a name is used without re-resolving the parent. + */ +final class NameAndArgNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface { public function enterNode(Node $node): ?Node { if ($node instanceof FuncCall && $node->name instanceof Name) { $node->name->setAttribute(AttributeKey::IS_FUNCCALL_NAME, true); + + if (! $node->isFirstClassCallable()) { + $funcCallName = $node->name->toString(); + foreach ($node->getArgs() as $arg) { + $arg->value->setAttribute(AttributeKey::FROM_FUNC_CALL_NAME, $funcCallName); + } + } + return null; } @@ -33,15 +47,16 @@ public function enterNode(Node $node): ?Node return null; } - if (! $node instanceof StaticCall) { + if ($node instanceof StaticCall && $node->class instanceof Name) { + $node->class->setAttribute(AttributeKey::IS_STATICCALL_CLASS_NAME, true); return null; } - if (! $node->class instanceof Name) { - return null; + // pass value metadata to class node + if ($node instanceof ClassConstFetch && $node->name instanceof Identifier) { + $node->class->setAttribute(AttributeKey::CLASS_CONST_FETCH_NAME, $node->name->toString()); } - $node->class->setAttribute(AttributeKey::IS_STATICCALL_CLASS_NAME, true); return null; } } diff --git a/src/PhpParser/NodeVisitor/ParamDefaultNodeVisitor.php b/src/PhpParser/NodeVisitor/ParamDefaultNodeVisitor.php deleted file mode 100644 index 68822bf50f1..00000000000 --- a/src/PhpParser/NodeVisitor/ParamDefaultNodeVisitor.php +++ /dev/null @@ -1,31 +0,0 @@ -default instanceof Expr) { - return null; - } - - SimpleNodeTraverser::decorateWithAttributeValue($node->default, AttributeKey::IS_PARAM_DEFAULT, true); - - return null; - } -} diff --git a/src/PhpParser/NodeVisitor/StaticVariableNodeVisitor.php b/src/PhpParser/NodeVisitor/StaticVariableNodeVisitor.php deleted file mode 100644 index 43829b955b5..00000000000 --- a/src/PhpParser/NodeVisitor/StaticVariableNodeVisitor.php +++ /dev/null @@ -1,96 +0,0 @@ -stmts === null) { - return null; - } - - /** @var string[] $staticVariableNames */ - $staticVariableNames = []; - - foreach ($node->stmts as $stmt) { - if (! $stmt instanceof Static_) { - $this->setIsStaticVarAttribute($stmt, $staticVariableNames); - continue; - } - - foreach ($stmt->vars as $staticVar) { - $staticVariableName = $staticVar->var->name; - - if (! is_string($staticVariableName)) { - continue; - } - - $staticVar->var->setAttribute(AttributeKey::IS_STATIC_VAR, true); - $staticVariableNames[] = $staticVariableName; - } - } - - return null; - } - - /** - * @param string[] $staticVariableNames - */ - private function setIsStaticVarAttribute(Stmt $stmt, array $staticVariableNames): void - { - if ($staticVariableNames === []) { - return; - } - - $this->simpleCallableNodeTraverser->traverseNodesWithCallable( - $stmt, - static function (Node $subNode) use ($staticVariableNames): int|null|Variable { - if ($subNode instanceof Class_) { - return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; - } - - if (! $subNode instanceof Variable) { - return null; - } - - if ($subNode->name instanceof Expr) { - return null; - } - - if (! in_array($subNode->name, $staticVariableNames, true)) { - return null; - } - - $subNode->setAttribute(AttributeKey::IS_STATIC_VAR, true); - return $subNode; - } - ); - } -}