From 0cec515440c92735ce3cc35cfb0891d89080a72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Fri, 8 Jul 2022 11:37:24 +0200 Subject: [PATCH] SlevomatCodingStandard.TypeHints.ParameterTypeHintSniff: "MissingTraversableTypeHintSpecification" is not reported when promoted property has @var annotation --- .../Helpers/FunctionHelper.php | 42 ++++++++++++++++++- .../Helpers/TypeHintHelper.php | 6 ++- .../TypeHints/ParameterTypeHintSniff.php | 17 +++++--- .../TypeHints/ParameterTypeHintSniffTest.php | 6 ++- .../data/parameterTypeHintErrors.fixed.php | 14 +++++++ .../data/parameterTypeHintErrors.php | 14 +++++++ .../data/parameterTypeHintNoErrors.php | 17 +++++++- 7 files changed, 105 insertions(+), 11 deletions(-) diff --git a/SlevomatCodingStandard/Helpers/FunctionHelper.php b/SlevomatCodingStandard/Helpers/FunctionHelper.php index 997b9ddb8..ea2ed9d4d 100644 --- a/SlevomatCodingStandard/Helpers/FunctionHelper.php +++ b/SlevomatCodingStandard/Helpers/FunctionHelper.php @@ -7,6 +7,7 @@ use PHP_CodeSniffer\Util\Tokens; use SlevomatCodingStandard\Helpers\Annotation\ParameterAnnotation; use SlevomatCodingStandard\Helpers\Annotation\ReturnAnnotation; +use SlevomatCodingStandard\Helpers\Annotation\VariableAnnotation; use function array_filter; use function array_map; use function array_merge; @@ -324,11 +325,30 @@ public static function getParametersAnnotations(File $phpcsFile, int $functionPo } /** - * @return array + * @return array */ public static function getValidParametersAnnotations(File $phpcsFile, int $functionPointer): array { + $tokens = $phpcsFile->getTokens(); + $parametersAnnotations = []; + + if (self::getName($phpcsFile, $functionPointer) === '__construct') { + for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) { + if ($tokens[$i]['code'] !== T_VARIABLE) { + continue; + } + + /** @var VariableAnnotation[] $varAnnotations */ + $varAnnotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $i, '@var'); + if ($varAnnotations === []) { + continue; + } + + $parametersAnnotations[$tokens[$i]['content']] = $varAnnotations[0]; + } + } + foreach (self::getParametersAnnotations($phpcsFile, $functionPointer) as $parameterAnnotation) { if ($parameterAnnotation->getContent() === null) { continue; @@ -345,12 +365,30 @@ public static function getValidParametersAnnotations(File $phpcsFile, int $funct } /** - * @return array + * @return array */ public static function getValidPrefixedParametersAnnotations(File $phpcsFile, int $functionPointer): array { + $tokens = $phpcsFile->getTokens(); + $parametersAnnotations = []; foreach (AnnotationHelper::PREFIXES as $prefix) { + if (self::getName($phpcsFile, $functionPointer) === '__construct') { + for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) { + if ($tokens[$i]['code'] !== T_VARIABLE) { + continue; + } + + /** @var VariableAnnotation[] $varAnnotations */ + $varAnnotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $i, sprintf('@%s-var', $prefix)); + if ($varAnnotations === []) { + continue; + } + + $parametersAnnotations[$tokens[$i]['content']] = $varAnnotations[0]; + } + } + /** @var ParameterAnnotation[] $annotations */ $annotations = AnnotationHelper::getAnnotationsByName($phpcsFile, $functionPointer, sprintf('@%s-param', $prefix)); foreach ($annotations as $parameterAnnotation) { diff --git a/SlevomatCodingStandard/Helpers/TypeHintHelper.php b/SlevomatCodingStandard/Helpers/TypeHintHelper.php index 024fd1f75..afbe9ccd6 100644 --- a/SlevomatCodingStandard/Helpers/TypeHintHelper.php +++ b/SlevomatCodingStandard/Helpers/TypeHintHelper.php @@ -102,8 +102,12 @@ public static function convertUnofficialUnionTypeHintToOfficialTypeHints(string public static function isTypeDefinedInAnnotation(File $phpcsFile, int $pointer, string $typeHint): bool { - /** @var int $docCommentOpenPointer */ $docCommentOpenPointer = DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $pointer); + + if ($docCommentOpenPointer === null) { + return false; + } + return self::isTemplate($phpcsFile, $docCommentOpenPointer, $typeHint) || self::isAlias($phpcsFile, $docCommentOpenPointer, $typeHint); } diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php index bde524e60..a0ce4f9a5 100644 --- a/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php +++ b/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php @@ -15,6 +15,7 @@ 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\AnnotationHelper; use SlevomatCodingStandard\Helpers\AnnotationTypeHelper; use SlevomatCodingStandard\Helpers\DocCommentHelper; @@ -40,6 +41,7 @@ use function strtolower; use const T_BITWISE_AND; use const T_DOC_COMMENT_CLOSE_TAG; +use const T_DOC_COMMENT_OPEN_TAG; use const T_DOC_COMMENT_STAR; use const T_ELLIPSIS; use const T_FUNCTION; @@ -124,8 +126,8 @@ public function process(File $phpcsFile, $functionPointer): void /** * @param (TypeHint|null)[] $parametersTypeHints - * @param ParameterAnnotation[] $parametersAnnotations - * @param ParameterAnnotation[] $prefixedParametersAnnotations + * @param array $parametersAnnotations + * @param array $prefixedParametersAnnotations */ private function checkTypeHints( File $phpcsFile, @@ -384,8 +386,8 @@ private function checkTypeHints( /** * @param (TypeHint|null)[] $parametersTypeHints - * @param ParameterAnnotation[] $parametersAnnotations - * @param ParameterAnnotation[] $prefixedParametersAnnotations + * @param array $parametersAnnotations + * @param array $prefixedParametersAnnotations */ private function checkTraversableTypeHintSpecification( File $phpcsFile, @@ -499,7 +501,7 @@ private function checkTraversableTypeHintSpecification( /** * @param (TypeHint|null)[] $parametersTypeHints - * @param ParameterAnnotation[] $parametersAnnotations + * @param array $parametersAnnotations */ private function checkUselessAnnotations( File $phpcsFile, @@ -554,7 +556,10 @@ private function checkUselessAnnotations( continue; } - $docCommentOpenPointer = DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $functionPointer); + $docCommentOpenPointer = $parameterAnnotation instanceof VariableAnnotation + ? TokenHelper::findPrevious($phpcsFile, T_DOC_COMMENT_OPEN_TAG, $parameterAnnotation->getStartPointer() - 1) + : DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $functionPointer); + $starPointer = TokenHelper::findPrevious( $phpcsFile, T_DOC_COMMENT_STAR, diff --git a/tests/Sniffs/TypeHints/ParameterTypeHintSniffTest.php b/tests/Sniffs/TypeHints/ParameterTypeHintSniffTest.php index 38097986a..fb2a0788b 100644 --- a/tests/Sniffs/TypeHints/ParameterTypeHintSniffTest.php +++ b/tests/Sniffs/TypeHints/ParameterTypeHintSniffTest.php @@ -28,7 +28,7 @@ public function testErrors(): void 'traversableTypeHints' => ['Traversable', '\ArrayIterator'], ]); - self::assertSame(47, $report->getErrorCount()); + self::assertSame(50, $report->getErrorCount()); self::assertSniffError($report, 6, ParameterTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT); self::assertSniffError($report, 14, ParameterTypeHintSniff::CODE_MISSING_NATIVE_TYPE_HINT); @@ -81,6 +81,10 @@ public function testErrors(): void self::assertSniffError($report, 285, ParameterTypeHintSniff::CODE_USELESS_ANNOTATION); self::assertSniffError($report, 292, 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::assertAllFixedInFile($report); } diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.fixed.php b/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.fixed.php index 644e3f486..1ac7aa88b 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.fixed.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.fixed.php @@ -282,3 +282,17 @@ private function noTypeHintNoAnnotationWithPhpdoc($a) } } + +class Promoted +{ + + public function __construct( + public array $promoted, + /***/ + public int $promoted2, /***/ private string $promoted3 + ) + { + + } + +} diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.php b/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.php index aee351c87..8461813b7 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintErrors.php @@ -294,3 +294,17 @@ private function noTypeHintNoAnnotationWithPhpdoc($a) } } + +class Promoted +{ + + public function __construct( + public array $promoted, + /** @var int */ + public int $promoted2, /** @var string */ private string $promoted3 + ) + { + + } + +} diff --git a/tests/Sniffs/TypeHints/data/parameterTypeHintNoErrors.php b/tests/Sniffs/TypeHints/data/parameterTypeHintNoErrors.php index 3f8aa0c03..2b233a8bb 100644 --- a/tests/Sniffs/TypeHints/data/parameterTypeHintNoErrors.php +++ b/tests/Sniffs/TypeHints/data/parameterTypeHintNoErrors.php @@ -1,4 +1,4 @@ -= 8.0 use Doctrine\Common\Collections\ArrayCollection; @@ -315,3 +315,18 @@ public function withNullableArrayAlias(?array $array) } } + +class Promoted +{ + + public function __construct( + /** @var array */ + public array $promoted, + /** @phpstan-var array */ + public array $promoted2 + ) + { + + } + +}