From ea34bea4e279c0aebcef26e9302dbc78c1e85931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sun, 25 Jun 2023 14:15:13 +0200 Subject: [PATCH] Don't parse invalid doccomments --- .../Helpers/AnnotationHelper.php | 16 +-- .../Helpers/DocCommentHelper.php | 33 +++++-- .../ReferenceUsedNamesOnlySniff.php | 97 ++++++++++--------- tests/Helpers/DocCommentHelperTest.php | 20 ++++ tests/Helpers/data/docComment.php | 12 +++ 5 files changed, 114 insertions(+), 64 deletions(-) diff --git a/SlevomatCodingStandard/Helpers/AnnotationHelper.php b/SlevomatCodingStandard/Helpers/AnnotationHelper.php index 40576bcac..d90c2330e 100644 --- a/SlevomatCodingStandard/Helpers/AnnotationHelper.php +++ b/SlevomatCodingStandard/Helpers/AnnotationHelper.php @@ -68,13 +68,15 @@ static function () use ($phpcsFile, $docCommentOpenPointer, $name): array { } else { $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); - foreach ($parsedDocComment->getNode()->getTags() as $node) { - $annotationStartPointer = self::getStartPointer($phpcsFile, $parsedDocComment->getOpenPointer(), $node); - $annotations[] = new Annotation( - $node, - $annotationStartPointer, - self::getEndPointer($phpcsFile, $parsedDocComment, $annotationStartPointer, $node) - ); + if ($parsedDocComment !== null) { + foreach ($parsedDocComment->getNode()->getTags() as $node) { + $annotationStartPointer = self::getStartPointer($phpcsFile, $parsedDocComment->getOpenPointer(), $node); + $annotations[] = new Annotation( + $node, + $annotationStartPointer, + self::getEndPointer($phpcsFile, $parsedDocComment, $annotationStartPointer, $node) + ); + } } } diff --git a/SlevomatCodingStandard/Helpers/DocCommentHelper.php b/SlevomatCodingStandard/Helpers/DocCommentHelper.php index 1cecd27a2..40339b819 100644 --- a/SlevomatCodingStandard/Helpers/DocCommentHelper.php +++ b/SlevomatCodingStandard/Helpers/DocCommentHelper.php @@ -5,6 +5,7 @@ use PHP_CodeSniffer\Files\File; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode; +use PHPStan\PhpDocParser\Parser\ParserException; use PHPStan\PhpDocParser\Parser\TokenIterator; use function array_merge; use function count; @@ -124,6 +125,10 @@ public static function hasInheritdocAnnotation(File $phpcsFile, int $pointer): b $parsedDocComment = self::parseDocComment($phpcsFile, $docCommentOpenPointer); + if ($parsedDocComment === null) { + return false; + } + foreach ($parsedDocComment->getNode()->children as $child) { if ($child instanceof PhpDocTextNode && strtolower($child->text) === '{@inheritdoc}') { return true; @@ -227,6 +232,10 @@ public static function isInline(File $phpcsFile, int $docCommentOpenPointer): bo $parsedDocComment = self::parseDocComment($phpcsFile, $docCommentOpenPointer); + if ($parsedDocComment === null) { + return false; + } + foreach ($parsedDocComment->getNode()->getTags() as $annotation) { if (preg_match('~^@(?:(?:phpstan|psalm)-)?var~i', $annotation->name) === 1) { return true; @@ -236,24 +245,28 @@ public static function isInline(File $phpcsFile, int $docCommentOpenPointer): bo return false; } - public static function parseDocComment(File $phpcsFile, int $docCommentOpenPointer): ParsedDocComment + 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 { + 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 - ); + try { + $parsedDocComment = PhpDocParserHelper::getParser()->parse($docCommentTokens); + + return new ParsedDocComment( + $docCommentOpenPointer, + $phpcsFile->getTokens()[$docCommentOpenPointer]['comment_closer'], + $parsedDocComment, + $docCommentTokens + ); + } catch (ParserException $e) { + return null; + } } ); } diff --git a/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php b/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php index d9bd71c23..c349fbf34 100644 --- a/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php +++ b/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php @@ -708,58 +708,61 @@ private function getReferences(File $phpcsFile, int $openTagPointer): array } $parsedDocComment = DocCommentHelper::parseDocComment($phpcsFile, $docCommentOpenPointer); - $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($annotations as $annotation) { - /** @var list $identifierTypeNodes */ - $identifierTypeNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), IdentifierTypeNode::class); + if ($parsedDocComment !== null) { + $annotations = AnnotationHelper::getAnnotations($phpcsFile, $docCommentOpenPointer); - foreach ($identifierTypeNodes as $typeHintNode) { - $typeHint = $typeHintNode->name; + foreach ($annotations as $annotation) { + /** @var list $identifierTypeNodes */ + $identifierTypeNodes = AnnotationHelper::getAnnotationNodesByType($annotation->getNode(), IdentifierTypeNode::class); - $lowercasedTypeHint = strtolower($typeHint); - if ( - TypeHintHelper::isSimpleTypeHint($lowercasedTypeHint) - || TypeHintHelper::isSimpleUnofficialTypeHints($lowercasedTypeHint) - || !TypeHelper::isTypeName($typeHint) - ) { - continue; - } + foreach ($identifierTypeNodes as $typeHintNode) { + $typeHint = $typeHintNode->name; - $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; - } + $lowercasedTypeHint = strtolower($typeHint); + if ( + TypeHintHelper::isSimpleTypeHint($lowercasedTypeHint) + || TypeHintHelper::isSimpleUnofficialTypeHints($lowercasedTypeHint) + || !TypeHelper::isTypeName($typeHint) + ) { + continue; + } + + $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; + } - /** @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; + /** @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/tests/Helpers/DocCommentHelperTest.php b/tests/Helpers/DocCommentHelperTest.php index 6c6005e9f..6dce5738c 100644 --- a/tests/Helpers/DocCommentHelperTest.php +++ b/tests/Helpers/DocCommentHelperTest.php @@ -391,6 +391,26 @@ public function testIsInline(): void } } + public function testIsInlineWithInvalidDocComment(): void + { + self::assertFalse( + DocCommentHelper::isInline( + $this->getTestedCodeSnifferFile(), + $this->findPointerByLineAndType($this->getTestedCodeSnifferFile(), 135, T_DOC_COMMENT_OPEN_TAG) + ) + ); + } + + public function testHasInheritdocAnnotationWithInvalidDocComment(): void + { + self::assertFalse( + DocCommentHelper::hasInheritdocAnnotation( + $this->getTestedCodeSnifferFile(), + $this->findPointerByLineAndType($this->getTestedCodeSnifferFile(), 129, T_DOC_COMMENT_OPEN_TAG) + ) + ); + } + private function getTestedCodeSnifferFile(): File { if ($this->testedCodeSnifferFile === null) { diff --git a/tests/Helpers/data/docComment.php b/tests/Helpers/data/docComment.php index e15ec0ed6..365a467da 100644 --- a/tests/Helpers/data/docComment.php +++ b/tests/Helpers/data/docComment.php @@ -125,3 +125,15 @@ class ClassWithAttribute class ClassWithAttributes { } + +/**** Invalid doccomment *****/ +class WithInvalidDocComment +{ + + public function __construct() + { + /**** Invalid doccomment *****/ + $var = 'var'; + } + +}