diff --git a/SlevomatCodingStandard/Helpers/Annotation.php b/SlevomatCodingStandard/Helpers/Annotation.php new file mode 100644 index 000000000..d5b6343c0 --- /dev/null +++ b/SlevomatCodingStandard/Helpers/Annotation.php @@ -0,0 +1,66 @@ +node = $node; + $this->startPointer = $startPointer; + $this->endPointer = $endPointer; + } + + public function getNode(): PhpDocTagNode + { + return $this->node; + } + + public function getName(): string + { + return $this->node->name; + } + + /** + * @return T + */ + public function getValue(): PhpDocTagValueNode + { + /** @phpstan-ignore-next-line */ + return $this->node->value; + } + + public function getStartPointer(): int + { + return $this->startPointer; + } + + public function getEndPointer(): int + { + return $this->endPointer; + } + + public function isInvalid(): bool + { + return $this->node->value instanceof InvalidTagValueNode; + } + +} diff --git a/SlevomatCodingStandard/Helpers/Annotation/Annotation.php b/SlevomatCodingStandard/Helpers/Annotation/Annotation.php deleted file mode 100644 index d6137b686..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/Annotation.php +++ /dev/null @@ -1,65 +0,0 @@ -name = $name; - $this->startPointer = $startPointer; - $this->endPointer = $endPointer; - $this->content = $content; - } - - public function getName(): string - { - return $this->name; - } - - public function getStartPointer(): int - { - return $this->startPointer; - } - - public function getEndPointer(): int - { - return $this->endPointer; - } - - public function getContent(): ?string - { - return $this->content; - } - - protected function errorWhenInvalid(): void - { - if ($this->isInvalid()) { - throw new LogicException(sprintf('Invalid %s annotation.', $this->name)); - } - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/AssertAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/AssertAnnotation.php deleted file mode 100644 index 862cd2483..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/AssertAnnotation.php +++ /dev/null @@ -1,80 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - /** - * @return AssertTagMethodValueNode|AssertTagPropertyValueNode|AssertTagValueNode|null - */ - public function getContentNode() - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - return $this->contentNode->type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/ExtendsAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/ExtendsAnnotation.php deleted file mode 100644 index fe6c180f3..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/ExtendsAnnotation.php +++ /dev/null @@ -1,68 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): ExtendsTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getType(): GenericTypeNode - { - $this->errorWhenInvalid(); - - return $this->contentNode->type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/GenericAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/GenericAnnotation.php deleted file mode 100644 index 3036eb57f..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/GenericAnnotation.php +++ /dev/null @@ -1,48 +0,0 @@ -parameters = $parameters; - } - - public function getParameters(): ?string - { - return $this->parameters; - } - - public function isInvalid(): bool - { - return false; - } - - public function print(): string - { - $printed = $this->name; - - if ($this->parameters !== null) { - $printed .= sprintf('(%s)', $this->parameters); - } - - if ($this->content !== null) { - $printed .= sprintf(' %s', $this->content); - } - - return $printed; - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/ImplementsAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/ImplementsAnnotation.php deleted file mode 100644 index fc38e3507..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/ImplementsAnnotation.php +++ /dev/null @@ -1,68 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): ImplementsTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getType(): GenericTypeNode - { - $this->errorWhenInvalid(); - - return $this->contentNode->type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/MethodAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/MethodAnnotation.php deleted file mode 100644 index 32b641182..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/MethodAnnotation.php +++ /dev/null @@ -1,109 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): MethodTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getMethodName(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->methodName !== '' ? $this->contentNode->methodName : null; - } - - /** - * @return GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|IdentifierTypeNode|ThisTypeNode - */ - public function getMethodReturnType(): ?TypeNode - { - $this->errorWhenInvalid(); - - /** @var GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|IdentifierTypeNode|ThisTypeNode $type */ - $type = $this->contentNode->returnType; - return $type; - } - - /** - * @return list - */ - public function getMethodTemplateTypes(): array - { - $this->errorWhenInvalid(); - - return $this->contentNode->templateTypes; - } - - /** - * @return list - */ - public function getMethodParameters(): array - { - $this->errorWhenInvalid(); - - return $this->contentNode->parameters; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/MixinAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/MixinAnnotation.php deleted file mode 100644 index 9baa03c67..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/MixinAnnotation.php +++ /dev/null @@ -1,74 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): MixinTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - /** - * @return GenericTypeNode|IdentifierTypeNode - */ - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - /** @var GenericTypeNode|IdentifierTypeNode $type */ - $type = $this->contentNode->type; - return $type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/ParameterAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/ParameterAnnotation.php deleted file mode 100644 index 390f5e772..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/ParameterAnnotation.php +++ /dev/null @@ -1,102 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - /** - * @return ParamTagValueNode|TypelessParamTagValueNode|null - */ - public function getContentNode() - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getParameterName(): string - { - $this->errorWhenInvalid(); - - return $this->contentNode->parameterName; - } - - /** - * @return GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode|null - */ - public function getType(): ?TypeNode - { - $this->errorWhenInvalid(); - - if ($this->contentNode instanceof TypelessParamTagValueNode) { - return null; - } - - /** @var GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode $type */ - $type = $this->contentNode->type; - return $type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/ParameterOutAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/ParameterOutAnnotation.php deleted file mode 100644 index d9f79a148..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/ParameterOutAnnotation.php +++ /dev/null @@ -1,91 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): ?ParamOutTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getParameterName(): string - { - $this->errorWhenInvalid(); - - return $this->contentNode->parameterName; - } - - /** - * @return GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode - */ - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - /** @var GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode $type */ - $type = $this->contentNode->type; - return $type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/PropertyAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/PropertyAnnotation.php deleted file mode 100644 index bb6182057..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/PropertyAnnotation.php +++ /dev/null @@ -1,92 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): PropertyTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getPropertyName(): string - { - $this->errorWhenInvalid(); - - return $this->contentNode->propertyName; - } - - /** - * @return GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode - */ - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - /** @var GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode $type */ - $type = $this->contentNode->type; - return $type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/ReturnAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/ReturnAnnotation.php deleted file mode 100644 index ac7d7b15a..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/ReturnAnnotation.php +++ /dev/null @@ -1,86 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): ReturnTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - /** - * @return GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode|ConditionalTypeNode|ConditionalTypeForParameterNode - */ - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - /** @var GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode|ConditionalTypeForParameterNode $type */ - $type = $this->contentNode->type; - return $type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/SelfOutAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/SelfOutAnnotation.php deleted file mode 100644 index b2da7d45c..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/SelfOutAnnotation.php +++ /dev/null @@ -1,84 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): ?SelfOutTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - /** - * @return GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode - */ - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - /** @var GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode $type */ - $type = $this->contentNode->type; - return $type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/TemplateAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/TemplateAnnotation.php deleted file mode 100644 index 917c2521d..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/TemplateAnnotation.php +++ /dev/null @@ -1,86 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): TemplateTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getTemplateName(): string - { - $this->errorWhenInvalid(); - - return $this->contentNode->name; - } - - public function getBound(): ?TypeNode - { - $this->errorWhenInvalid(); - - return $this->contentNode->bound; - } - - public function getDefault(): ?TypeNode - { - $this->errorWhenInvalid(); - - return $this->contentNode->default; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/ThrowsAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/ThrowsAnnotation.php deleted file mode 100644 index 9ab21faab..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/ThrowsAnnotation.php +++ /dev/null @@ -1,75 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): ThrowsTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - /** - * @return UnionTypeNode|IdentifierTypeNode - */ - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - /** @var UnionTypeNode|IdentifierTypeNode $type */ - $type = $this->contentNode->type; - return $type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/TypeAliasAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/TypeAliasAnnotation.php deleted file mode 100644 index 1a17f73c1..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/TypeAliasAnnotation.php +++ /dev/null @@ -1,67 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): TypeAliasTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function getAlias(): string - { - $this->errorWhenInvalid(); - - return $this->contentNode->alias; - } - - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - return $this->contentNode->type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/TypeImportAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/TypeImportAnnotation.php deleted file mode 100644 index 160ea47a3..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/TypeImportAnnotation.php +++ /dev/null @@ -1,89 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): TypeAliasImportTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function getAlias(): string - { - return $this->getImportedAs() ?? $this->getImportedAlias(); - } - - public function getImportedAlias(): string - { - $this->errorWhenInvalid(); - - return $this->contentNode->importedAlias; - } - - /** - * @return IdentifierTypeNode - */ - public function getImportedFrom(): TypeNode - { - $this->errorWhenInvalid(); - - return $this->contentNode->importedFrom; - } - - public function getImportedAs(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->importedAs; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/UseAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/UseAnnotation.php deleted file mode 100644 index ae12edade..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/UseAnnotation.php +++ /dev/null @@ -1,68 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): UsesTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getType(): GenericTypeNode - { - $this->errorWhenInvalid(); - - return $this->contentNode->type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/Annotation/VariableAnnotation.php b/SlevomatCodingStandard/Helpers/Annotation/VariableAnnotation.php deleted file mode 100644 index 0d6095f0e..000000000 --- a/SlevomatCodingStandard/Helpers/Annotation/VariableAnnotation.php +++ /dev/null @@ -1,91 +0,0 @@ -contentNode = $contentNode; - } - - public function isInvalid(): bool - { - return $this->contentNode === null; - } - - public function getContentNode(): VarTagValueNode - { - $this->errorWhenInvalid(); - - return $this->contentNode; - } - - public function hasDescription(): bool - { - return $this->getDescription() !== null; - } - - public function getDescription(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->description !== '' ? $this->contentNode->description : null; - } - - public function getVariableName(): ?string - { - $this->errorWhenInvalid(); - - return $this->contentNode->variableName !== '' ? $this->contentNode->variableName : null; - } - - /** - * @return GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode - */ - public function getType(): TypeNode - { - $this->errorWhenInvalid(); - - /** @var GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode $type */ - $type = $this->contentNode->type; - return $type; - } - - public function print(): string - { - return sprintf('%s %s', $this->name, AnnotationHelper::getPhpDocPrinter()->print($this->contentNode)); - } - -} diff --git a/SlevomatCodingStandard/Helpers/AnnotationConstantExpressionHelper.php b/SlevomatCodingStandard/Helpers/AnnotationConstantExpressionHelper.php deleted file mode 100644 index 7ccfda0db..000000000 --- a/SlevomatCodingStandard/Helpers/AnnotationConstantExpressionHelper.php +++ /dev/null @@ -1,73 +0,0 @@ - - */ - public static function getConstantFetchNodes(ConstExprNode $constantExpressionNode): array - { - if ($constantExpressionNode instanceof ConstExprArrayNode) { - $constantFetchNodes = []; - foreach ($constantExpressionNode->items as $itemConstantExpressionNode) { - $constantFetchNodes = array_merge($constantFetchNodes, self::getConstantFetchNodes($itemConstantExpressionNode)); - } - return $constantFetchNodes; - } - - if ($constantExpressionNode instanceof ConstExprArrayItemNode) { - $constantFetchNodes = self::getConstantFetchNodes($constantExpressionNode->value); - if ($constantExpressionNode->key !== null) { - $constantFetchNodes = array_merge($constantFetchNodes, self::getConstantFetchNodes($constantExpressionNode->key)); - } - return $constantFetchNodes; - } - - if ($constantExpressionNode instanceof ConstFetchNode) { - return [$constantExpressionNode]; - } - - return []; - } - - public static function change(ConstExprNode $masterNode, ConstExprNode $nodeToChange, ConstExprNode $changedNode): ConstExprNode - { - if ($masterNode === $nodeToChange) { - return $changedNode; - } - - if ($masterNode instanceof ConstExprArrayNode) { - $items = []; - foreach ($masterNode->items as $itemNode) { - /** @var ConstExprArrayItemNode $changedItemNode */ - $changedItemNode = self::change($itemNode, $nodeToChange, $changedNode); - - $items[] = $changedItemNode; - } - - return new ConstExprArrayNode($items); - } - - if ($masterNode instanceof ConstExprArrayItemNode) { - $key = $masterNode->key !== null ? self::change($masterNode->key, $nodeToChange, $changedNode) : null; - $value = self::change($masterNode->value, $nodeToChange, $changedNode); - - return new ConstExprArrayItemNode($key, $value); - } - - return clone $masterNode; - } - -} diff --git a/SlevomatCodingStandard/Helpers/AnnotationHelper.php b/SlevomatCodingStandard/Helpers/AnnotationHelper.php index 579fd2037..7a88d1cc7 100644 --- a/SlevomatCodingStandard/Helpers/AnnotationHelper.php +++ b/SlevomatCodingStandard/Helpers/AnnotationHelper.php @@ -3,12 +3,18 @@ namespace SlevomatCodingStandard\Helpers; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Tokens; -use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode; -use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; +use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor; +use PHPStan\PhpDocParser\Ast\Attribute; +use PHPStan\PhpDocParser\Ast\Node; +use PHPStan\PhpDocParser\Ast\NodeTraverser; +use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArgument; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode; use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode; @@ -16,52 +22,14 @@ use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; -use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; -use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use PHPStan\PhpDocParser\Lexer\Lexer; -use PHPStan\PhpDocParser\Parser\ConstExprParser; -use PHPStan\PhpDocParser\Parser\PhpDocParser; -use PHPStan\PhpDocParser\Parser\TokenIterator; -use PHPStan\PhpDocParser\Parser\TypeParser; -use PHPStan\PhpDocParser\Printer\Printer; -use SlevomatCodingStandard\Helpers\Annotation\Annotation; -use SlevomatCodingStandard\Helpers\Annotation\AssertAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ExtendsAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ImplementsAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\MethodAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\MixinAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ParameterAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ParameterOutAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\PropertyAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ReturnAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\SelfOutAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\TemplateAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ThrowsAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\TypeAliasAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\TypeImportAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\UseAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; -use function array_key_exists; -use function array_merge; -use function get_class; +use function count; use function in_array; -use function max; -use function preg_match; -use function preg_match_all; -use function preg_replace; +use function mb_strlen; use function sprintf; -use function strlen; use function strtolower; -use function substr; -use function trim; -use const T_DOC_COMMENT_CLOSE_TAG; -use const T_DOC_COMMENT_STAR; -use const T_DOC_COMMENT_STRING; -use const T_DOC_COMMENT_TAG; -use const T_DOC_COMMENT_WHITESPACE; /** * @internal @@ -71,317 +39,151 @@ class AnnotationHelper public const STATIC_ANALYSIS_PREFIXES = ['psalm', 'phpstan']; - private const MAPPING = [ - '@param' => ParameterAnnotation::class, - '@return' => ReturnAnnotation::class, - '@var' => VariableAnnotation::class, - '@throws' => ThrowsAnnotation::class, - '@property' => PropertyAnnotation::class, - '@property-read' => PropertyAnnotation::class, - '@property-write' => PropertyAnnotation::class, - '@method' => MethodAnnotation::class, - '@template' => TemplateAnnotation::class, - '@template-covariant' => TemplateAnnotation::class, - '@extends' => ExtendsAnnotation::class, - '@template-extends' => ExtendsAnnotation::class, - '@implements' => ImplementsAnnotation::class, - '@template-implements' => ImplementsAnnotation::class, - '@use' => UseAnnotation::class, - '@template-use' => UseAnnotation::class, - '@type' => TypeAliasAnnotation::class, - '@import-type' => TypeImportAnnotation::class, - '@mixin' => MixinAnnotation::class, - '@assert' => AssertAnnotation::class, - '@assert-if-true' => AssertAnnotation::class, - '@assert-if-false' => AssertAnnotation::class, - '@param-out' => ParameterOutAnnotation::class, - '@self-out' => SelfOutAnnotation::class, - '@this-out' => SelfOutAnnotation::class, - ]; - /** - * @param VariableAnnotation|ParameterAnnotation|ReturnAnnotation|ThrowsAnnotation|PropertyAnnotation|MethodAnnotation|TemplateAnnotation|ExtendsAnnotation|ImplementsAnnotation|UseAnnotation|MixinAnnotation|TypeAliasAnnotation|TypeImportAnnotation|AssertAnnotation|ParameterOutAnnotation|SelfOutAnnotation $annotation - * @return list + * @return list */ - public static function getAnnotationTypes(Annotation $annotation): array + public static function getAnnotations(File $phpcsFile, int $pointer, ?string $name = null): array { - $annotationTypes = []; - - if ($annotation instanceof MethodAnnotation) { - if ($annotation->getMethodReturnType() !== null) { - $annotationTypes[] = $annotation->getMethodReturnType(); - } - foreach ($annotation->getMethodTemplateTypes() as $methodTemplateType) { - if ($methodTemplateType->bound !== null) { - $annotationTypes[] = $methodTemplateType->bound; - } - if ($methodTemplateType->default !== null) { - $annotationTypes[] = $methodTemplateType->default; - } - } - foreach ($annotation->getMethodParameters() as $methodParameterAnnotation) { - if ($methodParameterAnnotation->type === null) { - continue; - } - - $annotationTypes[] = $methodParameterAnnotation->type; - } - } elseif ($annotation instanceof TemplateAnnotation) { - if ($annotation->getBound() !== null) { - $annotationTypes[] = $annotation->getBound(); - } - if ($annotation->getDefault() !== null) { - $annotationTypes[] = $annotation->getDefault(); - } - } elseif ($annotation instanceof TypeImportAnnotation) { - $annotationTypes[] = $annotation->getImportedFrom(); - } elseif ($annotation->getType() !== null) { - $annotationTypes[] = $annotation->getType(); + $docCommentOpenPointer = DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $pointer); + if ($docCommentOpenPointer === null) { + return []; } - return $annotationTypes; - } - - /** - * @param VariableAnnotation|ParameterAnnotation|ReturnAnnotation|ThrowsAnnotation|PropertyAnnotation|MethodAnnotation|TemplateAnnotation|ExtendsAnnotation|ImplementsAnnotation|UseAnnotation|MixinAnnotation|AssertAnnotation|ParameterOutAnnotation|SelfOutAnnotation $annotation - * @return list - */ - public static function getAnnotationConstantExpressions(Annotation $annotation): array - { - $constantExpressions = []; - - if ($annotation instanceof MethodAnnotation) { - foreach ($annotation->getMethodParameters() as $methodParameterAnnotation) { - if ($methodParameterAnnotation->defaultValue === null) { - continue; - } - - $constantExpressions[] = $methodParameterAnnotation->defaultValue; - } - } - - foreach (self::getAnnotationTypes($annotation) as $annotationType) { - foreach (AnnotationTypeHelper::getConstantTypeNodes($annotationType) as $constTypeNode) { - $constantExpressions[] = $constTypeNode->constExpr; - } - } - - return $constantExpressions; - } - - /** - * @param VariableAnnotation|ParameterAnnotation|ReturnAnnotation|ThrowsAnnotation|PropertyAnnotation|MethodAnnotation|TemplateAnnotation|ExtendsAnnotation|ImplementsAnnotation|UseAnnotation|MixinAnnotation|AssertAnnotation|ParameterOutAnnotation|SelfOutAnnotation $annotation - */ - public static function fixAnnotationType(File $phpcsFile, Annotation $annotation, TypeNode $typeNode, TypeNode $fixedTypeNode): string - { - $fixedAnnotation = self::fixAnnotation($annotation, $typeNode, $fixedTypeNode); - - return self::fix($phpcsFile, $annotation, $fixedAnnotation); - } - - /** - * @param VariableAnnotation|ParameterAnnotation|ReturnAnnotation|ThrowsAnnotation|PropertyAnnotation|MethodAnnotation|TemplateAnnotation|ExtendsAnnotation|ImplementsAnnotation|UseAnnotation|MixinAnnotation|AssertAnnotation|ParameterOutAnnotation|SelfOutAnnotation $annotation - */ - public static function fixAnnotationConstantFetchNode( - File $phpcsFile, - Annotation $annotation, - ConstFetchNode $node, - ConstFetchNode $fixedNode - ): string - { - if ($annotation instanceof MethodAnnotation) { - $fixedContentNode = clone $annotation->getContentNode(); - - foreach ($fixedContentNode->parameters as $parameterNo => $parameterNode) { - if ($parameterNode->defaultValue === null) { - continue; - } - - $fixedContentNode->parameters[$parameterNo] = clone $parameterNode; - $fixedContentNode->parameters[$parameterNo]->defaultValue = AnnotationConstantExpressionHelper::change( - $parameterNode->defaultValue, - $node, - $fixedNode - ); - } + return SniffLocalCache::getAndSetIfNotCached( + $phpcsFile, + sprintf('annotations-%d-%s', $docCommentOpenPointer, $name ?? 'all'), + static function () use ($phpcsFile, $docCommentOpenPointer, $name): array { + $annotations = []; - $fixedAnnotation = new MethodAnnotation( - $annotation->getName(), - $annotation->getStartPointer(), - $annotation->getEndPointer(), - $annotation->getContent(), - $fixedContentNode - ); - } else { - $fixedAnnotation = $annotation; - - foreach (self::getAnnotationTypes($annotation) as $annotationType) { - foreach (AnnotationTypeHelper::getConstantTypeNodes($annotationType) as $constTypeNode) { - foreach (AnnotationConstantExpressionHelper::getConstantFetchNodes($constTypeNode->constExpr) as $constFetchNode) { - if ($constFetchNode !== $node) { - continue; + if ($name !== null) { + foreach (self::getAnnotations($phpcsFile, $docCommentOpenPointer) as $annotation) { + if ($annotation->getName() === $name) { + $annotations[] = $annotation; } + } + } else { + $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); - $fixedConstTypeNode = new ConstTypeNode( - AnnotationConstantExpressionHelper::change($constTypeNode->constExpr, $node, $fixedNode) + foreach ($parsedDocComment->getNode()->getTags() as $node) { + $annotationStartPointer = self::getStartPointer($phpcsFile, $parsedDocComment->getOpenPointer(), $node); + $annotations[] = new Annotation( + $node, + $annotationStartPointer, + self::getEndPointer($phpcsFile, $parsedDocComment, $annotationStartPointer, $node) ); - $fixedAnnotation = self::fixAnnotation($annotation, $constTypeNode, $fixedConstTypeNode); - break 3; } } - } - } - - return self::fix($phpcsFile, $annotation, $fixedAnnotation); - } - /** - * @return list - */ - public static function getAnnotationsByName(File $phpcsFile, int $pointer, string $annotationName): array - { - $annotations = self::getAnnotations($phpcsFile, $pointer); - - return $annotations[$annotationName] ?? []; + return $annotations; + } + ); } /** - * @return array> + * @param class-string $type + * @return list */ - public static function getAnnotations(File $phpcsFile, int $pointer): array + public static function getAnnotationNodesByType(Node $node, string $type): array { - return SniffLocalCache::getAndSetIfNotCached( - $phpcsFile, - sprintf('annotations-%d', $pointer), - static function () use ($phpcsFile, $pointer): array { - $annotations = []; - - $docCommentOpenToken = DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $pointer); - if ($docCommentOpenToken === null) { - return $annotations; - } - - $annotationNameCodes = array_merge([T_DOC_COMMENT_TAG], Tokens::$phpcsCommentTokens); - - $tokens = $phpcsFile->getTokens(); - $i = $docCommentOpenToken + 1; - while ($i < $tokens[$docCommentOpenToken]['comment_closer']) { - if (!in_array($tokens[$i]['code'], $annotationNameCodes, true)) { - $i++; - continue; - } - - $annotationStartPointer = $i; - $annotationEndPointer = $i; - - // Fix for wrong PHPCS parsing - $parenthesesLevel = max((int) preg_match_all('~[({]~', $tokens[$i]['content']) - (int) preg_match_all( - '~[)}]~', - $tokens[$i]['content'] - ), 0); - - $annotationCode = $tokens[$i]['content']; - - for ($j = $i + 1; $j <= $tokens[$docCommentOpenToken]['comment_closer']; $j++) { - if ($tokens[$j]['code'] === T_DOC_COMMENT_CLOSE_TAG) { - $i = $j; - break; + 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 (in_array($tokens[$j]['code'], $annotationNameCodes, true) && $parenthesesLevel === 0) { - $i = $j; - break; - } + if ($node instanceof $this->type && !in_array($node, $this->nodesToIgnore, true)) { + $this->nodes[] = $node; + } - if ($tokens[$j]['code'] === T_DOC_COMMENT_STAR) { - continue; - } + return null; + } - if (in_array($tokens[$j]['code'], array_merge([T_DOC_COMMENT_STRING], $annotationNameCodes), true)) { - $annotationEndPointer = $j; - } elseif ($tokens[$j]['code'] === T_DOC_COMMENT_WHITESPACE) { - if (array_key_exists($j - 1, $tokens) && $tokens[$j - 1]['code'] === T_DOC_COMMENT_STAR) { - continue; - } - if (array_key_exists($j + 1, $tokens) && $tokens[$j + 1]['code'] === T_DOC_COMMENT_STAR) { - continue; - } - } + /** + * @param class-string $type + */ + public function setType(string $type): void + { + $this->type = $type; + } - $parenthesesLevel += (int) preg_match_all('~[({]~', $tokens[$j]['content']) - (int) preg_match_all( - '~[)}]~', - $tokens[$j]['content'] - ); - if ($parenthesesLevel < 0) { - $parenthesesLevel = 0; - } + public function clean(): void + { + $this->nodes = []; + $this->nodesToIgnore = []; + } - $annotationCode .= $tokens[$j]['content']; - } + /** + * @return list + */ + public function getNodes(): array + { + return $this->nodes; + } - $annotationName = $tokens[$annotationStartPointer]['content']; - $annotationParameters = null; - $annotationContent = null; - if (preg_match('~^(@[-a-zA-Z\\\\:]+)(?:\((.*)\))?(?:\\s+(.+))?(,|$)~s', trim($annotationCode), $matches) !== 0) { - $annotationName = $matches[1]; - $annotationParameters = trim($matches[2]); - if ($annotationParameters === '') { - $annotationParameters = null; - } - $annotationContent = trim($matches[3]); - if ($annotationContent === '') { - $annotationContent = null; - } - } + }; + } - $className = null; - if (array_key_exists($annotationName, self::MAPPING)) { - $className = self::MAPPING[$annotationName]; - } else { - foreach (self::STATIC_ANALYSIS_PREFIXES as $prefix) { - $annotationNameWithoutPrefix = preg_replace('~^@' . $prefix . '-~', '@', $annotationName); + if ($traverser === null) { + $traverser = new NodeTraverser([$visitor]); + } - if (!array_key_exists($annotationNameWithoutPrefix, self::MAPPING)) { - continue; - } + $visitor->setType($type); + $visitor->clean(); - $className = self::MAPPING[$annotationNameWithoutPrefix]; - break; - } - } + $traverser->traverse([$node]); - if ($className !== null) { - $parsedContent = null; - if ($annotationContent !== null) { - $parsedContent = self::parseAnnotationContent($annotationName, $annotationContent); - if ($parsedContent instanceof InvalidTagValueNode) { - $parsedContent = null; - } elseif (isset($parsedContent->description) && $parsedContent->description !== '') { - $parsedContent->description = substr($annotationContent, -strlen($parsedContent->description)); - } - } + return $visitor->getNodes(); + } - $annotation = new $className($annotationName, $annotationStartPointer, $annotationEndPointer, $annotationContent, $parsedContent); - } else { - $annotation = new GenericAnnotation( - $annotationName, - $annotationStartPointer, - $annotationEndPointer, - $annotationParameters, - $annotationContent - ); - } + public static function fixAnnotation( + ParsedDocComment $parsedDocComment, + Annotation $annotation, + Node $nodeToFix, + Node $fixedNode + ): string + { + $originalNode = $annotation->getNode(); - $annotations[$annotationName][] = $annotation; - } + /** @var PhpDocNode $newPhpDocNode */ + $newPhpDocNode = PhpDocParserHelper::cloneNode($parsedDocComment->getNode()); - return $annotations; + 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 ReturnAnnotation|ParameterAnnotation|VariableAnnotation $annotation * @param array $traversableTypeHints */ public static function isAnnotationUseless( @@ -399,15 +201,18 @@ public static function isAnnotationUseless( return false; } - if ($typeHint === null || $annotation->getContent() === null) { + if ($typeHint === null) { return false; } - if ($annotation->hasDescription()) { + /** @var ParamTagValueNode|TypelessParamTagValueNode|ReturnTagValueNode|VarTagValueNode $annotationValue */ + $annotationValue = $annotation->getValue(); + + if ($annotationValue->description !== '') { return false; } - if ($annotation->getType() === null) { + if ($annotationValue instanceof TypelessParamTagValueNode) { return true; } @@ -418,28 +223,30 @@ public static function isAnnotationUseless( return false; } - if (AnnotationTypeHelper::containsStaticOrThisType($annotation->getType())) { + $annotationType = $annotationValue->type; + + if (AnnotationTypeHelper::containsStaticOrThisType($annotationType)) { return false; } if ( - AnnotationTypeHelper::containsJustTwoTypes($annotation->getType()) + AnnotationTypeHelper::containsJustTwoTypes($annotationType) || ( $enableUnionTypeHint && ( - $annotation->getType() instanceof UnionTypeNode + $annotationType instanceof UnionTypeNode || ( - $annotation->getType() instanceof IdentifierTypeNode - && TypeHintHelper::isUnofficialUnionTypeHint($annotation->getType()->name) + $annotationType instanceof IdentifierTypeNode + && TypeHintHelper::isUnofficialUnionTypeHint($annotationType->name) ) ) ) || ( $enableIntersectionTypeHint - && $annotation->getType() instanceof IntersectionTypeNode + && $annotationType instanceof IntersectionTypeNode ) ) { - $annotationTypeHint = AnnotationTypeHelper::print($annotation->getType()); + $annotationTypeHint = AnnotationTypeHelper::print($annotationType); return TypeHintHelper::typeHintEqualsAnnotation( $phpcsFile, $functionPointer, @@ -448,36 +255,33 @@ public static function isAnnotationUseless( ); } - if ($annotation->getType() instanceof ObjectShapeNode) { + if ($annotationType instanceof ObjectShapeNode) { return false; } - if ($annotation->getType() instanceof ConstTypeNode) { + if ($annotationType instanceof ConstTypeNode) { return false; } - if ($annotation->getType() instanceof GenericTypeNode) { + if ($annotationType instanceof GenericTypeNode) { return false; } - if ($annotation->getType() instanceof CallableTypeNode) { + if ($annotationType instanceof CallableTypeNode) { return false; } - if ($annotation->getType() instanceof ConditionalTypeNode) { + if ($annotationType instanceof ConditionalTypeNode) { return false; } - if ($annotation->getType() instanceof ConditionalTypeForParameterNode) { + if ($annotationType instanceof ConditionalTypeForParameterNode) { return false; } - /** @var GenericTypeNode|IdentifierTypeNode|ThisTypeNode $annotationTypeNode */ - $annotationTypeNode = $annotation->getType(); - - if ($annotationTypeNode instanceof IdentifierTypeNode) { + if ($annotationType instanceof IdentifierTypeNode) { if (in_array( - strtolower($annotationTypeNode->name), + strtolower($annotationType->name), ['true', 'false', 'null'], true )) { @@ -485,7 +289,7 @@ public static function isAnnotationUseless( } if (in_array( - strtolower($annotationTypeNode->name), + strtolower($annotationType->name), ['class-string', 'trait-string', 'callable-string', 'numeric-string', 'non-empty-string', 'non-falsy-string', 'literal-string', 'positive-int', 'negative-int'], true )) { @@ -493,7 +297,7 @@ public static function isAnnotationUseless( } } - $annotationTypeHint = AnnotationTypeHelper::getTypeHintFromOneType($annotationTypeNode); + $annotationTypeHint = AnnotationTypeHelper::getTypeHintFromOneType($annotationType); return TypeHintHelper::typeHintEqualsAnnotation( $phpcsFile, $functionPointer, @@ -502,145 +306,102 @@ public static function isAnnotationUseless( ); } - public static function getPhpDocPrinter(): Printer + private static function getStartPointer(File $phpcsFile, int $docCommentOpenPointer, PhpDocTagNode $annotationNode): int { - static $phpDocPrinter; + $tokens = $phpcsFile->getTokens(); + + $tagStartLine = $tokens[$docCommentOpenPointer]['line'] + $annotationNode->getAttribute('startLine') - 1; - if ($phpDocPrinter === null) { - $phpDocPrinter = new Printer(); + $searchPointer = $docCommentOpenPointer + 1; + for ($i = $docCommentOpenPointer + 1; $i < $tokens[$docCommentOpenPointer]['comment_closer']; $i++) { + if ($tagStartLine === $tokens[$i]['line']) { + $searchPointer = $i; + break; + } } - return $phpDocPrinter; + return TokenHelper::findNext($phpcsFile, TokenHelper::$annotationTokenCodes, $searchPointer); } - /** - * @param VariableAnnotation|ParameterAnnotation|ReturnAnnotation|ThrowsAnnotation|PropertyAnnotation|MethodAnnotation|TemplateAnnotation|ExtendsAnnotation|ImplementsAnnotation|UseAnnotation|MixinAnnotation|TypeAliasAnnotation|TypeImportAnnotation|AssertAnnotation|ParameterOutAnnotation|SelfOutAnnotation $annotation - */ - private static function fixAnnotation(Annotation $annotation, TypeNode $typeNode, TypeNode $fixedTypeNode): Annotation + private static function getEndPointer( + File $phpcsFile, + ParsedDocComment $parsedDocComment, + int $annotationStartPointer, + PhpDocTagNode $annotationNode + ): int { - if ($annotation instanceof MethodAnnotation) { - $fixedContentNode = clone $annotation->getContentNode(); + $tokens = $phpcsFile->getTokens(); - if ($fixedContentNode->returnType !== null) { - $fixedContentNode->returnType = AnnotationTypeHelper::change($fixedContentNode->returnType, $typeNode, $fixedTypeNode); - } - foreach ($fixedContentNode->templateTypes as $templateTypeNo => $templateTypeNode) { - $fixedContentNode->templateTypes[$templateTypeNo] = self::fixTemplateTagValueNode( - $templateTypeNode, - $typeNode, - $fixedTypeNode - ); - } - foreach ($fixedContentNode->parameters as $parameterNo => $parameterNode) { - if ($parameterNode->type === null) { - continue; - } + $annotationContent = $parsedDocComment->getTokens()->getContentBetween( + $annotationNode->getAttribute(Attribute::START_INDEX), + $annotationNode->getAttribute(Attribute::END_INDEX) + 1 + ); + $annotationLength = mb_strlen($annotationContent, $phpcsFile->config->encoding); + + $searchPointer = $annotationStartPointer; - $fixedContentNode->parameters[$parameterNo] = clone $parameterNode; - $fixedContentNode->parameters[$parameterNo]->type = AnnotationTypeHelper::change( - $parameterNode->type, - $typeNode, - $fixedTypeNode - ); + $content = ''; + for ($i = $annotationStartPointer; $i < count($tokens); $i++) { + $content .= $tokens[$i]['content']; + + if (mb_strlen($content, $phpcsFile->config->encoding) >= $annotationLength) { + $searchPointer = $i; + break; } - } elseif ($annotation instanceof TemplateAnnotation) { - $fixedContentNode = self::fixTemplateTagValueNode($annotation->getContentNode(), $typeNode, $fixedTypeNode); - } elseif ($annotation instanceof TypeImportAnnotation) { - $fixedContentNode = clone $annotation->getContentNode(); - /** @var IdentifierTypeNode $fixedType */ - $fixedType = AnnotationTypeHelper::change($annotation->getImportedFrom(), $typeNode, $fixedTypeNode); - $fixedContentNode->importedFrom = $fixedType; - } elseif ( - $annotation instanceof ExtendsAnnotation - || $annotation instanceof ImplementsAnnotation - || $annotation instanceof UseAnnotation - ) { - $fixedContentNode = clone $annotation->getContentNode(); - /** @var GenericTypeNode $fixedType */ - $fixedType = AnnotationTypeHelper::change($annotation->getType(), $typeNode, $fixedTypeNode); - $fixedContentNode->type = $fixedType; - } else { - $fixedContentNode = clone $annotation->getContentNode(); - $fixedContentNode->type = AnnotationTypeHelper::change($annotation->getType(), $typeNode, $fixedTypeNode); } - $annotationClassName = get_class($annotation); - - return new $annotationClassName( - $annotation->getName(), - $annotation->getStartPointer(), - $annotation->getEndPointer(), - $annotation->getContent(), - $fixedContentNode - ); + return $searchPointer; } - private static function fixTemplateTagValueNode( - TemplateTagValueNode $node, - TypeNode $typeNode, - TypeNode $fixedTypeNode - ): TemplateTagValueNode + private static function changeAnnotationNode(PhpDocTagNode $tagNode, Node $nodeToChange, Node $changedNode): PhpDocTagNode { - $fixedNode = clone $node; - - if ($fixedNode->bound !== null) { - $fixedNode->bound = AnnotationTypeHelper::change($node->bound, $typeNode, $fixedTypeNode); - } - if ($fixedNode->default !== null) { - $fixedNode->default = AnnotationTypeHelper::change($node->default, $typeNode, $fixedTypeNode); - } + static $visitor; + static $traverser; - return $fixedNode; - } + if ($visitor === null) { + $visitor = new class extends AbstractNodeVisitor { - private static function fix(File $phpcsFile, Annotation $annotation, Annotation $fixedAnnotation): string - { - $spaceAfterContent = ''; - if (preg_match( - '~(\\s+)$~', - TokenHelper::getContent($phpcsFile, $annotation->getStartPointer(), $annotation->getEndPointer()), - $matches - ) > 0) { - $spaceAfterContent = $matches[1]; - } + /** @var Node */ + private $nodeToChange; - $fixedAnnotationContent = $fixedAnnotation->print() . $spaceAfterContent; + /** @var Node */ + private $changedNode; - return preg_replace('~(\r\n|\n|\r)~', '\1 * ', $fixedAnnotationContent); - } + /** + * @return Node|list|NodeTraverser::*|null + */ + public function enterNode(Node $node) + { + if ($node->getAttribute(Attribute::ORIGINAL_NODE) === $this->nodeToChange) { + return $this->changedNode; + } - private static function parseAnnotationContent(string $annotationName, string $annotationContent): PhpDocTagValueNode - { - $annotationContentWithoutNewLines = preg_replace('~[\r\n]~', ' ', $annotationContent); + return null; + } - $tokens = new TokenIterator(self::getPhpDocLexer()->tokenize($annotationContentWithoutNewLines)); - return self::getPhpDocParser()->parseTagValue($tokens, $annotationName); - } + public function setNodeToChange(Node $nodeToChange): void + { + $this->nodeToChange = $nodeToChange; + } - private static function getPhpDocLexer(): Lexer - { - static $phpDocLexer; + public function setChangedNode(Node $changedNode): void + { + $this->changedNode = $changedNode; + } - if ($phpDocLexer === null) { - $phpDocLexer = new Lexer(); + }; } - return $phpDocLexer; - } + if ($traverser === null) { + $traverser = new NodeTraverser([$visitor]); + } - private static function getPhpDocParser(): PhpDocParser - { - static $phpDocParser; + $visitor->setNodeToChange($nodeToChange); + $visitor->setChangedNode($changedNode); - if ($phpDocParser === null) { - $constantExpressionParser = new ConstExprParser(true, true); - $phpDocParser = new PhpDocParser( - new TypeParser($constantExpressionParser, true), - $constantExpressionParser - ); - } + [$changedTagNode] = $traverser->traverse([$tagNode]); - return $phpDocParser; + return $changedTagNode; } } diff --git a/SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php b/SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php index a80f6119a..058c5b33e 100644 --- a/SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php +++ b/SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php @@ -6,11 +6,9 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; -use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; -use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode; use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; @@ -18,13 +16,10 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; -use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; -use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode; use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use function array_merge; use function count; use function in_array; use function strtolower; @@ -35,462 +30,9 @@ class AnnotationTypeHelper { - /** - * @return list|list - */ - public static function getIdentifierTypeNodes(TypeNode $typeNode): array - { - if ($typeNode instanceof ArrayTypeNode) { - return self::getIdentifierTypeNodes($typeNode->type); - } - - if ($typeNode instanceof ArrayShapeNode || $typeNode instanceof ObjectShapeNode) { - $identifierTypeNodes = []; - foreach ($typeNode->items as $shapeItemNode) { - $identifierTypeNodes = array_merge($identifierTypeNodes, self::getIdentifierTypeNodes($shapeItemNode->valueType)); - } - return $identifierTypeNodes; - } - - if ( - $typeNode instanceof UnionTypeNode - || $typeNode instanceof IntersectionTypeNode - ) { - $identifierTypeNodes = []; - foreach ($typeNode->types as $innerTypeNode) { - $identifierTypeNodes = array_merge($identifierTypeNodes, self::getIdentifierTypeNodes($innerTypeNode)); - } - return $identifierTypeNodes; - } - - if ($typeNode instanceof GenericTypeNode) { - $identifierTypeNodes = self::getIdentifierTypeNodes($typeNode->type); - foreach ($typeNode->genericTypes as $innerTypeNode) { - $identifierTypeNodes = array_merge($identifierTypeNodes, self::getIdentifierTypeNodes($innerTypeNode)); - } - return $identifierTypeNodes; - } - - if ($typeNode instanceof NullableTypeNode) { - return self::getIdentifierTypeNodes($typeNode->type); - } - - if ($typeNode instanceof CallableTypeNode) { - $identifierTypeNodes = array_merge([$typeNode->identifier], self::getIdentifierTypeNodes($typeNode->returnType)); - foreach ($typeNode->parameters as $callableParameterNode) { - $identifierTypeNodes = array_merge($identifierTypeNodes, self::getIdentifierTypeNodes($callableParameterNode->type)); - } - return $identifierTypeNodes; - } - - if ($typeNode instanceof ConstTypeNode) { - return []; - } - - if ($typeNode instanceof ConditionalTypeNode) { - return array_merge( - self::getIdentifierTypeNodes($typeNode->subjectType), - self::getIdentifierTypeNodes($typeNode->targetType), - self::getIdentifierTypeNodes($typeNode->if), - self::getIdentifierTypeNodes($typeNode->else) - ); - } - - if ($typeNode instanceof ConditionalTypeForParameterNode) { - return array_merge( - self::getIdentifierTypeNodes($typeNode->targetType), - self::getIdentifierTypeNodes($typeNode->if), - self::getIdentifierTypeNodes($typeNode->else) - ); - } - - if ($typeNode instanceof OffsetAccessTypeNode) { - return array_merge( - self::getIdentifierTypeNodes($typeNode->type), - self::getIdentifierTypeNodes($typeNode->offset) - ); - } - - /** @var IdentifierTypeNode|ThisTypeNode $typeNode */ - $typeNode = $typeNode; - return [$typeNode]; - } - - /** - * @return list - */ - public static function getConstantTypeNodes(TypeNode $typeNode): array - { - if ($typeNode instanceof ArrayTypeNode) { - return self::getConstantTypeNodes($typeNode->type); - } - - if ($typeNode instanceof ArrayShapeNode || $typeNode instanceof ObjectShapeNode) { - $constTypeNodes = []; - foreach ($typeNode->items as $shapeItemNode) { - $constTypeNodes = array_merge($constTypeNodes, self::getConstantTypeNodes($shapeItemNode->valueType)); - } - return $constTypeNodes; - } - - if ( - $typeNode instanceof UnionTypeNode - || $typeNode instanceof IntersectionTypeNode - ) { - $constTypeNodes = []; - foreach ($typeNode->types as $innerTypeNode) { - $constTypeNodes = array_merge($constTypeNodes, self::getConstantTypeNodes($innerTypeNode)); - } - return $constTypeNodes; - } - - if ($typeNode instanceof GenericTypeNode) { - $constTypeNodes = []; - foreach ($typeNode->genericTypes as $innerTypeNode) { - $constTypeNodes = array_merge($constTypeNodes, self::getConstantTypeNodes($innerTypeNode)); - } - return $constTypeNodes; - } - - if ($typeNode instanceof NullableTypeNode) { - return self::getConstantTypeNodes($typeNode->type); - } - - if ($typeNode instanceof CallableTypeNode) { - $constTypeNodes = self::getConstantTypeNodes($typeNode->returnType); - foreach ($typeNode->parameters as $callableParameterNode) { - $constTypeNodes = array_merge($constTypeNodes, self::getConstantTypeNodes($callableParameterNode->type)); - } - return $constTypeNodes; - } - - if ($typeNode instanceof ConditionalTypeNode) { - return array_merge( - self::getConstantTypeNodes($typeNode->subjectType), - self::getConstantTypeNodes($typeNode->targetType), - self::getConstantTypeNodes($typeNode->if), - self::getConstantTypeNodes($typeNode->else) - ); - } - - if ($typeNode instanceof ConditionalTypeForParameterNode) { - return array_merge( - self::getConstantTypeNodes($typeNode->targetType), - self::getConstantTypeNodes($typeNode->if), - self::getConstantTypeNodes($typeNode->else) - ); - } - - if ($typeNode instanceof OffsetAccessTypeNode) { - return array_merge( - self::getConstantTypeNodes($typeNode->type), - self::getConstantTypeNodes($typeNode->offset) - ); - } - - if (!$typeNode instanceof ConstTypeNode) { - return []; - } - - return [$typeNode]; - } - - /** - * @return list - */ - public static function getUnionTypeNodes(TypeNode $typeNode): array - { - if ($typeNode instanceof UnionTypeNode) { - return [$typeNode]; - } - - if ($typeNode instanceof NullableTypeNode) { - return self::getUnionTypeNodes($typeNode->type); - } - - if ($typeNode instanceof ArrayTypeNode) { - return self::getUnionTypeNodes($typeNode->type); - } - - if ($typeNode instanceof ArrayShapeNode || $typeNode instanceof ObjectShapeNode) { - $unionTypeNodes = []; - foreach ($typeNode->items as $shapeItemNode) { - $unionTypeNodes = array_merge($unionTypeNodes, self::getUnionTypeNodes($shapeItemNode->valueType)); - } - return $unionTypeNodes; - } - - if ($typeNode instanceof IntersectionTypeNode) { - $unionTypeNodes = []; - foreach ($typeNode->types as $innerTypeNode) { - $unionTypeNodes = array_merge($unionTypeNodes, self::getUnionTypeNodes($innerTypeNode)); - } - return $unionTypeNodes; - } - - if ($typeNode instanceof GenericTypeNode) { - $unionTypeNodes = []; - foreach ($typeNode->genericTypes as $innerTypeNode) { - $unionTypeNodes = array_merge($unionTypeNodes, self::getUnionTypeNodes($innerTypeNode)); - } - return $unionTypeNodes; - } - - if ($typeNode instanceof CallableTypeNode) { - $unionTypeNodes = self::getUnionTypeNodes($typeNode->returnType); - foreach ($typeNode->parameters as $callableParameterNode) { - $unionTypeNodes = array_merge($unionTypeNodes, self::getUnionTypeNodes($callableParameterNode->type)); - } - return $unionTypeNodes; - } - - if ($typeNode instanceof ConditionalTypeNode) { - return array_merge( - self::getUnionTypeNodes($typeNode->subjectType), - self::getUnionTypeNodes($typeNode->targetType), - self::getUnionTypeNodes($typeNode->if), - self::getUnionTypeNodes($typeNode->else) - ); - } - - if ($typeNode instanceof ConditionalTypeForParameterNode) { - return array_merge( - self::getUnionTypeNodes($typeNode->targetType), - self::getUnionTypeNodes($typeNode->if), - self::getUnionTypeNodes($typeNode->else) - ); - } - - if ($typeNode instanceof OffsetAccessTypeNode) { - return array_merge( - self::getUnionTypeNodes($typeNode->type), - self::getUnionTypeNodes($typeNode->offset) - ); - } - - return []; - } - - /** - * @return list - */ - public static function getArrayTypeNodes(TypeNode $typeNode): array - { - if ($typeNode instanceof ArrayTypeNode) { - return array_merge([$typeNode], self::getArrayTypeNodes($typeNode->type)); - } - - if ($typeNode instanceof ArrayShapeNode || $typeNode instanceof ObjectShapeNode) { - $arrayTypeNodes = []; - foreach ($typeNode->items as $shapeItemNode) { - $arrayTypeNodes = array_merge($arrayTypeNodes, self::getArrayTypeNodes($shapeItemNode->valueType)); - } - return $arrayTypeNodes; - } - - if ($typeNode instanceof NullableTypeNode) { - return self::getArrayTypeNodes($typeNode->type); - } - - if ( - $typeNode instanceof UnionTypeNode - || $typeNode instanceof IntersectionTypeNode - ) { - $arrayTypeNodes = []; - foreach ($typeNode->types as $innerTypeNode) { - $arrayTypeNodes = array_merge($arrayTypeNodes, self::getArrayTypeNodes($innerTypeNode)); - } - return $arrayTypeNodes; - } - - if ($typeNode instanceof GenericTypeNode) { - $arrayTypeNodes = []; - foreach ($typeNode->genericTypes as $innerTypeNode) { - $arrayTypeNodes = array_merge($arrayTypeNodes, self::getArrayTypeNodes($innerTypeNode)); - } - return $arrayTypeNodes; - } - - if ($typeNode instanceof CallableTypeNode) { - $arrayTypeNodes = self::getArrayTypeNodes($typeNode->returnType); - foreach ($typeNode->parameters as $callableParameterNode) { - $arrayTypeNodes = array_merge($arrayTypeNodes, self::getArrayTypeNodes($callableParameterNode->type)); - } - return $arrayTypeNodes; - } - - if ($typeNode instanceof ConditionalTypeNode) { - return array_merge( - self::getArrayTypeNodes($typeNode->subjectType), - self::getArrayTypeNodes($typeNode->targetType), - self::getArrayTypeNodes($typeNode->if), - self::getArrayTypeNodes($typeNode->else) - ); - } - - if ($typeNode instanceof ConditionalTypeForParameterNode) { - return array_merge( - self::getArrayTypeNodes($typeNode->targetType), - self::getArrayTypeNodes($typeNode->if), - self::getArrayTypeNodes($typeNode->else) - ); - } - - if ($typeNode instanceof OffsetAccessTypeNode) { - return array_merge( - self::getArrayTypeNodes($typeNode->type), - self::getArrayTypeNodes($typeNode->offset) - ); - } - - return []; - } - - /** - * @param IdentifierTypeNode|ThisTypeNode $typeNode - */ - public static function getTypeHintFromNode(TypeNode $typeNode): string - { - return $typeNode instanceof ThisTypeNode - ? (string) $typeNode - : $typeNode->name; - } - public static function print(TypeNode $typeNode): string { - return AnnotationHelper::getPhpDocPrinter()->print($typeNode); - } - - public static function change(TypeNode $masterTypeNode, TypeNode $typeNodeToChange, TypeNode $changedTypeNode): TypeNode - { - if ($masterTypeNode === $typeNodeToChange) { - return $changedTypeNode; - } - - if ($masterTypeNode instanceof UnionTypeNode) { - $types = []; - foreach ($masterTypeNode->types as $typeNone) { - $types[] = self::change($typeNone, $typeNodeToChange, $changedTypeNode); - } - - return new UnionTypeNode($types); - } - - if ($masterTypeNode instanceof IntersectionTypeNode) { - $types = []; - foreach ($masterTypeNode->types as $typeNone) { - $types[] = self::change($typeNone, $typeNodeToChange, $changedTypeNode); - } - - return new IntersectionTypeNode($types); - } - - if ($masterTypeNode instanceof GenericTypeNode) { - $genericTypes = []; - foreach ($masterTypeNode->genericTypes as $genericTypeNode) { - $genericTypes[] = self::change($genericTypeNode, $typeNodeToChange, $changedTypeNode); - } - - /** @var IdentifierTypeNode $identificatorTypeNode */ - $identificatorTypeNode = self::change($masterTypeNode->type, $typeNodeToChange, $changedTypeNode); - return new GenericTypeNode($identificatorTypeNode, $genericTypes, $masterTypeNode->variances); - } - - if ($masterTypeNode instanceof ArrayTypeNode) { - return new ArrayTypeNode(self::change($masterTypeNode->type, $typeNodeToChange, $changedTypeNode)); - } - - if ($masterTypeNode instanceof ArrayShapeNode) { - $arrayShapeItemNodes = []; - foreach ($masterTypeNode->items as $arrayShapeItemNode) { - /** @var ArrayShapeItemNode $changedArrayShapeItemNode */ - $changedArrayShapeItemNode = self::change($arrayShapeItemNode, $typeNodeToChange, $changedTypeNode); - $arrayShapeItemNodes[] = $changedArrayShapeItemNode; - } - - return new ArrayShapeNode($arrayShapeItemNodes, $masterTypeNode->sealed, $masterTypeNode->kind); - } - - if ($masterTypeNode instanceof ArrayShapeItemNode) { - return new ArrayShapeItemNode( - $masterTypeNode->keyName, - $masterTypeNode->optional, - self::change($masterTypeNode->valueType, $typeNodeToChange, $changedTypeNode) - ); - } - - if ($masterTypeNode instanceof ObjectShapeNode) { - $objectShapeItemNodes = []; - foreach ($masterTypeNode->items as $objectShapeItemNode) { - /** @var ObjectShapeItemNode $changedObjectShapeItemNode */ - $changedObjectShapeItemNode = self::change($objectShapeItemNode, $typeNodeToChange, $changedTypeNode); - $objectShapeItemNodes[] = $changedObjectShapeItemNode; - } - - return new ObjectShapeNode($objectShapeItemNodes); - } - - if ($masterTypeNode instanceof ObjectShapeItemNode) { - return new ObjectShapeItemNode( - $masterTypeNode->keyName, - $masterTypeNode->optional, - self::change($masterTypeNode->valueType, $typeNodeToChange, $changedTypeNode) - ); - } - - if ($masterTypeNode instanceof NullableTypeNode) { - return new NullableTypeNode(self::change($masterTypeNode->type, $typeNodeToChange, $changedTypeNode)); - } - - if ($masterTypeNode instanceof CallableTypeNode) { - $callableParameters = []; - foreach ($masterTypeNode->parameters as $parameterTypeNode) { - $callableParameters[] = new CallableTypeParameterNode( - self::change($parameterTypeNode->type, $typeNodeToChange, $changedTypeNode), - $parameterTypeNode->isReference, - $parameterTypeNode->isVariadic, - $parameterTypeNode->parameterName, - $parameterTypeNode->isOptional - ); - } - - /** @var IdentifierTypeNode $identificatorTypeNode */ - $identificatorTypeNode = self::change($masterTypeNode->identifier, $typeNodeToChange, $changedTypeNode); - return new CallableTypeNode( - $identificatorTypeNode, - $callableParameters, - self::change($masterTypeNode->returnType, $typeNodeToChange, $changedTypeNode) - ); - } - - if ($masterTypeNode instanceof ConditionalTypeNode) { - return new ConditionalTypeNode( - self::change($masterTypeNode->subjectType, $typeNodeToChange, $changedTypeNode), - self::change($masterTypeNode->targetType, $typeNodeToChange, $changedTypeNode), - self::change($masterTypeNode->if, $typeNodeToChange, $changedTypeNode), - self::change($masterTypeNode->else, $typeNodeToChange, $changedTypeNode), - $masterTypeNode->negated - ); - } - - if ($masterTypeNode instanceof ConditionalTypeForParameterNode) { - return new ConditionalTypeForParameterNode( - $masterTypeNode->parameterName, - self::change($masterTypeNode->targetType, $typeNodeToChange, $changedTypeNode), - self::change($masterTypeNode->if, $typeNodeToChange, $changedTypeNode), - self::change($masterTypeNode->else, $typeNodeToChange, $changedTypeNode), - $masterTypeNode->negated - ); - } - - if ($masterTypeNode instanceof OffsetAccessTypeNode) { - return new OffsetAccessTypeNode( - self::change($masterTypeNode->type, $typeNodeToChange, $changedTypeNode), - self::change($masterTypeNode->offset, $typeNodeToChange, $changedTypeNode) - ); - } - - return clone $masterTypeNode; + return PhpDocParserHelper::getPrinter()->print($typeNode); } public static function containsStaticOrThisType(TypeNode $typeNode): bool @@ -736,9 +278,6 @@ public static function containsItemsSpecificationForTraversable( return false; } - /** - * @param CallableTypeNode|GenericTypeNode|IdentifierTypeNode|ThisTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|ConstTypeNode $typeNode - */ public static function getTypeHintFromOneType( TypeNode $typeNode, bool $enableUnionTypeHint = false, diff --git a/SlevomatCodingStandard/Helpers/DocCommentHelper.php b/SlevomatCodingStandard/Helpers/DocCommentHelper.php index 07a18a250..1cecd27a2 100644 --- a/SlevomatCodingStandard/Helpers/DocCommentHelper.php +++ b/SlevomatCodingStandard/Helpers/DocCommentHelper.php @@ -3,11 +3,15 @@ namespace SlevomatCodingStandard\Helpers; use PHP_CodeSniffer\Files\File; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode; +use PHPStan\PhpDocParser\Parser\TokenIterator; use function array_merge; use function count; use function in_array; use function preg_match; -use function stripos; +use function sprintf; +use function strtolower; use function trim; use const T_ABSTRACT; use const T_ATTRIBUTE; @@ -57,8 +61,8 @@ public static function getDocComment(File $phpcsFile, int $pointer): ?string return trim( TokenHelper::getContent( $phpcsFile, - $docCommentOpenToken + 1, - $phpcsFile->getTokens()[$docCommentOpenToken]['comment_closer'] - 1 + $docCommentOpenToken, + $phpcsFile->getTokens()[$docCommentOpenToken]['comment_closer'] ) ); } @@ -112,12 +116,25 @@ public static function getDocCommentDescription(File $phpcsFile, int $pointer): public static function hasInheritdocAnnotation(File $phpcsFile, int $pointer): bool { - $docComment = self::getDocComment($phpcsFile, $pointer); - if ($docComment === null) { + $docCommentOpenPointer = self::findDocCommentOpenPointer($phpcsFile, $pointer); + + if ($docCommentOpenPointer === null) { return false; } - return stripos($docComment, '@inheritdoc') !== false; + $parsedDocComment = self::parseDocComment($phpcsFile, $docCommentOpenPointer); + + foreach ($parsedDocComment->getNode()->children as $child) { + if ($child instanceof PhpDocTextNode && strtolower($child->text) === '{@inheritdoc}') { + return true; + } + + if ($child instanceof PhpDocTagNode && strtolower($child->name) === '@inheritdoc') { + return true; + } + } + + return false; } public static function hasDocCommentDescription(File $phpcsFile, int $pointer): bool @@ -127,22 +144,28 @@ public static function hasDocCommentDescription(File $phpcsFile, int $pointer): public static function findDocCommentOpenPointer(File $phpcsFile, int $pointer): ?int { - $tokens = $phpcsFile->getTokens(); - - if ($tokens[$pointer]['code'] === T_DOC_COMMENT_OPEN_TAG) { - return $pointer; - } - - $found = TokenHelper::findPrevious( + return SniffLocalCache::getAndSetIfNotCached( $phpcsFile, - [T_DOC_COMMENT_CLOSE_TAG, T_SEMICOLON, T_CLOSE_CURLY_BRACKET, T_OPEN_CURLY_BRACKET], - $pointer - 1 + sprintf('doc-comment-open-pointer-%d', $pointer), + static function () use ($phpcsFile, $pointer): ?int { + $tokens = $phpcsFile->getTokens(); + + if ($tokens[$pointer]['code'] === T_DOC_COMMENT_OPEN_TAG) { + return $pointer; + } + + $found = TokenHelper::findPrevious( + $phpcsFile, + [T_DOC_COMMENT_CLOSE_TAG, T_SEMICOLON, T_CLOSE_CURLY_BRACKET, T_OPEN_CURLY_BRACKET], + $pointer - 1 + ); + if ($found !== null && $tokens[$found]['code'] === T_DOC_COMMENT_CLOSE_TAG) { + return $tokens[$found]['comment_opener']; + } + + return null; + } ); - if ($found !== null && $tokens[$found]['code'] === T_DOC_COMMENT_CLOSE_TAG) { - return $tokens[$found]['comment_opener']; - } - - return null; } public static function findDocCommentOwnerPointer(File $phpcsFile, int $docCommentOpenPointer): ?int @@ -202,8 +225,37 @@ public static function isInline(File $phpcsFile, int $docCommentOpenPointer): bo return false; } - $docCommentContent = self::getDocComment($phpcsFile, $docCommentOpenPointer); - return preg_match('~^@(?:(?:phpstan|psalm)-)?var~i', $docCommentContent) === 1; + $parsedDocComment = self::parseDocComment($phpcsFile, $docCommentOpenPointer); + + foreach ($parsedDocComment->getNode()->getTags() as $annotation) { + if (preg_match('~^@(?:(?:phpstan|psalm)-)?var~i', $annotation->name) === 1) { + return true; + } + } + + return false; + } + + public static function parseDocComment(File $phpcsFile, int $docCommentOpenPointer): ParsedDocComment + { + return SniffLocalCache::getAndSetIfNotCached( + $phpcsFile, + sprintf('parsed-doc-comment-%d', $docCommentOpenPointer), + static function () use ($phpcsFile, $docCommentOpenPointer): ParsedDocComment { + $docComment = self::getDocComment($phpcsFile, $docCommentOpenPointer); + + $docCommentTokens = new TokenIterator(PhpDocParserHelper::getLexer()->tokenize($docComment)); + + $parsedDocComment = PhpDocParserHelper::getParser()->parse($docCommentTokens); + + return new ParsedDocComment( + $docCommentOpenPointer, + $phpcsFile->getTokens()[$docCommentOpenPointer]['comment_closer'], + $parsedDocComment, + $docCommentTokens + ); + } + ); } } diff --git a/SlevomatCodingStandard/Helpers/FunctionHelper.php b/SlevomatCodingStandard/Helpers/FunctionHelper.php index 0134a89b0..633e80b1c 100644 --- a/SlevomatCodingStandard/Helpers/FunctionHelper.php +++ b/SlevomatCodingStandard/Helpers/FunctionHelper.php @@ -5,15 +5,15 @@ use Generator; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Tokens; -use SlevomatCodingStandard\Helpers\Annotation\ParameterAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ReturnAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use function array_filter; use function array_map; use function array_merge; use function array_pop; use function array_reverse; -use function count; use function in_array; use function iterator_to_array; use function preg_match; @@ -324,17 +324,15 @@ public static function hasReturnTypeHint(File $phpcsFile, int $functionPointer): } /** - * @return list + * @return list|Annotation> */ public static function getParametersAnnotations(File $phpcsFile, int $functionPointer): array { - /** @var list $parametersAnnotations */ - $parametersAnnotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $functionPointer, '@param'); - return $parametersAnnotations; + return AnnotationHelper::getAnnotations($phpcsFile, $functionPointer, '@param'); } /** - * @return array + * @return array|Annotation|Annotation> */ public static function getValidParametersAnnotations(File $phpcsFile, int $functionPointer): array { @@ -348,8 +346,7 @@ public static function getValidParametersAnnotations(File $phpcsFile, int $funct continue; } - /** @var list $varAnnotations */ - $varAnnotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $i, '@var'); + $varAnnotations = AnnotationHelper::getAnnotations($phpcsFile, $i, '@var'); if ($varAnnotations === []) { continue; } @@ -359,22 +356,18 @@ public static function getValidParametersAnnotations(File $phpcsFile, int $funct } foreach (self::getParametersAnnotations($phpcsFile, $functionPointer) as $parameterAnnotation) { - if ($parameterAnnotation->getContent() === null) { - continue; - } - if ($parameterAnnotation->isInvalid()) { continue; } - $parametersAnnotations[$parameterAnnotation->getParameterName()] = $parameterAnnotation; + $parametersAnnotations[$parameterAnnotation->getValue()->parameterName] = $parameterAnnotation; } return $parametersAnnotations; } /** - * @return array + * @return array|Annotation> */ public static function getValidPrefixedParametersAnnotations(File $phpcsFile, int $functionPointer): array { @@ -388,8 +381,8 @@ public static function getValidPrefixedParametersAnnotations(File $phpcsFile, in continue; } - /** @var list $varAnnotations */ - $varAnnotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $i, sprintf('@%s-var', $prefix)); + /** @var list> $varAnnotations */ + $varAnnotations = AnnotationHelper::getAnnotations($phpcsFile, $i, sprintf('@%s-var', $prefix)); if ($varAnnotations === []) { continue; } @@ -398,30 +391,29 @@ public static function getValidPrefixedParametersAnnotations(File $phpcsFile, in } } - /** @var list $annotations */ - $annotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $functionPointer, sprintf('@%s-param', $prefix)); + /** @var list> $annotations */ + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $functionPointer, sprintf('@%s-param', $prefix)); foreach ($annotations as $parameterAnnotation) { - if ($parameterAnnotation->getContent() === null) { - continue; - } - if ($parameterAnnotation->isInvalid()) { continue; } - $parametersAnnotations[$parameterAnnotation->getParameterName()] = $parameterAnnotation; + $parametersAnnotations[$parameterAnnotation->getValue()->parameterName] = $parameterAnnotation; } } return $parametersAnnotations; } - public static function findReturnAnnotation(File $phpcsFile, int $functionPointer): ?ReturnAnnotation + /** + * @return Annotation|null + */ + public static function findReturnAnnotation(File $phpcsFile, int $functionPointer): ?Annotation { - /** @var list $returnAnnotations */ - $returnAnnotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $functionPointer, '@return'); + /** @var list> $returnAnnotations */ + $returnAnnotations = AnnotationHelper::getAnnotations($phpcsFile, $functionPointer, '@return'); - if (count($returnAnnotations) === 0) { + if ($returnAnnotations === []) { return null; } @@ -429,19 +421,24 @@ public static function findReturnAnnotation(File $phpcsFile, int $functionPointe } /** - * @return list + * @return list */ public static function getValidPrefixedReturnAnnotations(File $phpcsFile, int $functionPointer): array { $returnAnnotations = []; + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $functionPointer); + foreach (AnnotationHelper::STATIC_ANALYSIS_PREFIXES as $prefix) { - /** @var list $annotations */ - $annotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $functionPointer, sprintf('@%s-return', $prefix)); + $prefixedAnnotationName = sprintf('@%s-return', $prefix); + foreach ($annotations as $annotation) { - if (!$annotation->isInvalid()) { + if ($annotation->isInvalid()) { + continue; + } + + if ($annotation->getName() === $prefixedAnnotationName) { $returnAnnotations[] = $annotation; - break; } } } diff --git a/SlevomatCodingStandard/Helpers/ParsedDocComment.php b/SlevomatCodingStandard/Helpers/ParsedDocComment.php new file mode 100644 index 000000000..8eccb3d6f --- /dev/null +++ b/SlevomatCodingStandard/Helpers/ParsedDocComment.php @@ -0,0 +1,54 @@ +openPointer = $openPointer; + $this->closePointer = $closePointer; + $this->node = $node; + $this->tokens = $tokens; + } + + public function getOpenPointer(): int + { + return $this->openPointer; + } + + public function getClosePointer(): int + { + return $this->closePointer; + } + + public function getNode(): PhpDocNode + { + return $this->node; + } + + public function getTokens(): TokenIterator + { + return $this->tokens; + } + +} diff --git a/SlevomatCodingStandard/Helpers/PhpDocParserHelper.php b/SlevomatCodingStandard/Helpers/PhpDocParserHelper.php new file mode 100644 index 000000000..1c46fdd95 --- /dev/null +++ b/SlevomatCodingStandard/Helpers/PhpDocParserHelper.php @@ -0,0 +1,81 @@ + true, 'indexes' => true]; + + $constantExpressionParser = new ConstExprParser(true, true, $usedAttributes); + $parser = new PhpDocParser( + new TypeParser($constantExpressionParser, true, $usedAttributes), + $constantExpressionParser, + true, + true, + $usedAttributes, + true + ); + } + + return $parser; + } + + public static function getPrinter(): Printer + { + static $printer; + + if ($printer === null) { + $printer = new Printer(); + } + + return $printer; + } + + /** + * @template T of Node + * @param T $node + * @return T + */ + public static function cloneNode(Node $node): Node + { + static $cloningTraverser; + + if ($cloningTraverser === null) { + $cloningTraverser = new NodeTraverser([new CloningVisitor()]); + } + + [$cloneNode] = $cloningTraverser->traverse([$node]); + + return $cloneNode; + } + +} diff --git a/SlevomatCodingStandard/Helpers/SuppressHelper.php b/SlevomatCodingStandard/Helpers/SuppressHelper.php index 88d5c51d5..96b1d2c04 100644 --- a/SlevomatCodingStandard/Helpers/SuppressHelper.php +++ b/SlevomatCodingStandard/Helpers/SuppressHelper.php @@ -3,13 +3,14 @@ namespace SlevomatCodingStandard\Helpers; use PHP_CodeSniffer\Files\File; -use SlevomatCodingStandard\Helpers\Annotation\Annotation; +use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use function array_reduce; use function assert; use function explode; use function sprintf; use function strpos; use const T_DOC_COMMENT_CLOSE_TAG; +use const T_DOC_COMMENT_OPEN_TAG; use const T_DOC_COMMENT_STAR; /** @@ -22,14 +23,13 @@ class SuppressHelper public static function isSniffSuppressed(File $phpcsFile, int $pointer, string $suppressName): bool { + /** @var list> $annotations */ + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $pointer, self::ANNOTATION); + return array_reduce( - AnnotationHelper::getAnnotationsByName($phpcsFile, $pointer, self::ANNOTATION), + $annotations, static function (bool $carry, Annotation $annotation) use ($suppressName): bool { - if ($annotation->getContent() === null) { - return $carry; - } - - $annotationSuppressName = explode(' ', $annotation->getContent())[0]; + $annotationSuppressName = explode(' ', $annotation->getValue()->value)[0]; if ( $suppressName === $annotationSuppressName @@ -37,6 +37,7 @@ static function (bool $carry, Annotation $annotation) use ($suppressName): bool ) { $carry = true; } + return $carry; }, false @@ -46,8 +47,9 @@ static function (bool $carry, Annotation $annotation) use ($suppressName): bool public static function removeSuppressAnnotation(File $phpcsFile, int $pointer, string $suppressName): void { $suppressAnnotation = null; - foreach (AnnotationHelper::getAnnotationsByName($phpcsFile, $pointer, self::ANNOTATION) as $annotation) { - if ($annotation->getContent() === $suppressName) { + /** @var Annotation $annotation */ + foreach (AnnotationHelper::getAnnotations($phpcsFile, $pointer, self::ANNOTATION) as $annotation) { + if ($annotation->getValue()->value === $suppressName) { $suppressAnnotation = $annotation; break; } @@ -55,14 +57,24 @@ public static function removeSuppressAnnotation(File $phpcsFile, int $pointer, s assert($suppressAnnotation !== null); - /** @var int $changeStart */ - $changeStart = TokenHelper::findPrevious($phpcsFile, T_DOC_COMMENT_STAR, $suppressAnnotation->getStartPointer() - 1); + $tokens = $phpcsFile->getTokens(); + + /** @var int $pointerBefore */ + $pointerBefore = TokenHelper::findPrevious( + $phpcsFile, + [T_DOC_COMMENT_OPEN_TAG, T_DOC_COMMENT_STAR], + $suppressAnnotation->getStartPointer() - 1 + ); + + $changeStart = $tokens[$pointerBefore]['code'] === T_DOC_COMMENT_STAR ? $pointerBefore : $suppressAnnotation->getStartPointer(); + /** @var int $changeEnd */ $changeEnd = TokenHelper::findNext( $phpcsFile, [T_DOC_COMMENT_CLOSE_TAG, T_DOC_COMMENT_STAR], $suppressAnnotation->getEndPointer() + 1 ) - 1; + $phpcsFile->fixer->beginChangeset(); FixerHelper::removeBetweenIncluding($phpcsFile, $changeStart, $changeEnd); $phpcsFile->fixer->endChangeset(); diff --git a/SlevomatCodingStandard/Helpers/TokenHelper.php b/SlevomatCodingStandard/Helpers/TokenHelper.php index c9f5040a0..fc4119d94 100644 --- a/SlevomatCodingStandard/Helpers/TokenHelper.php +++ b/SlevomatCodingStandard/Helpers/TokenHelper.php @@ -93,6 +93,16 @@ class TokenHelper T_PHPCS_SET, ]; + /** @var array */ + public static $annotationTokenCodes = [ + T_DOC_COMMENT_TAG, + T_PHPCS_DISABLE, + T_PHPCS_ENABLE, + T_PHPCS_IGNORE, + T_PHPCS_IGNORE_FILE, + T_PHPCS_SET, + ]; + /** @var array */ public static $inlineCommentTokenCodes = [ T_COMMENT, diff --git a/SlevomatCodingStandard/Helpers/TypeHintHelper.php b/SlevomatCodingStandard/Helpers/TypeHintHelper.php index 17a965a7d..91d7364d5 100644 --- a/SlevomatCodingStandard/Helpers/TypeHintHelper.php +++ b/SlevomatCodingStandard/Helpers/TypeHintHelper.php @@ -3,9 +3,9 @@ namespace SlevomatCodingStandard\Helpers; use PHP_CodeSniffer\Files\File; -use SlevomatCodingStandard\Helpers\Annotation\TemplateAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\TypeAliasAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\TypeImportAnnotation; +use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode; use function array_key_exists; use function array_map; use function array_merge; @@ -270,19 +270,16 @@ private static function isTemplate(File $phpcsFile, int $docCommentOpenPointer, } $containsTypeHintInTemplateAnnotation = static function (int $docCommentOpenPointer) use ($phpcsFile, $templateAnnotationNames, $typeHint): bool { - $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); foreach ($templateAnnotationNames as $templateAnnotationName) { - if (!array_key_exists($templateAnnotationName, $annotations)) { - continue; - } + /** @var list> $annotations */ + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer, $templateAnnotationName); - /** @var TemplateAnnotation $templateAnnotation */ - foreach ($annotations[$templateAnnotationName] as $templateAnnotation) { + foreach ($annotations as $templateAnnotation) { if ($templateAnnotation->isInvalid()) { continue; } - if ($templateAnnotation->getTemplateName() === $typeHint) { + if ($templateAnnotation->getValue()->name === $typeHint) { return true; } } @@ -340,19 +337,29 @@ private static function isAlias(File $phpcsFile, int $docCommentOpenPointer, str return false; } - $annotations = AnnotationHelper::getAnnotations($phpcsFile, $classDocCommentOpenPointer); foreach ($aliasAnnotationNames as $aliasAnnotationName) { - if (!array_key_exists($aliasAnnotationName, $annotations)) { - continue; - } + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $classDocCommentOpenPointer, $aliasAnnotationName); - /** @var TypeAliasAnnotation|TypeImportAnnotation $aliasAnnotation */ - foreach ($annotations[$aliasAnnotationName] as $aliasAnnotation) { + foreach ($annotations as $aliasAnnotation) { if ($aliasAnnotation->isInvalid()) { continue; } - if ($aliasAnnotation->getAlias() === $typeHint) { + $aliasAnnotationValue = $aliasAnnotation->getValue(); + + if ($aliasAnnotationValue instanceof TypeAliasTagValueNode && $aliasAnnotationValue->alias === $typeHint) { + return true; + } + + if (!($aliasAnnotationValue instanceof TypeAliasImportTagValueNode)) { + continue; + } + + if ($aliasAnnotationValue->importedAs === $typeHint) { + return true; + } + + if ($aliasAnnotationValue->importedAlias === $typeHint) { return true; } } diff --git a/SlevomatCodingStandard/Sniffs/Classes/ClassStructureSniff.php b/SlevomatCodingStandard/Sniffs/Classes/ClassStructureSniff.php index 951c81667..e01288ba7 100644 --- a/SlevomatCodingStandard/Sniffs/Classes/ClassStructureSniff.php +++ b/SlevomatCodingStandard/Sniffs/Classes/ClassStructureSniff.php @@ -448,7 +448,7 @@ private function isStaticConstructor(File $phpcsFile, int $pointer): bool return false; } - return in_array($returnAnnotation->getContent(), ['static', 'self', $parentClassName], true); + return in_array((string) $returnAnnotation->getValue()->type, ['static', 'self', $parentClassName], true); } private function getParentClassName(File $phpcsFile, int $pointer): string diff --git a/SlevomatCodingStandard/Sniffs/Classes/RequireConstructorPropertyPromotionSniff.php b/SlevomatCodingStandard/Sniffs/Classes/RequireConstructorPropertyPromotionSniff.php index a492f2a3e..d1736c8aa 100644 --- a/SlevomatCodingStandard/Sniffs/Classes/RequireConstructorPropertyPromotionSniff.php +++ b/SlevomatCodingStandard/Sniffs/Classes/RequireConstructorPropertyPromotionSniff.php @@ -5,7 +5,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; -use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\DocCommentHelper; use SlevomatCodingStandard\Helpers\FixerHelper; @@ -319,16 +319,14 @@ private function isPropertyDocCommentUseful(File $phpcsFile, int $propertyPointe return true; } - foreach (AnnotationHelper::getAnnotations($phpcsFile, $propertyPointer) as $annotationType => $annotations) { - if (!in_array($annotationType, ['@var', '@phpstan-var', '@psalm-var'], true)) { + foreach (AnnotationHelper::getAnnotations($phpcsFile, $propertyPointer) as $annotation) { + $annotationValue = $annotation->getValue(); + if (!$annotationValue instanceof VarTagValueNode) { return true; } - /** @var VariableAnnotation $annotation */ - foreach ($annotations as $annotation) { - if ($annotation->hasDescription()) { - return true; - } + if ($annotationValue->description !== '') { + return true; } } diff --git a/SlevomatCodingStandard/Sniffs/Commenting/AnnotationNameSniff.php b/SlevomatCodingStandard/Sniffs/Commenting/AnnotationNameSniff.php index 03e6df89b..9b8fbfcdb 100644 --- a/SlevomatCodingStandard/Sniffs/Commenting/AnnotationNameSniff.php +++ b/SlevomatCodingStandard/Sniffs/Commenting/AnnotationNameSniff.php @@ -171,8 +171,8 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); $correctAnnotationNames = $this->getNormalizedAnnotationNames(); - foreach ($annotations as $annotationName => $annotationsByName) { - $lowerCasedAnnotationName = strtolower($annotationName); + foreach ($annotations as $annotation) { + $lowerCasedAnnotationName = strtolower($annotation->getName()); if (!array_key_exists($lowerCasedAnnotationName, $correctAnnotationNames)) { continue; @@ -180,37 +180,35 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $correctAnnotationName = $correctAnnotationNames[$lowerCasedAnnotationName]; - if ($correctAnnotationName === $annotationName) { + if ($correctAnnotationName === $annotation->getName()) { continue; } - foreach ($annotationsByName as $annotation) { - $annotationNameWithoutAtSign = ltrim($annotationName, '@'); - $fullyQualifiedAnnotationName = NamespaceHelper::resolveClassName( - $phpcsFile, - $annotationNameWithoutAtSign, - $annotation->getStartPointer() - ); + $annotationNameWithoutAtSign = ltrim($annotation->getName(), '@'); + $fullyQualifiedAnnotationName = NamespaceHelper::resolveClassName( + $phpcsFile, + $annotationNameWithoutAtSign, + $annotation->getStartPointer() + ); - if (NamespaceHelper::normalizeToCanonicalName($fullyQualifiedAnnotationName) !== $annotationNameWithoutAtSign) { - continue; - } + if (NamespaceHelper::normalizeToCanonicalName($fullyQualifiedAnnotationName) !== $annotationNameWithoutAtSign) { + continue; + } - $fix = $phpcsFile->addFixableError( - sprintf('Annotation name is incorrect. Expected %s, found %s.', $correctAnnotationName, $annotationName), - $annotation->getStartPointer(), - self::CODE_ANNOTATION_NAME_INCORRECT - ); - if (!$fix) { - continue; - } + $fix = $phpcsFile->addFixableError( + sprintf('Annotation name is incorrect. Expected %s, found %s.', $correctAnnotationName, $annotation->getName()), + $annotation->getStartPointer(), + self::CODE_ANNOTATION_NAME_INCORRECT + ); + if (!$fix) { + continue; + } - $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->replaceToken($annotation->getStartPointer(), $correctAnnotationName); + $phpcsFile->fixer->replaceToken($annotation->getStartPointer(), $correctAnnotationName); - $phpcsFile->fixer->endChangeset(); - } + $phpcsFile->fixer->endChangeset(); } $tokens = $phpcsFile->getTokens(); diff --git a/SlevomatCodingStandard/Sniffs/Commenting/DeprecatedAnnotationDeclarationSniff.php b/SlevomatCodingStandard/Sniffs/Commenting/DeprecatedAnnotationDeclarationSniff.php index 7eb5deb37..633403d2b 100644 --- a/SlevomatCodingStandard/Sniffs/Commenting/DeprecatedAnnotationDeclarationSniff.php +++ b/SlevomatCodingStandard/Sniffs/Commenting/DeprecatedAnnotationDeclarationSniff.php @@ -4,7 +4,8 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; +use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode; +use SlevomatCodingStandard\Helpers\Annotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use function count; use const T_DOC_COMMENT_OPEN_TAG; @@ -26,15 +27,15 @@ public function register(): array */ public function process(File $phpcsFile, $docCommentStartPointer): void { - /** @var list $annotations */ - $annotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $docCommentStartPointer, '@deprecated'); + /** @var list> $annotations */ + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentStartPointer, '@deprecated'); if (count($annotations) === 0) { return; } foreach ($annotations as $annotation) { - if ($annotation->getContent() !== null) { + if ($annotation->getValue()->description !== '') { continue; } diff --git a/SlevomatCodingStandard/Sniffs/Commenting/DocCommentSpacingSniff.php b/SlevomatCodingStandard/Sniffs/Commenting/DocCommentSpacingSniff.php index 60028a2bc..fca6a67ce 100644 --- a/SlevomatCodingStandard/Sniffs/Commenting/DocCommentSpacingSniff.php +++ b/SlevomatCodingStandard/Sniffs/Commenting/DocCommentSpacingSniff.php @@ -4,7 +4,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use SlevomatCodingStandard\Helpers\Annotation\Annotation; +use SlevomatCodingStandard\Helpers\Annotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\DocCommentHelper; use SlevomatCodingStandard\Helpers\FixerHelper; @@ -17,7 +17,6 @@ use function array_key_exists; use function array_keys; use function array_map; -use function array_merge; use function array_values; use function asort; use function count; @@ -32,7 +31,6 @@ use function substr; use function substr_count; use function trim; -use function uasort; use function usort; use const T_DOC_COMMENT_OPEN_TAG; use const T_DOC_COMMENT_STAR; @@ -133,14 +131,13 @@ public function process(File $phpcsFile, $docCommentOpenerPointer): void $firstContentEndPointer = $actualPointer; } while (true); - $annotations = array_merge([], ...array_values(AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenerPointer))); - uasort($annotations, static function (Annotation $a, Annotation $b): int { - return $a->getStartPointer() <=> $b->getEndPointer(); + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenerPointer); + usort($annotations, static function (Annotation $a, Annotation $b): int { + return $a->getStartPointer() <=> $b->getStartPointer(); }); - $annotations = array_values($annotations); $annotationsCount = count($annotations); - $firstAnnotation = $annotationsCount > 0 ? $annotations[0] : null; + $firstAnnotationPointer = $annotationsCount > 0 ? $annotations[0]->getStartPointer() : null; $lastContentEndPointer = $annotationsCount > 0 ? $annotations[$annotationsCount - 1]->getEndPointer() : $firstContentEndPointer; @@ -150,7 +147,7 @@ public function process(File $phpcsFile, $docCommentOpenerPointer): void $docCommentOpenerPointer, $firstContentStartPointer, $firstContentEndPointer, - $firstAnnotation + $firstAnnotationPointer ); if (count($annotations) > 1) { @@ -218,14 +215,14 @@ private function checkLinesBetweenDescriptionAndFirstAnnotation( int $docCommentOpenerPointer, int $firstContentStartPointer, int $firstContentEndPointer, - ?Annotation $firstAnnotation + ?int $firstAnnotationPointer ): void { - if ($firstAnnotation === null) { + if ($firstAnnotationPointer === null) { return; } - if ($firstContentStartPointer === $firstAnnotation->getStartPointer()) { + if ($firstContentStartPointer === $firstAnnotationPointer) { return; } @@ -237,7 +234,7 @@ private function checkLinesBetweenDescriptionAndFirstAnnotation( $whitespaceBetweenDescriptionAndFirstAnnotation .= TokenHelper::getContent( $phpcsFile, $firstContentEndPointer + 1, - $firstAnnotation->getStartPointer() - 1 + $firstAnnotationPointer - 1 ); $linesCountBetweenDescriptionAndAnnotations = max( @@ -255,7 +252,7 @@ private function checkLinesBetweenDescriptionAndFirstAnnotation( $this->linesCountBetweenDescriptionAndAnnotations === 1 ? '' : 's', $linesCountBetweenDescriptionAndAnnotations ), - $firstAnnotation->getStartPointer(), + $firstAnnotationPointer, self::CODE_INCORRECT_LINES_COUNT_BETWEEN_DESCRIPTION_AND_ANNOTATIONS ); @@ -269,13 +266,13 @@ private function checkLinesBetweenDescriptionAndFirstAnnotation( $phpcsFile->fixer->addNewline($firstContentEndPointer); - FixerHelper::removeBetween($phpcsFile, $firstContentEndPointer, $firstAnnotation->getStartPointer()); + FixerHelper::removeBetween($phpcsFile, $firstContentEndPointer, $firstAnnotationPointer); for ($i = 1; $i <= $this->linesCountBetweenDescriptionAndAnnotations; $i++) { $phpcsFile->fixer->addContent($firstContentEndPointer, sprintf('%s *%s', $indentation, $phpcsFile->eolChar)); } - $phpcsFile->fixer->addContentBefore($firstAnnotation->getStartPointer(), $indentation . ' * '); + $phpcsFile->fixer->addContentBefore($firstAnnotationPointer, $indentation . ' * '); $phpcsFile->fixer->endChangeset(); } @@ -285,8 +282,6 @@ private function checkLinesBetweenDescriptionAndFirstAnnotation( */ private function checkLinesBetweenDifferentAnnotationsTypes(File $phpcsFile, int $docCommentOpenerPointer, array $annotations): void { - $tokens = $phpcsFile->getTokens(); - $indentation = IndentationHelper::getIndentation($phpcsFile, $docCommentOpenerPointer); $previousAnnotation = null; @@ -301,16 +296,13 @@ private function checkLinesBetweenDifferentAnnotationsTypes(File $phpcsFile, int continue; } - preg_match('~(\\s+)$~', $tokens[$previousAnnotation->getEndPointer()]['content'], $matches); - - $linesCountAfterPreviousAnnotation = $matches[1] ?? ''; - $linesCountAfterPreviousAnnotation .= TokenHelper::getContent( + $whitespaceAfterPreviousAnnotation = TokenHelper::getContent( $phpcsFile, $previousAnnotation->getEndPointer() + 1, $annotation->getStartPointer() - 1 ); - $linesCountAfterPreviousAnnotation = max(substr_count($linesCountAfterPreviousAnnotation, $phpcsFile->eolChar) - 1, 0); + $linesCountAfterPreviousAnnotation = max(substr_count($whitespaceAfterPreviousAnnotation, $phpcsFile->eolChar) - 1, 0); if ($linesCountAfterPreviousAnnotation === $this->linesCountBetweenDifferentAnnotationsTypes) { $previousAnnotation = $annotation; @@ -335,10 +327,10 @@ private function checkLinesBetweenDifferentAnnotationsTypes(File $phpcsFile, int $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->addNewline($previousAnnotation->getEndPointer()); - FixerHelper::removeBetween($phpcsFile, $previousAnnotation->getEndPointer(), $annotation->getStartPointer()); + $phpcsFile->fixer->addNewline($previousAnnotation->getEndPointer()); + for ($i = 1; $i <= $this->linesCountBetweenDifferentAnnotationsTypes; $i++) { $phpcsFile->fixer->addContent($previousAnnotation->getEndPointer(), sprintf('%s *%s', $indentation, $phpcsFile->eolChar)); } @@ -346,6 +338,8 @@ private function checkLinesBetweenDifferentAnnotationsTypes(File $phpcsFile, int $phpcsFile->fixer->addContentBefore($annotation->getStartPointer(), $indentation . ' * '); $phpcsFile->fixer->endChangeset(); + + $previousAnnotation = $annotation; } } @@ -383,7 +377,7 @@ private function checkAnnotationsGroups(File $phpcsFile, int $docCommentOpenerPo } /** - * @param array> $annotationsGroups + * @param list> $annotationsGroups */ private function checkLinesBetweenAnnotationsGroups(File $phpcsFile, int $docCommentOpenerPointer, array $annotationsGroups): void { @@ -396,7 +390,6 @@ private function checkLinesBetweenAnnotationsGroups(File $phpcsFile, int $docCom continue; } - /** @var Annotation $lastAnnotationInPreviousGroup */ $lastAnnotationInPreviousGroup = $previousAnnotationsGroup[count($previousAnnotationsGroup) - 1]; $firstAnnotationInActualGroup = $annotationsGroup[0]; @@ -451,7 +444,7 @@ private function checkLinesBetweenAnnotationsGroups(File $phpcsFile, int $docCom } /** - * @param array> $annotationsGroups + * @param list> $annotationsGroups * @param list $annotations */ private function checkAnnotationsGroupsOrder( @@ -461,11 +454,11 @@ private function checkAnnotationsGroupsOrder( array $annotations ): void { - $equals = static function (array $firstAnnotationsGroup, array $secondAnnotationsGroup): bool { - $getAnnotationsPointers = static function (Annotation $annotation): int { - return $annotation->getStartPointer(); - }; + $getAnnotationsPointers = static function (Annotation $annotation): int { + return $annotation->getStartPointer(); + }; + $equals = static function (array $firstAnnotationsGroup, array $secondAnnotationsGroup) use ($getAnnotationsPointers): bool { $firstAnnotationsPointers = array_map($getAnnotationsPointers, $firstAnnotationsGroup); $secondAnnotationsPointers = array_map($getAnnotationsPointers, $secondAnnotationsGroup); @@ -645,7 +638,7 @@ private function checkAnnotationsGroupsOrder( /** * @param list $annotations - * @return array> + * @return list> */ private function sortAnnotationsToGroups(array $annotations): array { @@ -762,10 +755,10 @@ private function checkLinesAfterLastContent( $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->addNewline($lastContentEndPointer); - FixerHelper::removeBetween($phpcsFile, $lastContentEndPointer, $docCommentCloserPointer); + $phpcsFile->fixer->addNewline($lastContentEndPointer); + for ($i = 1; $i <= $this->linesCountAfterLastContent; $i++) { $phpcsFile->fixer->addContent($lastContentEndPointer, sprintf('%s *%s', $indentation, $phpcsFile->eolChar)); } diff --git a/SlevomatCodingStandard/Sniffs/Commenting/ForbiddenAnnotationsSniff.php b/SlevomatCodingStandard/Sniffs/Commenting/ForbiddenAnnotationsSniff.php index 46d3523ed..bbce185bc 100644 --- a/SlevomatCodingStandard/Sniffs/Commenting/ForbiddenAnnotationsSniff.php +++ b/SlevomatCodingStandard/Sniffs/Commenting/ForbiddenAnnotationsSniff.php @@ -48,75 +48,73 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($annotations as $annotationName => $annotationsByName) { - if (!in_array($annotationName, $this->getNormalizedForbiddenAnnotations(), true)) { + foreach ($annotations as $annotation) { + if (!in_array($annotation->getName(), $this->getNormalizedForbiddenAnnotations(), true)) { continue; } - foreach ($annotationsByName as $annotation) { - $fix = $phpcsFile->addFixableError( - sprintf('Use of annotation %s is forbidden.', $annotationName), - $annotation->getStartPointer(), - self::CODE_ANNOTATION_FORBIDDEN - ); - if (!$fix) { - continue; - } + $fix = $phpcsFile->addFixableError( + sprintf('Use of annotation %s is forbidden.', $annotation->getName()), + $annotation->getStartPointer(), + self::CODE_ANNOTATION_FORBIDDEN + ); + if (!$fix) { + continue; + } - $starPointer = TokenHelper::findPrevious( - $phpcsFile, - T_DOC_COMMENT_STAR, - $annotation->getStartPointer() - 1, - $docCommentOpenPointer - ); - $annotationStartPointer = $starPointer ?? $docCommentOpenPointer + 1; + $starPointer = TokenHelper::findPrevious( + $phpcsFile, + T_DOC_COMMENT_STAR, + $annotation->getStartPointer() - 1, + $docCommentOpenPointer + ); + $annotationStartPointer = $starPointer ?? $annotation->getStartPointer(); + + /** @var int $nextPointer */ + $nextPointer = TokenHelper::findNext( + $phpcsFile, + [T_DOC_COMMENT_TAG, T_DOC_COMMENT_CLOSE_TAG], + $annotation->getEndPointer() + 1 + ); + if ($tokens[$nextPointer]['code'] === T_DOC_COMMENT_TAG) { + $nextPointer = TokenHelper::findPrevious($phpcsFile, T_DOC_COMMENT_STAR, $nextPointer - 1); + } + $annotationEndPointer = $nextPointer - 1; - /** @var int $nextPointer */ - $nextPointer = TokenHelper::findNext( + if ($tokens[$nextPointer]['code'] === T_DOC_COMMENT_CLOSE_TAG && $starPointer !== null) { + $pointerBeforeWhitespace = TokenHelper::findPreviousExcluding( $phpcsFile, - [T_DOC_COMMENT_TAG, T_DOC_COMMENT_CLOSE_TAG], - $annotation->getEndPointer() + 1 + [T_DOC_COMMENT_WHITESPACE, T_DOC_COMMENT_STAR], + $annotationStartPointer - 1 ); - if ($tokens[$nextPointer]['code'] === T_DOC_COMMENT_TAG) { - $nextPointer = TokenHelper::findPrevious($phpcsFile, T_DOC_COMMENT_STAR, $nextPointer - 1); - } - $annotationEndPointer = $nextPointer - 1; - - if ($tokens[$nextPointer]['code'] === T_DOC_COMMENT_CLOSE_TAG && $starPointer !== null) { - $pointerBeforeWhitespace = TokenHelper::findPreviousExcluding( - $phpcsFile, - [T_DOC_COMMENT_WHITESPACE, T_DOC_COMMENT_STAR], - $annotationStartPointer - 1 - ); - /** @var int $annotationStartPointer */ - $annotationStartPointer = TokenHelper::findNext($phpcsFile, T_DOC_COMMENT_STAR, $pointerBeforeWhitespace + 1); - } - - $phpcsFile->fixer->beginChangeset(); + /** @var int $annotationStartPointer */ + $annotationStartPointer = TokenHelper::findNext($phpcsFile, T_DOC_COMMENT_STAR, $pointerBeforeWhitespace + 1); + } - FixerHelper::removeBetweenIncluding($phpcsFile, $annotationStartPointer, $annotationEndPointer); + $phpcsFile->fixer->beginChangeset(); - $docCommentUseful = false; - $docCommentClosePointer = $tokens[$docCommentOpenPointer]['comment_closer']; - for ($i = $docCommentOpenPointer + 1; $i < $docCommentClosePointer; $i++) { - $tokenContent = trim($phpcsFile->fixer->getTokenContent($i)); - if ($tokenContent === '' || $tokenContent === '*') { - continue; - } + FixerHelper::removeBetweenIncluding($phpcsFile, $annotationStartPointer, $annotationEndPointer); - $docCommentUseful = true; - break; + $docCommentUseful = false; + $docCommentClosePointer = $tokens[$docCommentOpenPointer]['comment_closer']; + for ($i = $docCommentOpenPointer + 1; $i < $docCommentClosePointer; $i++) { + $tokenContent = trim($phpcsFile->fixer->getTokenContent($i)); + if ($tokenContent === '' || $tokenContent === '*') { + continue; } - if (!$docCommentUseful) { - /** @var int $nextPointerAfterDocComment */ - $nextPointerAfterDocComment = TokenHelper::findNextEffective($phpcsFile, $docCommentClosePointer + 1); + $docCommentUseful = true; + break; + } - FixerHelper::removeBetweenIncluding($phpcsFile, $docCommentOpenPointer, $nextPointerAfterDocComment - 1); - } + if (!$docCommentUseful) { + /** @var int $nextPointerAfterDocComment */ + $nextPointerAfterDocComment = TokenHelper::findNextEffective($phpcsFile, $docCommentClosePointer + 1); - $phpcsFile->fixer->endChangeset(); + FixerHelper::removeBetweenIncluding($phpcsFile, $docCommentOpenPointer, $nextPointerAfterDocComment - 1); } + + $phpcsFile->fixer->endChangeset(); } } diff --git a/SlevomatCodingStandard/Sniffs/Commenting/InlineDocCommentDeclarationSniff.php b/SlevomatCodingStandard/Sniffs/Commenting/InlineDocCommentDeclarationSniff.php index 24209ace7..ebdd43e64 100644 --- a/SlevomatCodingStandard/Sniffs/Commenting/InlineDocCommentDeclarationSniff.php +++ b/SlevomatCodingStandard/Sniffs/Commenting/InlineDocCommentDeclarationSniff.php @@ -4,7 +4,8 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use SlevomatCodingStandard\Helpers\Annotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\FixerHelper; use SlevomatCodingStandard\Helpers\PropertyHelper; @@ -111,8 +112,8 @@ public function process(File $phpcsFile, $commentOpenPointer): void return; } - /** @var list $annotations */ - $annotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $commentOpenPointer, '@var'); + /** @var list> $annotations */ + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $commentOpenPointer, '@var'); if ($annotations === []) { return; @@ -153,23 +154,24 @@ private function checkCommentType(File $phpcsFile, int $commentOpenPointer): voi } /** - * @param list $annotations + * @param list> $annotations */ private function checkFormat(File $phpcsFile, array $annotations): void { foreach ($annotations as $annotation) { - if (!$annotation->isInvalid() && $annotation->getVariableName() !== null) { + if (!$annotation->isInvalid() && $annotation->getValue()->variableName !== '') { continue; } - $annotationContent = $annotation->getContent(); + $variableName = '$variableName'; + + $annotationContent = (string) $annotation->getValue(); - $variableName = '$variable'; $type = null; if ( - $annotationContent !== null - && preg_match('~(\$\w+)(?:\s+(.+))?$~i', $annotation->getContent(), $matches) === 1 + $annotationContent !== '' + && preg_match('~(\$\w+)(?:\s+(.+))?$~i', $annotationContent, $matches) === 1 ) { $variableName = $matches[1]; $type = $matches[2] ?? null; @@ -182,7 +184,7 @@ private function checkFormat(File $phpcsFile, array $annotations): void $phpcsFile->addError( sprintf( 'Invalid inline documentation comment format "@var %1$s", expected "@var type %2$s Optional description".', - $annotation->getContent(), + $annotationContent, $variableName ), $annotation->getStartPointer(), @@ -195,7 +197,7 @@ private function checkFormat(File $phpcsFile, array $annotations): void $fix = $phpcsFile->addFixableError( sprintf( 'Invalid inline documentation comment format "@var %1$s", expected "@var %2$s %3$s".', - $annotation->getContent(), + $annotationContent, $type, $variableName ), @@ -225,7 +227,7 @@ private function checkFormat(File $phpcsFile, array $annotations): void } /** - * @param list $annotations + * @param list> $annotations */ private function checkVariable(File $phpcsFile, array $annotations, int $docCommentOpenerPointer, int $docCommentCloserPointer): void { @@ -240,8 +242,8 @@ private function checkVariable(File $phpcsFile, array $annotations, int $docComm continue; } - $variableName = $variableAnnotation->getVariableName(); - if ($variableName === null) { + $variableName = $variableAnnotation->getValue()->variableName; + if ($variableName === '') { continue; } @@ -304,8 +306,8 @@ private function checkVariable(File $phpcsFile, array $annotations, int $docComm continue; } - $variableName = $variableAnnotation->getVariableName(); - if ($variableName === null) { + $variableName = $variableAnnotation->getValue()->variableName; + if ($variableName === '') { continue; } diff --git a/SlevomatCodingStandard/Sniffs/Commenting/UselessFunctionDocCommentSniff.php b/SlevomatCodingStandard/Sniffs/Commenting/UselessFunctionDocCommentSniff.php index 2825cea49..502b058ec 100644 --- a/SlevomatCodingStandard/Sniffs/Commenting/UselessFunctionDocCommentSniff.php +++ b/SlevomatCodingStandard/Sniffs/Commenting/UselessFunctionDocCommentSniff.php @@ -91,7 +91,7 @@ public function process(File $phpcsFile, $functionPointer): void } } - foreach (AnnotationHelper::getAnnotations($phpcsFile, $functionPointer) as [$annotation]) { + foreach (AnnotationHelper::getAnnotations($phpcsFile, $functionPointer) as $annotation) { if (!in_array($annotation->getName(), ['@param', '@return'], true)) { return; } diff --git a/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedClassNameInAnnotationSniff.php b/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedClassNameInAnnotationSniff.php index 2ff7b3bf8..5b59cfb87 100644 --- a/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedClassNameInAnnotationSniff.php +++ b/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedClassNameInAnnotationSniff.php @@ -6,12 +6,11 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; -use SlevomatCodingStandard\Helpers\AnnotationConstantExpressionHelper; use SlevomatCodingStandard\Helpers\AnnotationHelper; -use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; +use SlevomatCodingStandard\Helpers\DocCommentHelper; use SlevomatCodingStandard\Helpers\FixerHelper; use SlevomatCodingStandard\Helpers\NamespaceHelper; +use SlevomatCodingStandard\Helpers\PhpDocParserHelper; use SlevomatCodingStandard\Helpers\ReferencedName; use SlevomatCodingStandard\Helpers\TypeHelper; use SlevomatCodingStandard\Helpers\TypeHintHelper; @@ -42,125 +41,121 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void { $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($annotations as $annotationName => $annotationsByName) { - foreach ($annotationsByName as $annotation) { - if ($annotation instanceof GenericAnnotation) { + foreach ($annotations as $annotation) { + /** @var list $identifierTypeNodes */ + $identifierTypeNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), IdentifierTypeNode::class); + + foreach ($identifierTypeNodes as $typeHintNode) { + $typeHint = $typeHintNode->name; + + $lowercasedTypeHint = strtolower($typeHint); + if ( + TypeHintHelper::isSimpleTypeHint($lowercasedTypeHint) + || TypeHintHelper::isSimpleUnofficialTypeHints($lowercasedTypeHint) + || !TypeHelper::isTypeName($typeHint) + || TypeHintHelper::isTypeDefinedInAnnotation($phpcsFile, $docCommentOpenPointer, $typeHint) + ) { + continue; + } + + $fullyQualifiedTypeHint = TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $docCommentOpenPointer, $typeHint); + if ($fullyQualifiedTypeHint === $typeHint) { + continue; + } + + $fix = $phpcsFile->addFixableError(sprintf( + 'Class name %s in %s should be referenced via a fully qualified name.', + $fullyQualifiedTypeHint, + $annotation->getName() + ), $annotation->getStartPointer(), self::CODE_NON_FULLY_QUALIFIED_CLASS_NAME); + + if (!$fix) { + continue; + } + + $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); + + $fixedDocComment = AnnotationHelper::fixAnnotation( + $parsedDocComment, + $annotation, + $typeHintNode, + new IdentifierTypeNode($fullyQualifiedTypeHint) + ); + + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($parsedDocComment->getOpenPointer(), $fixedDocComment); + + FixerHelper::removeBetweenIncluding( + $phpcsFile, + $parsedDocComment->getOpenPointer() + 1, + $parsedDocComment->getClosePointer() + ); + + $phpcsFile->fixer->endChangeset(); + } + + /** @var list $constantFetchNodes */ + $constantFetchNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), ConstFetchNode::class); + + foreach ($constantFetchNodes as $constantFetchNode) { + $isClassConstant = $constantFetchNode->className !== ''; + + $typeHint = $isClassConstant + ? $constantFetchNode->className + : $constantFetchNode->name; + + if ($typeHint === 'self') { continue; } - if ($annotation->isInvalid()) { + $fullyQualifiedTypeHint = $isClassConstant + ? NamespaceHelper::resolveClassName($phpcsFile, $typeHint, $docCommentOpenPointer) + : NamespaceHelper::resolveName($phpcsFile, $typeHint, ReferencedName::TYPE_CONSTANT, $docCommentOpenPointer); + + if ($fullyQualifiedTypeHint === $typeHint) { continue; } - foreach (AnnotationHelper::getAnnotationTypes($annotation) as $annotationType) { - foreach (AnnotationTypeHelper::getIdentifierTypeNodes($annotationType) as $typeHintNode) { - $typeHint = AnnotationTypeHelper::getTypeHintFromNode($typeHintNode); - - $lowercasedTypeHint = strtolower($typeHint); - if ( - TypeHintHelper::isSimpleTypeHint($lowercasedTypeHint) - || TypeHintHelper::isSimpleUnofficialTypeHints($lowercasedTypeHint) - || !TypeHelper::isTypeName($typeHint) - || TypeHintHelper::isTypeDefinedInAnnotation($phpcsFile, $docCommentOpenPointer, $typeHint) - ) { - continue; - } - - $fullyQualifiedTypeHint = TypeHintHelper::getFullyQualifiedTypeHint( - $phpcsFile, - $annotation->getStartPointer(), - $typeHint - ); - if ($fullyQualifiedTypeHint === $typeHint) { - continue; - } - $fix = $phpcsFile->addFixableError(sprintf( - 'Class name %s in %s should be referenced via a fully qualified name.', - $fullyQualifiedTypeHint, - $annotationName - ), $annotation->getStartPointer(), self::CODE_NON_FULLY_QUALIFIED_CLASS_NAME); - - if (!$fix) { - continue; - } - - $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( - $phpcsFile, - $annotation, - $typeHintNode, - new IdentifierTypeNode($fullyQualifiedTypeHint) - ); - - $phpcsFile->fixer->beginChangeset(); - - $phpcsFile->fixer->replaceToken($annotation->getStartPointer(), $fixedAnnotationContent); - - FixerHelper::removeBetweenIncluding($phpcsFile, $annotation->getStartPointer() + 1, $annotation->getEndPointer()); - - $phpcsFile->fixer->endChangeset(); - } + $fix = $phpcsFile->addFixableError(sprintf( + '%s name %s in %s should be referenced via a fully qualified name.', + $isClassConstant ? 'Class' : 'Constant', + $fullyQualifiedTypeHint, + $annotation->getName() + ), $annotation->getStartPointer(), self::CODE_NON_FULLY_QUALIFIED_CLASS_NAME); + + if (!$fix) { + continue; } - foreach (AnnotationHelper::getAnnotationConstantExpressions($annotation) as $constantExpression) { - foreach (AnnotationConstantExpressionHelper::getConstantFetchNodes($constantExpression) as $constantFetchNode) { - $isClassConstant = $constantFetchNode->className !== ''; - - $typeHint = $isClassConstant - ? $constantFetchNode->className - : $constantFetchNode->name; - - if ($typeHint === 'self') { - continue; - } - - $fullyQualifiedTypeHint = $isClassConstant - ? NamespaceHelper::resolveClassName( - $phpcsFile, - $typeHint, - $annotation->getStartPointer() - ) - : NamespaceHelper::resolveName( - $phpcsFile, - $typeHint, - ReferencedName::TYPE_CONSTANT, - $annotation->getStartPointer() - ); - - if ($fullyQualifiedTypeHint === $typeHint) { - continue; - } - - $fix = $phpcsFile->addFixableError(sprintf( - '%s name %s in %s should be referenced via a fully qualified name.', - $isClassConstant ? 'Class' : 'Constant', - $fullyQualifiedTypeHint, - $annotationName - ), $annotation->getStartPointer(), self::CODE_NON_FULLY_QUALIFIED_CLASS_NAME); - - if (!$fix) { - continue; - } - - $fixedConstantFetchNode = $isClassConstant - ? new ConstFetchNode($fullyQualifiedTypeHint, $constantFetchNode->name) - : new ConstFetchNode('', $fullyQualifiedTypeHint); - - $fixedAnnotationContent = AnnotationHelper::fixAnnotationConstantFetchNode( - $phpcsFile, - $annotation, - $constantFetchNode, - $fixedConstantFetchNode - ); - - $phpcsFile->fixer->beginChangeset(); - - $phpcsFile->fixer->replaceToken($annotation->getStartPointer(), $fixedAnnotationContent); - - FixerHelper::removeBetweenIncluding($phpcsFile, $annotation->getStartPointer() + 1, $annotation->getEndPointer()); - - $phpcsFile->fixer->endChangeset(); - } + $fixedConstantFetchNode = PhpDocParserHelper::cloneNode($constantFetchNode); + if ($isClassConstant) { + $fixedConstantFetchNode->className = $fullyQualifiedTypeHint; + } else { + $fixedConstantFetchNode->name = $fullyQualifiedTypeHint; } + + $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); + + $fixedDocComment = AnnotationHelper::fixAnnotation( + $parsedDocComment, + $annotation, + $constantFetchNode, + $fixedConstantFetchNode + ); + + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($parsedDocComment->getOpenPointer(), $fixedDocComment); + + FixerHelper::removeBetweenIncluding( + $phpcsFile, + $parsedDocComment->getOpenPointer() + 1, + $parsedDocComment->getClosePointer() + ); + + $phpcsFile->fixer->endChangeset(); + } } } diff --git a/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php b/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php index 0c5ee0b0d..fccd58d16 100644 --- a/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php +++ b/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php @@ -7,13 +7,11 @@ use PHP_CodeSniffer\Util\Tokens; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; -use SlevomatCodingStandard\Helpers\AnnotationConstantExpressionHelper; use SlevomatCodingStandard\Helpers\AnnotationHelper; -use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; use SlevomatCodingStandard\Helpers\ClassHelper; use SlevomatCodingStandard\Helpers\CommentHelper; use SlevomatCodingStandard\Helpers\ConstantHelper; +use SlevomatCodingStandard\Helpers\DocCommentHelper; use SlevomatCodingStandard\Helpers\FixerHelper; use SlevomatCodingStandard\Helpers\FunctionHelper; use SlevomatCodingStandard\Helpers\NamespaceHelper; @@ -306,28 +304,36 @@ public function process(File $phpcsFile, $openTagPointer): void $phpcsFile->fixer->beginChangeset(); if ($reference->source === self::SOURCE_ANNOTATION) { - $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( - $phpcsFile, + $fixedDocComment = AnnotationHelper::fixAnnotation( + $reference->parsedDocComment, $reference->annotation, $reference->nameNode, new IdentifierTypeNode(substr($reference->name, 1)) ); - $phpcsFile->fixer->replaceToken($startPointer, $fixedAnnotationContent); + $phpcsFile->fixer->replaceToken($reference->parsedDocComment->getOpenPointer(), $fixedDocComment); - FixerHelper::removeBetweenIncluding($phpcsFile, $startPointer + 1, $reference->endPointer); + FixerHelper::removeBetweenIncluding( + $phpcsFile, + $reference->parsedDocComment->getOpenPointer() + 1, + $reference->parsedDocComment->getClosePointer() + ); } elseif ($reference->source === self::SOURCE_ANNOTATION_CONSTANT_FETCH) { - $fixedAnnotationContent = AnnotationHelper::fixAnnotationConstantFetchNode( - $phpcsFile, + $fixedDocComment = AnnotationHelper::fixAnnotation( + $reference->parsedDocComment, $reference->annotation, $reference->constantFetchNode, new ConstFetchNode(substr($reference->name, 1), $reference->constantFetchNode->name) ); - $phpcsFile->fixer->replaceToken($startPointer, $fixedAnnotationContent); + $phpcsFile->fixer->replaceToken($reference->parsedDocComment->getOpenPointer(), $fixedDocComment); - FixerHelper::removeBetweenIncluding($phpcsFile, $startPointer + 1, $reference->endPointer); + FixerHelper::removeBetweenIncluding( + $phpcsFile, + $reference->parsedDocComment->getOpenPointer() + 1, + $reference->parsedDocComment->getClosePointer() + ); } else { $phpcsFile->fixer->replaceToken($startPointer, substr($tokens[$startPointer]['content'], 1)); } @@ -502,21 +508,35 @@ static function (bool $carry, string $use) use ($canonicalName): bool { } if ($reference->source === self::SOURCE_ANNOTATION) { - $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( - $phpcsFile, + $fixedDocComment = AnnotationHelper::fixAnnotation( + $reference->parsedDocComment, $reference->annotation, $reference->nameNode, new IdentifierTypeNode($nameToReference) ); - $phpcsFile->fixer->replaceToken($startPointer, $fixedAnnotationContent); - } elseif ($reference->source === self::SOURCE_ANNOTATION_CONSTANT_FETCH) { - $fixedAnnotationContent = AnnotationHelper::fixAnnotationConstantFetchNode( + + $phpcsFile->fixer->replaceToken($reference->parsedDocComment->getOpenPointer(), $fixedDocComment); + FixerHelper::removeBetweenIncluding( $phpcsFile, + $reference->parsedDocComment->getOpenPointer() + 1, + $reference->parsedDocComment->getClosePointer() + ); + + } elseif ($reference->source === self::SOURCE_ANNOTATION_CONSTANT_FETCH) { + $fixedDocComment = AnnotationHelper::fixAnnotation( + $reference->parsedDocComment, $reference->annotation, $reference->constantFetchNode, new ConstFetchNode($nameToReference, $reference->constantFetchNode->name) ); - $phpcsFile->fixer->replaceToken($startPointer, $fixedAnnotationContent); + + $phpcsFile->fixer->replaceToken($reference->parsedDocComment->getOpenPointer(), $fixedDocComment); + FixerHelper::removeBetweenIncluding( + $phpcsFile, + $reference->parsedDocComment->getOpenPointer() + 1, + $reference->parsedDocComment->getClosePointer() + ); + } elseif ($reference->source === self::SOURCE_ATTRIBUTE) { $attributeContent = TokenHelper::getContent($phpcsFile, $startPointer, $reference->endPointer); $fixedAttributeContent = preg_replace( @@ -525,11 +545,11 @@ static function (bool $carry, string $use) use ($canonicalName): bool { $attributeContent ); $phpcsFile->fixer->replaceToken($startPointer, $fixedAttributeContent); + FixerHelper::removeBetweenIncluding($phpcsFile, $startPointer + 1, $reference->endPointer); } else { $phpcsFile->fixer->replaceToken($startPointer, $nameToReference); + FixerHelper::removeBetweenIncluding($phpcsFile, $startPointer + 1, $reference->endPointer); } - - FixerHelper::removeBetweenIncluding($phpcsFile, $startPointer + 1, $reference->endPointer); } $phpcsFile->fixer->endChangeset(); @@ -691,65 +711,59 @@ private function getReferences(File $phpcsFile, int $openTagPointer): array break; } + $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($annotations as $annotationsByName) { - foreach ($annotationsByName as $annotation) { - if ($annotation instanceof GenericAnnotation) { - continue; - } + foreach ($annotations as $annotation) { + /** @var list $identifierTypeNodes */ + $identifierTypeNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), IdentifierTypeNode::class); + + foreach ($identifierTypeNodes as $typeHintNode) { + $typeHint = $typeHintNode->name; - if ($annotation->isInvalid()) { + $lowercasedTypeHint = strtolower($typeHint); + if ( + TypeHintHelper::isSimpleTypeHint($lowercasedTypeHint) + || TypeHintHelper::isSimpleUnofficialTypeHints($lowercasedTypeHint) + || !TypeHelper::isTypeName($typeHint) + ) { continue; } - foreach (AnnotationHelper::getAnnotationTypes($annotation) as $annotationType) { - foreach (AnnotationTypeHelper::getIdentifierTypeNodes($annotationType) as $typeHintNode) { - $typeHint = AnnotationTypeHelper::getTypeHintFromNode($typeHintNode); - - $lowercasedTypeHint = strtolower($typeHint); - if ( - TypeHintHelper::isSimpleTypeHint($lowercasedTypeHint) - || TypeHintHelper::isSimpleUnofficialTypeHints($lowercasedTypeHint) - || !TypeHelper::isTypeName($typeHint) - ) { - continue; - } - - $reference = new stdClass(); - $reference->source = self::SOURCE_ANNOTATION; - $reference->annotation = $annotation; - $reference->nameNode = $typeHintNode; - $reference->name = $typeHint; - $reference->type = ReferencedName::TYPE_CLASS; - $reference->startPointer = $annotation->getStartPointer(); - $reference->endPointer = $annotation->getEndPointer(); - $reference->isClass = true; - $reference->isConstant = false; - $reference->isFunction = false; - - $references[] = $reference; - } - } + $reference = new stdClass(); + $reference->source = self::SOURCE_ANNOTATION; + $reference->parsedDocComment = $parsedDocComment; + $reference->annotation = $annotation; + $reference->nameNode = $typeHintNode; + $reference->name = $typeHint; + $reference->type = ReferencedName::TYPE_CLASS; + $reference->startPointer = $annotation->getStartPointer(); + $reference->endPointer = null; + $reference->isClass = true; + $reference->isConstant = false; + $reference->isFunction = false; + + $references[] = $reference; + } - foreach (AnnotationHelper::getAnnotationConstantExpressions($annotation) as $constantExpression) { - foreach (AnnotationConstantExpressionHelper::getConstantFetchNodes($constantExpression) as $constantFetchNode) { - - $reference = new stdClass(); - $reference->source = self::SOURCE_ANNOTATION_CONSTANT_FETCH; - $reference->annotation = $annotation; - $reference->constantFetchNode = $constantFetchNode; - $reference->name = $constantFetchNode->className; - $reference->type = ReferencedName::TYPE_CLASS; - $reference->startPointer = $annotation->getStartPointer(); - $reference->endPointer = $annotation->getEndPointer(); - $reference->isClass = true; - $reference->isConstant = false; - $reference->isFunction = false; - - $references[] = $reference; - } - } + /** @var list $constantFetchNodes */ + $constantFetchNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), ConstFetchNode::class); + + foreach ($constantFetchNodes as $constantFetchNode) { + $reference = new stdClass(); + $reference->source = self::SOURCE_ANNOTATION_CONSTANT_FETCH; + $reference->parsedDocComment = $parsedDocComment; + $reference->annotation = $annotation; + $reference->constantFetchNode = $constantFetchNode; + $reference->name = $constantFetchNode->className; + $reference->type = ReferencedName::TYPE_CLASS; + $reference->startPointer = $annotation->getStartPointer(); + $reference->endPointer = null; + $reference->isClass = true; + $reference->isConstant = false; + $reference->isFunction = false; + + $references[] = $reference; } } diff --git a/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php b/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php index d78219286..d1d46d50f 100644 --- a/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php +++ b/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php @@ -5,17 +5,10 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineAnnotation; +use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\MethodAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ParameterAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\PropertyAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ReturnAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ThrowsAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; -use SlevomatCodingStandard\Helpers\AnnotationConstantExpressionHelper; use SlevomatCodingStandard\Helpers\AnnotationHelper; -use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; use SlevomatCodingStandard\Helpers\FixerHelper; use SlevomatCodingStandard\Helpers\NamespaceHelper; use SlevomatCodingStandard\Helpers\ReferencedName; @@ -27,6 +20,7 @@ use SlevomatCodingStandard\Helpers\UseStatement; use SlevomatCodingStandard\Helpers\UseStatementHelper; use function array_diff_key; +use function array_filter; use function array_key_exists; use function array_map; use function array_merge; @@ -35,7 +29,6 @@ use function in_array; use function preg_match; use function preg_quote; -use function preg_split; use function sprintf; use const T_DOC_COMMENT_OPEN_TAG; use const T_NAMESPACE; @@ -128,7 +121,7 @@ public function process(File $phpcsFile, $openTagPointer): void $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - if (count($annotations) === 0) { + if ($annotations === []) { $searchAnnotationsPointer = $tokens[$docCommentOpenPointer]['comment_closer'] + 1; continue; } @@ -152,109 +145,68 @@ public function process(File $phpcsFile, $openTagPointer): void $nameAsReferencedInFile = $useStatement->getNameAsReferencedInFile(); $uniqueId = UseStatement::getUniqueId($useStatement->getType(), $nameAsReferencedInFile); - /** @var string $annotationName */ - foreach ($annotations as $annotationName => $annotationsByName) { - if (in_array($annotationName, $this->getIgnoredAnnotations(), true)) { + foreach ($annotations as $annotation) { + if (in_array($annotation->getName(), $this->getIgnoredAnnotations(), true)) { continue; } - if ( - !in_array($annotationName, $this->getIgnoredAnnotationNames(), true) - && preg_match( - '~^@(' . preg_quote($nameAsReferencedInFile, '~') . ')(?=[^-a-z\\d]|$)~i', - $annotationName, - $matches - ) !== 0 - ) { - $allUsedNames[$pointerBeforeUseStatements][$uniqueId] = true; + if ($annotation->isInvalid()) { + continue; } - foreach ($annotationsByName as $annotation) { - if (!$annotation instanceof GenericAnnotation) { - continue; - } - - if ($annotation->getParameters() === null) { - continue; - } + $contentsToCheck = []; + + if ($annotation->getValue() instanceof GenericTagValueNode) { + $contentsToCheck[] = $annotation->getValue()->value; + } else { + /** @var list $identifierTypeNodes */ + $identifierTypeNodes = AnnotationHelper::getAnnotationNodesByType( + $annotation->getNode(), + IdentifierTypeNode::class + ); + /** @var list $doctrineAnnotations */ + $doctrineAnnotations = AnnotationHelper::getAnnotationNodesByType( + $annotation->getNode(), + DoctrineAnnotation::class + ); + /** @var list $constFetchNodes */ + $constFetchNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), ConstFetchNode::class); + + $contentsToCheck = array_filter(array_merge( + $contentsToCheck, + array_map(static function (IdentifierTypeNode $identifierTypeNode): ?string { + if ( + TypeHintHelper::isSimpleTypeHint($identifierTypeNode->name) + || TypeHintHelper::isSimpleUnofficialTypeHints($identifierTypeNode->name) + || !TypeHelper::isTypeName($identifierTypeNode->name) + ) { + return null; + } - if ( - preg_match( - '~(?<=^|[^a-z\\\\])(' . preg_quote($nameAsReferencedInFile, '~') . ')(\\\\\\w+)*(?=::)~i', - $annotation->getParameters(), - $matches - ) === 0 - && preg_match( - '~(?<=@)(' . preg_quote($nameAsReferencedInFile, '~') . ')(?=[^\\w])~i', - $annotation->getParameters(), - $matches - ) === 0 - ) { - continue; - } + return $identifierTypeNode->name; + }, $identifierTypeNodes), + array_map(function (DoctrineAnnotation $doctrineAnnotation): ?string { + if (in_array($doctrineAnnotation->name, $this->getIgnoredAnnotationNames(), true)) { + return null; + } - $allUsedNames[$pointerBeforeUseStatements][$uniqueId] = true; + return $doctrineAnnotation->name; + }, $doctrineAnnotations), + array_map(static function (ConstFetchNode $constFetchNode): string { + return $constFetchNode->className; + }, $constFetchNodes) + )); } - /** @var VariableAnnotation|ParameterAnnotation|ReturnAnnotation|ThrowsAnnotation|PropertyAnnotation|MethodAnnotation|GenericAnnotation $annotation */ - foreach ($annotationsByName as $annotation) { - if ($annotation->getContent() === null) { + foreach ($contentsToCheck as $contentToCheck) { + if (preg_match( + '~(?<=^|[^a-z\\\\])(' . preg_quote($nameAsReferencedInFile, '~') . ')(?=\\s|::|\\\\|\||\[|$)~im', + $contentToCheck + ) === 0) { continue; } - if ($annotation->isInvalid()) { - continue; - } - - $content = $annotation->getContent(); - - $contentsToCheck = []; - if (!$annotation instanceof GenericAnnotation) { - foreach (AnnotationHelper::getAnnotationTypes($annotation) as $annotationType) { - foreach (AnnotationTypeHelper::getIdentifierTypeNodes($annotationType) as $typeNode) { - if (!$typeNode instanceof IdentifierTypeNode) { - continue; - } - - if ( - TypeHintHelper::isSimpleTypeHint($typeNode->name) - || TypeHintHelper::isSimpleUnofficialTypeHints($typeNode->name) - || !TypeHelper::isTypeName($typeNode->name) - ) { - continue; - } - - $contentsToCheck[] = $typeNode->name; - } - } - foreach (AnnotationHelper::getAnnotationConstantExpressions($annotation) as $annotationConstantExpression) { - $contentsToCheck = array_merge( - $contentsToCheck, - array_map(static function (ConstFetchNode $constFetchNode): string { - return $constFetchNode->className; - }, AnnotationConstantExpressionHelper::getConstantFetchNodes($annotationConstantExpression)) - ); - } - } elseif ($annotationName === '@see') { - $parts = preg_split('~(\\s+|::)~', $content); - if ($parts !== false) { - $contentsToCheck[] = $parts[0]; - } - } else { - $contentsToCheck[] = $content; - } - - foreach ($contentsToCheck as $contentToCheck) { - if (preg_match( - '~(?<=^|\|)(' . preg_quote($nameAsReferencedInFile, '~') . ')(?=\\s|\\\\|\||\[|$)~i', - $contentToCheck, - $matches - ) === 0) { - continue; - } - - $allUsedNames[$pointerBeforeUseStatements][$uniqueId] = true; - } + $allUsedNames[$pointerBeforeUseStatements][$uniqueId] = true; } } } diff --git a/SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php b/SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php index e43b0d15d..49466b11d 100644 --- a/SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php +++ b/SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php @@ -5,6 +5,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; @@ -12,7 +13,7 @@ use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; +use SlevomatCodingStandard\Helpers\Annotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\FixerHelper; use SlevomatCodingStandard\Helpers\IndentationHelper; @@ -86,22 +87,24 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $codePointer = $firstPointerOnPreviousLine; } - $variableAnnotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $docCommentOpenPointer, '@var'); + /** @var list> $variableAnnotations */ + $variableAnnotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer, '@var'); if (count($variableAnnotations) === 0) { return; } - /** @var VariableAnnotation $variableAnnotation */ foreach (array_reverse($variableAnnotations) as $variableAnnotation) { if ($variableAnnotation->isInvalid()) { continue; } - if ($variableAnnotation->getVariableName() === null) { + $variableName = $variableAnnotation->getValue()->variableName; + + if ($variableName === '') { continue; } - $variableAnnotationType = $variableAnnotation->getType(); + $variableAnnotationType = $variableAnnotation->getValue()->type; if ( $variableAnnotationType instanceof UnionTypeNode @@ -119,7 +122,7 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void /** @var IdentifierTypeNode|ThisTypeNode|UnionTypeNode|GenericTypeNode $variableAnnotationType */ $variableAnnotationType = $variableAnnotationType; - $assertion = $this->createAssert($variableAnnotation->getVariableName(), $variableAnnotationType); + $assertion = $this->createAssert($variableName, $variableAnnotationType); if ($assertion === null) { continue; @@ -131,7 +134,7 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void continue; } - if ($variableAnnotation->getVariableName() !== $tokens[$codePointer]['content']) { + if ($variableName !== $tokens[$codePointer]['content']) { continue; } @@ -144,7 +147,7 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $variablePointerInList = TokenHelper::findNextContent( $phpcsFile, T_VARIABLE, - $variableAnnotation->getVariableName(), + $variableName, $listParenthesisOpener + 1, $tokens[$listParenthesisOpener]['parenthesis_closer'] ); @@ -164,7 +167,7 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $variablePointerInList = TokenHelper::findNextContent( $phpcsFile, T_VARIABLE, - $variableAnnotation->getVariableName(), + $variableName, $codePointer + 1, $tokens[$codePointer]['bracket_closer'] ); @@ -184,7 +187,7 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $variablePointerInWhile = TokenHelper::findNextContent( $phpcsFile, T_VARIABLE, - $variableAnnotation->getVariableName(), + $variableName, $tokens[$codePointer]['parenthesis_opener'] + 1, $tokens[$codePointer]['parenthesis_closer'] ); @@ -206,7 +209,7 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $variablePointerInForeach = TokenHelper::findNextContent( $phpcsFile, T_VARIABLE, - $variableAnnotation->getVariableName(), + $variableName, $asPointer + 1, $tokens[$codePointer]['parenthesis_closer'] ); diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/DisallowArrayTypeHintSyntaxSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/DisallowArrayTypeHintSyntaxSniff.php index d44aa7879..a1f5641f9 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/DisallowArrayTypeHintSyntaxSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/DisallowArrayTypeHintSyntaxSniff.php @@ -4,17 +4,20 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor; +use PHPStan\PhpDocParser\Ast\Node; +use PHPStan\PhpDocParser\Ast\NodeTraverser; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\Annotation; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ParameterAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\ReturnAnnotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; +use SlevomatCodingStandard\Helpers\DocCommentHelper; use SlevomatCodingStandard\Helpers\FixerHelper; use SlevomatCodingStandard\Helpers\FunctionHelper; use SlevomatCodingStandard\Helpers\NamespaceHelper; @@ -24,7 +27,6 @@ use function array_flip; use function array_key_exists; use function array_map; -use function array_values; use function count; use function in_array; use function sprintf; @@ -60,80 +62,86 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void { $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($annotations as $annotationByName) { - foreach ($annotationByName as $annotation) { - if ($annotation instanceof GenericAnnotation) { - continue; - } + foreach ($annotations as $annotation) { + if ($annotation->isInvalid()) { + continue; + } - if ($annotation->isInvalid()) { + /** @var list $unionTypeNodes */ + $unionTypeNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), UnionTypeNode::class); + $arrayTypeNodes = $this->getArrayTypeNodes($annotation->getValue()); + + foreach ($arrayTypeNodes as $arrayTypeNode) { + $fix = $phpcsFile->addFixableError( + sprintf( + 'Usage of array type hint syntax in "%s" is disallowed, use generic type hint syntax instead.', + AnnotationTypeHelper::print($arrayTypeNode) + ), + $annotation->getStartPointer(), + self::CODE_DISALLOWED_ARRAY_TYPE_HINT_SYNTAX + ); + + if (!$fix) { continue; } - foreach (AnnotationHelper::getAnnotationTypes($annotation) as $annotationType) { - $unionTypeNodes = AnnotationTypeHelper::getUnionTypeNodes($annotationType); - foreach ($this->getArrayTypeNodes($annotationType) as $arrayTypeNode) { - $fix = $phpcsFile->addFixableError( - sprintf( - 'Usage of array type hint syntax in "%s" is disallowed, use generic type hint syntax instead.', - AnnotationTypeHelper::print($arrayTypeNode) - ), - $annotation->getStartPointer(), - self::CODE_DISALLOWED_ARRAY_TYPE_HINT_SYNTAX + $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); + + $unionTypeNode = $this->findUnionTypeThatContainsArrayType($arrayTypeNode, $unionTypeNodes); + + if ($unionTypeNode !== null) { + $genericIdentifier = $this->findGenericIdentifier( + $phpcsFile, + $docCommentOpenPointer, + $unionTypeNode, + $annotation->getValue() + ); + if ($genericIdentifier !== null) { + $genericTypeNode = new GenericTypeNode( + new IdentifierTypeNode($genericIdentifier), + [$this->fixArrayNode($arrayTypeNode->type)] + ); + $fixedDocComment = AnnotationHelper::fixAnnotation( + $parsedDocComment, + $annotation, + $unionTypeNode, + $genericTypeNode + ); + } else { + $genericTypeNode = new GenericTypeNode( + new IdentifierTypeNode('array'), + [$this->fixArrayNode($arrayTypeNode->type)] + ); + $fixedDocComment = AnnotationHelper::fixAnnotation( + $parsedDocComment, + $annotation, + $arrayTypeNode, + $genericTypeNode ); - - if (!$fix) { - continue; - } - - $unionTypeNode = $this->findUnionTypeThatContainsArrayType($arrayTypeNode, $unionTypeNodes); - - if ($unionTypeNode !== null) { - $genericIdentifier = $this->findGenericIdentifier($phpcsFile, $unionTypeNode, $annotation); - if ($genericIdentifier !== null) { - $genericTypeNode = new GenericTypeNode( - new IdentifierTypeNode($genericIdentifier), - [$this->fixArrayNode($arrayTypeNode->type)] - ); - $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( - $phpcsFile, - $annotation, - $unionTypeNode, - $genericTypeNode - ); - } else { - $genericTypeNode = new GenericTypeNode( - new IdentifierTypeNode('array'), - [$this->fixArrayNode($arrayTypeNode->type)] - ); - $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( - $phpcsFile, - $annotation, - $arrayTypeNode, - $genericTypeNode - ); - } - } else { - $genericIdentifier = $this->findGenericIdentifier($phpcsFile, $arrayTypeNode, $annotation) ?? 'array'; - - $genericTypeNode = new GenericTypeNode( - new IdentifierTypeNode($genericIdentifier), - [$this->fixArrayNode($arrayTypeNode->type)] - ); - $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( - $phpcsFile, - $annotation, - $arrayTypeNode, - $genericTypeNode - ); - } - - $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->replaceToken($annotation->getStartPointer(), $fixedAnnotationContent); - FixerHelper::removeBetweenIncluding($phpcsFile, $annotation->getStartPointer() + 1, $annotation->getEndPointer()); - $phpcsFile->fixer->endChangeset(); } + } else { + $genericIdentifier = $this->findGenericIdentifier( + $phpcsFile, + $docCommentOpenPointer, + $arrayTypeNode, + $annotation->getValue() + ) ?? 'array'; + + $genericTypeNode = new GenericTypeNode( + new IdentifierTypeNode($genericIdentifier), + [$this->fixArrayNode($arrayTypeNode->type)] + ); + $fixedDocComment = AnnotationHelper::fixAnnotation($parsedDocComment, $annotation, $arrayTypeNode, $genericTypeNode); } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($parsedDocComment->getOpenPointer(), $fixedDocComment); + FixerHelper::removeBetweenIncluding( + $phpcsFile, + $parsedDocComment->getOpenPointer() + 1, + $parsedDocComment->getClosePointer() + ); + $phpcsFile->fixer->endChangeset(); } } } @@ -141,28 +149,58 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void /** * @return list */ - public function getArrayTypeNodes(TypeNode $typeNode): array + public function getArrayTypeNodes(Node $node): array { - $arrayTypeNodes = AnnotationTypeHelper::getArrayTypeNodes($typeNode); + static $visitor; + static $traverser; - $arrayTypeNodesToIgnore = []; - foreach ($arrayTypeNodes as $arrayTypeNode) { - if (!($arrayTypeNode->type instanceof ArrayTypeNode)) { - continue; - } + if ($visitor === null) { + $visitor = new class extends AbstractNodeVisitor { - $arrayTypeNodesToIgnore[] = $arrayTypeNode->type; - } + /** @var list */ + private $nodes = []; - foreach ($arrayTypeNodes as $no => $arrayTypeNode) { - if (!in_array($arrayTypeNode, $arrayTypeNodesToIgnore, true)) { - continue; - } + /** + * @return Node|list|NodeTraverser::*|null + */ + public function enterNode(Node $node) + { + if ($node instanceof ArrayTypeNode) { + $this->nodes[] = $node; + + if ($node->type instanceof ArrayTypeNode) { + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + } + + return null; + } - unset($arrayTypeNodes[$no]); + public function cleanNodes(): void + { + $this->nodes = []; + } + + /** + * @return list + */ + public function getNodes(): array + { + return $this->nodes; + } + + }; } - return array_values($arrayTypeNodes); + if ($traverser === null) { + $traverser = new NodeTraverser([$visitor]); + } + + $visitor->cleanNodes(); + + $traverser->traverse([$node]); + + return $visitor->getNodes(); } private function fixArrayNode(TypeNode $node): TypeNode @@ -188,26 +226,31 @@ private function findUnionTypeThatContainsArrayType(ArrayTypeNode $arrayTypeNode return null; } - private function findGenericIdentifier(File $phpcsFile, TypeNode $typeNode, Annotation $annotation): ?string + private function findGenericIdentifier( + File $phpcsFile, + int $docCommentOpenPointer, + TypeNode $typeNode, + PhpDocTagValueNode $annotationValue + ): ?string { if (!$typeNode instanceof UnionTypeNode) { - if (!$annotation instanceof ParameterAnnotation && !$annotation instanceof ReturnAnnotation) { + if (!$annotationValue instanceof ParamTagValueNode && !$annotationValue instanceof ReturnTagValueNode) { return null; } - $functionPointer = TokenHelper::findNext($phpcsFile, TokenHelper::$functionTokenCodes, $annotation->getStartPointer() + 1); + $functionPointer = TokenHelper::findNext($phpcsFile, TokenHelper::$functionTokenCodes, $docCommentOpenPointer + 1); if ($functionPointer === null || $phpcsFile->getTokens()[$functionPointer]['code'] !== T_FUNCTION) { return null; } - if ($annotation instanceof ParameterAnnotation) { + if ($annotationValue instanceof ParamTagValueNode) { $parameterTypeHints = FunctionHelper::getParametersTypeHints($phpcsFile, $functionPointer); return array_key_exists( - $annotation->getParameterName(), + $annotationValue->parameterName, $parameterTypeHints - ) && $parameterTypeHints[$annotation->getParameterName()] !== null - ? $parameterTypeHints[$annotation->getParameterName()]->getTypeHint() + ) && $parameterTypeHints[$annotationValue->parameterName] !== null + ? $parameterTypeHints[$annotationValue->parameterName]->getTypeHint() : null; } @@ -223,7 +266,7 @@ private function findGenericIdentifier(File $phpcsFile, TypeNode $typeNode, Anno $typeNode->types[0] instanceof ArrayTypeNode && $typeNode->types[1] instanceof IdentifierTypeNode && $this->isTraversableType( - TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $annotation->getStartPointer(), $typeNode->types[1]->name) + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $docCommentOpenPointer, $typeNode->types[1]->name) ) ) { return $typeNode->types[1]->name; @@ -233,7 +276,7 @@ private function findGenericIdentifier(File $phpcsFile, TypeNode $typeNode, Anno $typeNode->types[1] instanceof ArrayTypeNode && $typeNode->types[0] instanceof IdentifierTypeNode && $this->isTraversableType( - TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $annotation->getStartPointer(), $typeNode->types[0]->name) + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $docCommentOpenPointer, $typeNode->types[0]->name) ) ) { return $typeNode->types[0]->name; diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/DisallowMixedTypeHintSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/DisallowMixedTypeHintSniff.php index 73c27e8b8..2c29985ed 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/DisallowMixedTypeHintSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/DisallowMixedTypeHintSniff.php @@ -4,9 +4,8 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; +use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use SlevomatCodingStandard\Helpers\AnnotationHelper; -use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; use SlevomatCodingStandard\Helpers\SuppressHelper; use function sprintf; use function strtolower; @@ -45,31 +44,22 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($annotations as $annotationByName) { - foreach ($annotationByName as $annotation) { - if ($annotation instanceof GenericAnnotation) { - continue; - } + foreach ($annotations as $annotation) { + /** @var list $identifierTypeNodes */ + $identifierTypeNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), IdentifierTypeNode::class); + + foreach ($identifierTypeNodes as $typeHintNode) { + $typeHint = $typeHintNode->name; - if ($annotation->isInvalid()) { + if (strtolower($typeHint) !== 'mixed') { continue; } - foreach (AnnotationHelper::getAnnotationTypes($annotation) as $annotationType) { - foreach (AnnotationTypeHelper::getIdentifierTypeNodes($annotationType) as $typeHintNode) { - $typeHint = AnnotationTypeHelper::getTypeHintFromNode($typeHintNode); - - if (strtolower($typeHint) !== 'mixed') { - continue; - } - - $phpcsFile->addError( - 'Usage of "mixed" type hint is disallowed.', - $annotation->getStartPointer(), - self::CODE_DISALLOWED_MIXED_TYPE_HINT - ); - } - } + $phpcsFile->addError( + 'Usage of "mixed" type hint is disallowed.', + $annotation->getStartPointer(), + self::CODE_DISALLOWED_MIXED_TYPE_HINT + ); } } } diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/LongTypeHintsSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/LongTypeHintsSniff.php index b8cb362f8..7aefcd9ff 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/LongTypeHintsSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/LongTypeHintsSniff.php @@ -5,9 +5,8 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; -use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; +use SlevomatCodingStandard\Helpers\DocCommentHelper; use SlevomatCodingStandard\Helpers\FixerHelper; use function sprintf; use function strtolower; @@ -36,59 +35,56 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void { $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($annotations as $annotationName => $annotationByName) { - foreach ($annotationByName as $annotation) { - if ($annotation instanceof GenericAnnotation) { - continue; - } + foreach ($annotations as $annotation) { + /** @var list $identifierTypeNodes */ + $identifierTypeNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), IdentifierTypeNode::class); - if ($annotation->isInvalid()) { - continue; - } + foreach ($identifierTypeNodes as $typeHintNode) { + $typeHint = $typeHintNode->name; - foreach (AnnotationHelper::getAnnotationTypes($annotation) as $annotationType) { - foreach (AnnotationTypeHelper::getIdentifierTypeNodes($annotationType) as $typeHintNode) { - $typeHint = AnnotationTypeHelper::getTypeHintFromNode($typeHintNode); + $lowercasedTypeHint = strtolower($typeHint); - $lowercasedTypeHint = strtolower($typeHint); + $shortTypeHint = null; + if ($lowercasedTypeHint === 'integer') { + $shortTypeHint = 'int'; + } elseif ($lowercasedTypeHint === 'boolean') { + $shortTypeHint = 'bool'; + } - $shortTypeHint = null; - if ($lowercasedTypeHint === 'integer') { - $shortTypeHint = 'int'; - } elseif ($lowercasedTypeHint === 'boolean') { - $shortTypeHint = 'bool'; - } + if ($shortTypeHint === null) { + continue; + } - if ($shortTypeHint === null) { - continue; - } + $fix = $phpcsFile->addFixableError(sprintf( + 'Expected "%s" but found "%s" in %s annotation.', + $shortTypeHint, + $typeHint, + $annotation->getName() + ), $annotation->getStartPointer(), self::CODE_USED_LONG_TYPE_HINT); - $fix = $phpcsFile->addFixableError(sprintf( - 'Expected "%s" but found "%s" in %s annotation.', - $shortTypeHint, - $typeHint, - $annotationName - ), $annotation->getStartPointer(), self::CODE_USED_LONG_TYPE_HINT); + if (!$fix) { + continue; + } - if (!$fix) { - continue; - } + $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); - $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( - $phpcsFile, - $annotation, - $typeHintNode, - new IdentifierTypeNode($shortTypeHint) - ); + $fixedDocComment = AnnotationHelper::fixAnnotation( + $parsedDocComment, + $annotation, + $typeHintNode, + new IdentifierTypeNode($shortTypeHint) + ); - $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->replaceToken($annotation->getStartPointer(), $fixedAnnotationContent); - FixerHelper::removeBetweenIncluding($phpcsFile, $annotation->getStartPointer() + 1, $annotation->getEndPointer()); + $phpcsFile->fixer->replaceToken($parsedDocComment->getOpenPointer(), $fixedDocComment); + FixerHelper::removeBetweenIncluding( + $phpcsFile, + $parsedDocComment->getOpenPointer() + 1, + $parsedDocComment->getClosePointer() + ); - $phpcsFile->fixer->endChangeset(); - } - } + $phpcsFile->fixer->endChangeset(); } } } diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/NullTypeHintOnLastPositionSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/NullTypeHintOnLastPositionSniff.php index 792f1305a..9fcb69080 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/NullTypeHintOnLastPositionSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/NullTypeHintOnLastPositionSniff.php @@ -6,10 +6,11 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\GenericAnnotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; +use SlevomatCodingStandard\Helpers\DocCommentHelper; use SlevomatCodingStandard\Helpers\FixerHelper; +use SlevomatCodingStandard\Helpers\PhpDocParserHelper; use function count; use function sprintf; use function strtolower; @@ -38,77 +39,73 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void { $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($annotations as $annotationByName) { - foreach ($annotationByName as $annotation) { - if ($annotation instanceof GenericAnnotation) { + foreach ($annotations as $annotation) { + if ($annotation->isInvalid()) { + continue; + } + + /** @var list $unionTypeNodes */ + $unionTypeNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), UnionTypeNode::class); + + foreach ($unionTypeNodes as $unionTypeNode) { + $nullTypeNode = null; + $nullPosition = 0; + $position = 0; + foreach ($unionTypeNode->types as $typeNode) { + if ($typeNode instanceof IdentifierTypeNode && strtolower($typeNode->name) === 'null') { + $nullTypeNode = $typeNode; + $nullPosition = $position; + break; + } + + $position++; + } + + if ($nullTypeNode === null) { continue; } - if ($annotation->isInvalid()) { + if ($nullPosition === count($unionTypeNode->types) - 1) { continue; } - foreach (AnnotationHelper::getAnnotationTypes($annotation) as $annotationType) { - foreach (AnnotationTypeHelper::getUnionTypeNodes($annotationType) as $unionTypeNode) { - $nullTypeNode = null; - $nullPosition = 0; - $position = 0; - foreach ($unionTypeNode->types as $typeNode) { - if ($typeNode instanceof IdentifierTypeNode && strtolower($typeNode->name) === 'null') { - $nullTypeNode = $typeNode; - $nullPosition = $position; - break; - } - - $position++; - } - - if ($nullTypeNode === null) { - continue; - } - - if ($nullPosition === count($unionTypeNode->types) - 1) { - continue; - } - - $fix = $phpcsFile->addFixableError( - sprintf('Null type hint should be on last position in "%s".', AnnotationTypeHelper::print($unionTypeNode)), - $annotation->getStartPointer(), - self::CODE_NULL_TYPE_HINT_NOT_ON_LAST_POSITION - ); - - if (!$fix) { - continue; - } - - $phpcsFile->fixer->beginChangeset(); - - FixerHelper::removeBetweenIncluding($phpcsFile, $annotation->getStartPointer(), $annotation->getEndPointer()); - - $fixedTypeNodes = []; - foreach ($unionTypeNode->types as $typeNode) { - if ($typeNode === $nullTypeNode) { - continue; - } - - $fixedTypeNodes[] = $typeNode; - } - $fixedTypeNodes[] = $nullTypeNode; - $fixedUnionTypeNode = new UnionTypeNode($fixedTypeNodes); - - $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( - $phpcsFile, - $annotation, - $unionTypeNode, - $fixedUnionTypeNode - ); - - $phpcsFile->fixer->replaceToken($annotation->getStartPointer(), $fixedAnnotationContent); - FixerHelper::removeBetweenIncluding($phpcsFile, $annotation->getStartPointer() + 1, $annotation->getEndPointer()); - - $phpcsFile->fixer->endChangeset(); + $fix = $phpcsFile->addFixableError( + sprintf('Null type hint should be on last position in "%s".', AnnotationTypeHelper::print($unionTypeNode)), + $annotation->getStartPointer(), + self::CODE_NULL_TYPE_HINT_NOT_ON_LAST_POSITION + ); + + if (!$fix) { + continue; + } + + $fixedTypeNodes = []; + foreach ($unionTypeNode->types as $typeNode) { + if ($typeNode === $nullTypeNode) { + continue; } + + $fixedTypeNodes[] = $typeNode; } + $fixedTypeNodes[] = $nullTypeNode; + + $fixedUnionTypeNode = PhpDocParserHelper::cloneNode($unionTypeNode); + $fixedUnionTypeNode->types = $fixedTypeNodes; + + $phpcsFile->fixer->beginChangeset(); + + $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); + + $fixedDocComment = AnnotationHelper::fixAnnotation($parsedDocComment, $annotation, $unionTypeNode, $fixedUnionTypeNode); + + $phpcsFile->fixer->replaceToken($parsedDocComment->getOpenPointer(), $fixedDocComment); + FixerHelper::removeBetweenIncluding( + $phpcsFile, + $parsedDocComment->getOpenPointer() + 1, + $parsedDocComment->getClosePointer() + ); + + $phpcsFile->fixer->endChangeset(); } } } diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php index dc7c272d5..4f7c3bc5b 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php @@ -4,6 +4,9 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; @@ -15,8 +18,7 @@ use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\ParameterAnnotation; -use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; +use SlevomatCodingStandard\Helpers\Annotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; use SlevomatCodingStandard\Helpers\DocCommentHelper; @@ -135,8 +137,8 @@ public function process(File $phpcsFile, $functionPointer): void /** * @param array $parametersTypeHints - * @param array $parametersAnnotations - * @param array $prefixedParametersAnnotations + * @param array|Annotation|Annotation> $parametersAnnotations + * @param array|Annotation> $prefixedParametersAnnotations */ private function checkTypeHints( File $phpcsFile, @@ -161,7 +163,10 @@ private function checkTypeHints( ); foreach ($parametersWithoutTypeHint as $parameterName) { - if (!array_key_exists($parameterName, $parametersAnnotations) || $parametersAnnotations[$parameterName]->getType() === null) { + if ( + !array_key_exists($parameterName, $parametersAnnotations) + || $parametersAnnotations[$parameterName]->getValue() instanceof TypelessParamTagValueNode + ) { if (array_key_exists($parameterName, $prefixedParametersAnnotations)) { continue; } @@ -185,7 +190,7 @@ private function checkTypeHints( continue; } - $parameterTypeNode = $parametersAnnotations[$parameterName]->getType(); + $parameterTypeNode = $parametersAnnotations[$parameterName]->getValue()->type; if ( $parameterTypeNode instanceof IdentifierTypeNode @@ -415,8 +420,8 @@ private function checkTypeHints( /** * @param array $parametersTypeHints - * @param array $parametersAnnotations - * @param array $prefixedParametersAnnotations + * @param array|Annotation|Annotation> $parametersAnnotations + * @param array|Annotation> $prefixedParametersAnnotations */ private function checkTraversableTypeHintSpecification( File $phpcsFile, @@ -446,9 +451,9 @@ private function checkTraversableTypeHintSpecification( $hasTraversableTypeHint = true; } elseif ( array_key_exists($parameterName, $parametersAnnotations) - && $parametersAnnotations[$parameterName]->getType() !== null + && !$parametersAnnotations[$parameterName]->getValue() instanceof TypelessParamTagValueNode && AnnotationTypeHelper::containsTraversableType( - $parametersAnnotations[$parameterName]->getType(), + $parametersAnnotations[$parameterName]->getValue()->type, $phpcsFile, $functionPointer, $this->getTraversableTypeHints() @@ -480,11 +485,12 @@ private function checkTraversableTypeHintSpecification( continue; } - $parameterTypeNode = $parametersAnnotations[$parameterName]->getType(); - if ($parameterTypeNode === null) { + if ($parametersAnnotations[$parameterName]->getValue() instanceof TypelessParamTagValueNode) { continue; } + $parameterTypeNode = $parametersAnnotations[$parameterName]->getValue()->type; + if ( ( !$hasTraversableTypeHint @@ -530,7 +536,7 @@ private function checkTraversableTypeHintSpecification( /** * @param array $parametersTypeHints - * @param array $parametersAnnotations + * @param array $parametersAnnotations */ private function checkUselessAnnotations( File $phpcsFile, @@ -549,7 +555,8 @@ private function checkUselessAnnotations( } $parameterAnnotation = $parametersAnnotations[$parameterName]; - if ($parameterAnnotation->getType() === null) { + + if ($parameterAnnotation->getValue() instanceof TypelessParamTagValueNode) { continue; } @@ -586,7 +593,7 @@ private function checkUselessAnnotations( continue; } - $docCommentOpenPointer = $parameterAnnotation instanceof VariableAnnotation + $docCommentOpenPointer = $parameterAnnotation->getValue() instanceof VarTagValueNode ? TokenHelper::findPrevious($phpcsFile, T_DOC_COMMENT_OPEN_TAG, $parameterAnnotation->getStartPointer() - 1) : DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $functionPointer); @@ -597,12 +604,12 @@ private function checkUselessAnnotations( $docCommentOpenPointer ); - $changeStart = $starPointer ?? $docCommentOpenPointer + 1; + $changeStart = $starPointer ?? $parameterAnnotation->getStartPointer(); /** @var int $changeEnd */ $changeEnd = TokenHelper::findNext( $phpcsFile, [T_DOC_COMMENT_CLOSE_TAG, T_DOC_COMMENT_STAR], - $parameterAnnotation->getEndPointer() + 1 + $parameterAnnotation->getEndPointer() ) - 1; $phpcsFile->fixer->beginChangeset(); diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/PropertyTypeHintSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/PropertyTypeHintSniff.php index 74aca9e53..82a100fdd 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/PropertyTypeHintSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/PropertyTypeHintSniff.php @@ -4,6 +4,8 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; @@ -14,7 +16,7 @@ use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; +use SlevomatCodingStandard\Helpers\Annotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; use SlevomatCodingStandard\Helpers\DocCommentHelper; @@ -31,6 +33,7 @@ use function array_unique; use function array_values; use function count; +use function current; use function implode; use function in_array; use function sprintf; @@ -147,16 +150,22 @@ public function process(File $phpcsFile, $pointer): void return; } - if (DocCommentHelper::hasInheritdocAnnotation($phpcsFile, $propertyPointer)) { - return; - } + $docCommentOpenPointer = DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $propertyPointer); + if ($docCommentOpenPointer !== null) { + if (DocCommentHelper::hasInheritdocAnnotation($phpcsFile, $docCommentOpenPointer)) { + return; + } - /** @var list $varAnnotations */ - $varAnnotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $propertyPointer, '@var'); - $prefixedPropertyAnnotations = $this->getValidPrefixedAnnotations($phpcsFile, $propertyPointer); + $varAnnotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer, '@var'); + $prefixedPropertyAnnotations = $this->getValidPrefixedAnnotations($phpcsFile, $docCommentOpenPointer); + + $propertyAnnotation = count($varAnnotations) > 0 ? current($varAnnotations) : null; + } else { + $propertyAnnotation = null; + $prefixedPropertyAnnotations = []; + } $propertyTypeHint = PropertyHelper::findTypeHint($phpcsFile, $propertyPointer); - $propertyAnnotation = count($varAnnotations) > 0 ? $varAnnotations[0] : null; $this->checkTypeHint($phpcsFile, $propertyPointer, $propertyTypeHint, $propertyAnnotation, $prefixedPropertyAnnotations); $this->checkTraversableTypeHintSpecification( @@ -170,13 +179,14 @@ public function process(File $phpcsFile, $pointer): void } /** - * @param list $prefixedPropertyAnnotations + * @param Annotation|null $propertyAnnotation + * @param list> $prefixedPropertyAnnotations */ private function checkTypeHint( File $phpcsFile, int $propertyPointer, ?TypeHint $propertyTypeHint, - ?VariableAnnotation $propertyAnnotation, + ?Annotation $propertyAnnotation, array $prefixedPropertyAnnotations ): void { @@ -218,7 +228,7 @@ private function checkTypeHint( return; } - $typeNode = $propertyAnnotation->getType(); + $typeNode = $propertyAnnotation->getValue()->type; $originalTypeNode = $typeNode; if ($typeNode instanceof NullableTypeNode) { $typeNode = $typeNode->type; @@ -416,13 +426,14 @@ private function checkTypeHint( } /** - * @param list $prefixedPropertyAnnotations + * @param Annotation|null $propertyAnnotation + * @param list> $prefixedPropertyAnnotations */ private function checkTraversableTypeHintSpecification( File $phpcsFile, int $propertyPointer, ?TypeHint $propertyTypeHint, - ?VariableAnnotation $propertyAnnotation, + ?Annotation $propertyAnnotation, array $prefixedPropertyAnnotations ): void { @@ -454,7 +465,7 @@ private function checkTraversableTypeHintSpecification( return; } - $typeNode = $propertyAnnotation->getType(); + $typeNode = $propertyAnnotation->getValue()->type; if ( !$hasTraversableTypeHint @@ -492,7 +503,7 @@ private function checkUselessAnnotation( File $phpcsFile, int $propertyPointer, ?TypeHint $propertyTypeHint, - ?VariableAnnotation $propertyAnnotation + ?Annotation $propertyAnnotation ): void { if ($propertyAnnotation === null) { @@ -534,7 +545,6 @@ private function checkUselessAnnotation( } if ($this->isDocCommentUseless($phpcsFile, $propertyPointer)) { - /** @var int $docCommentOpenPointer */ $docCommentOpenPointer = DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $propertyPointer); $docCommentClosePointer = $phpcsFile->getTokens()[$docCommentOpenPointer]['comment_closer']; @@ -551,6 +561,7 @@ private function checkUselessAnnotation( /** @var int $changeStart */ $changeStart = TokenHelper::findPrevious($phpcsFile, T_DOC_COMMENT_STAR, $propertyAnnotation->getStartPointer() - 1); + /** @var int $changeEnd */ $changeEnd = TokenHelper::findNext( $phpcsFile, @@ -569,10 +580,13 @@ private function isDocCommentUseless(File $phpcsFile, int $propertyPointer): boo return false; } - $annotations = AnnotationHelper::getAnnotations($phpcsFile, $propertyPointer); - unset($annotations['@var']); + foreach (AnnotationHelper::getAnnotations($phpcsFile, $propertyPointer) as $annotation) { + if ($annotation->getName() !== '@var') { + return false; + } + } - return count($annotations) === 0; + return true; } private function reportUselessSuppress(File $phpcsFile, int $pointer, bool $isSuppressed, string $suppressName): void @@ -612,16 +626,19 @@ private function getTraversableTypeHints(): array return $this->normalizedTraversableTypeHints; } - private function hasAnnotation(?VariableAnnotation $propertyAnnotation): bool + private function hasAnnotation(?Annotation $propertyAnnotation): bool { - return $propertyAnnotation !== null && $propertyAnnotation->getContent() !== null && !$propertyAnnotation->isInvalid(); + return $propertyAnnotation !== null && $propertyAnnotation->getValue() instanceof VarTagValueNode; } + /** + * @param Annotation|null $propertyAnnotation + */ private function hasTraversableTypeHint( File $phpcsFile, int $propertyPointer, ?TypeHint $propertyTypeHint, - ?VariableAnnotation $propertyAnnotation + ?Annotation $propertyAnnotation ): bool { if ( @@ -641,7 +658,7 @@ private function hasTraversableTypeHint( return $this->hasAnnotation($propertyAnnotation) && AnnotationTypeHelper::containsTraversableType( - $propertyAnnotation->getType(), + $propertyAnnotation->getValue()->type, $phpcsFile, $propertyPointer, $this->getTraversableTypeHints() @@ -649,24 +666,27 @@ private function hasTraversableTypeHint( } /** - * @return list + * @return list> */ - private function getValidPrefixedAnnotations(File $phpcsFile, int $propertyPointer): array + private function getValidPrefixedAnnotations(File $phpcsFile, int $docCommentOpenPointer): array { - $returnAnnotations = []; + $varAnnotations = []; + + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); foreach (AnnotationHelper::STATIC_ANALYSIS_PREFIXES as $prefix) { - /** @var list $annotations */ - $annotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $propertyPointer, sprintf('@%s-var', $prefix)); foreach ($annotations as $annotation) { - if (!$annotation->isInvalid()) { - $returnAnnotations[] = $annotation; - break; + if ($annotation->isInvalid()) { + continue; + } + + if ($annotation->getName() === sprintf('@%s-var', $prefix)) { + $varAnnotations[] = $annotation; } } } - return $returnAnnotations; + return $varAnnotations; } } diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/ReturnTypeHintSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/ReturnTypeHintSniff.php index e772b506a..594dc77f7 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/ReturnTypeHintSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/ReturnTypeHintSniff.php @@ -4,12 +4,10 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; -use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode; -use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode; -use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; @@ -18,7 +16,7 @@ use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use SlevomatCodingStandard\Helpers\Annotation\ReturnAnnotation; +use SlevomatCodingStandard\Helpers\Annotation; use SlevomatCodingStandard\Helpers\AnnotationHelper; use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; use SlevomatCodingStandard\Helpers\DocCommentHelper; @@ -148,13 +146,13 @@ public function process(File $phpcsFile, $pointer): void } /** - * @param list $prefixedReturnAnnotations + * @param list $prefixedReturnAnnotations */ private function checkFunctionTypeHint( File $phpcsFile, int $functionPointer, ?TypeHint $returnTypeHint, - ?ReturnAnnotation $returnAnnotation, + ?Annotation $returnAnnotation, array $prefixedReturnAnnotations ): void { @@ -468,13 +466,13 @@ private function checkFunctionTypeHint( } /** - * @param list $prefixedReturnAnnotations + * @param list $prefixedReturnAnnotations */ private function checkFunctionTraversableTypeHintSpecification( File $phpcsFile, int $functionPointer, ?TypeHint $returnTypeHint, - ?ReturnAnnotation $returnAnnotation, + ?Annotation $returnAnnotation, array $prefixedReturnAnnotations ): void { @@ -536,7 +534,7 @@ private function checkFunctionTraversableTypeHintSpecification( return; } - /** @var ReturnAnnotation $returnAnnotation */ + /** @var Annotation $returnAnnotation */ $returnAnnotation = $returnAnnotation; $phpcsFile->addError( @@ -554,7 +552,7 @@ private function checkFunctionUselessAnnotation( File $phpcsFile, int $functionPointer, ?TypeHint $returnTypeHint, - ?ReturnAnnotation $returnAnnotation + ?Annotation $returnAnnotation ): void { if ($returnAnnotation === null) { @@ -604,7 +602,7 @@ private function checkFunctionUselessAnnotation( $docCommentOpenPointer ); - $changeStart = $starPointer ?? $docCommentOpenPointer + 1; + $changeStart = $starPointer ?? $returnAnnotation->getStartPointer(); /** @var int $changeEnd */ $changeEnd = TokenHelper::findNext( @@ -647,12 +645,12 @@ private function checkClosureTypeHint(File $phpcsFile, int $closurePointer): voi } /** - * @return GenericTypeNode|CallableTypeNode|IntersectionTypeNode|UnionTypeNode|ArrayTypeNode|ArrayShapeNode|ObjectShapeNode|IdentifierTypeNode|ThisTypeNode|NullableTypeNode|ConstTypeNode|ConditionalTypeNode|ConditionalTypeForParameterNode|null + * @param Annotation|null $returnAnnotation */ - private function getReturnTypeNode(?ReturnAnnotation $returnAnnotation): ?TypeNode + private function getReturnTypeNode(?Annotation $returnAnnotation): ?TypeNode { if ($this->hasReturnAnnotation($returnAnnotation)) { - return $returnAnnotation->getType(); + return $returnAnnotation->getValue()->type; } return null; @@ -662,7 +660,7 @@ private function hasTraversableTypeHint( File $phpcsFile, int $functionPointer, ?TypeHint $returnTypeHint, - ?ReturnAnnotation $returnAnnotation + ?Annotation $returnAnnotation ): bool { if ( @@ -689,9 +687,9 @@ private function hasTraversableTypeHint( ); } - private function hasReturnAnnotation(?ReturnAnnotation $returnAnnotation): bool + private function hasReturnAnnotation(?Annotation $returnAnnotation): bool { - return $returnAnnotation !== null && $returnAnnotation->getContent() !== null && !$returnAnnotation->isInvalid(); + return $returnAnnotation !== null && !$returnAnnotation->isInvalid(); } private function reportUselessSuppress(File $phpcsFile, int $pointer, bool $isSuppressed, string $suppressName): void diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/UselessConstantTypeHintSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/UselessConstantTypeHintSniff.php index 266f6b626..b061adc60 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/UselessConstantTypeHintSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/UselessConstantTypeHintSniff.php @@ -8,7 +8,6 @@ use SlevomatCodingStandard\Helpers\DocCommentHelper; use SlevomatCodingStandard\Helpers\FixerHelper; use SlevomatCodingStandard\Helpers\TokenHelper; -use function array_key_exists; use function count; use const T_CONST; use const T_DOC_COMMENT_WHITESPACE; @@ -42,10 +41,9 @@ public function process(File $phpcsFile, $constantPointer): void return; } - $annotations = AnnotationHelper::getAnnotations($phpcsFile, $constantPointer); + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $constantPointer, '@var'); - $uselessAnnotation = array_key_exists('@var', $annotations); - if (!$uselessAnnotation) { + if ($annotations === []) { return; } @@ -57,7 +55,7 @@ public function process(File $phpcsFile, $constantPointer): void $fixerStart = TokenHelper::findLastTokenOnPreviousLine($phpcsFile, $docCommentOpenPointer); $fixerEnd = $tokens[$docCommentOpenPointer]['comment_closer']; } else { - $annotation = $annotations['@var'][0]; + $annotation = $annotations[0]; $fix = $phpcsFile->addFixableError( 'Useless @var annotation.', diff --git a/build/PHPStan/phpstan.neon b/build/PHPStan/phpstan.neon index 0a29cf62a..ef8b16809 100644 --- a/build/PHPStan/phpstan.neon +++ b/build/PHPStan/phpstan.neon @@ -23,14 +23,6 @@ parameters: message: '#Offset ''nested_parenthesis'' does not exist on array#' count: 1 path: %currentWorkingDirectory%/SlevomatCodingStandard/Sniffs/ControlStructures/AssignmentInConditionSniff.php - - - message: '#Parameter \#5 \$contentNode of class SlevomatCodingStandard\\Helpers\\Annotation\\\w+Annotation constructor expects PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\\\w+(?:\\|PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\\\w+)*\\|null, PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode\|null given.#' - count: 16 - path: %currentWorkingDirectory%/SlevomatCodingStandard/Helpers/AnnotationHelper.php - - - message: "#^Access to an undefined property PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\\\w+(?:\\|PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\\\w+)*\\:\\:\\$type\\.$#" - count: 1 - path: %currentWorkingDirectory%/SlevomatCodingStandard/Helpers/AnnotationHelper.php services: - diff --git a/build/phpcs.xml b/build/phpcs.xml index 8c15e2e04..b999b8aa1 100644 --- a/build/phpcs.xml +++ b/build/phpcs.xml @@ -262,6 +262,7 @@ @codeCoverageIgnore, @phpcsSuppress, @dataProvider, + @template, @link, @var, @param, diff --git a/composer.json b/composer.json index a09a92a73..c669da82e 100644 --- a/composer.json +++ b/composer.json @@ -17,8 +17,9 @@ }, "require": { "php": "^7.2 || ^8.0", + "ext-mbstring": "*", "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", - "phpstan/phpdoc-parser": ">=1.21.0 <1.22.0", + "phpstan/phpdoc-parser": ">=1.22.0 <1.23.0", "squizlabs/php_codesniffer": "^3.7.1" }, "require-dev": { diff --git a/tests/Helpers/Annotation/AssertAnnotationTest.php b/tests/Helpers/Annotation/AssertAnnotationTest.php deleted file mode 100644 index 73ae26dda..000000000 --- a/tests/Helpers/Annotation/AssertAnnotationTest.php +++ /dev/null @@ -1,71 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('@phpstan-assert string $parameter Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new AssertAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-assert annotation.'); - $annotation = new AssertAnnotation('@phpstan-assert', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-assert annotation.'); - $annotation = new AssertAnnotation('@phpstan-assert', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-assert annotation.'); - $annotation = new AssertAnnotation('@phpstan-assert', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/ExtendsAnnotationTest.php b/tests/Helpers/Annotation/ExtendsAnnotationTest.php deleted file mode 100644 index 228cf55df..000000000 --- a/tests/Helpers/Annotation/ExtendsAnnotationTest.php +++ /dev/null @@ -1,70 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('@extends Whatever Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new ExtendsAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @extends annotation.'); - $annotation = new ExtendsAnnotation('@extends', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @extends annotation.'); - $annotation = new ExtendsAnnotation('@extends', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @extends annotation.'); - $annotation = new ExtendsAnnotation('@extends', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/GenericAnnotationTest.php b/tests/Helpers/Annotation/GenericAnnotationTest.php deleted file mode 100644 index 0aa13fbdd..000000000 --- a/tests/Helpers/Annotation/GenericAnnotationTest.php +++ /dev/null @@ -1,24 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Anything', $annotation->getParameters()); - self::assertSame('Whatever', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertSame('@Something(Anything) Whatever', $annotation->print()); - } - -} diff --git a/tests/Helpers/Annotation/ImplementsAnnotationTest.php b/tests/Helpers/Annotation/ImplementsAnnotationTest.php deleted file mode 100644 index 80b8ef0b5..000000000 --- a/tests/Helpers/Annotation/ImplementsAnnotationTest.php +++ /dev/null @@ -1,70 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('@implements Whatever Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new ImplementsAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @implements annotation.'); - $annotation = new ImplementsAnnotation('@implements', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @implements annotation.'); - $annotation = new ImplementsAnnotation('@implements', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @implements annotation.'); - $annotation = new ImplementsAnnotation('@implements', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/MethodAnnotationTest.php b/tests/Helpers/Annotation/MethodAnnotationTest.php deleted file mode 100644 index 910e83b78..000000000 --- a/tests/Helpers/Annotation/MethodAnnotationTest.php +++ /dev/null @@ -1,108 +0,0 @@ -(int $p) Description', - new MethodTagValueNode( - false, - new IdentifierTypeNode('string'), - 'method', - [new MethodTagValueParameterNode(new IdentifierTypeNode('int'), false, false, '$p', null)], - 'Description', - [ - new TemplateTagValueNode('T1', null, ''), - new TemplateTagValueNode('T2', new IdentifierTypeNode('Bar'), ''), - new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''), - ] - ) - ); - - self::assertSame('@method', $annotation->getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('string method(int $p) Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('method', $annotation->getMethodName()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getMethodReturnType()); - self::assertCount(3, $annotation->getMethodTemplateTypes()); - self::assertContainsOnlyInstancesOf(TemplateTagValueNode::class, $annotation->getMethodTemplateTypes()); - self::assertCount(1, $annotation->getMethodParameters()); - self::assertSame('@method string method(int $p) Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @var.'); - new MethodAnnotation('@var', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @method annotation.'); - $annotation = new MethodAnnotation('@method', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @method annotation.'); - $annotation = new MethodAnnotation('@method', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetMethodNameWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @method annotation.'); - $annotation = new MethodAnnotation('@method', 1, 1, null, null); - $annotation->getMethodName(); - } - - public function testGetMethodReturnTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @method annotation.'); - $annotation = new MethodAnnotation('@method', 1, 1, null, null); - $annotation->getMethodReturnType(); - } - - public function testGetMethodTemplateTypesWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @method annotation.'); - $annotation = new MethodAnnotation('@method', 1, 1, null, null); - $annotation->getMethodTemplateTypes(); - } - - public function testGetMethodParametersWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @method annotation.'); - $annotation = new MethodAnnotation('@method', 1, 1, null, null); - $annotation->getMethodParameters(); - } - -} diff --git a/tests/Helpers/Annotation/MixinAnnotationTest.php b/tests/Helpers/Annotation/MixinAnnotationTest.php deleted file mode 100644 index 1d6939c56..000000000 --- a/tests/Helpers/Annotation/MixinAnnotationTest.php +++ /dev/null @@ -1,67 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@mixin Exception Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new MixinAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @mixin annotation.'); - $annotation = new MixinAnnotation('@mixin', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @mixin annotation.'); - $annotation = new MixinAnnotation('@mixin', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @mixin annotation.'); - $annotation = new MixinAnnotation('@mixin', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/ParameterAnnotationTest.php b/tests/Helpers/Annotation/ParameterAnnotationTest.php deleted file mode 100644 index 1d851c567..000000000 --- a/tests/Helpers/Annotation/ParameterAnnotationTest.php +++ /dev/null @@ -1,76 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('$parameter', $annotation->getParameterName()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@param string $parameter Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @var.'); - new ParameterAnnotation('@var', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @param annotation.'); - $annotation = new ParameterAnnotation('@param', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @param annotation.'); - $annotation = new ParameterAnnotation('@param', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetParameterNameWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @param annotation.'); - $annotation = new ParameterAnnotation('@param', 1, 1, null, null); - $annotation->getParameterName(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @param annotation.'); - $annotation = new ParameterAnnotation('@param', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/ParameterOutAnnotationTest.php b/tests/Helpers/Annotation/ParameterOutAnnotationTest.php deleted file mode 100644 index 11c3f50a5..000000000 --- a/tests/Helpers/Annotation/ParameterOutAnnotationTest.php +++ /dev/null @@ -1,76 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('$parameter', $annotation->getParameterName()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@param-out string $parameter Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @var.'); - new ParameterOutAnnotation('@var', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @param-out annotation.'); - $annotation = new ParameterOutAnnotation('@param-out', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @param-out annotation.'); - $annotation = new ParameterOutAnnotation('@param-out', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetParameterNameWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @param-out annotation.'); - $annotation = new ParameterOutAnnotation('@param-out', 1, 1, null, null); - $annotation->getParameterName(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @param-out annotation.'); - $annotation = new ParameterOutAnnotation('@param-out', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/PropertyAnnotationTest.php b/tests/Helpers/Annotation/PropertyAnnotationTest.php deleted file mode 100644 index 86904ad07..000000000 --- a/tests/Helpers/Annotation/PropertyAnnotationTest.php +++ /dev/null @@ -1,76 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('$property', $annotation->getPropertyName()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@property string $property Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @var.'); - new PropertyAnnotation('@var', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @property annotation.'); - $annotation = new PropertyAnnotation('@property', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @property annotation.'); - $annotation = new PropertyAnnotation('@property', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetPropertyNameWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @property-read annotation.'); - $annotation = new PropertyAnnotation('@property-read', 1, 1, null, null); - $annotation->getPropertyName(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @property-write annotation.'); - $annotation = new PropertyAnnotation('@property-write', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/ReturnAnnotationTest.php b/tests/Helpers/Annotation/ReturnAnnotationTest.php deleted file mode 100644 index d21fccb08..000000000 --- a/tests/Helpers/Annotation/ReturnAnnotationTest.php +++ /dev/null @@ -1,67 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@return string Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new ReturnAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @return annotation.'); - $annotation = new ReturnAnnotation('@return', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @return annotation.'); - $annotation = new ReturnAnnotation('@return', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @return annotation.'); - $annotation = new ReturnAnnotation('@return', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/SelfOutAnnotationTest.php b/tests/Helpers/Annotation/SelfOutAnnotationTest.php deleted file mode 100644 index 81339a208..000000000 --- a/tests/Helpers/Annotation/SelfOutAnnotationTest.php +++ /dev/null @@ -1,67 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@phpstan-self-out string Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @var.'); - new SelfOutAnnotation('@var', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-self-out annotation.'); - $annotation = new SelfOutAnnotation('@phpstan-self-out', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-self-out annotation.'); - $annotation = new SelfOutAnnotation('@phpstan-self-out', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-self-out annotation.'); - $annotation = new SelfOutAnnotation('@phpstan-self-out', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/TemplateAnnotationTest.php b/tests/Helpers/Annotation/TemplateAnnotationTest.php deleted file mode 100644 index 5c9edccda..000000000 --- a/tests/Helpers/Annotation/TemplateAnnotationTest.php +++ /dev/null @@ -1,77 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('Whatever', $annotation->getTemplateName()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getBound()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getDefault()); - self::assertSame('@template Whatever of Anything = null Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new TemplateAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @template annotation.'); - $annotation = new TemplateAnnotation('@template', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @template annotation.'); - $annotation = new TemplateAnnotation('@template', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetBoundWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @template annotation.'); - $annotation = new TemplateAnnotation('@template', 1, 1, null, null); - $annotation->getBound(); - } - - public function testGetDefaultWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @template annotation.'); - $annotation = new TemplateAnnotation('@template', 1, 1, null, null); - $annotation->getDefault(); - } - -} diff --git a/tests/Helpers/Annotation/ThrowsAnnotationTest.php b/tests/Helpers/Annotation/ThrowsAnnotationTest.php deleted file mode 100644 index 11f496e65..000000000 --- a/tests/Helpers/Annotation/ThrowsAnnotationTest.php +++ /dev/null @@ -1,67 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@throws Exception Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new ThrowsAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @throws annotation.'); - $annotation = new ThrowsAnnotation('@throws', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @throws annotation.'); - $annotation = new ThrowsAnnotation('@throws', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @throws annotation.'); - $annotation = new ThrowsAnnotation('@throws', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/TypeAliasAnnotationTest.php b/tests/Helpers/Annotation/TypeAliasAnnotationTest.php deleted file mode 100644 index 65766ed6e..000000000 --- a/tests/Helpers/Annotation/TypeAliasAnnotationTest.php +++ /dev/null @@ -1,66 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertNull($annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertSame('Whatever', $annotation->getAlias()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@phpstan-type Whatever Anything', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new TypeAliasAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-type annotation.'); - $annotation = new TypeAliasAnnotation('@phpstan-type', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetAliasWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-type annotation.'); - $annotation = new TypeAliasAnnotation('@phpstan-type', 1, 1, null, null); - $annotation->getAlias(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-type annotation.'); - $annotation = new TypeAliasAnnotation('@phpstan-type', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/TypeImportAnnotationTest.php b/tests/Helpers/Annotation/TypeImportAnnotationTest.php deleted file mode 100644 index 27a99495b..000000000 --- a/tests/Helpers/Annotation/TypeImportAnnotationTest.php +++ /dev/null @@ -1,74 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertNull($annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertSame('Whatever', $annotation->getImportedAlias()); - self::assertSame('ImportedAs', $annotation->getImportedAs()); - self::assertSame('@phpstan-import-type Whatever from Anything as ImportedAs', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new TypeImportAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-import-type annotation.'); - $annotation = new TypeImportAnnotation('@phpstan-import-type', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetImportedAliasWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-import-type annotation.'); - $annotation = new TypeImportAnnotation('@phpstan-import-type', 1, 1, null, null); - $annotation->getImportedAlias(); - } - - public function testGetImportedFromWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-import-type annotation.'); - $annotation = new TypeImportAnnotation('@phpstan-import-type', 1, 1, null, null); - $annotation->getImportedFrom(); - } - - public function testGetImportedAsWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @phpstan-import-type annotation.'); - $annotation = new TypeImportAnnotation('@phpstan-import-type', 1, 1, null, null); - $annotation->getImportedAs(); - } - -} diff --git a/tests/Helpers/Annotation/UseAnnotationTest.php b/tests/Helpers/Annotation/UseAnnotationTest.php deleted file mode 100644 index 9cc1dfafb..000000000 --- a/tests/Helpers/Annotation/UseAnnotationTest.php +++ /dev/null @@ -1,70 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('@use Whatever Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new UseAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @use annotation.'); - $annotation = new UseAnnotation('@use', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @use annotation.'); - $annotation = new UseAnnotation('@use', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @use annotation.'); - $annotation = new UseAnnotation('@use', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/Annotation/VariableAnnotationTest.php b/tests/Helpers/Annotation/VariableAnnotationTest.php deleted file mode 100644 index c57292bdf..000000000 --- a/tests/Helpers/Annotation/VariableAnnotationTest.php +++ /dev/null @@ -1,76 +0,0 @@ -getName()); - self::assertSame(1, $annotation->getStartPointer()); - self::assertSame(10, $annotation->getEndPointer()); - self::assertSame('Description', $annotation->getContent()); - - self::assertFalse($annotation->isInvalid()); - self::assertTrue($annotation->hasDescription()); - self::assertSame('Description', $annotation->getDescription()); - self::assertSame('$variable', $annotation->getVariableName()); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('@var string $variable Description', $annotation->print()); - } - - public function testUnsupportedAnnotation(): void - { - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('Unsupported annotation @param.'); - new VariableAnnotation('@param', 1, 1, null, null); - } - - public function testGetContentNodeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @var annotation.'); - $annotation = new VariableAnnotation('@var', 1, 1, null, null); - $annotation->getContentNode(); - } - - public function testGetDescriptionWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @var annotation.'); - $annotation = new VariableAnnotation('@var', 1, 1, null, null); - $annotation->getDescription(); - } - - public function testGetVariableNameWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @var annotation.'); - $annotation = new VariableAnnotation('@var', 1, 1, null, null); - $annotation->getVariableName(); - } - - public function testGetTypeWhenInvalid(): void - { - self::expectException(LogicException::class); - self::expectExceptionMessage('Invalid @var annotation.'); - $annotation = new VariableAnnotation('@var', 1, 1, null, null); - $annotation->getType(); - } - -} diff --git a/tests/Helpers/AnnotationHelperTest.php b/tests/Helpers/AnnotationHelperTest.php deleted file mode 100644 index 40a0f4861..000000000 --- a/tests/Helpers/AnnotationHelperTest.php +++ /dev/null @@ -1,319 +0,0 @@ -getTestedCodeSnifferFile(), - $this->findClassPointerByName($this->getTestedCodeSnifferFile(), 'WithAnnotation'), - '@see' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(GenericAnnotation::class, $annotations[0]); - self::assertSame('@see', $annotations[0]->getName()); - self::assertSame(4, $this->getLineByPointer($annotations[0]->getStartPointer())); - self::assertSame('https://www.slevomat.cz', $annotations[0]->getContent()); - } - - public function testClassWithoutAnnotation(): void - { - self::assertCount( - 0, - AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findClassPointerByName($this->getTestedCodeSnifferFile(), 'WithoutAnnotation'), - '@see' - ) - ); - } - - public function testConstantWithAnnotation(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findConstantPointerByName($this->getTestedCodeSnifferFile(), 'WITH_ANNOTATION'), - '@var' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(VariableAnnotation::class, $annotations[0]); - self::assertSame('@var', $annotations[0]->getName()); - self::assertSame(10, $this->getLineByPointer($annotations[0]->getStartPointer())); - self::assertSame('bool', $annotations[0]->getContent()); - self::assertFalse($annotations[0]->hasDescription()); - self::assertNull($annotations[0]->getDescription()); - self::assertSame('bool', (string) $annotations[0]->getType()); - } - - public function testConstantWithoutAnnotation(): void - { - self::assertCount( - 0, - AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findConstantPointerByName($this->getTestedCodeSnifferFile(), 'WITHOUT_ANNOTATION'), - '@var' - ) - ); - } - - public function testPropertyWithAnnotation(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findPropertyPointerByName($this->getTestedCodeSnifferFile(), 'withAnnotation'), - '@var' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(VariableAnnotation::class, $annotations[0]); - self::assertSame('@var', $annotations[0]->getName()); - self::assertSame(17, $this->getLineByPointer($annotations[0]->getStartPointer())); - self::assertSame('null|int|float', $annotations[0]->getContent()); - self::assertFalse($annotations[0]->hasDescription()); - self::assertNull($annotations[0]->getDescription()); - self::assertSame('(null | int | float)', (string) $annotations[0]->getType()); - } - - public function testPropertyWithoutAnnotation(): void - { - self::assertCount( - 0, - AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findPropertyPointerByName($this->getTestedCodeSnifferFile(), 'withoutAnnotation'), - '@var' - ) - ); - } - - public function testReturnAnnotation(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findFunctionPointerByName($this->getTestedCodeSnifferFile(), 'withReturnAnnotation'), - '@return' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(ReturnAnnotation::class, $annotations[0]); - self::assertSame('@return', $annotations[0]->getName()); - self::assertSame(81, $this->getLineByPointer($annotations[0]->getStartPointer())); - self::assertSame('string|null', $annotations[0]->getContent()); - self::assertFalse($annotations[0]->hasDescription()); - self::assertNull($annotations[0]->getDescription()); - self::assertSame('(string | null)', (string) $annotations[0]->getType()); - } - - public function testFunctionWithAnnotation(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findFunctionPointerByName($this->getTestedCodeSnifferFile(), 'withAnnotation'), - '@param' - ); - - self::assertCount(2, $annotations); - - self::assertInstanceOf(ParameterAnnotation::class, $annotations[0]); - self::assertSame('@param', $annotations[0]->getName()); - self::assertSame(29, $this->getLineByPointer($annotations[0]->getStartPointer())); - self::assertSame('string $a', $annotations[0]->getContent()); - self::assertFalse($annotations[0]->hasDescription()); - self::assertNull($annotations[0]->getDescription()); - self::assertSame('$a', $annotations[0]->getParameterName()); - self::assertSame('string', (string) $annotations[0]->getType()); - - self::assertInstanceOf(ParameterAnnotation::class, $annotations[1]); - self::assertSame('@param', $annotations[1]->getName()); - self::assertSame(30, $this->getLineByPointer($annotations[1]->getStartPointer())); - self::assertSame('int|null $b', $annotations[1]->getContent()); - self::assertFalse($annotations[1]->hasDescription()); - self::assertNull($annotations[1]->getDescription()); - self::assertSame('$b', $annotations[1]->getParameterName()); - self::assertSame('(int | null)', (string) $annotations[1]->getType()); - } - - public function testFunctionWithParametrizedAnnotation(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findFunctionPointerByName($this->getTestedCodeSnifferFile(), 'withParametrizedAnnotation'), - '@Route' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(GenericAnnotation::class, $annotations[0]); - self::assertSame('"/", name="homepage"', $annotations[0]->getParameters()); - self::assertNull($annotations[0]->getContent()); - } - - public function testFunctionWithParametrizedAnnotationContainingParenthesis(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findFunctionPointerByName($this->getTestedCodeSnifferFile(), 'withParametrizedAnnotationContainingParenthesis'), - '@Security' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(GenericAnnotation::class, $annotations[0]); - self::assertSame('"is_granted(\'ROLE_ADMIN\')"', $annotations[0]->getParameters()); - self::assertNull($annotations[0]->getContent()); - } - - public function testFunctionWithMultiLineParametrizedAnnotation(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findFunctionPointerByName($this->getTestedCodeSnifferFile(), 'withMultiLineParametrizedAnnotation'), - '@Route' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(GenericAnnotation::class, $annotations[0]); - self::assertSame( - "\"/configs/{config}/domains/{domain}/locales/{locale}/messages\", name=\"jms_translation_update_message\",\ndefaults = {\"id\" = null}, options = {\"i18n\" = false}, methods={\"PUT\"}", - $annotations[0]->getParameters() - ); - self::assertNull($annotations[0]->getContent()); - } - - public function testFunctionWithParametrizedAnnotationWithoutParameters(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findFunctionPointerByName($this->getTestedCodeSnifferFile(), 'withParametrizedAnnotationWithoutParameters'), - '@Assert\Callback' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(GenericAnnotation::class, $annotations[0]); - self::assertNull($annotations[0]->getParameters()); - self::assertNull($annotations[0]->getContent()); - } - - public function testInlineDocCommentWithParametrizedAnnotation(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findPropertyPointerByName($this->getTestedCodeSnifferFile(), 'inlineDocComment'), - '@ORM\OneToMany' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(GenericAnnotation::class, $annotations[0]); - self::assertSame('targetEntity=Bar::class, mappedBy="boo"', $annotations[0]->getParameters()); - self::assertNull($annotations[0]->getContent()); - } - - public function testWordPressAnnotations(): void - { - $annotations = AnnotationHelper::getAnnotations( - $this->getTestedCodeSnifferFile(), - $this->findFunctionPointerByName($this->getTestedCodeSnifferFile(), 'wordPress') - ); - - self::assertCount(1, $annotations); - self::assertCount(1, $annotations['@param']); - - $annotation = $annotations['@param'][0]; - - self::assertInstanceOf(ParameterAnnotation::class, $annotation); - self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getType()); - self::assertSame('$parameters', $annotation->getParameterName()); - self::assertSame( - "{\nOptional. Parameters for filtering the list of user assignments. Default empty array.\n@type bool \$is_active Pass `true` to only return active user assignments and `false` to\nreturn inactive user assignments.\n@type DateTime|string \$updated_since Only return user assignments that have been updated since the given\ndate and time.\n}", - $annotation->getDescription() - ); - } - - public function testFunctionWithoutAnnotation(): void - { - self::assertCount( - 0, - AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findFunctionPointerByName($this->getTestedCodeSnifferFile(), 'withoutAnnotation'), - '@param' - ) - ); - } - - public function testMultiLineIndentedAnnotation(): void - { - $annotations = AnnotationHelper::getAnnotations( - $this->getTestedCodeSnifferFile(), - $this->findPropertyPointerByName($this->getTestedCodeSnifferFile(), 'multiLineIndentedAnnotation') - ); - - self::assertCount(1, $annotations); - self::assertArrayHasKey('@X', $annotations); - - $xAnnotations = $annotations['@X']; - - self::assertCount(1, $xAnnotations); - - self::assertInstanceOf(GenericAnnotation::class, $xAnnotations[0]); - self::assertSame('@X', $xAnnotations[0]->getName()); - self::assertSame('Content', $xAnnotations[0]->getContent()); - self::assertSame(64, $this->getLineByPointer($xAnnotations[0]->getStartPointer())); - self::assertSame(71, $this->getLineByPointer($xAnnotations[0]->getEndPointer())); - } - - public function testAnnotationWithDash(): void - { - $annotations = AnnotationHelper::getAnnotationsByName( - $this->getTestedCodeSnifferFile(), - $this->findPropertyPointerByName($this->getTestedCodeSnifferFile(), 'annotationWithDash'), - '@property-read' - ); - - self::assertCount(1, $annotations); - - self::assertInstanceOf(PropertyAnnotation::class, $annotations[0]); - self::assertSame('Foo $propertyRead Description', $annotations[0]->getContent()); - self::assertSame('$propertyRead', $annotations[0]->getPropertyName()); - self::assertTrue($annotations[0]->hasDescription()); - self::assertSame('Description', $annotations[0]->getDescription()); - } - - private function getTestedCodeSnifferFile(): File - { - if ($this->testedCodeSnifferFile === null) { - $this->testedCodeSnifferFile = $this->getCodeSnifferFile(__DIR__ . '/data/annotation.php'); - } - return $this->testedCodeSnifferFile; - } - - private function getLineByPointer(int $pointer): int - { - return $this->getTestedCodeSnifferFile()->getTokens()[$pointer]['line']; - } - -} diff --git a/tests/Helpers/DocCommentHelperTest.php b/tests/Helpers/DocCommentHelperTest.php index bc1e9c512..6c6005e9f 100644 --- a/tests/Helpers/DocCommentHelperTest.php +++ b/tests/Helpers/DocCommentHelperTest.php @@ -31,7 +31,7 @@ public function testClassHasDocComment(): void public function testClassGetDocComment(): void { self::assertSame( - "* Class WithDocComment\n *\n * @see https://www.slevomat.cz", + "/**\n * Class WithDocComment\n *\n * @see https://www.slevomat.cz\n */", DocCommentHelper::getDocComment( $this->getTestedCodeSnifferFile(), $this->findClassPointerByName($this->getTestedCodeSnifferFile(), 'WithDocCommentAndDescription') diff --git a/tests/Helpers/FunctionHelperTest.php b/tests/Helpers/FunctionHelperTest.php index 0d6d2d4b1..91b66c541 100644 --- a/tests/Helpers/FunctionHelperTest.php +++ b/tests/Helpers/FunctionHelperTest.php @@ -2,7 +2,6 @@ namespace SlevomatCodingStandard\Helpers; -use SlevomatCodingStandard\Helpers\Annotation\Annotation; use function array_map; use function sprintf; use const T_CLOSURE; @@ -413,14 +412,14 @@ public function testAnnotations(): void $functionPointer = $this->findFunctionPointerByName($phpcsFile, 'withAnnotations'); - $parametersAnnotations = array_map(static function (Annotation $annotation): ?string { - return $annotation->getContent(); + $parametersAnnotations = array_map(static function (Annotation $annotation): string { + return (string) $annotation->getValue(); }, FunctionHelper::getParametersAnnotations($phpcsFile, $functionPointer)); self::assertSame([ 'string $a', 'int $b', ], $parametersAnnotations); - self::assertSame('bool', FunctionHelper::findReturnAnnotation($phpcsFile, $functionPointer)->getContent()); + self::assertSame('bool', (string) FunctionHelper::findReturnAnnotation($phpcsFile, $functionPointer)->getValue()); $functionPointer = $this->findFunctionPointerByName($phpcsFile, 'withoutAnnotations'); self::assertCount(0, FunctionHelper::getParametersAnnotations($phpcsFile, $functionPointer)); diff --git a/tests/Helpers/TypeHintHelperTest.php b/tests/Helpers/TypeHintHelperTest.php index d16ff04ac..01330dcbe 100644 --- a/tests/Helpers/TypeHintHelperTest.php +++ b/tests/Helpers/TypeHintHelperTest.php @@ -2,7 +2,7 @@ namespace SlevomatCodingStandard\Helpers; -use function preg_split; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use const T_DOC_COMMENT_OPEN_TAG; class TypeHintHelperTest extends TestCase @@ -125,7 +125,10 @@ public function testFunctionReturnAnnotationWithNamespace(): void $functionPointer = $this->findFunctionPointerByName($phpcsFile, 'fooFunctionWithReturnAnnotation'); $returnAnnotation = FunctionHelper::findReturnAnnotation($phpcsFile, $functionPointer); - self::assertSame('void', TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, $returnAnnotation->getContent())); + self::assertSame( + 'void', + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, (string) $returnAnnotation->getValue()->type) + ); } public function testFunctionReturnTypeHintWithNamespace(): void @@ -145,13 +148,12 @@ public function testFunctionParameterAnnotationWithNamespace(): void $phpcsFile = $this->getCodeSnifferFile(__DIR__ . '/data/typeHintWithNamespace.php'); $functionPointer = $this->findFunctionPointerByName($phpcsFile, 'fooFunctionWithParameterAnnotation'); + /** @var Annotation $parameterAnnotation */ $parameterAnnotation = FunctionHelper::getParametersAnnotations($phpcsFile, $functionPointer)[0]; - $parts = preg_split('~\\s+~', $parameterAnnotation->getContent()); - self::assertIsArray($parts); - $parameterTypeHint = $parts[0]; + self::assertSame( '\Doctrine\Common\Collections\ArrayCollection', - TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, $parameterTypeHint) + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, (string) $parameterAnnotation->getValue()->type) ); } @@ -173,9 +175,10 @@ public function testMethodReturnAnnotationWithNamespace(): void $methodPointer = $this->findFunctionPointerByName($phpcsFile, 'fooMethodWithReturnAnnotation'); $returnAnnotation = FunctionHelper::findReturnAnnotation($phpcsFile, $methodPointer); + self::assertSame( '\FooNamespace\FooClass', - TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $methodPointer, $returnAnnotation->getContent()) + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $methodPointer, (string) $returnAnnotation->getValue()->type) ); } @@ -193,13 +196,12 @@ public function testMethodParameterAnnotationWithNamespace(): void $phpcsFile = $this->getCodeSnifferFile(__DIR__ . '/data/typeHintWithNamespace.php'); $methodPointer = $this->findFunctionPointerByName($phpcsFile, 'fooMethodWithParameterAnnotation'); + /** @var Annotation $parameterAnnotation */ $parameterAnnotation = FunctionHelper::getParametersAnnotations($phpcsFile, $methodPointer)[0]; - $parts = preg_split('~\\s+~', $parameterAnnotation->getContent()); - self::assertIsArray($parts); - $parameterTypeHint = $parts[0]; + self::assertSame( '\Doctrine\ORM\Mapping\Id', - TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $methodPointer, $parameterTypeHint) + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $methodPointer, (string) $parameterAnnotation->getValue()->type) ); } @@ -221,7 +223,10 @@ public function testFunctionReturnAnnotationWithoutNamespace(): void $functionPointer = $this->findFunctionPointerByName($phpcsFile, 'fooFunctionWithReturnAnnotation'); $returnAnnotation = FunctionHelper::findReturnAnnotation($phpcsFile, $functionPointer); - self::assertSame('void', TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, $returnAnnotation->getContent())); + self::assertSame( + 'void', + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, (string) $returnAnnotation->getValue()->type) + ); } public function testFunctionReturnTypeHintWithoutNamespace(): void @@ -241,13 +246,12 @@ public function testFunctionParameterAnnotationWithoutNamespace(): void $phpcsFile = $this->getCodeSnifferFile(__DIR__ . '/data/typeHintWithoutNamespace.php'); $functionPointer = $this->findFunctionPointerByName($phpcsFile, 'fooFunctionWithParameterAnnotation'); + /** @var Annotation $parameterAnnotation */ $parameterAnnotation = FunctionHelper::getParametersAnnotations($phpcsFile, $functionPointer)[0]; - $parts = preg_split('~\\s+~', $parameterAnnotation->getContent()); - self::assertIsArray($parts); - $parameterTypeHint = $parts[0]; + self::assertSame( '\Doctrine\Common\Collections\ArrayCollection', - TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, $parameterTypeHint) + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $functionPointer, (string) $parameterAnnotation->getValue()->type) ); } @@ -269,9 +273,10 @@ public function testMethodReturnAnnotationWithoutNamespace(): void $methodPointer = $this->findFunctionPointerByName($phpcsFile, 'fooMethodWithReturnAnnotation'); $returnAnnotation = FunctionHelper::findReturnAnnotation($phpcsFile, $methodPointer); + self::assertSame( '\FooClass', - TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $methodPointer, $returnAnnotation->getContent()) + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $methodPointer, (string) $returnAnnotation->getValue()->type) ); } @@ -289,13 +294,12 @@ public function testMethodParameterAnnotationWithoutNamespace(): void $phpcsFile = $this->getCodeSnifferFile(__DIR__ . '/data/typeHintWithoutNamespace.php'); $methodPointer = $this->findFunctionPointerByName($phpcsFile, 'fooMethodWithParameterAnnotation'); + /** @var Annotation $parameterAnnotation */ $parameterAnnotation = FunctionHelper::getParametersAnnotations($phpcsFile, $methodPointer)[0]; - $parts = preg_split('~\\s+~', $parameterAnnotation->getContent()); - self::assertIsArray($parts); - $parameterTypeHint = $parts[0]; + self::assertSame( '\Doctrine\ORM\Mapping\Id', - TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $methodPointer, $parameterTypeHint) + TypeHintHelper::getFullyQualifiedTypeHint($phpcsFile, $methodPointer, (string) $parameterAnnotation->getValue()->type) ); } @@ -336,20 +340,20 @@ public function testIsTypeDefinedInAnnotation(string $typeHintName, bool $isTemp } /** - * @return list + * @return list */ public static function dataIsTypeDefinedInAnnotationWhenAnnotationIsInvalid(): array { return [ - [22, 'Alias'], - [29, 'Template'], + [22, 'Alias', true], + [29, 'Template', false], ]; } /** * @dataProvider dataIsTypeDefinedInAnnotationWhenAnnotationIsInvalid */ - public function testIsTypeDefinedInAnnotationWhenAnnotationIsInvalid(int $line, string $type): void + public function testIsTypeDefinedInAnnotationWhenAnnotationIsInvalid(int $line, string $type, bool $isDefined): void { $phpcsFile = $this->getCodeSnifferFile(__DIR__ . '/data/typeHintDefinedInAnnotation.php'); @@ -357,7 +361,7 @@ public function testIsTypeDefinedInAnnotationWhenAnnotationIsInvalid(int $line, self::assertNotNull($docCommentOpenPointer); - self::assertFalse(TypeHintHelper::isTypeDefinedInAnnotation($phpcsFile, $docCommentOpenPointer, $type)); + self::assertSame($isDefined, TypeHintHelper::isTypeDefinedInAnnotation($phpcsFile, $docCommentOpenPointer, $type)); } /** @@ -392,7 +396,7 @@ public function testTypeHintEqualsAnnotation(string $functionName, bool $equals) $phpcsFile, $functionPointer, $returnTypeHint->getTypeHint(), - AnnotationTypeHelper::print($returnAnnotation->getType()) + AnnotationTypeHelper::print($returnAnnotation->getValue()->type) ) ); } diff --git a/tests/Helpers/data/annotation.php b/tests/Helpers/data/annotation.php deleted file mode 100644 index 320046b37..000000000 --- a/tests/Helpers/data/annotation.php +++ /dev/null @@ -1,102 +0,0 @@ - ['Traversable', '\ArrayIterator'], ]); - self::assertSame(50, $report->getErrorCount()); + self::assertSame(51, $report->getErrorCount()); self::assertSniffError($report, 6, ParameterTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT); self::assertSniffError($report, 14, ParameterTypeHintSniff::CODE_MISSING_NATIVE_TYPE_HINT); @@ -79,13 +79,14 @@ public function testErrors(): void self::assertSniffError($report, 264, ParameterTypeHintSniff::CODE_USELESS_SUPPRESS); self::assertSniffError($report, 272, ParameterTypeHintSniff::CODE_USELESS_SUPPRESS); self::assertSniffError($report, 280, ParameterTypeHintSniff::CODE_USELESS_SUPPRESS); + self::assertSniffError($report, 285, ParameterTypeHintSniff::CODE_USELESS_SUPPRESS); - self::assertSniffError($report, 285, ParameterTypeHintSniff::CODE_USELESS_ANNOTATION); - self::assertSniffError($report, 292, ParameterTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT); + self::assertSniffError($report, 290, ParameterTypeHintSniff::CODE_USELESS_ANNOTATION); + self::assertSniffError($report, 297, ParameterTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT); - self::assertSniffError($report, 301, ParameterTypeHintSniff::CODE_MISSING_TRAVERSABLE_TYPE_HINT_SPECIFICATION); - self::assertSniffError($report, 303, ParameterTypeHintSniff::CODE_USELESS_ANNOTATION); - self::assertSniffError($report, 304, ParameterTypeHintSniff::CODE_USELESS_ANNOTATION); + self::assertSniffError($report, 306, ParameterTypeHintSniff::CODE_MISSING_TRAVERSABLE_TYPE_HINT_SPECIFICATION); + self::assertSniffError($report, 308, ParameterTypeHintSniff::CODE_USELESS_ANNOTATION); + self::assertSniffError($report, 309, ParameterTypeHintSniff::CODE_USELESS_ANNOTATION); self::assertAllFixedInFile($report); } diff --git a/tests/Sniffs/TypeHints/data/longTypeHintsErrors.fixed.php b/tests/Sniffs/TypeHints/data/longTypeHintsErrors.fixed.php index b9ef55287..20cb5ec5e 100644 --- a/tests/Sniffs/TypeHints/data/longTypeHintsErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/longTypeHintsErrors.fixed.php @@ -112,7 +112,7 @@ public function returnsCallable() /** * @return bool - true - if some cond - * false - if some other cond + * false - if some other cond */ function multilineDescription() { diff --git a/tests/Sniffs/TypeHints/data/nullTypeHintOnLastPositionErrors.fixed.php b/tests/Sniffs/TypeHints/data/nullTypeHintOnLastPositionErrors.fixed.php index 47fc8fd40..de8f431c4 100644 --- a/tests/Sniffs/TypeHints/data/nullTypeHintOnLastPositionErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/nullTypeHintOnLastPositionErrors.fixed.php @@ -55,7 +55,7 @@ public function nullInTheMiddle() * @property-write int|null $propertyWrite * @method bool|null method(int $m, bool ...$m2) * @method bool[]|array|null method2(bool $m) - * @method method3(?\Foo $m) + * @method method3(?\Foo<(int|null)> $m) */ class Boo { @@ -77,7 +77,7 @@ class IntersectionAndGeneric /** @var int|(string&bool)[]|null */ public $d; - /** @var \Foo */ + /** @var \Foo<(int|null), (bool|null)> */ public $e; /** @var \Foo<\Foo> */ @@ -89,7 +89,7 @@ class CallableType { /** - * @return callable(bool|null $bool): (int|null) + * @return callable((bool|null) $bool): (int|null) */ public function returnsCallable() { @@ -98,24 +98,24 @@ public function returnsCallable() } -/** @var array{int, int|null, bool|null} $arrayShape1 */ +/** @var array{int, (int|null), (bool|null)} $arrayShape1 */ $arrayShape1 = []; -/** @var array{foo: int|null} $arrayShape2 */ +/** @var array{foo: (int|null)} $arrayShape2 */ $arrayShape2 = []; class Conditional { /** - * @return (Conditional1 is Conditional2 ? Conditional3|null : false) + * @return (Conditional1 is Conditional2 ? (Conditional3|null) : false) */ public function withConditional() { } /** - * @return ($parameter is Conditional2 ? Conditional3|null : false) + * @return ($parameter is Conditional2 ? (Conditional3|null) : false) */ public function withConditionalParameter($parameter) { @@ -127,7 +127,7 @@ class OffsetAccess { /** - * @return \Foo\OffsetAccessType[\Foo\OffsetAccessOffset2|null] + * @return \Foo\OffsetAccessType[(\Foo\OffsetAccessOffset2|null)] */ public function returnOffsetAccess() {} diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.fixed.php b/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.fixed.php index 1ac7aa88b..71f92620d 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.fixed.php @@ -180,7 +180,7 @@ public function traversableArray(array $a) { } - /***/ + /** */ public function oneLineDocComment(string $a) { } @@ -225,12 +225,12 @@ public function classString(string $a) { } - /***/ + /** */ public function mixedType(mixed $a) { } - /***/ + /** */ public function nullableMixedType(mixed $a) { } @@ -270,6 +270,11 @@ public function uselessSuppressOfMissingNativeTypeHint($a) { } + /** */ + public function totallyUselessSuppress(?int $a) + { + } + /** */ public function uselessAnnotationWithShortNullable(?int $a) @@ -288,8 +293,8 @@ class Promoted public function __construct( public array $promoted, - /***/ - public int $promoted2, /***/ private string $promoted3 + /** */ + public int $promoted2, /** */ private string $promoted3 ) { diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.php b/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.php index 8461813b7..fc631af2d 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.php @@ -281,6 +281,11 @@ public function uselessSuppressOfMissingNativeTypeHint($a) { } + /** @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ + public function totallyUselessSuppress(?int $a) + { + } + /** * @param ?int $a */ diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintNoErrors.php b/tests/Sniffs/TypeHints/data/parameterTypeHintNoErrors.php index 2b233a8bb..ae658ec32 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintNoErrors.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintNoErrors.php @@ -21,6 +21,14 @@ private function hasInheritdocAnnotation($a) } + /** + * @inheritdoc + */ + private function hasInheritdocAnnotation2($a) + { + + } + /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingAnyTypeHint */ diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintWithIntersectionErrors.fixed.php b/tests/Sniffs/TypeHints/data/parameterTypeHintWithIntersectionErrors.fixed.php index 03b5c536c..da1f3909b 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintWithIntersectionErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintWithIntersectionErrors.fixed.php @@ -9,7 +9,7 @@ private function two(Foo&Bar $a) { } - /***/ + /** */ public function three(Foo&Bar&Boo $a) { } diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintWithNullTrueFalseErrors.fixed.php b/tests/Sniffs/TypeHints/data/parameterTypeHintWithNullTrueFalseErrors.fixed.php index 5b07b10ec..5945c4656 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintWithNullTrueFalseErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintWithNullTrueFalseErrors.fixed.php @@ -3,7 +3,7 @@ class Whatever { - /***/ + /** */ public function parameterNull(null $a) {} @@ -17,7 +17,7 @@ public function parameterTrue(true $a) public function parameterFalse(false $a) {} - /***/ + /** */ public function parameterNullWithUselessAnnotation(null $a) {} diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintWithUnionErrors.fixed.php b/tests/Sniffs/TypeHints/data/parameterTypeHintWithUnionErrors.fixed.php index b0f93ea99..742f29d51 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintWithUnionErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintWithUnionErrors.fixed.php @@ -3,7 +3,7 @@ class Whatever { - /***/ + /** */ private function simple(string|int $a) {} @@ -11,15 +11,15 @@ private function simple(string|int $a) private function withTrue(string|bool $a) {} - /***/ + /** */ private function withFalse(string|false $a) {} - /***/ + /** */ private function withNull(?string $a) {} - /***/ + /** */ private function moreWithNull(int|string|null $a) {} @@ -39,23 +39,23 @@ private function moreTraversable(Traversable|ArrayIterator $a) private function unionWithMixedArray(string|array $a = null) {} - /***/ + /** */ private function scalar(string|int|float|bool $a) {} - /***/ + /** */ private function scalarNullable(string|int|float|bool|null $a) {} - /***/ + /** */ private function numeric(int|float $a) {} - /***/ + /** */ private function numericNullable(int|float|null $a) {} - /***/ + /** */ private function scalarAndnumericNullable(string|int|float|bool|null $a) {} diff --git a/tests/Sniffs/TypeHints/data/returnTypeHintErrors.fixed.php b/tests/Sniffs/TypeHints/data/returnTypeHintErrors.fixed.php index d5cf55923..7585d5b58 100644 --- a/tests/Sniffs/TypeHints/data/returnTypeHintErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/returnTypeHintErrors.fixed.php @@ -193,7 +193,7 @@ public function traversableArray(): array { } - /***/ + /** */ public function oneLineDocComment(): string { } @@ -243,12 +243,12 @@ public function staticReference(): static { } - /***/ + /** */ public function returnsMixed(): mixed { } - /***/ + /** */ public function returnsNullableMixed(): mixed { } diff --git a/tests/Sniffs/TypeHints/data/returnTypeHintNoErrors.php b/tests/Sniffs/TypeHints/data/returnTypeHintNoErrors.php index 8f6734d5a..cc3f6b54c 100644 --- a/tests/Sniffs/TypeHints/data/returnTypeHintNoErrors.php +++ b/tests/Sniffs/TypeHints/data/returnTypeHintNoErrors.php @@ -7,9 +7,7 @@ public function __construct() { } - /** - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint - */ + /** @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint */ private function isSniffSuppressed() { return true; diff --git a/tests/Sniffs/TypeHints/data/returnTypeHintWithIntersectionErrors.fixed.php b/tests/Sniffs/TypeHints/data/returnTypeHintWithIntersectionErrors.fixed.php index 48c13827f..5c49ba5ff 100644 --- a/tests/Sniffs/TypeHints/data/returnTypeHintWithIntersectionErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/returnTypeHintWithIntersectionErrors.fixed.php @@ -9,7 +9,7 @@ private function two(): Foo&Bar { } - /***/ + /** */ public function three(): Foo&Bar&Boo { } diff --git a/tests/Sniffs/TypeHints/data/returnTypeHintWithNeverErrors.fixed.php b/tests/Sniffs/TypeHints/data/returnTypeHintWithNeverErrors.fixed.php index 78e66eca6..298915711 100644 --- a/tests/Sniffs/TypeHints/data/returnTypeHintWithNeverErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/returnTypeHintWithNeverErrors.fixed.php @@ -3,7 +3,7 @@ class Whatever { - /***/ + /** */ public function returnsNever(): never {} diff --git a/tests/Sniffs/TypeHints/data/returnTypeHintWithNullTrueFalseErrors.fixed.php b/tests/Sniffs/TypeHints/data/returnTypeHintWithNullTrueFalseErrors.fixed.php index 715e3ab69..c76a2838d 100644 --- a/tests/Sniffs/TypeHints/data/returnTypeHintWithNullTrueFalseErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/returnTypeHintWithNullTrueFalseErrors.fixed.php @@ -3,7 +3,7 @@ class Whatever { - /***/ + /** */ public function returnsNull(): null {} @@ -17,7 +17,7 @@ public function returnsTrue(): true public function returnsFalse(): false {} - /***/ + /** */ public function returnsNullWithUselessAnnotation(): null {} diff --git a/tests/Sniffs/TypeHints/data/returnTypeHintWithUnionErrors.fixed.php b/tests/Sniffs/TypeHints/data/returnTypeHintWithUnionErrors.fixed.php index 3b587996d..fbeab3a2d 100644 --- a/tests/Sniffs/TypeHints/data/returnTypeHintWithUnionErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/returnTypeHintWithUnionErrors.fixed.php @@ -3,7 +3,7 @@ class Whatever { - /***/ + /** */ private function simple(): string|int {} @@ -11,15 +11,15 @@ private function simple(): string|int private function withTrue(): string|bool {} - /***/ + /** */ private function withFalse(): string|false {} - /***/ + /** */ private function withNull(): ?string {} - /***/ + /** */ private function moreWithNull(): int|string|null {} @@ -35,35 +35,35 @@ private function traversable(): Traversable private function moreTraversable(): Traversable|ArrayIterator {} - /***/ + /** */ private function unionWithShortNullable(): ?string {} - /***/ + /** */ private function scalar(): string|int|float|bool {} - /***/ + /** */ private function scalarNullable(): string|int|float|bool|null {} - /***/ + /** */ private function numeric(): int|float {} - /***/ + /** */ private function numericNullable(): int|float|null {} - /***/ + /** */ private function scalarAndnumericNullable(): string|int|float|bool|null {} - /***/ + /** */ private function objectAndVoid(): object|null {} - /***/ + /** */ private function mixedAndVoid(): mixed {}