123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- declare(strict_types=1);
- namespace Magento\Sniffs\Annotation;
- use PHP_CodeSniffer\Sniffs\Sniff;
- use PHP_CodeSniffer\Files\File;
- /**
- * Sniff to validate method arguments annotations
- */
- class MethodArgumentsSniff implements Sniff
- {
- /**
- * @var array
- */
- private $validTokensBeforeClosingCommentTag = [
- 'T_WHITESPACE',
- 'T_PUBLIC',
- 'T_PRIVATE',
- 'T_PROTECTED',
- 'T_STATIC',
- 'T_ABSTRACT',
- 'T_FINAL'
- ];
- /**
- * @var array
- */
- private $invalidTypes = [
- 'null',
- 'false',
- 'true',
- 'self'
- ];
- /**
- * @inheritdoc
- */
- public function register() : array
- {
- return [
- T_FUNCTION
- ];
- }
- /**
- * Validates whether valid token exists before closing comment tag
- *
- * @param string $type
- * @return bool
- */
- private function isTokenBeforeClosingCommentTagValid(string $type) : bool
- {
- return in_array($type, $this->validTokensBeforeClosingCommentTag);
- }
- /**
- * Validates whether comment block exists
- *
- * @param File $phpcsFile
- * @param int $previousCommentClosePtr
- * @param int $stackPtr
- * @return bool
- */
- private function validateCommentBlockExists(File $phpcsFile, int $previousCommentClosePtr, int $stackPtr) : bool
- {
- $tokens = $phpcsFile->getTokens();
- for ($tempPtr = $previousCommentClosePtr + 1; $tempPtr < $stackPtr; $tempPtr++) {
- if (!$this->isTokenBeforeClosingCommentTagValid($tokens[$tempPtr]['type'])) {
- return false;
- }
- }
- return true;
- }
- /**
- * Checks whether the parameter type is invalid
- *
- * @param string $type
- * @return bool
- */
- private function isInvalidType(string $type) : bool
- {
- return in_array(strtolower($type), $this->invalidTypes);
- }
- /**
- * Get arguments from method signature
- *
- * @param File $phpcsFile
- * @param int $openParenthesisPtr
- * @param int $closedParenthesisPtr
- * @return array
- */
- private function getMethodArguments(File $phpcsFile, int $openParenthesisPtr, int $closedParenthesisPtr) : array
- {
- $tokens = $phpcsFile->getTokens();
- $methodArguments = [];
- for ($i = $openParenthesisPtr; $i < $closedParenthesisPtr; $i++) {
- $argumentsPtr = $phpcsFile->findNext(T_VARIABLE, $i + 1, $closedParenthesisPtr);
- if ($argumentsPtr === false) {
- break;
- } elseif ($argumentsPtr < $closedParenthesisPtr) {
- $arguments = $tokens[$argumentsPtr]['content'];
- $methodArguments[] = $arguments;
- $i = $argumentsPtr - 1;
- }
- }
- return $methodArguments;
- }
- /**
- * Get parameters from method annotation
- *
- * @param array $paramDefinitions
- * @return array
- */
- private function getMethodParameters(array $paramDefinitions) : array
- {
- $paramName = [];
- for ($i = 0; $i < count($paramDefinitions); $i++) {
- if (isset($paramDefinitions[$i]['paramName'])) {
- $paramName[] = $paramDefinitions[$i]['paramName'];
- }
- }
- return $paramName;
- }
- /**
- * Validates whether @inheritdoc without braces [@inheritdoc] exists or not
- *
- * @param File $phpcsFile
- * @param int $previousCommentOpenPtr
- * @param int $previousCommentClosePtr
- */
- private function validateInheritdocAnnotationWithoutBracesExists(
- File $phpcsFile,
- int $previousCommentOpenPtr,
- int $previousCommentClosePtr
- ) : bool {
- return $this->validateInheritdocAnnotationExists(
- $phpcsFile,
- $previousCommentOpenPtr,
- $previousCommentClosePtr,
- '@inheritdoc'
- );
- }
- /**
- * Validates whether @inheritdoc with braces [{@inheritdoc}] exists or not
- *
- * @param File $phpcsFile
- * @param int $previousCommentOpenPtr
- * @param int $previousCommentClosePtr
- */
- private function validateInheritdocAnnotationWithBracesExists(
- File $phpcsFile,
- int $previousCommentOpenPtr,
- int $previousCommentClosePtr
- ) : bool {
- return $this->validateInheritdocAnnotationExists(
- $phpcsFile,
- $previousCommentOpenPtr,
- $previousCommentClosePtr,
- '{@inheritdoc}'
- );
- }
- /**
- * Validates inheritdoc annotation exists
- *
- * @param File $phpcsFile
- * @param int $previousCommentOpenPtr
- * @param int $previousCommentClosePtr
- * @param string $inheritdocAnnotation
- * @return bool
- */
- private function validateInheritdocAnnotationExists(
- File $phpcsFile,
- int $previousCommentOpenPtr,
- int $previousCommentClosePtr,
- string $inheritdocAnnotation
- ) : bool {
- $tokens = $phpcsFile->getTokens();
- for ($ptr = $previousCommentOpenPtr; $ptr < $previousCommentClosePtr; $ptr++) {
- if (strtolower($tokens[$ptr]['content']) === $inheritdocAnnotation) {
- return true;
- }
- }
- return false;
- }
- /**
- * Validates if annotation exists for parameter in method annotation
- *
- * @param File $phpcsFile
- * @param int $argumentsCount
- * @param int $parametersCount
- * @param int $previousCommentOpenPtr
- * @param int $previousCommentClosePtr
- * @param int $stackPtr
- */
- private function validateParameterAnnotationForArgumentExists(
- File $phpcsFile,
- int $argumentsCount,
- int $parametersCount,
- int $previousCommentOpenPtr,
- int $previousCommentClosePtr,
- int $stackPtr
- ) : void {
- if ($argumentsCount > 0 && $parametersCount === 0) {
- $inheritdocAnnotationWithoutBracesExists = $this->validateInheritdocAnnotationWithoutBracesExists(
- $phpcsFile,
- $previousCommentOpenPtr,
- $previousCommentClosePtr
- );
- $inheritdocAnnotationWithBracesExists = $this->validateInheritdocAnnotationWithBracesExists(
- $phpcsFile,
- $previousCommentOpenPtr,
- $previousCommentClosePtr
- );
- if ($inheritdocAnnotationWithBracesExists) {
- $phpcsFile->addFixableError(
- '{@inheritdoc} does not import parameter annotation',
- $stackPtr,
- 'MethodArguments'
- );
- } elseif ($this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr)
- && !$inheritdocAnnotationWithoutBracesExists
- ) {
- $phpcsFile->addFixableError(
- 'Missing @param for argument in method annotation',
- $stackPtr,
- 'MethodArguments'
- );
- }
- }
- }
- /**
- * Validates whether comment block have extra the parameters listed in method annotation
- *
- * @param File $phpcsFile
- * @param int $argumentsCount
- * @param int $parametersCount
- * @param int $stackPtr
- */
- private function validateCommentBlockDoesnotHaveExtraParameterAnnotation(
- File $phpcsFile,
- int $argumentsCount,
- int $parametersCount,
- int $stackPtr
- ) : void {
- if ($argumentsCount < $parametersCount && $argumentsCount > 0) {
- $phpcsFile->addFixableError(
- 'Extra @param found in method annotation',
- $stackPtr,
- 'MethodArguments'
- );
- } elseif ($argumentsCount > 0 && $argumentsCount != $parametersCount && $parametersCount != 0) {
- $phpcsFile->addFixableError(
- '@param is not found for one or more params in method annotation',
- $stackPtr,
- 'MethodArguments'
- );
- }
- }
- /**
- * Validates whether the argument name exists in method parameter annotation
- *
- * @param int $stackPtr
- * @param int $ptr
- * @param File $phpcsFile
- * @param array $methodArguments
- * @param array $paramDefinitions
- */
- private function validateArgumentNameInParameterAnnotationExists(
- int $stackPtr,
- int $ptr,
- File $phpcsFile,
- array $methodArguments,
- array $paramDefinitions
- ) : void {
- $parameterNames = $this->getMethodParameters($paramDefinitions);
- if (!in_array($methodArguments[$ptr], $parameterNames)) {
- $error = $methodArguments[$ptr]. ' parameter is missing in method annotation';
- $phpcsFile->addFixableError($error, $stackPtr, 'MethodArguments');
- }
- }
- /**
- * Validates whether parameter present in method signature
- *
- * @param int $ptr
- * @param int $paramDefinitionsArguments
- * @param array $methodArguments
- * @param File $phpcsFile
- * @param array $paramPointers
- */
- private function validateParameterPresentInMethodSignature(
- int $ptr,
- string $paramDefinitionsArguments,
- array $methodArguments,
- File $phpcsFile,
- array $paramPointers
- ) : void {
- if (!in_array($paramDefinitionsArguments, $methodArguments)) {
- $phpcsFile->addFixableError(
- $paramDefinitionsArguments . ' parameter is missing in method arguments signature',
- $paramPointers[$ptr],
- 'MethodArguments'
- );
- }
- }
- /**
- * Validates whether the parameters are in order or not in method annotation
- *
- * @param array $paramDefinitions
- * @param array $methodArguments
- * @param File $phpcsFile
- * @param array $paramPointers
- */
- private function validateParameterOrderIsCorrect(
- array $paramDefinitions,
- array $methodArguments,
- File $phpcsFile,
- array $paramPointers
- ) : void {
- $parameterNames = $this->getMethodParameters($paramDefinitions);
- $paramDefinitionsCount = count($paramDefinitions);
- for ($ptr = 0; $ptr < $paramDefinitionsCount; $ptr++) {
- if (isset($methodArguments[$ptr]) && isset($parameterNames[$ptr])
- && in_array($methodArguments[$ptr], $parameterNames)
- ) {
- if ($methodArguments[$ptr] != $parameterNames[$ptr]) {
- $phpcsFile->addFixableError(
- $methodArguments[$ptr].' parameter is not in order',
- $paramPointers[$ptr],
- 'MethodArguments'
- );
- }
- }
- }
- }
- /**
- * Validate whether duplicate annotation present in method annotation
- *
- * @param int $stackPtr
- * @param array $paramDefinitions
- * @param array $paramPointers
- * @param File $phpcsFile
- * @param array $methodArguments
- */
- private function validateDuplicateAnnotationDoesnotExists(
- int $stackPtr,
- array $paramDefinitions,
- array $paramPointers,
- File $phpcsFile,
- array $methodArguments
- ) : void {
- $argumentsCount = count($methodArguments);
- $parametersCount = count($paramPointers);
- if ($argumentsCount <= $parametersCount && $argumentsCount > 0) {
- $duplicateParameters = [];
- for ($i = 0; $i < sizeof($paramDefinitions); $i++) {
- if (isset($paramDefinitions[$i]['paramName'])) {
- $parameterContent = $paramDefinitions[$i]['paramName'];
- for ($j = $i + 1; $j < count($paramDefinitions); $j++) {
- if (isset($paramDefinitions[$j]['paramName'])
- && $parameterContent === $paramDefinitions[$j]['paramName']
- ) {
- $duplicateParameters[] = $parameterContent;
- }
- }
- }
- }
- foreach ($duplicateParameters as $value) {
- $phpcsFile->addFixableError(
- $value . ' duplicate found in method annotation',
- $stackPtr,
- 'MethodArguments'
- );
- }
- }
- }
- /**
- * Validate parameter annotation format is correct or not
- *
- * @param int $ptr
- * @param File $phpcsFile
- * @param array $methodArguments
- * @param array $paramDefinitions
- * @param array $paramPointers
- */
- private function validateParameterAnnotationFormatIsCorrect(
- int $ptr,
- File $phpcsFile,
- array $methodArguments,
- array $paramDefinitions,
- array $paramPointers
- ) : void {
- switch (count($paramDefinitions)) {
- case 0:
- $phpcsFile->addFixableError(
- 'Missing both type and parameter',
- $paramPointers[$ptr],
- 'MethodArguments'
- );
- break;
- case 1:
- if (preg_match('/^\$.*/', $paramDefinitions[0])) {
- $phpcsFile->addError(
- 'Type is not specified',
- $paramPointers[$ptr],
- 'MethodArguments'
- );
- }
- break;
- case 2:
- if ($this->isInvalidType($paramDefinitions[0])) {
- $phpcsFile->addFixableError(
- $paramDefinitions[0].' is not a valid PHP type',
- $paramPointers[$ptr],
- 'MethodArguments'
- );
- }
- $this->validateParameterPresentInMethodSignature(
- $ptr,
- ltrim($paramDefinitions[1], '&'),
- $methodArguments,
- $phpcsFile,
- $paramPointers
- );
- break;
- default:
- if (preg_match('/^\$.*/', $paramDefinitions[0])) {
- $phpcsFile->addError(
- 'Type is not specified',
- $paramPointers[$ptr],
- 'MethodArguments'
- );
- if ($this->isInvalidType($paramDefinitions[0])) {
- $phpcsFile->addFixableError(
- $paramDefinitions[0].' is not a valid PHP type',
- $paramPointers[$ptr],
- 'MethodArguments'
- );
- }
- }
- break;
- }
- }
- /**
- * Validate method parameter annotations
- *
- * @param int $stackPtr
- * @param array $paramDefinitions
- * @param array $paramPointers
- * @param File $phpcsFile
- * @param array $methodArguments
- * @param int $previousCommentOpenPtr
- * @param int $previousCommentClosePtr
- */
- private function validateMethodParameterAnnotations(
- int $stackPtr,
- array $paramDefinitions,
- array $paramPointers,
- File $phpcsFile,
- array $methodArguments,
- int $previousCommentOpenPtr,
- int $previousCommentClosePtr
- ) : void {
- $argumentCount = count($methodArguments);
- $paramCount = count($paramPointers);
- $this->validateParameterAnnotationForArgumentExists(
- $phpcsFile,
- $argumentCount,
- $paramCount,
- $previousCommentOpenPtr,
- $previousCommentClosePtr,
- $stackPtr
- );
- $this->validateCommentBlockDoesnotHaveExtraParameterAnnotation(
- $phpcsFile,
- $argumentCount,
- $paramCount,
- $stackPtr
- );
- $this->validateDuplicateAnnotationDoesnotExists(
- $stackPtr,
- $paramDefinitions,
- $paramPointers,
- $phpcsFile,
- $methodArguments
- );
- $this->validateParameterOrderIsCorrect(
- $paramDefinitions,
- $methodArguments,
- $phpcsFile,
- $paramPointers
- );
- for ($ptr = 0; $ptr < count($methodArguments); $ptr++) {
- $tokens = $phpcsFile->getTokens();
- if (isset($paramPointers[$ptr])) {
- $this->validateArgumentNameInParameterAnnotationExists(
- $stackPtr,
- $ptr,
- $phpcsFile,
- $methodArguments,
- $paramDefinitions
- );
- $paramContent = $tokens[$paramPointers[$ptr]+2]['content'];
- $paramContentExplode = explode(' ', $paramContent);
- $this->validateParameterAnnotationFormatIsCorrect(
- $ptr,
- $phpcsFile,
- $methodArguments,
- $paramContentExplode,
- $paramPointers
- );
- }
- }
- }
- /**
- * @inheritdoc
- */
- public function process(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- $numTokens = count($tokens);
- $previousCommentOpenPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr-1, 0);
- $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr-1, 0);
- if (!$this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr)) {
- $phpcsFile->addError('Comment block is missing', $stackPtr, 'MethodArguments');
- return;
- }
- $openParenthesisPtr = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $stackPtr+1, $numTokens);
- $closedParenthesisPtr = $phpcsFile->findNext(T_CLOSE_PARENTHESIS, $stackPtr+1, $numTokens);
- $methodArguments = $this->getMethodArguments($phpcsFile, $openParenthesisPtr, $closedParenthesisPtr);
- $paramPointers = $paramDefinitions = [];
- for ($tempPtr = $previousCommentOpenPtr; $tempPtr < $previousCommentClosePtr; $tempPtr++) {
- if (strtolower($tokens[$tempPtr]['content']) === '@param') {
- $paramPointers[] = $tempPtr;
- $paramAnnotationParts = explode(' ', $tokens[$tempPtr+2]['content']);
- if (count($paramAnnotationParts) === 1) {
- if ((preg_match('/^\$.*/', $paramAnnotationParts[0]))) {
- $paramDefinitions[] = [
- 'type' => null,
- 'paramName' => rtrim(ltrim($tokens[$tempPtr+2]['content'], '&'), ',')
- ];
- } else {
- $paramDefinitions[] = [
- 'type' => $tokens[$tempPtr+2]['content'],
- 'paramName' => null
- ];
- }
- } else {
- $paramDefinitions[] = [
- 'type' => $paramAnnotationParts[0],
- 'paramName' => rtrim(ltrim($paramAnnotationParts[1], '&'), ',')
- ];
- }
- }
- }
- $this->validateMethodParameterAnnotations(
- $stackPtr,
- $paramDefinitions,
- $paramPointers,
- $phpcsFile,
- $methodArguments,
- $previousCommentOpenPtr,
- $previousCommentClosePtr
- );
- }
- }
|