> */ private const NODES_TO_MATCH = [Assign::class, AssignRef::class, Foreach_::class, Static_::class, Echo_::class, Return_::class, Expression::class, Throw_::class, If_::class, While_::class, Switch_::class, Nop::class]; /** * @readonly * @var \Rector\DeadCode\NodeAnalyzer\ExprUsedInNodeAnalyzer */ private $exprUsedInNodeAnalyzer; /** * @readonly * @var \Rector\Core\Util\MultiInstanceofChecker */ private $multiInstanceofChecker; public function __construct(ExprUsedInNodeAnalyzer $exprUsedInNodeAnalyzer, MultiInstanceofChecker $multiInstanceofChecker) { $this->exprUsedInNodeAnalyzer = $exprUsedInNodeAnalyzer; $this->multiInstanceofChecker = $multiInstanceofChecker; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Removes non-existing @var annotations above the code', [new CodeSample(<<<'CODE_SAMPLE' class SomeClass { public function get() { /** @var Training[] $trainings */ return $this->getData(); } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function get() { return $this->getData(); } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [Node::class]; } public function refactor(Node $node) : ?Node { if ($this->shouldSkip($node)) { return null; } $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); $varTagValueNode = $phpDocInfo->getVarTagValueNode(); if (!$varTagValueNode instanceof VarTagValueNode) { return null; } if ($this->isObjectShapePseudoType($varTagValueNode)) { return null; } $variableName = \ltrim($varTagValueNode->variableName, '$'); if ($variableName === '' && $this->isAnnotatableReturn($node)) { return null; } if ($this->hasVariableName($node, $variableName)) { return null; } if ($this->isUsedInNextNodeWithExtractPreviouslyCalled($node, $variableName)) { return null; } $comments = $node->getComments(); if (isset($comments[1])) { // skip edge case with double comment, as impossible to resolve by PHPStan doc parser return null; } $phpDocInfo->removeByType(VarTagValueNode::class); return $node; } private function isUsedInNextNodeWithExtractPreviouslyCalled(Node $node, string $variableName) : bool { $variable = new Variable($variableName); $isUsedInNextNode = (bool) $this->betterNodeFinder->findFirstNext($node, function (Node $node) use($variable) : bool { return $this->exprUsedInNodeAnalyzer->isUsed($node, $variable); }); if (!$isUsedInNextNode) { return \false; } return (bool) $this->betterNodeFinder->findFirstPrevious($node, function (Node $subNode) : bool { if (!$subNode instanceof FuncCall) { return \false; } return $this->nodeNameResolver->isName($subNode, 'extract'); }); } private function shouldSkip(Node $node) : bool { if (!$node instanceof Nop) { return !$this->multiInstanceofChecker->isInstanceOf($node, self::NODES_TO_MATCH); } if (\count($node->getComments()) <= 1) { return !$this->multiInstanceofChecker->isInstanceOf($node, self::NODES_TO_MATCH); } return \true; } private function hasVariableName(Node $node, string $variableName) : bool { return (bool) $this->betterNodeFinder->findFirst($node, function (Node $node) use($variableName) : bool { if (!$node instanceof Variable) { return \false; } return $this->isName($node, $variableName); }); } /** * This is a hack, * that waits on phpdoc-parser to get merged - https://github.com/phpstan/phpdoc-parser/pull/145 */ private function isObjectShapePseudoType(VarTagValueNode $varTagValueNode) : bool { if (!$varTagValueNode->type instanceof IdentifierTypeNode) { return \false; } if ($varTagValueNode->type->name !== 'object') { return \false; } if (\strncmp($varTagValueNode->description, '{', \strlen('{')) !== 0) { return \false; } return \strpos($varTagValueNode->description, '}') !== \false; } private function isAnnotatableReturn(Node $node) : bool { return $node instanceof Return_ && $node->expr instanceof CallLike && !$node->expr instanceof New_; } }