diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php index 3f477f7ce5033..33df6e8ae54f1 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ declare(strict_types=1); + namespace Magento\Sniffs\Annotation; use PHP_CodeSniffer\Files\File; @@ -249,6 +250,60 @@ public function validateTagGroupingFormat(File $phpcsFile, int $commentStartPtr) } } + /** + * Validates tag aligning format + * + * @param File $phpcsFile + * @param int $commentStartPtr + */ + public function validateTagAligningFormat(File $phpcsFile, int $commentStartPtr) : void + { + $tokens = $phpcsFile->getTokens(); + $noAlignmentPositions = []; + $actualPositions = []; + $stackPtr = null; + foreach ($tokens[$commentStartPtr]['comment_tags'] as $tag) { + $content = $tokens[$tag]['content']; + if (preg_match('/^@/', $content) && ($tokens[$tag]['line'] === $tokens[$tag + 2]['line'])) { + $noAlignmentPositions[] = $tokens[$tag + 1]['column'] + 1; + $actualPositions[] = $tokens[$tag + 2]['column']; + $stackPtr = $stackPtr ?? $tag; + } + } + + if (!$this->allTagsAligned($actualPositions) + && !$this->noneTagsAligned($actualPositions, $noAlignmentPositions)) { + $phpcsFile->addFixableError( + 'Tags visual alignment must be consistent', + $stackPtr, + 'MethodArguments' + ); + } + } + + /** + * Check whether all docblock params are aligned. + * + * @param array $actualPositions + * @return bool + */ + private function allTagsAligned(array $actualPositions) + { + return count(array_unique($actualPositions)) === 1; + } + + /** + * Check whether all docblock params are not aligned. + * + * @param array $actualPositions + * @param array $noAlignmentPositions + * @return bool + */ + private function noneTagsAligned(array $actualPositions, array $noAlignmentPositions) + { + return $actualPositions === $noAlignmentPositions; + } + /** * Validates extra newline before short description * diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php index 445671d245e03..f05ae64a170d7 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php @@ -75,6 +75,7 @@ public function process(File $phpcsFile, $stackPtr) $emptyTypeTokens ); $this->annotationFormatValidator->validateTagGroupingFormat($phpcsFile, $commentStartPtr); + $this->annotationFormatValidator->validateTagAligningFormat($phpcsFile, $commentStartPtr); } } } diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php index 879334d8c553b..50efca9b1ed23 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php @@ -8,8 +8,8 @@ namespace Magento\Sniffs\Annotation; -use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; /** * Sniff to validate method arguments annotations @@ -42,7 +42,7 @@ class MethodArgumentsSniff implements Sniff /** * @inheritdoc */ - public function register() : array + public function register(): array { return [ T_FUNCTION @@ -55,7 +55,7 @@ public function register() : array * @param string $type * @return bool */ - private function isTokenBeforeClosingCommentTagValid(string $type) : bool + private function isTokenBeforeClosingCommentTagValid(string $type): bool { return in_array($type, $this->validTokensBeforeClosingCommentTag); } @@ -68,7 +68,7 @@ private function isTokenBeforeClosingCommentTagValid(string $type) : bool * @param int $stackPtr * @return bool */ - private function validateCommentBlockExists(File $phpcsFile, int $previousCommentClosePtr, int $stackPtr) : bool + private function validateCommentBlockExists(File $phpcsFile, int $previousCommentClosePtr, int $stackPtr): bool { $tokens = $phpcsFile->getTokens(); for ($tempPtr = $previousCommentClosePtr + 1; $tempPtr < $stackPtr; $tempPtr++) { @@ -85,7 +85,7 @@ private function validateCommentBlockExists(File $phpcsFile, int $previousCommen * @param string $type * @return bool */ - private function isInvalidType(string $type) : bool + private function isInvalidType(string $type): bool { return in_array(strtolower($type), $this->invalidTypes); } @@ -98,7 +98,7 @@ private function isInvalidType(string $type) : bool * @param int $closedParenthesisPtr * @return array */ - private function getMethodArguments(File $phpcsFile, int $openParenthesisPtr, int $closedParenthesisPtr) : array + private function getMethodArguments(File $phpcsFile, int $openParenthesisPtr, int $closedParenthesisPtr): array { $tokens = $phpcsFile->getTokens(); $methodArguments = []; @@ -121,10 +121,11 @@ private function getMethodArguments(File $phpcsFile, int $openParenthesisPtr, in * @param array $paramDefinitions * @return array */ - private function getMethodParameters(array $paramDefinitions) : array + private function getMethodParameters(array $paramDefinitions): array { $paramName = []; - for ($i = 0; $i < count($paramDefinitions); $i++) { + $paramCount = count($paramDefinitions); + for ($i = 0; $i < $paramCount; $i++) { if (isset($paramDefinitions[$i]['paramName'])) { $paramName[] = $paramDefinitions[$i]['paramName']; } @@ -143,7 +144,7 @@ private function validateInheritdocAnnotationWithoutBracesExists( File $phpcsFile, int $previousCommentOpenPtr, int $previousCommentClosePtr - ) : bool { + ): bool { return $this->validateInheritdocAnnotationExists( $phpcsFile, $previousCommentOpenPtr, @@ -163,7 +164,7 @@ private function validateInheritdocAnnotationWithBracesExists( File $phpcsFile, int $previousCommentOpenPtr, int $previousCommentClosePtr - ) : bool { + ): bool { return $this->validateInheritdocAnnotationExists( $phpcsFile, $previousCommentOpenPtr, @@ -186,7 +187,7 @@ private function validateInheritdocAnnotationExists( int $previousCommentOpenPtr, int $previousCommentClosePtr, string $inheritdocAnnotation - ) : bool { + ): bool { $tokens = $phpcsFile->getTokens(); for ($ptr = $previousCommentOpenPtr; $ptr < $previousCommentClosePtr; $ptr++) { if (strtolower($tokens[$ptr]['content']) === $inheritdocAnnotation) { @@ -213,7 +214,7 @@ private function validateParameterAnnotationForArgumentExists( int $previousCommentOpenPtr, int $previousCommentClosePtr, int $stackPtr - ) : void { + ): void { if ($argumentsCount > 0 && $parametersCount === 0) { $inheritdocAnnotationWithoutBracesExists = $this->validateInheritdocAnnotationWithoutBracesExists( $phpcsFile, @@ -256,7 +257,7 @@ private function validateCommentBlockDoesnotHaveExtraParameterAnnotation( int $argumentsCount, int $parametersCount, int $stackPtr - ) : void { + ): void { if ($argumentsCount < $parametersCount && $argumentsCount > 0) { $phpcsFile->addFixableError( 'Extra @param found in method annotation', @@ -287,10 +288,10 @@ private function validateArgumentNameInParameterAnnotationExists( File $phpcsFile, array $methodArguments, array $paramDefinitions - ) : void { + ): void { $parameterNames = $this->getMethodParameters($paramDefinitions); if (!in_array($methodArguments[$ptr], $parameterNames)) { - $error = $methodArguments[$ptr]. ' parameter is missing in method annotation'; + $error = $methodArguments[$ptr] . ' parameter is missing in method annotation'; $phpcsFile->addFixableError($error, $stackPtr, 'MethodArguments'); } } @@ -310,7 +311,7 @@ private function validateParameterPresentInMethodSignature( array $methodArguments, File $phpcsFile, array $paramPointers - ) : void { + ): void { if (!in_array($paramDefinitionsArguments, $methodArguments)) { $phpcsFile->addFixableError( $paramDefinitionsArguments . ' parameter is missing in method arguments signature', @@ -333,7 +334,7 @@ private function validateParameterOrderIsCorrect( array $methodArguments, File $phpcsFile, array $paramPointers - ) : void { + ): void { $parameterNames = $this->getMethodParameters($paramDefinitions); $paramDefinitionsCount = count($paramDefinitions); for ($ptr = 0; $ptr < $paramDefinitionsCount; $ptr++) { @@ -342,7 +343,7 @@ private function validateParameterOrderIsCorrect( ) { if ($methodArguments[$ptr] != $parameterNames[$ptr]) { $phpcsFile->addFixableError( - $methodArguments[$ptr].' parameter is not in order', + $methodArguments[$ptr] . ' parameter is not in order', $paramPointers[$ptr], 'MethodArguments' ); @@ -366,15 +367,16 @@ private function validateDuplicateAnnotationDoesnotExists( array $paramPointers, File $phpcsFile, array $methodArguments - ) : void { + ): void { $argumentsCount = count($methodArguments); $parametersCount = count($paramPointers); if ($argumentsCount <= $parametersCount && $argumentsCount > 0) { $duplicateParameters = []; - for ($i = 0; $i < sizeof($paramDefinitions); $i++) { + $paramCount = count($paramDefinitions); + for ($i = 0; $i < $paramCount; $i++) { if (isset($paramDefinitions[$i]['paramName'])) { $parameterContent = $paramDefinitions[$i]['paramName']; - for ($j = $i + 1; $j < count($paramDefinitions); $j++) { + for ($j = $i + 1; $j < $paramCount; $j++) { if (isset($paramDefinitions[$j]['paramName']) && $parameterContent === $paramDefinitions[$j]['paramName'] ) { @@ -408,7 +410,7 @@ private function validateParameterAnnotationFormatIsCorrect( array $methodArguments, array $paramDefinitions, array $paramPointers - ) : void { + ): void { switch (count($paramDefinitions)) { case 0: $phpcsFile->addFixableError( @@ -429,7 +431,7 @@ private function validateParameterAnnotationFormatIsCorrect( case 2: if ($this->isInvalidType($paramDefinitions[0])) { $phpcsFile->addFixableError( - $paramDefinitions[0].' is not a valid PHP type', + $paramDefinitions[0] . ' is not a valid PHP type', $paramPointers[$ptr], 'MethodArguments' ); @@ -451,7 +453,7 @@ private function validateParameterAnnotationFormatIsCorrect( ); if ($this->isInvalidType($paramDefinitions[0])) { $phpcsFile->addFixableError( - $paramDefinitions[0].' is not a valid PHP type', + $paramDefinitions[0] . ' is not a valid PHP type', $paramPointers[$ptr], 'MethodArguments' ); @@ -480,7 +482,7 @@ private function validateMethodParameterAnnotations( array $methodArguments, int $previousCommentOpenPtr, int $previousCommentClosePtr - ) : void { + ): void { $argumentCount = count($methodArguments); $paramCount = count($paramPointers); $this->validateParameterAnnotationForArgumentExists( @@ -510,8 +512,14 @@ private function validateMethodParameterAnnotations( $phpcsFile, $paramPointers ); - for ($ptr = 0; $ptr < count($methodArguments); $ptr++) { - $tokens = $phpcsFile->getTokens(); + $this->validateFormattingConsistency( + $paramDefinitions, + $methodArguments, + $phpcsFile, + $paramPointers + ); + $tokens = $phpcsFile->getTokens(); + for ($ptr = 0; $ptr < $argumentCount; $ptr++) { if (isset($paramPointers[$ptr])) { $this->validateArgumentNameInParameterAnnotationExists( $stackPtr, @@ -520,7 +528,7 @@ private function validateMethodParameterAnnotations( $methodArguments, $paramDefinitions ); - $paramContent = $tokens[$paramPointers[$ptr]+2]['content']; + $paramContent = $tokens[$paramPointers[$ptr] + 2]['content']; $paramContentExplode = explode(' ', $paramContent); $this->validateParameterAnnotationFormatIsCorrect( $ptr, @@ -540,36 +548,40 @@ 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); + $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); + $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']); + $content = preg_replace('/\s+/', ' ', $tokens[$tempPtr + 2]['content'], 2); + $paramAnnotationParts = explode(' ', $content, 3); if (count($paramAnnotationParts) === 1) { if ((preg_match('/^\$.*/', $paramAnnotationParts[0]))) { $paramDefinitions[] = [ 'type' => null, - 'paramName' => rtrim(ltrim($tokens[$tempPtr+2]['content'], '&'), ',') + 'paramName' => rtrim(ltrim($tokens[$tempPtr + 2]['content'], '&'), ','), + 'comment' => null ]; } else { $paramDefinitions[] = [ - 'type' => $tokens[$tempPtr+2]['content'], - 'paramName' => null + 'type' => $tokens[$tempPtr + 2]['content'], + 'paramName' => null, + 'comment' => null ]; } } else { $paramDefinitions[] = [ 'type' => $paramAnnotationParts[0], - 'paramName' => rtrim(ltrim($paramAnnotationParts[1], '&'), ',') + 'paramName' => rtrim(ltrim($paramAnnotationParts[1], '&'), ','), + 'comment' => $paramAnnotationParts[2] ?? null, ]; } } @@ -584,4 +596,81 @@ public function process(File $phpcsFile, $stackPtr) $previousCommentClosePtr ); } + + /** + * Validates function params format consistency. + * + * @param array $paramDefinitions + * @param array $methodArguments + * @param File $phpcsFile + * @param array $paramPointers + * + * @see https://devdocs.magento.com/guides/v2.3/coding-standards/docblock-standard-general.html#format-consistency + */ + private function validateFormattingConsistency( + array $paramDefinitions, + array $methodArguments, + File $phpcsFile, + array $paramPointers + ): void { + $argumentPositions = []; + $commentPositions = []; + $tokens = $phpcsFile->getTokens(); + $argumentCount = count($methodArguments); + for ($ptr = 0; $ptr < $argumentCount; $ptr++) { + if (isset($paramPointers[$ptr])) { + $paramContent = $tokens[$paramPointers[$ptr] + 2]['content']; + $paramDefinition = $paramDefinitions[$ptr]; + $argumentPositions[] = strpos($paramContent, $paramDefinition['paramName']); + $commentPositions[] = $paramDefinition['comment'] + ? strpos($paramContent, $paramDefinition['comment']) : null; + } + } + if (!$this->allParamsAligned($argumentPositions, $commentPositions) + && !$this->noneParamsAligned($argumentPositions, $commentPositions, $paramDefinitions)) { + $phpcsFile->addFixableError( + 'Visual alignment must be consistent', + $paramPointers[0], + 'MethodArguments' + ); + } + } + + /** + * Check all params are aligned. + * + * @param array $argumentPositions + * @param array $commentPositions + * @return bool + */ + private function allParamsAligned(array $argumentPositions, array $commentPositions): bool + { + return count(array_unique($argumentPositions)) === 1 + && count(array_unique(array_filter($commentPositions))) <= 1; + } + + /** + * Check none of params are aligned. + * + * @param array $argumentPositions + * @param array $commentPositions + * @param array $paramDefinitions + * @return bool + */ + private function noneParamsAligned(array $argumentPositions, array $commentPositions, array $paramDefinitions): bool + { + $flag = true; + foreach ($argumentPositions as $index => $argumentPosition) { + $commentPosition = $commentPositions[$index]; + $type = $paramDefinitions[$index]['type']; + $paramName = $paramDefinitions[$index]['paramName']; + if (($argumentPosition !== strlen($type) + 1) || + (isset($commentPosition) && ($commentPosition !== $argumentPosition + strlen($paramName) + 1))) { + $flag = false; + break; + } + } + + return $flag; + } }