*/ public static function getAnnotations(File $phpcsFile, int $pointer, ?string $name = null): array { $docCommentOpenPointer = DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $pointer); if ($docCommentOpenPointer === null) { return []; } return SniffLocalCache::getAndSetIfNotCached( $phpcsFile, sprintf('annotations-%d-%s', $docCommentOpenPointer, $name ?? 'all'), static function () use ($phpcsFile, $docCommentOpenPointer, $name): array { $annotations = []; if ($name !== null) { foreach (self::getAnnotations($phpcsFile, $docCommentOpenPointer) as $annotation) { if ($annotation->getName() === $name) { $annotations[] = $annotation; } } } else { $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); if ($parsedDocComment !== null) { foreach ($parsedDocComment->getNode()->getTags() as $node) { $annotationStartPointer = $parsedDocComment->getNodeStartPointer($phpcsFile, $node); $annotations[] = new Annotation( $node, $annotationStartPointer, $parsedDocComment->getNodeEndPointer($phpcsFile, $node, $annotationStartPointer) ); } } } return $annotations; } ); } /** * @param class-string $type * @return list */ public static function getAnnotationNodesByType(Node $node, string $type): array { static $visitor; static $traverser; if ($visitor === null) { $visitor = new class extends AbstractNodeVisitor { /** @var class-string */ private $type; /** @var list */ private $nodes = []; /** @var list */ private $nodesToIgnore = []; /** * @return Node|list|NodeTraverser::*|null */ public function enterNode(Node $node) { if ($this->type === IdentifierTypeNode::class) { if ($node instanceof ArrayShapeItemNode || $node instanceof ObjectShapeItemNode) { $this->nodesToIgnore[] = $node->keyName; } elseif ($node instanceof DoctrineArgument) { $this->nodesToIgnore[] = $node->key; } } if ($node instanceof $this->type && !in_array($node, $this->nodesToIgnore, true)) { $this->nodes[] = $node; } return null; } /** * @param class-string $type */ public function setType(string $type): void { $this->type = $type; } public function clean(): void { $this->nodes = []; $this->nodesToIgnore = []; } /** * @return list */ public function getNodes(): array { return $this->nodes; } }; } if ($traverser === null) { $traverser = new NodeTraverser([$visitor]); } $visitor->setType($type); $visitor->clean(); $traverser->traverse([$node]); return $visitor->getNodes(); } public static function fixAnnotation( ParsedDocComment $parsedDocComment, Annotation $annotation, Node $nodeToFix, Node $fixedNode ): string { $originalNode = $annotation->getNode(); /** @var PhpDocNode $newPhpDocNode */ $newPhpDocNode = PhpDocParserHelper::cloneNode($parsedDocComment->getNode()); foreach ($newPhpDocNode->getTags() as $node) { if ($node->getAttribute(Attribute::ORIGINAL_NODE) === $originalNode) { self::changeAnnotationNode($node, $nodeToFix, $fixedNode); break; } } return PhpDocParserHelper::getPrinter()->printFormatPreserving( $newPhpDocNode, $parsedDocComment->getNode(), $parsedDocComment->getTokens() ); } /** * @param array $traversableTypeHints */ public static function isAnnotationUseless( File $phpcsFile, int $functionPointer, ?TypeHint $typeHint, Annotation $annotation, array $traversableTypeHints, bool $enableUnionTypeHint = false, bool $enableIntersectionTypeHint = false, bool $enableStandaloneNullTrueFalseTypeHints = false ): bool { if ($annotation->isInvalid()) { return false; } if ($typeHint === null) { return false; } /** @var ParamTagValueNode|TypelessParamTagValueNode|ReturnTagValueNode|VarTagValueNode $annotationValue */ $annotationValue = $annotation->getValue(); if ($annotationValue->description !== '') { return false; } if ($annotationValue instanceof TypelessParamTagValueNode) { return true; } $annotationType = $annotationValue->type; if ( TypeHintHelper::isTraversableType( TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, $typeHint->getTypeHintWithoutNullabilitySymbol()), $traversableTypeHints ) && !( $annotationType instanceof IdentifierTypeNode && TypeHintHelper::isSimpleIterableTypeHint(strtolower($annotationType->name)) ) ) { return false; } if (AnnotationTypeHelper::containsStaticOrThisType($annotationType)) { return false; } if ( AnnotationTypeHelper::containsJustTwoTypes($annotationType) || ( $enableUnionTypeHint && ( $annotationType instanceof UnionTypeNode || ( $annotationType instanceof IdentifierTypeNode && TypeHintHelper::isUnofficialUnionTypeHint($annotationType->name) ) ) ) || ( $enableIntersectionTypeHint && $annotationType instanceof IntersectionTypeNode ) ) { $annotationTypeHint = AnnotationTypeHelper::print($annotationType); return TypeHintHelper::typeHintEqualsAnnotation( $phpcsFile, $functionPointer, $typeHint->getTypeHint(), $annotationTypeHint ); } if ($annotationType instanceof ObjectShapeNode) { return false; } if ($annotationType instanceof ConstTypeNode) { return false; } if ($annotationType instanceof GenericTypeNode) { return false; } if ($annotationType instanceof CallableTypeNode) { return false; } if ($annotationType instanceof ConditionalTypeNode) { return false; } if ($annotationType instanceof ConditionalTypeForParameterNode) { return false; } if ($annotationType instanceof IdentifierTypeNode) { if (in_array( strtolower($annotationType->name), ['true', 'false', 'null'], true )) { return $enableStandaloneNullTrueFalseTypeHints; } if (in_array( strtolower($annotationType->name), ['class-string', 'trait-string', 'callable-string', 'numeric-string', 'non-empty-string', 'non-falsy-string', 'literal-string', 'positive-int', 'negative-int'], true )) { return false; } } $annotationTypeHint = AnnotationTypeHelper::getTypeHintFromOneType($annotationType); return TypeHintHelper::typeHintEqualsAnnotation( $phpcsFile, $functionPointer, $typeHint->getTypeHintWithoutNullabilitySymbol(), $annotationTypeHint ); } private static function changeAnnotationNode(PhpDocTagNode $tagNode, Node $nodeToChange, Node $changedNode): PhpDocTagNode { static $visitor; static $traverser; if ($visitor === null) { $visitor = new class extends AbstractNodeVisitor { /** @var Node */ private $nodeToChange; /** @var Node */ private $changedNode; /** * @return Node|list|NodeTraverser::*|null */ public function enterNode(Node $node) { if ($node->getAttribute(Attribute::ORIGINAL_NODE) === $this->nodeToChange) { return $this->changedNode; } return null; } public function setNodeToChange(Node $nodeToChange): void { $this->nodeToChange = $nodeToChange; } public function setChangedNode(Node $changedNode): void { $this->changedNode = $changedNode; } }; } if ($traverser === null) { $traverser = new NodeTraverser([$visitor]); } $visitor->setNodeToChange($nodeToChange); $visitor->setChangedNode($changedNode); [$changedTagNode] = $traverser->traverse([$tagNode]); return $changedTagNode; } }