> */ private const NODE_TYPES = [Foreach_::class, Static_::class, Echo_::class, Return_::class, Expression::class, Throw_::class, If_::class, While_::class, Switch_::class, Nop::class]; public function __construct(StmtsManipulator $stmtsManipulator, DocBlockUpdater $docBlockUpdater, PhpDocInfoFactory $phpDocInfoFactory, ValueResolver $valueResolver, BetterNodeFinder $betterNodeFinder) { $this->stmtsManipulator = $stmtsManipulator; $this->docBlockUpdater = $docBlockUpdater; $this->phpDocInfoFactory = $phpDocInfoFactory; $this->valueResolver = $valueResolver; $this->betterNodeFinder = $betterNodeFinder; } 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 [StmtsAwareInterface::class]; } /** * @param StmtsAwareInterface $node */ public function refactor(Node $node) : ?Node { if ($node->stmts === null) { return null; } $hasChanged = \false; $extractValues = []; foreach ($node->stmts as $key => $stmt) { if ($stmt instanceof Expression && $stmt->expr instanceof FuncCall && $this->isName($stmt->expr, 'extract') && !$stmt->expr->isFirstClassCallable()) { $appendExtractValues = $this->valueResolver->getValue($stmt->expr->getArgs()[0]->value); if (!\is_array($appendExtractValues)) { // nothing can do as value is dynamic break; } $extractValues = \array_merge($extractValues, \array_keys($appendExtractValues)); continue; } if ($this->shouldSkip($node, $key, $stmt, $extractValues)) { continue; } $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($stmt); $varTagValueNode = $phpDocInfo->getVarTagValueNode(); if (!$varTagValueNode instanceof VarTagValueNode) { continue; } if ($this->isObjectShapePseudoType($varTagValueNode)) { continue; } $variableName = \ltrim($varTagValueNode->variableName, '$'); if ($variableName === '' && $this->isAllowedEmptyVariableName($stmt)) { continue; } if ($this->hasVariableName($stmt, $variableName)) { continue; } $comments = $node->getComments(); if (isset($comments[1])) { // skip edge case with double comment, as impossible to resolve by PHPStan doc parser continue; } $phpDocInfo->removeByType(VarTagValueNode::class); $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($stmt); $hasChanged = \true; } if ($hasChanged) { return $node; } return null; } /** * @param string[] $extractValues */ private function shouldSkip(StmtsAwareInterface $stmtsAware, int $key, Stmt $stmt, array $extractValues) : bool { if (!\in_array(\get_class($stmt), self::NODE_TYPES, \true)) { return \true; } if (\count($stmt->getComments()) !== 1) { return \true; } foreach ($extractValues as $extractValue) { if ($this->stmtsManipulator->isVariableUsedInNextStmt($stmtsAware, $key + 1, $extractValue)) { return \true; } } return isset($stmtsAware->stmts[$key + 1]) && $stmtsAware->stmts[$key + 1] instanceof InlineHTML; } private function hasVariableName(Stmt $stmt, string $variableName) : bool { return (bool) $this->betterNodeFinder->findFirst($stmt, 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 isAllowedEmptyVariableName(Stmt $stmt) : bool { if ($stmt instanceof Return_ && $stmt->expr instanceof CallLike && !$stmt->expr instanceof New_) { return \true; } return $stmt instanceof Expression && $stmt->expr instanceof Assign && $stmt->expr->var instanceof Variable; } }