diff --git a/.travis.yml b/.travis.yml index 58e89616..32f5042b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: php php: - - 5.5 - 5.6 - 7.0 - 7.1 diff --git a/Magento/Sniffs/Annotation/AnnotationFormatValidator.php b/Magento/Sniffs/Annotation/AnnotationFormatValidator.php new file mode 100644 index 00000000..93679614 --- /dev/null +++ b/Magento/Sniffs/Annotation/AnnotationFormatValidator.php @@ -0,0 +1,318 @@ +getTokens(); + $shortPtrEnd = $shortPtr; + for ($i = ($shortPtr + 1); $i < $commentEndPtr; $i++) { + if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { + if ($tokens[$i]['line'] === $tokens[$shortPtrEnd]['line'] + 1) { + $shortPtrEnd = $i; + } else { + break; + } + } + } + return $shortPtrEnd; + } + + /** + * Validates whether the short description has multi lines in description + * + * @param File $phpcsFile + * @param int $shortPtr + * @param int $commentEndPtr + */ + private function validateMultiLinesInShortDescription( + File $phpcsFile, + $shortPtr, + $commentEndPtr + ) { + $tokens = $phpcsFile->getTokens(); + $shortPtrEnd = $this->getShortDescriptionEndPosition( + $phpcsFile, + (int)$shortPtr, + $commentEndPtr + ); + $shortPtrEndContent = $tokens[$shortPtrEnd]['content']; + if (preg_match('/^[a-z]/', $shortPtrEndContent) + && $shortPtrEnd != $shortPtr + && !preg_match('/\bSee\b/', $shortPtrEndContent) + && $tokens[$shortPtr]['line'] + 1 === $tokens[$shortPtrEnd]['line'] + && $tokens[$shortPtrEnd]['code'] !== T_DOC_COMMENT_TAG + ) { + $error = 'Short description should not be in multi lines'; + $phpcsFile->addWarning($error, $shortPtrEnd + 1, 'MethodAnnotation'); + } + } + + /** + * Validates whether the spacing between short and long descriptions + * + * @param File $phpcsFile + * @param int $shortPtr + * @param int $commentEndPtr + * @param array $emptyTypeTokens + */ + private function validateSpacingBetweenShortAndLongDescriptions( + File $phpcsFile, + $shortPtr, + $commentEndPtr, + array $emptyTypeTokens + ) { + $tokens = $phpcsFile->getTokens(); + $shortPtrEnd = $this->getShortDescriptionEndPosition( + $phpcsFile, + (int)$shortPtr, + $commentEndPtr + ); + $shortPtrEndContent = $tokens[$shortPtrEnd]['content']; + if (preg_match('/^[A-Z]/', $shortPtrEndContent) + && !preg_match('/\bSee\b/', $shortPtrEndContent) + && $tokens[$shortPtr]['line'] + 1 === $tokens[$shortPtrEnd]['line'] + && $tokens[$shortPtrEnd]['code'] !== T_DOC_COMMENT_TAG + ) { + $error = 'There must be exactly one blank line between lines'; + $phpcsFile->addWarning($error, $shortPtrEnd + 1, 'MethodAnnotation'); + } + if ($shortPtrEnd != $shortPtr) { + $this->validateLongDescriptionFormat($phpcsFile, $shortPtrEnd, $commentEndPtr, $emptyTypeTokens); + } else { + $this->validateLongDescriptionFormat($phpcsFile, $shortPtr, $commentEndPtr, $emptyTypeTokens); + } + } + + /** + * Validates short description format + * + * @param File $phpcsFile + * @param int $shortPtr + * @param int $stackPtr + * @param int $commentEndPtr + * @param array $emptyTypeTokens + */ + private function validateShortDescriptionFormat( + File $phpcsFile, + $shortPtr, + $stackPtr, + $commentEndPtr, + array $emptyTypeTokens + ) { + $tokens = $phpcsFile->getTokens(); + if ($tokens[$shortPtr]['line'] !== $tokens[$stackPtr]['line'] + 1) { + $error = 'No blank lines are allowed before short description'; + $phpcsFile->addWarning($error, $shortPtr, 'MethodAnnotation'); + } + if (strtolower($tokens[$shortPtr]['content']) === '{@inheritdoc}') { + $error = 'If the @inheritdoc not inline it shouldn’t have braces'; + $phpcsFile->addWarning($error, $shortPtr, 'MethodAnnotation'); + } + $shortPtrContent = $tokens[$shortPtr]['content']; + if (preg_match('/^\p{Ll}/u', $shortPtrContent) === 1) { + $error = 'Short description must start with a capital letter'; + $phpcsFile->addWarning($error, $shortPtr, 'MethodAnnotation'); + } + $this->validateNoExtraNewLineBeforeShortDescription( + $phpcsFile, + $stackPtr, + $commentEndPtr, + $emptyTypeTokens + ); + $this->validateSpacingBetweenShortAndLongDescriptions( + $phpcsFile, + $shortPtr, + $commentEndPtr, + $emptyTypeTokens + ); + $this->validateMultiLinesInShortDescription( + $phpcsFile, + $shortPtr, + $commentEndPtr + ); + } + + /** + * Validates long description format + * + * @param File $phpcsFile + * @param int $shortPtrEnd + * @param int $commentEndPtr + * @param array $emptyTypeTokens + */ + private function validateLongDescriptionFormat( + File $phpcsFile, + $shortPtrEnd, + $commentEndPtr, + array $emptyTypeTokens + ) { + $tokens = $phpcsFile->getTokens(); + $longPtr = $phpcsFile->findNext($emptyTypeTokens, $shortPtrEnd + 1, $commentEndPtr - 1, true); + if (strtolower($tokens[$longPtr]['content']) === '@inheritdoc') { + $error = '@inheritdoc imports only short description, annotation must have long description'; + $phpcsFile->addWarning($error, $longPtr, 'MethodAnnotation'); + } + if ($longPtr !== false && $tokens[$longPtr]['code'] === T_DOC_COMMENT_STRING) { + if ($tokens[$longPtr]['line'] !== $tokens[$shortPtrEnd]['line'] + 2) { + $error = 'There must be exactly one blank line between descriptions'; + $phpcsFile->addWarning($error, $longPtr, 'MethodAnnotation'); + } + if (preg_match('/^\p{Ll}/u', $tokens[$longPtr]['content']) === 1) { + $error = 'Long description must start with a capital letter'; + $phpcsFile->addWarning($error, $longPtr, 'MethodAnnotation'); + } + } + } + + /** + * Validates tags spacing format + * + * @param File $phpcsFile + * @param int $commentStartPtr + * @param array $emptyTypeTokens + */ + public function validateTagsSpacingFormat(File $phpcsFile, $commentStartPtr, array $emptyTypeTokens) + { + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$commentStartPtr]['comment_tags'][0])) { + $firstTagPtr = $tokens[$commentStartPtr]['comment_tags'][0]; + $commentTagPtrContent = $tokens[$firstTagPtr]['content']; + $prevPtr = $phpcsFile->findPrevious($emptyTypeTokens, $firstTagPtr - 1, $commentStartPtr, true); + if ($tokens[$firstTagPtr]['line'] !== $tokens[$prevPtr]['line'] + 2 + && strtolower($commentTagPtrContent) !== '@inheritdoc' + ) { + $error = 'There must be exactly one blank line before tags'; + $phpcsFile->addWarning($error, $firstTagPtr, 'MethodAnnotation'); + } + } + } + + /** + * Validates tag grouping format + * + * @param File $phpcsFile + * @param int $commentStartPtr + */ + public function validateTagGroupingFormat(File $phpcsFile, $commentStartPtr) + { + $tokens = $phpcsFile->getTokens(); + $tagGroups = []; + $groupId = 0; + $paramGroupId = null; + foreach ($tokens[$commentStartPtr]['comment_tags'] as $position => $tag) { + if ($position > 0) { + $prevPtr = $phpcsFile->findPrevious( + T_DOC_COMMENT_STRING, + $tag - 1, + $tokens[$commentStartPtr]['comment_tags'][$position - 1] + ); + if ($prevPtr === false) { + $prevPtr = $tokens[$commentStartPtr]['comment_tags'][$position - 1]; + } + + if ($tokens[$prevPtr]['line'] !== $tokens[$tag]['line'] - 1) { + $groupId++; + } + } + + if (strtolower($tokens[$tag]['content']) === '@param') { + if ($paramGroupId !== null + && $paramGroupId !== $groupId) { + $error = 'Parameter tags must be grouped together'; + $phpcsFile->addWarning($error, $tag, 'MethodAnnotation'); + } + if ($paramGroupId === null) { + $paramGroupId = $groupId; + } + } + $tagGroups[$groupId][] = $tag; + } + } + + /** + * Validates extra newline before short description + * + * @param File $phpcsFile + * @param int $commentStartPtr + * @param int $commentEndPtr + * @param array $emptyTypeTokens + */ + private function validateNoExtraNewLineBeforeShortDescription( + File $phpcsFile, + $commentStartPtr, + $commentEndPtr, + array $emptyTypeTokens + ) { + $tokens = $phpcsFile->getTokens(); + $prevPtr = $phpcsFile->findPrevious($emptyTypeTokens, $commentEndPtr - 1, $commentStartPtr, true); + if ($tokens[$prevPtr]['line'] < ($tokens[$commentEndPtr]['line'] - 1)) { + $error = 'Additional blank lines found at end of the annotation block'; + $phpcsFile->addWarning($error, $commentEndPtr, 'MethodAnnotation'); + } + } + + /** + * Validates structure description format + * + * @param File $phpcsFile + * @param int $commentStartPtr + * @param int $shortPtr + * @param int $commentEndPtr + * @param array $emptyTypeTokens + */ + public function validateDescriptionFormatStructure( + File $phpcsFile, + $commentStartPtr, + $shortPtr, + $commentEndPtr, + array $emptyTypeTokens + ) { + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$commentStartPtr]['comment_tags'][0]) + ) { + $commentTagPtr = $tokens[$commentStartPtr]['comment_tags'][0]; + $commentTagPtrContent = $tokens[$commentTagPtr]['content']; + if ($tokens[$shortPtr]['code'] !== T_DOC_COMMENT_STRING + && strtolower($commentTagPtrContent) !== '@inheritdoc' + ) { + $error = 'Missing short description'; + $phpcsFile->addWarning($error, $commentStartPtr, 'MethodAnnotation'); + } else { + $this->validateShortDescriptionFormat( + $phpcsFile, + (int)$shortPtr, + $commentStartPtr, + $commentEndPtr, + $emptyTypeTokens + ); + } + } else { + $this->validateShortDescriptionFormat( + $phpcsFile, + (int)$shortPtr, + $commentStartPtr, + $commentEndPtr, + $emptyTypeTokens + ); + } + } +} diff --git a/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php b/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php new file mode 100644 index 00000000..9e82bc91 --- /dev/null +++ b/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php @@ -0,0 +1,123 @@ +annotationFormatValidator = new AnnotationFormatValidator(); + } + + /** + * Validates whether annotation block exists for interface, abstract or final classes + * + * @param File $phpcsFile + * @param int $previousCommentClosePtr + * @param int $stackPtr + */ + private function validateInterfaceOrAbstractOrFinalClassAnnotationBlockExists( + File $phpcsFile, + $previousCommentClosePtr, + $stackPtr + ) { + $tokens = $phpcsFile->getTokens(); + if ($tokens[$stackPtr]['type'] === 'T_CLASS') { + if ($tokens[$stackPtr - 2]['type'] === 'T_ABSTRACT' && + $tokens[$stackPtr - 4]['content'] != $tokens[$previousCommentClosePtr]['content'] + ) { + $error = 'Interface or abstract class is missing annotation block'; + $phpcsFile->addWarning($error, $stackPtr, 'ClassAnnotation'); + } + if ($tokens[$stackPtr - 2]['type'] === 'T_FINAL' && + $tokens[$stackPtr - 4]['content'] != $tokens[$previousCommentClosePtr]['content'] + ) { + $error = 'Final class is missing annotation block'; + $phpcsFile->addWarning($error, $stackPtr, 'ClassAnnotation'); + } + } + } + + /** + * Validates whether annotation block exists + * + * @param File $phpcsFile + * @param int $previousCommentClosePtr + * @param int $stackPtr + */ + private function validateAnnotationBlockExists(File $phpcsFile, $previousCommentClosePtr, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $this->validateInterfaceOrAbstractOrFinalClassAnnotationBlockExists( + $phpcsFile, + $previousCommentClosePtr, + $stackPtr + ); + if ($tokens[$stackPtr - 2]['content'] != 'class' && $tokens[$stackPtr - 2]['content'] != 'abstract' + && $tokens[$stackPtr - 2]['content'] != 'final' + && $tokens[$stackPtr - 2]['content'] !== $tokens[$previousCommentClosePtr]['content'] + ) { + $error = 'Class is missing annotation block'; + $phpcsFile->addWarning($error, $stackPtr, 'ClassAnnotation'); + } + } + + /** + * @inheritdoc + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr - 1, 0); + $this->validateAnnotationBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr); + $commentStartPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1, 0); + if ($commentStartPtr === false) { + return; + } + $commentCloserPtr = $tokens[$commentStartPtr]['comment_closer']; + $emptyTypeTokens = [ + T_DOC_COMMENT_WHITESPACE, + T_DOC_COMMENT_STAR + ]; + $shortPtr = $phpcsFile->findNext($emptyTypeTokens, $commentStartPtr +1, $commentCloserPtr, true); + if ($shortPtr === false) { + $error = 'Annotation block is empty'; + $phpcsFile->addWarning($error, $commentStartPtr, 'MethodAnnotation'); + } else { + $this->annotationFormatValidator->validateDescriptionFormatStructure( + $phpcsFile, + $commentStartPtr, + (int) $shortPtr, + $previousCommentClosePtr, + $emptyTypeTokens + ); + } + } +} diff --git a/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php b/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php new file mode 100644 index 00000000..b4c51f63 --- /dev/null +++ b/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php @@ -0,0 +1,79 @@ +annotationFormatValidator = new AnnotationFormatValidator(); + } + + /** + * @inheritdoc + */ + public function register() + { + return [ + T_FUNCTION + ]; + } + + /** + * @inheritdoc + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $commentStartPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, ($stackPtr), 0); + $commentEndPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, ($stackPtr), 0); + $commentCloserPtr = $tokens[$commentStartPtr]['comment_closer']; + $functionPtrContent = $tokens[$stackPtr+2]['content'] ; + if (preg_match('/(?i)__construct/', $functionPtrContent)) { + return; + } + $emptyTypeTokens = [ + T_DOC_COMMENT_WHITESPACE, + T_DOC_COMMENT_STAR + ]; + $shortPtr = $phpcsFile->findNext($emptyTypeTokens, $commentStartPtr + 1, $commentCloserPtr, true); + if ($shortPtr === false) { + $error = 'Annotation block is empty'; + $phpcsFile->addWarning($error, $commentStartPtr, 'MethodAnnotation'); + } else { + $this->annotationFormatValidator->validateDescriptionFormatStructure( + $phpcsFile, + $commentStartPtr, + (int) $shortPtr, + $commentEndPtr, + $emptyTypeTokens + ); + if (empty($tokens[$commentStartPtr]['comment_tags'])) { + return; + } + $this->annotationFormatValidator->validateTagsSpacingFormat( + $phpcsFile, + $commentStartPtr, + $emptyTypeTokens + ); + $this->annotationFormatValidator->validateTagGroupingFormat($phpcsFile, $commentStartPtr); + } + } +} diff --git a/Magento/Sniffs/Annotation/MethodArgumentsSniff.php b/Magento/Sniffs/Annotation/MethodArgumentsSniff.php new file mode 100644 index 00000000..b0b03a38 --- /dev/null +++ b/Magento/Sniffs/Annotation/MethodArgumentsSniff.php @@ -0,0 +1,584 @@ +validTokensBeforeClosingCommentTag); + } + + /** + * Validates whether comment block exists + * + * @param File $phpcsFile + * @param int $previousCommentClosePtr + * @param int $stackPtr + * @return bool + */ + private function validateCommentBlockExists(File $phpcsFile, $previousCommentClosePtr, $stackPtr) + { + $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($type) + { + 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, $openParenthesisPtr, $closedParenthesisPtr) + { + $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) + { + $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, + $previousCommentOpenPtr, + $previousCommentClosePtr + ) { + 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, + $previousCommentOpenPtr, + $previousCommentClosePtr + ) { + 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, + $previousCommentOpenPtr, + $previousCommentClosePtr, + $inheritdocAnnotation + ) { + $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, + $argumentsCount, + $parametersCount, + $previousCommentOpenPtr, + $previousCommentClosePtr, + $stackPtr + ) { + if ($argumentsCount > 0 && $parametersCount === 0) { + $inheritdocAnnotationWithoutBracesExists = $this->validateInheritdocAnnotationWithoutBracesExists( + $phpcsFile, + $previousCommentOpenPtr, + $previousCommentClosePtr + ); + $inheritdocAnnotationWithBracesExists = $this->validateInheritdocAnnotationWithBracesExists( + $phpcsFile, + $previousCommentOpenPtr, + $previousCommentClosePtr + ); + if ($inheritdocAnnotationWithBracesExists) { + $phpcsFile->addWarning( + '{@inheritdoc} does not import parameter annotation', + $stackPtr, + 'MethodArguments' + ); + } elseif ($this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr) + && !$inheritdocAnnotationWithoutBracesExists + ) { + $phpcsFile->addWarning( + '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, + $argumentsCount, + $parametersCount, + $stackPtr + ) { + if ($argumentsCount < $parametersCount && $argumentsCount > 0) { + $phpcsFile->addWarning( + 'Extra @param found in method annotation', + $stackPtr, + 'MethodArguments' + ); + } elseif ($argumentsCount > 0 && $argumentsCount != $parametersCount && $parametersCount != 0) { + $phpcsFile->addWarning( + '@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( + $stackPtr, + $ptr, + File $phpcsFile, + array $methodArguments, + array $paramDefinitions + ) { + $parameterNames = $this->getMethodParameters($paramDefinitions); + if (!in_array($methodArguments[$ptr], $parameterNames)) { + $error = $methodArguments[$ptr]. ' parameter is missing in method annotation'; + $phpcsFile->addWarning($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( + $ptr, + $paramDefinitionsArguments, + array $methodArguments, + File $phpcsFile, + array $paramPointers + ) { + if (!in_array($paramDefinitionsArguments, $methodArguments)) { + $phpcsFile->addWarning( + $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 + ) { + $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->addWarning( + $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( + $stackPtr, + array $paramDefinitions, + array $paramPointers, + File $phpcsFile, + array $methodArguments + ) { + $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->addWarning( + $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( + $ptr, + File $phpcsFile, + array $methodArguments, + array $paramDefinitions, + array $paramPointers + ) { + switch (count($paramDefinitions)) { + case 0: + $phpcsFile->addWarning( + 'Missing both type and parameter', + $paramPointers[$ptr], + 'MethodArguments' + ); + break; + case 1: + if (preg_match('/^\$.*/', $paramDefinitions[0])) { + $phpcsFile->addWarning( + 'Type is not specified', + $paramPointers[$ptr], + 'MethodArguments' + ); + } + break; + case 2: + if ($this->isInvalidType($paramDefinitions[0])) { + $phpcsFile->addWarning( + $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->addWarning( + 'Type is not specified', + $paramPointers[$ptr], + 'MethodArguments' + ); + if ($this->isInvalidType($paramDefinitions[0])) { + $phpcsFile->addWarning( + $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( + $stackPtr, + array $paramDefinitions, + array $paramPointers, + File $phpcsFile, + array $methodArguments, + $previousCommentOpenPtr, + $previousCommentClosePtr + ) { + $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->addWarning('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 + ); + } +} diff --git a/Magento/Sniffs/Classes/ObjectManagerSniff.php b/Magento/Sniffs/Classes/ObjectManagerSniff.php new file mode 100644 index 00000000..e2c41ae6 --- /dev/null +++ b/Magento/Sniffs/Classes/ObjectManagerSniff.php @@ -0,0 +1,85 @@ +getTokens(); + $methodPosition = $phpcsFile->findNext(T_STRING, $stackPtr + 1); + if ($methodPosition !== false && + in_array($tokens[$methodPosition]['content'], $this->objectManagerMethods) + ) { + $objectManagerPosition = $phpcsFile->findPrevious([T_STRING, T_VARIABLE], $stackPtr - 1); + if ($objectManagerPosition !== false) { + $objectManagerName = strtolower($tokens[$objectManagerPosition]['content']); + if ($tokens[$objectManagerPosition]['code'] === T_VARIABLE) { + $objectManagerName = substr($objectManagerName, 1); + } + if (in_array($objectManagerName, $this->objectManagerNames)) { + $phpcsFile->addWarning($this->warningMessage, $stackPtr, $this->warningCode); + } + } + } + } +} diff --git a/Magento/Sniffs/Exceptions/NamespaceSniff.php b/Magento/Sniffs/Exceptions/NamespaceSniff.php index 8c2a244d..5d0e14eb 100644 --- a/Magento/Sniffs/Exceptions/NamespaceSniff.php +++ b/Magento/Sniffs/Exceptions/NamespaceSniff.php @@ -52,7 +52,7 @@ public function process(File $phpcsFile, $stackPtr) $exceptionClassName = trim($tokens[$posOfExceptionClassName]['content']); $posOfClassInUse = $phpcsFile->findNext(T_STRING, 0, $stackPtr, false, $exceptionClassName); if ($posOfClassInUse === false || $tokens[$posOfClassInUse]['level'] != 0) { - $phpcsFile->addError( + $phpcsFile->addWarning( $this->errorMessage, $stackPtr, $this->errorCode, diff --git a/Magento/Sniffs/Files/LineLengthSniff.php b/Magento/Sniffs/Files/LineLengthSniff.php new file mode 100644 index 00000000..528baeed --- /dev/null +++ b/Magento/Sniffs/Files/LineLengthSniff.php @@ -0,0 +1,36 @@ +previousLineContent) !== 0; + $this->previousLineContent = $lineContent; + if (! $currentLineMatch && !$previousLineMatch) { + parent::checkLineLength($phpcsFile, $stackPtr, $lineContent); + } + } +} diff --git a/Magento/Sniffs/Less/AvoidIdSniff.php b/Magento/Sniffs/Less/AvoidIdSniff.php new file mode 100644 index 00000000..ae0c7e23 --- /dev/null +++ b/Magento/Sniffs/Less/AvoidIdSniff.php @@ -0,0 +1,100 @@ + div, + * #foo ~ div, + * #foo\3Abar ~ div, + * #foo\:bar ~ div, + * #foo.bar .baz, + * div#foo { + * blah: 'abc'; + * } + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + // Find the next non-selector token + $nextToken = $phpcsFile->findNext($this->selectorTokens, $stackPtr + 1, null, true); + + // Anything except a { or a , means this is not a selector + if ($nextToken !== false && in_array($tokens[$nextToken]['code'], [T_OPEN_CURLY_BRACKET, T_COMMA])) { + $phpcsFile->addWarning('Id selector is used', $stackPtr, 'IdSelectorUsage'); + } + } +} diff --git a/Magento/Sniffs/Less/BracesFormattingSniff.php b/Magento/Sniffs/Less/BracesFormattingSniff.php new file mode 100644 index 00000000..f56fe3e3 --- /dev/null +++ b/Magento/Sniffs/Less/BracesFormattingSniff.php @@ -0,0 +1,88 @@ +getTokens(); + + if (T_OPEN_CURLY_BRACKET === $tokens[$stackPtr]['code']) { + if (TokenizerSymbolsInterface::WHITESPACE !== $tokens[$stackPtr - 1]['content']) { + $phpcsFile->addWarning('Space before opening brace is missing', $stackPtr, 'SpacingBeforeOpen'); + } + + return; + } + + $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($next === false) { + return; + } + + if (!in_array($tokens[$next]['code'], [T_CLOSE_TAG, T_CLOSE_CURLY_BRACKET])) { + $found = (($tokens[$next]['line'] - $tokens[$stackPtr]['line']) - 1); + if ($found !== 1) { + $error = 'Expected one blank line after closing brace of class definition; %s found'; + $data = [$found]; + // Will be implemented in MAGETWO-49778 + //$phpcsFile->addWarning($error, $stackPtr, 'SpacingAfterClose', $data); + } + } + + // Ignore nested style definitions from here on. The spacing before the closing brace + // (a single blank line) will be enforced by the above check, which ensures there is a + // blank line after the last nested class. + $found = $phpcsFile->findPrevious( + T_CLOSE_CURLY_BRACKET, + ($stackPtr - 1), + $tokens[$stackPtr]['bracket_opener'] + ); + + if ($found !== false) { + return; + } + + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($prev !== false && $tokens[$prev]['line'] !== ($tokens[$stackPtr]['line'] - 1)) { + $num = ($tokens[$stackPtr]['line'] - $tokens[$prev]['line'] - 1); + $error = 'Expected 0 blank lines before closing brace of class definition; %s found'; + $data = [$num]; + $phpcsFile->addWarning($error, $stackPtr, 'SpacingBeforeClose', $data); + } + } +} diff --git a/Magento/Sniffs/Less/ClassNamingSniff.php b/Magento/Sniffs/Less/ClassNamingSniff.php new file mode 100644 index 00000000..64995712 --- /dev/null +++ b/Magento/Sniffs/Less/ClassNamingSniff.php @@ -0,0 +1,65 @@ +getTokens(); + + if (T_WHITESPACE !== $tokens[$stackPtr - 1]['code'] + && !in_array($tokens[$stackPtr - 1]['content'], [ + TokenizerSymbolsInterface::INDENT_SPACES, + TokenizerSymbolsInterface::NEW_LINE, + ]) + ) { + return; + } + + $className = $tokens[$stackPtr + 1]['content']; + if (preg_match_all('/[^a-z0-9\-_]/U', $className, $matches)) { + $phpcsFile->addWarning('Class name contains not allowed symbols', $stackPtr, 'NotAllowedSymbol', $matches); + } + } +} diff --git a/Magento/Sniffs/Less/ColonSpacingSniff.php b/Magento/Sniffs/Less/ColonSpacingSniff.php new file mode 100644 index 00000000..82c8b5ad --- /dev/null +++ b/Magento/Sniffs/Less/ColonSpacingSniff.php @@ -0,0 +1,113 @@ +getTokens(); + + if ($this->needValidateSpaces($phpcsFile, $stackPtr, $tokens)) { + $this->validateSpaces($phpcsFile, $stackPtr, $tokens); + } + } + + /** + * Check is it need to check spaces + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * + * @return bool + */ + private function needValidateSpaces(File $phpcsFile, $stackPtr, $tokens) + { + $nextSemicolon = $phpcsFile->findNext(T_SEMICOLON, $stackPtr); + + if (false === $nextSemicolon + || ($tokens[$nextSemicolon]['line'] !== $tokens[$stackPtr]['line']) + || TokenizerSymbolsInterface::BITWISE_AND === $tokens[$stackPtr - 1]['content'] + ) { + return false; + } + + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($tokens[$prev]['code'] !== T_STYLE) { + // The colon is not part of a style definition. + return false; + } + + if ($tokens[$prev]['content'] === 'progid') { + // Special case for IE filters. + return false; + } + + return true; + } + + /** + * Validate Colon Spacing according to requirements + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * + * @return void + */ + private function validateSpaces(File $phpcsFile, $stackPtr, array $tokens) + { + if (T_WHITESPACE === $tokens[($stackPtr - 1)]['code']) { + $phpcsFile->addWarning('There must be no space before a colon in a style definition', $stackPtr, 'Before'); + } + + if (T_WHITESPACE !== $tokens[($stackPtr + 1)]['code']) { + $phpcsFile->addWarning('Expected 1 space after colon in style definition; 0 found', $stackPtr, 'NoneAfter'); + } else { + $content = $tokens[($stackPtr + 1)]['content']; + if (false === strpos($content, $phpcsFile->eolChar)) { + $length = strlen($content); + if ($length !== 1) { + $error = 'Expected 1 space after colon in style definition; %s found'; + $phpcsFile->addWarning($error, $stackPtr, 'After'); + } + } else { + $error = 'Expected 1 space after colon in style definition; newline found'; + $phpcsFile->addWarning($error, $stackPtr, 'AfterNewline'); + } + } + } +} diff --git a/Magento/Sniffs/Less/ColourDefinitionSniff.php b/Magento/Sniffs/Less/ColourDefinitionSniff.php new file mode 100644 index 00000000..80e4642a --- /dev/null +++ b/Magento/Sniffs/Less/ColourDefinitionSniff.php @@ -0,0 +1,65 @@ +getTokens(); + $colour = $tokens[$stackPtr]['content']; + + $variablePtr = $phpcsFile->findPrevious(T_ASPERAND, $stackPtr); + if ((false === $variablePtr) || ($tokens[$stackPtr]['line'] !== $tokens[$variablePtr]['line'])) { + $phpcsFile->addWarning('Hexadecimal value should be used for variable', $stackPtr, 'NotInVariable'); + } + + $expected = strtolower($colour); + if ($colour !== $expected) { + $error = 'CSS colours must be defined in lowercase; expected %s but found %s'; + $phpcsFile->addWarning($error, $stackPtr, 'NotLower', [$expected, $colour]); + } + + // Now check if shorthand can be used. + if (strlen($colour) !== 7) { + return; + } + + if ($colour[1] === $colour[2] && $colour[3] === $colour[4] && $colour[5] === $colour[6]) { + $expected = '#' . $colour[1] . $colour[3] . $colour[5]; + $error = 'CSS colours must use shorthand if available; expected %s but found %s'; + $phpcsFile->addWarning($error, $stackPtr, 'Shorthand', [$expected, $colour]); + } + } +} diff --git a/Magento/Sniffs/Less/CombinatorIndentationSniff.php b/Magento/Sniffs/Less/CombinatorIndentationSniff.php new file mode 100644 index 00000000..e7e87122 --- /dev/null +++ b/Magento/Sniffs/Less/CombinatorIndentationSniff.php @@ -0,0 +1,49 @@ +getTokens(); + + $prevPtr = $stackPtr - 1; + $nextPtr = $stackPtr + 1; + + if (($tokens[$prevPtr]['code'] !== T_WHITESPACE) || ($tokens[$nextPtr]['code'] !== T_WHITESPACE)) { + $phpcsFile->addWarning('Spaces should be before and after combinators', $stackPtr, 'NoSpaces'); + } + } +} diff --git a/Magento/Sniffs/Less/CommentLevelsSniff.php b/Magento/Sniffs/Less/CommentLevelsSniff.php new file mode 100644 index 00000000..660acfc7 --- /dev/null +++ b/Magento/Sniffs/Less/CommentLevelsSniff.php @@ -0,0 +1,198 @@ + T_STRING, + self::SECOND_LEVEL_COMMENT => T_DEC, + ]; + + /** + * A list of tokenizers this sniff supports. + * + * @var array + */ + public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; + + /** + * @inheritdoc + */ + public function register() + { + return [T_STRING]; + } + + /** + * @inheritdoc + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if ((T_STRING !== $tokens[$stackPtr]['code']) + || (self::COMMENT_STRING !== $tokens[$stackPtr]['content']) + || (1 === $tokens[$stackPtr]['line']) + ) { + return; + } + + $textInSameLine = $phpcsFile->findPrevious([T_STRING, T_STYLE], $stackPtr - 1); + + // is inline comment + if ((false !== $textInSameLine) + && ($tokens[$textInSameLine]['line'] === $tokens[$stackPtr]['line']) + ) { + $this->validateInlineComment($phpcsFile, $stackPtr, $tokens); + return; + } + + // validation of levels comments + if (!in_array($tokens[$stackPtr + 1]['content'], [ + TokenizerSymbolsInterface::DOUBLE_WHITESPACE, + TokenizerSymbolsInterface::NEW_LINE, + ]) + ) { + $phpcsFile->addWarning('Level\'s comment does not have 2 spaces after "//"', $stackPtr, 'SpacesMissed'); + } + + if (!$this->isNthLevelComment($phpcsFile, $stackPtr, $tokens)) { + return; + } + + if (!$this->checkNthLevelComment($phpcsFile, $stackPtr, $tokens)) { + $phpcsFile->addWarning( + 'First and second level comments must be surrounded by empty lines', + $stackPtr, + 'SpaceMissed' + ); + } + } + + /** + * Validate that inline comment responds to given requirements + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * @return bool + */ + private function validateInlineComment(File $phpcsFile, $stackPtr, array $tokens) + { + if ($tokens[$stackPtr + 1]['content'] !== TokenizerSymbolsInterface::WHITESPACE) { + $phpcsFile->addWarning('Inline comment should have 1 space after "//"', $stackPtr, 'SpaceMissedAfter'); + } + if ($tokens[$stackPtr - 1]['content'] !== TokenizerSymbolsInterface::WHITESPACE) { + $phpcsFile->addWarning('Inline comment should have 1 space before "//"', $stackPtr, 'SpaceMissedBefore'); + } + } + + /** + * Check is it n-th level comment was found + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * @return bool + */ + private function isNthLevelComment(File $phpcsFile, $stackPtr, array $tokens) + { + $nthLevelCommentFound = false; + $levelComment = 0; + + foreach ($this->levelComments as $code => $comment) { + $levelComment = $phpcsFile->findNext($comment, $stackPtr, null, false, $code); + if (false !== $levelComment) { + $nthLevelCommentFound = true; + break; + } + } + + if (false === $nthLevelCommentFound) { + return false; + } + + $currentLine = $tokens[$stackPtr]['line']; + $levelCommentLine = $tokens[$levelComment]['line']; + + if ($currentLine !== $levelCommentLine) { + return false; + } + + return true; + } + + /** + * Check is it n-th level comment is correct + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * @return bool + */ + private function checkNthLevelComment(File $phpcsFile, $stackPtr, array $tokens) + { + $correct = false; + + $nextLine = $phpcsFile->findNext( + T_WHITESPACE, + $stackPtr, + null, + false, + TokenizerSymbolsInterface::NEW_LINE + ); + + if (false === $nextLine) { + return $correct; + } + + if (($tokens[$nextLine]['content'] !== TokenizerSymbolsInterface::NEW_LINE) + || ($tokens[$nextLine + 1]['content'] !== TokenizerSymbolsInterface::NEW_LINE) + ) { + return $correct; + } + + $commentLinePtr = $stackPtr; + while ($tokens[$commentLinePtr - 2]['line'] > 1) { + $commentLinePtr = $phpcsFile->findPrevious(T_STRING, $commentLinePtr - 1, null, false, '//'); + + if (false === $commentLinePtr) { + continue; + } + + if (($tokens[$commentLinePtr - 1]['content'] === TokenizerSymbolsInterface::NEW_LINE) + && ($tokens[$commentLinePtr - 2]['content'] === TokenizerSymbolsInterface::NEW_LINE) + ) { + $correct = true; + break; + } + } + + return $correct; + } +} diff --git a/Magento/Sniffs/Less/ImportantPropertySniff.php b/Magento/Sniffs/Less/ImportantPropertySniff.php new file mode 100644 index 00000000..c097bae1 --- /dev/null +++ b/Magento/Sniffs/Less/ImportantPropertySniff.php @@ -0,0 +1,51 @@ +getTokens(); + + // Will be implemented in MAGETWO-49778 + //$phpcsFile->addWarning('!important is used', $stackPtr, '!ImportantIsUsed'); + + if (($tokens[$stackPtr + 1]['content'] === 'important') + && ($tokens[$stackPtr - 1]['content'] !== TokenizerSymbolsInterface::WHITESPACE) + ) { + $phpcsFile->addWarning('Space before !important is missing', $stackPtr, 'NoSpace'); + } + } +} diff --git a/Magento/Sniffs/Less/IndentationSniff.php b/Magento/Sniffs/Less/IndentationSniff.php new file mode 100644 index 00000000..aeaaf89a --- /dev/null +++ b/Magento/Sniffs/Less/IndentationSniff.php @@ -0,0 +1,106 @@ +getTokens(); + + $numTokens = (count($tokens) - 2); + $indentLevel = 0; + for ($i = 1; $i < $numTokens; $i++) { + if ($tokens[$i]['code'] === T_COMMENT) { + // Don't check the indent of comments. + continue; + } + + if ($tokens[$i]['code'] === T_OPEN_CURLY_BRACKET) { + $indentLevel++; + } elseif ($tokens[($i + 1)]['code'] === T_CLOSE_CURLY_BRACKET) { + $indentLevel--; + } + + if ($tokens[$i]['column'] !== 1) { + continue; + } + + // We started a new line, so check indent. + if ($tokens[$i]['code'] === T_WHITESPACE) { + $content = str_replace($phpcsFile->eolChar, '', $tokens[$i]['content']); + $foundIndent = strlen($content); + } else { + $foundIndent = 0; + } + + $expectedIndent = ($indentLevel * $this->indent); + if (!($expectedIndent > 0 && strpos($tokens[$i]['content'], $phpcsFile->eolChar) !== false) + && ($foundIndent !== $expectedIndent) + && (!in_array($tokens[$i + 1]['code'], $this->styleCodesToSkip)) + ) { + $error = 'Line indented incorrectly; expected %s spaces, found %s'; + $phpcsFile->addWarning($error, $i, 'Incorrect', [$expectedIndent, $foundIndent]); + } + + if ($indentLevel > $this->maxIndentLevel) { + // Will be implemented in MAGETWO-49778 + // $phpcsFile->addWarning('Avoid using more than three levels of nesting', $i, 'IncorrectNestingLevel'); + } + } + } +} diff --git a/Magento/Sniffs/Less/PropertiesLineBreakSniff.php b/Magento/Sniffs/Less/PropertiesLineBreakSniff.php new file mode 100644 index 00000000..23c130b5 --- /dev/null +++ b/Magento/Sniffs/Less/PropertiesLineBreakSniff.php @@ -0,0 +1,52 @@ +getTokens(); + + $prevPtr = $phpcsFile->findPrevious(T_SEMICOLON, ($stackPtr - 1)); + if (false === $prevPtr) { + return; + } + + if ($tokens[$prevPtr]['line'] === $tokens[$stackPtr]['line']) { + $error = 'Each property must be on a line by itself'; + $phpcsFile->addWarning($error, $stackPtr, 'SameLine'); + } + } +} diff --git a/Magento/Sniffs/Less/PropertiesSortingSniff.php b/Magento/Sniffs/Less/PropertiesSortingSniff.php new file mode 100644 index 00000000..4ad04444 --- /dev/null +++ b/Magento/Sniffs/Less/PropertiesSortingSniff.php @@ -0,0 +1,119 @@ +getTokens(); + $currentToken = $tokens[$stackPtr]; + + // if variables, mixins, extends area used - skip + if ((T_ASPERAND === $tokens[$stackPtr - 1]['code']) + || in_array($tokens[$stackPtr]['content'], $this->styleSymbolsToSkip) + ) { + return; + } + + $nextCurlyBracket = $phpcsFile->findNext(T_OPEN_CURLY_BRACKET, $stackPtr + 1); + if (in_array($currentToken['code'], [T_OPEN_CURLY_BRACKET, T_CLOSE_CURLY_BRACKET]) + || ((false !== $nextCurlyBracket) && ($tokens[$nextCurlyBracket]['line'] === $tokens[$stackPtr]['line'])) + ) { + if ($this->properties) { + // validate collected properties before erase them + $this->validatePropertiesSorting($phpcsFile, $stackPtr, $this->properties); + } + + $this->properties = []; + return; + } + + if (T_STYLE === $currentToken['code']) { + $this->properties[] = $currentToken['content']; + } + } + + /** + * Validate sorting of properties of class + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $properties + * + * @return void + */ + private function validatePropertiesSorting(File $phpcsFile, $stackPtr, array $properties) + { + // Fix needed for cases when incorrect properties passed for validation due to bug in PHP tokens. + $symbolsForSkip = ['(', 'block', 'field']; + $properties = array_filter( + $properties, + function ($var) use ($symbolsForSkip) { + return !in_array($var, $symbolsForSkip); + } + ); + + $originalProperties = $properties; + sort($properties); + + if ($originalProperties !== $properties) { + $delimiter = $phpcsFile->findPrevious(T_SEMICOLON, $stackPtr); + $phpcsFile->addWarning('Properties sorted not alphabetically', $delimiter, 'PropertySorting'); + } + } +} diff --git a/Magento/Sniffs/Less/QuotesSniff.php b/Magento/Sniffs/Less/QuotesSniff.php new file mode 100644 index 00000000..fb5bb1d8 --- /dev/null +++ b/Magento/Sniffs/Less/QuotesSniff.php @@ -0,0 +1,46 @@ +getTokens(); + + if (false !== strpos($tokens[$stackPtr]['content'], '"')) { + $phpcsFile->addWarning('Use single quotes', $stackPtr, 'DoubleQuotes'); + } + } +} diff --git a/Magento/Sniffs/Less/SelectorDelimiterSniff.php b/Magento/Sniffs/Less/SelectorDelimiterSniff.php new file mode 100644 index 00000000..838f00d6 --- /dev/null +++ b/Magento/Sniffs/Less/SelectorDelimiterSniff.php @@ -0,0 +1,90 @@ +getTokens(); + + // Check that there's no spaces before delimiter + if ($tokens[$stackPtr - 1]['code'] === T_WHITESPACE) { + $phpcsFile->addWarning('Spaces should not be before delimiter', $stackPtr - 1, 'SpacesBeforeDelimiter'); + } + + $this->validateParenthesis($phpcsFile, $stackPtr, $tokens); + } + + /** + * Parenthesis validation. + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * @return void + */ + private function validateParenthesis(File $phpcsFile, $stackPtr, array $tokens) + { + $nextPtr = $stackPtr + 1; + + $nextClassPtr = $phpcsFile->findNext(T_STRING_CONCAT, $nextPtr); + $nextOpenBrace = $phpcsFile->findNext(T_OPEN_CURLY_BRACKET, $nextPtr); + + if ($nextClassPtr === false || $nextOpenBrace === false) { + return; + } + + $stackLine = $tokens[$stackPtr]['line']; + $nextClassLine = $tokens[$nextPtr]['line']; + $nextOpenBraceLine = $tokens[$nextOpenBrace]['line']; + + // Check that each class declaration goes from new line + if (($stackLine === $nextClassLine) && ($stackLine === $nextOpenBraceLine)) { + $prevParenthesis = $phpcsFile->findPrevious(T_OPEN_PARENTHESIS, $stackPtr); + $nextParenthesis = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $stackPtr); + + if ((false !== $prevParenthesis) && (false !== $nextParenthesis) + && ($tokens[$prevParenthesis]['line'] === $tokens[$stackPtr]['line']) + && ($tokens[$nextParenthesis]['line'] === $tokens[$stackPtr]['line']) + ) { + return; + } + + $error = 'Add a line break after each selector delimiter'; + $phpcsFile->addWarning($error, $nextOpenBrace, 'LineBreakAfterDelimiter'); + } + } +} diff --git a/Magento/Sniffs/Less/SemicolonSpacingSniff.php b/Magento/Sniffs/Less/SemicolonSpacingSniff.php new file mode 100644 index 00000000..12bb3e7c --- /dev/null +++ b/Magento/Sniffs/Less/SemicolonSpacingSniff.php @@ -0,0 +1,115 @@ +getTokens(); + + if (in_array($tokens[$stackPtr]['content'], $this->styleSymbolsToSkip)) { + return; + } + + $semicolonPtr = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1)); + if ($tokens[$semicolonPtr]['line'] !== $tokens[$stackPtr]['line']) { + $semicolonPtr = $phpcsFile->findNext(T_STYLE, ($stackPtr + 1), null, false, ";"); + } + + $this->validateSemicolon($phpcsFile, $stackPtr, $tokens, $semicolonPtr); + $this->validateSpaces($phpcsFile, $stackPtr, $tokens, $semicolonPtr); + } + + /** + * Semicolon validation. + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * @param int $semicolonPtr + * @return void + */ + private function validateSemicolon(File $phpcsFile, $stackPtr, array $tokens, $semicolonPtr) + { + if ((false === $semicolonPtr || $tokens[$semicolonPtr]['line'] !== $tokens[$stackPtr]['line']) + && (isset($tokens[$stackPtr - 1]) && !in_array($tokens[$stackPtr - 1]['code'], $this->styleCodesToSkip)) + && (T_COLON !== $tokens[$stackPtr + 1]['code']) + ) { + $error = 'Style definitions must end with a semicolon'; + $phpcsFile->addWarning($error, $stackPtr, 'NotAtEnd'); + } + } + + /** + * Spaces validation. + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * @param int $semicolonPtr + * @return void + */ + private function validateSpaces(File $phpcsFile, $stackPtr, array $tokens, $semicolonPtr) + { + if (!isset($tokens[($semicolonPtr - 1)])) { + return; + } + + if ($tokens[($semicolonPtr - 1)]['code'] === T_WHITESPACE) { + $length = strlen($tokens[($semicolonPtr - 1)]['content']); + $error = 'Expected 0 spaces before semicolon in style definition; %s found'; + $data = [$length]; + $phpcsFile->addWarning($error, $stackPtr, 'SpaceFound', $data); + } + } +} diff --git a/Magento/Sniffs/Less/TokenizerSymbolsInterface.php b/Magento/Sniffs/Less/TokenizerSymbolsInterface.php new file mode 100644 index 00000000..db6a2fe1 --- /dev/null +++ b/Magento/Sniffs/Less/TokenizerSymbolsInterface.php @@ -0,0 +1,27 @@ +getTokens(); + + if (0 === strpos($tokens[$stackPtr + 1]['content'], '-') + && in_array($tokens[$stackPtr - 1]['content'], $this->symbolsBeforeConcat) + ) { + $phpcsFile->addWarning('Concatenation is used', $stackPtr, 'ConcatenationUsage'); + } + } +} diff --git a/Magento/Sniffs/Less/TypeSelectorsSniff.php b/Magento/Sniffs/Less/TypeSelectorsSniff.php new file mode 100644 index 00000000..5d2823f8 --- /dev/null +++ b/Magento/Sniffs/Less/TypeSelectorsSniff.php @@ -0,0 +1,97 @@ +getTokens(); + + $bracketPtr = $phpcsFile->findNext(T_OPEN_CURLY_BRACKET, $stackPtr); + + if (false === $bracketPtr) { + return; + } + + $isBracketOnSameLane = (bool)($tokens[$bracketPtr]['line'] === $tokens[$stackPtr]['line']); + + if (!$isBracketOnSameLane) { + return; + } + + if ((T_STRING === $tokens[$stackPtr - 1]['code']) + && in_array($tokens[$stackPtr - 1]['content'], $this->tags) + ) { + // Will be implemented in MAGETWO-49778 + //$phpcsFile->addWarning('Type selector is used', $stackPtr, 'TypeSelector'); + } + + for ($i = $stackPtr; $i < $bracketPtr; $i++) { + if (preg_match('/[A-Z]/', $tokens[$i]['content'])) { + $phpcsFile->addWarning('Selector contains uppercase symbols', $stackPtr, 'UpperCaseSelector'); + } + } + } +} diff --git a/Magento/Sniffs/Less/VariablesSniff.php b/Magento/Sniffs/Less/VariablesSniff.php new file mode 100644 index 00000000..3663254f --- /dev/null +++ b/Magento/Sniffs/Less/VariablesSniff.php @@ -0,0 +1,80 @@ +getTokens(); + $currentToken = $tokens[$stackPtr]; + + $nextColon = $phpcsFile->findNext(T_COLON, $stackPtr); + $nextSemicolon = $phpcsFile->findNext(T_SEMICOLON, $stackPtr); + if ((false === $nextColon) || (false === $nextSemicolon)) { + return; + } + + $isVariableDeclaration = ($currentToken['line'] === $tokens[$nextColon]['line']) + && ($currentToken['line'] === $tokens[$nextSemicolon]['line']) + && (T_STRING === $tokens[$stackPtr + 1]['code']) + && (T_COLON === $tokens[$stackPtr + 2]['code']); + + if (!$isVariableDeclaration) { + return; + } + + $classBefore = $phpcsFile->findPrevious(T_STYLE, $stackPtr); + if (false !== $classBefore) { + $phpcsFile->addWarning( + 'Variable declaration located not in the beginning of general comments', + $stackPtr, + 'VariableLocation' + ); + } + + $variableName = $tokens[$stackPtr + 1]['content']; + if (preg_match('/[A-Z]/', $variableName)) { + $phpcsFile->addWarning( + 'Variable declaration contains uppercase symbols', + $stackPtr, + 'VariableUppercase' + ); + } + } +} diff --git a/Magento/Sniffs/Less/ZeroUnitsSniff.php b/Magento/Sniffs/Less/ZeroUnitsSniff.php new file mode 100644 index 00000000..6ab2fc6c --- /dev/null +++ b/Magento/Sniffs/Less/ZeroUnitsSniff.php @@ -0,0 +1,78 @@ +getTokens(); + $tokenCode = $tokens[$stackPtr]['code']; + $tokenContent = $tokens[$stackPtr]['content']; + + $nextToken = $tokens[$stackPtr + 1]; + + if (T_LNUMBER === $tokenCode + && "0" === $tokenContent + && T_STRING === $nextToken['code'] + && in_array($nextToken['content'], $this->units) + ) { + $phpcsFile->addWarning('Units specified for "0" value', $stackPtr, 'ZeroUnitFound'); + } + + if ((T_DNUMBER === $tokenCode) + && 0 === strpos($tokenContent, "0") + && ((float)$tokenContent < 1) + ) { + $phpcsFile->addWarning('Values starts from "0"', $stackPtr, 'ZeroUnitFound'); + } + } +} diff --git a/Magento/Sniffs/NamingConvention/ReservedWordsSniff.php b/Magento/Sniffs/NamingConvention/ReservedWordsSniff.php new file mode 100644 index 00000000..69c9c793 --- /dev/null +++ b/Magento/Sniffs/NamingConvention/ReservedWordsSniff.php @@ -0,0 +1,116 @@ + '7', + 'float' => '7', + 'bool' => '7', + 'string' => '7', + 'true' => '7', + 'false' => '7', + 'null' => '7', + 'void' => '7.1', + 'iterable' => '7.1', + 'resource' => '7', + 'object' => '7', + 'mixed' => '7', + 'numeric' => '7', + ]; + + /** + * @inheritdoc + */ + public function register() + { + return [T_CLASS, T_INTERFACE, T_TRAIT, T_NAMESPACE]; + } + + /** + * Check all namespace parts + * + * @param File $sourceFile + * @param int $stackPtr + * @return void + */ + protected function validateNamespace(File $sourceFile, $stackPtr) + { + $stackPtr += 2; + $tokens = $sourceFile->getTokens(); + while ($stackPtr < $sourceFile->numTokens && $tokens[$stackPtr]['code'] !== T_SEMICOLON) { + if ($tokens[$stackPtr]['code'] === T_WHITESPACE || $tokens[$stackPtr]['code'] === T_NS_SEPARATOR) { + $stackPtr++; //skip "namespace" and whitespace + continue; + } + $namespacePart = $tokens[$stackPtr]['content']; + if (isset($this->reservedWords[strtolower($namespacePart)])) { + $sourceFile->addError( + 'Cannot use "%s" in namespace as it is reserved since PHP %s', + $stackPtr, + 'Namespace', + [$namespacePart, $this->reservedWords[strtolower($namespacePart)]] + ); + } + $stackPtr++; + } + } + + /** + * Check class name not having reserved words + * + * @param File $sourceFile + * @param int $stackPtr + * @return void + */ + protected function validateClass(File $sourceFile, $stackPtr) + { + $tokens = $sourceFile->getTokens(); + $stackPtr += 2; //skip "class" and whitespace + $className = strtolower($tokens[$stackPtr]['content']); + if (isset($this->reservedWords[$className])) { + $sourceFile->addError( + 'Cannot use "%s" as class name as it is reserved since PHP %s', + $stackPtr, + 'Class', + [$className, $this->reservedWords[$className]] + ); + } + } + + /** + * @inheritdoc + */ + public function process(File $sourceFile, $stackPtr) + { + $tokens = $sourceFile->getTokens(); + switch ($tokens[$stackPtr]['code']) { + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + $this->validateClass($sourceFile, $stackPtr); + break; + case T_NAMESPACE: + $this->validateNamespace($sourceFile, $stackPtr); + break; + } + } +} diff --git a/Magento/Sniffs/PHP/DiscouragedFunctionSniff.php b/Magento/Sniffs/PHP/DiscouragedFunctionSniff.php index 8a0fbe6e..7cf74e84 100644 --- a/Magento/Sniffs/PHP/DiscouragedFunctionSniff.php +++ b/Magento/Sniffs/PHP/DiscouragedFunctionSniff.php @@ -176,6 +176,7 @@ class DiscouragedFunctionSniff extends ForbiddenFunctionsSniff '^ngettext$' => null, '^ob_get_contents$' => null, '^ob_get_flush$' => null, + '^ob_start$' => null, '^rawurldecode$' => null, '^shm_get_var$' => null, '^stripcslashes$' => null, diff --git a/Magento/Sniffs/PHP/LiteralNamespacesSniff.php b/Magento/Sniffs/PHP/LiteralNamespacesSniff.php new file mode 100644 index 00000000..c1e83b96 --- /dev/null +++ b/Magento/Sniffs/PHP/LiteralNamespacesSniff.php @@ -0,0 +1,75 @@ +getTokens(); + if ($sourceFile->findPrevious(T_STRING_CONCAT, $stackPtr, $stackPtr - 3) || + $sourceFile->findNext(T_STRING_CONCAT, $stackPtr, $stackPtr + 3) + ) { + return; + } + + $content = trim($tokens[$stackPtr]['content'], "\"'"); + // replace double slashes from class name for avoiding problems with class autoload + if (strpos($content, '\\') !== false) { + $content = preg_replace('|\\\{2,}|', '\\', $content); + } + + if (preg_match($this->literalNamespacePattern, $content) === 1 && $this->classExists($content)) { + $sourceFile->addWarning( + "Use ::class notation instead.", + $stackPtr, + 'LiteralClassUsage' + ); + } + } + + /** + * @param string $className + * @return bool + */ + private function classExists($className) + { + if (!isset($this->classNames[$className])) { + $this->classNames[$className] = class_exists($className) || interface_exists($className); + } + return $this->classNames[$className]; + } +} diff --git a/Magento/Sniffs/PHP/ShortEchoSyntaxSniff.php b/Magento/Sniffs/PHP/ShortEchoSyntaxSniff.php new file mode 100644 index 00000000..7cbc87e8 --- /dev/null +++ b/Magento/Sniffs/PHP/ShortEchoSyntaxSniff.php @@ -0,0 +1,47 @@ +getTokens(); + $openTag = $tokens[$stackPtr]; + + // HHVM Will classify findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($tokens[$nextToken]['code'] == T_ECHO) { + $phpcsFile->addWarning( + 'Short echo tag syntax must be used; expected "getTokens(); - $doubleColon = $tokens[$stackPtr]; - $currentLine = $doubleColon['line']; - $lineContent = $this->getLineContent($phpcsFile, $currentLine); - $previousLineContent = $this->getLineContent($phpcsFile, $currentLine - 1); - $previousLineRegexp = '~__\($|Phrase\($~'; - $currentLineRegexp = '~__\(.+\)|Phrase\(.+\)~'; - $currentLineMatch = preg_match($currentLineRegexp, $lineContent) !== 0; - $previousLineMatch = preg_match($previousLineRegexp, $previousLineContent) !== 0; - $this->previousLineContent = $lineContent; - $constantRegexp = '[^\'"]+::[A-Z_0-9]+.*'; - if ($currentLineMatch) { - $variableRegexp = "~__\({$constantRegexp}\)|Phrase\({$constantRegexp}\)~"; - if (preg_match($variableRegexp, $lineContent) !== 0) { - $phpcsFile->addWarning( - $this->warningMessage, - $this->getFirstLineToken($phpcsFile, $currentLine), - $this->warningCode - ); - } - } else { - if ($previousLineMatch) { - $variableRegexp = "~^\s+{$constantRegexp}~"; - if (preg_match($variableRegexp, $lineContent) !== 0) { - $phpcsFile->addWarning( - $this->warningMessage, - $this->getFirstLineToken($phpcsFile, $currentLine - 1), - $this->warningCode - ); - } + + // Make sure this is the first open tag + $previousOpenTag = $phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)); + if ($previousOpenTag !== false) { + return; + } + + $tokenCount = 0; + $currentLineContent = ''; + $currentLine = 1; + + for (; $tokenCount < $phpcsFile->numTokens; $tokenCount++) { + if ($tokens[$tokenCount]['line'] === $currentLine) { + $currentLineContent .= $tokens[$tokenCount]['content']; + } else { + $this->checkIfFirstArgumentConstant($phpcsFile, ($tokenCount - 1), $currentLineContent); + $currentLineContent = $tokens[$tokenCount]['content']; + $currentLine++; } } - } - /** - * Get line content by it's line number. - * - * @param File $phpcsFile - * @param int $line - * @return string - */ - private function getLineContent(File $phpcsFile, $line) - { - $tokens = $phpcsFile->getTokens(); - return implode('', array_column(array_filter($tokens, function ($item) use ($line) { - return $item['line'] == $line; - }), 'content')); + $this->checkIfFirstArgumentConstant($phpcsFile, ($tokenCount - 1), $currentLineContent); } /** - * Get index of first token in line. + * Checks if first argument of \Magento\Framework\Phrase or translation function is a constant * * @param File $phpcsFile - * @param int $line - * @return int + * @param int $stackPtr + * @param string $lineContent + * @return void */ - private function getFirstLineToken(File $phpcsFile, $line) - { - $tokens = $phpcsFile->getTokens(); - return array_keys(array_filter($tokens, function ($item) use ($line) { - return $item['line'] == $line; - }))[0]; + private function checkIfFirstArgumentConstant( + File $phpcsFile, + $stackPtr, + $lineContent + ) { + $previousLineRegexp = '/(__|Phrase)\($/im'; + $currentLineRegexp = '/(__|Phrase)\(.+\)/'; + $currentLineMatch = preg_match($currentLineRegexp, $lineContent) !== 0; + $previousLineMatch = preg_match($previousLineRegexp, $this->previousLineContent) !== 0; + $this->previousLineContent = $lineContent; + $error = 'Constants are not allowed as the first argument of translation function, use string literal instead'; + $constantRegexp = '[^\$\'"]+::[A-Z_0-9]+.*'; + if ($currentLineMatch) { + $variableRegexp = "/(__|Phrase)\({$constantRegexp}\)/"; + if (preg_match($variableRegexp, $lineContent) !== 0) { + $phpcsFile->addWarning($error, $stackPtr, 'VariableTranslation'); + } + } elseif ($previousLineMatch) { + $variableRegexp = "/^{$constantRegexp}/"; + if (preg_match($variableRegexp, $lineContent) !== 0) { + $phpcsFile->addWarning($error, $stackPtr, 'VariableTranslation'); + } + } } } diff --git a/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php b/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php new file mode 100644 index 00000000..9afd983b --- /dev/null +++ b/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php @@ -0,0 +1,68 @@ +getTokens(); + if ($this->doCheck($phpcsFile, $stackPtr, $tokens)) { + $previous = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); + if ($tokens[$stackPtr]['line'] - $tokens[$previous]['line'] < 2) { + $error = 'Empty line missed'; + $phpcsFile->addWarning($error, $stackPtr, '', null); + } + } + } + + /** + * Execute empty line missed check. + * + * @param File $phpcsFile + * @param int $stackPtr + * @param array $tokens + * @return bool + */ + private function doCheck(File $phpcsFile, $stackPtr, $tokens) + { + $result = false; + if ($phpcsFile->hasCondition($stackPtr, T_CLASS) || $phpcsFile->hasCondition($stackPtr, T_INTERFACE)) { + $result = true; + } + + if ($phpcsFile->hasCondition($stackPtr, T_FUNCTION)) { + $result = false; + } + $previous = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); + if ($tokens[$previous]['type'] === 'T_OPEN_CURLY_BRACKET') { + $result = false; + } + + if (strpos($tokens[$stackPtr]['content'], '/**') === false) { + $result = false; + } + + return $result; + } +} diff --git a/Magento/Tests/Annotation/ClassAnnotationStructureUnitTest.inc b/Magento/Tests/Annotation/ClassAnnotationStructureUnitTest.inc new file mode 100644 index 00000000..46b46037 --- /dev/null +++ b/Magento/Tests/Annotation/ClassAnnotationStructureUnitTest.inc @@ -0,0 +1,31 @@ + 1, + 26 => 1, + ]; + } +} diff --git a/Magento/Tests/Annotation/MethodAnnotationStructureUnitTest.inc b/Magento/Tests/Annotation/MethodAnnotationStructureUnitTest.inc new file mode 100644 index 00000000..6d8f0d60 --- /dev/null +++ b/Magento/Tests/Annotation/MethodAnnotationStructureUnitTest.inc @@ -0,0 +1,309 @@ +productVisibility = $productVisibility; + } + + /** + * block description + * + * {@inheritdoc} + * + * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + * @return void + */ + public function construct(AbstractDb $collection) + { + /** @var */ + $collection->setVisibility($this->productVisibility->getVisibleInCatalogIds()); + } + + /** + * Move category + * + * + * @param int $parentId new parent category id + * + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException|\Exception + */ + public function move($parentId) + { + /** + * Validate new parent category id. (category model is used for backward + * compatibility in event params) + */ + try { + $this->categoryRepository->get($parentId, $this->getStoreId()); + } catch (NoSuchEntityException $e) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Sorry, but we can\'t find the new parent category you selected.'), + $e + ); + } + return true; + } + + /** + * Block for short description + * + * This a long description {@inheritdoc} consists more lines as part of the long description + * on multi line. + * + * @param int $store + * + * + */ + public function getProductListDefaultSortBy26032($store) + { + return $store; + } + + /** + * + * + * + */ + public function getProductListDefaultSortBy2632() + { + } + + /** + * Block for short description + * + * This a long description {@inheritdoc} consists more lines as part of the long description + * on multi line. + * + * @param int $store + * + * + * + */ + public function getProductListDefaultSortBy2002($store) + { + return $store; + } + + /** + * + * block for short description + * + * @param int $store + * @return int + */ + public function getProductListDefaultSortBy3002($store) + { + return $store; + } + + /** + * Block for short description + * + * @see consists more lines as part of the long description + * on multi line. + * + * @param string $store + * @param string $foo + */ + public function getProductListDefaultSortBy12($store, $foo) + { + return $store === $foo; + } + + /** + * Block for short description + * + * {@inheritdoc} + * + * @param string $store + * @param string $foo + */ + public function getProductListDefaultSort2($store, $foo) + { + return $store === $foo; + } + + /** + * Block for short description + * + * a long description {@inheritdoc} consists more lines as part of the long description + * on multi line. + * + * @param string $store + * @param string $foo + */ + public function getProductListDefault($store, $foo) + { + return $store === $foo; + } + + /** + * Retrieve custom options + * + * @param ProductOptionInterface $productOption + * + * @return array + */ + protected function getCustomOptions(ProductOptionInterface $productOption) + { + if ($productOption + && $productOption->getExtensionAttributes() + && $productOption->getExtensionAttributes()->getCustomOptions() + ) { + return $productOption->getExtensionAttributes() + ->getCustomOptions(); + } + return []; + } + + /** + * This is the summary for a DocBlock. + * + * This is the description for a DocBlock. This text may contain + * multiple lines and even some _markdown_. + * * Markdown style lists function too + * * Just try this out once + * The section after the description contains the tags; which provide + * structured meta-data concerning the given element. + * + * @param int $example This is an example function/method parameter description. + * @param string $example2 This is a second example. + * + */ + public function getProductListDefaultSortBy2($example, $example2) + { + return $example === $example2; + } + + /** + * Returns the content of the tokens from the specified start position in + * the token stack for the specified length. + * + * @param int $start + * @param int $length + * + * @return string The token contents. + */ + public function getProductListDefaultSortBy($start, $length) + { + return $start === $length; + } + + /** + * Some text about this step/method returns the content of the tokens the token stack for the specified length + * + * @param string $name + * @param string $folder + * + * @see this file + * @When I create a file called :name in :folder + */ + public function getProductListDefaultSortBy222($name, $folder) + { + return $name === $folder; + } + + public function setExtensionAs(\Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } + + /** + * + * short description + * @param \Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes + * @return mixed + */ + public function setEn(\Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } + + /** + * @param \Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes + * @return mixed + */ + public function setExtenw(\Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } + + /** + * + * Short description + * @param \Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes + * @return mixed + */ + public function setExff(\Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } + + /** + * {@inheritdoc} + * + * @param int $start + * @param int $length + * + * @return string The token contents. + */ + public function getProductSortBy($start, $length) + { + return $start === $length; + } +} diff --git a/Magento/Tests/Annotation/MethodAnnotationStructureUnitTest.php b/Magento/Tests/Annotation/MethodAnnotationStructureUnitTest.php new file mode 100644 index 00000000..3744ec6c --- /dev/null +++ b/Magento/Tests/Annotation/MethodAnnotationStructureUnitTest.php @@ -0,0 +1,54 @@ + 1, + 18 => 1, + 30 => 1, + 36 => 1, + 45 => 2, + 47 => 1, + 55 => 1, + 63 => 1, + 80 => 1, + 111 => 1, + 117 => 1, + 136 => 1, + 144 => 2, + 184 => 1, + 227 => 1, + 235 => 1, + 268 => 2, + 269 => 1, + 277 => 1, + 278 => 1, + 288 => 1, + 289 => 1, + 298 => 1, + ]; + } +} diff --git a/Magento/Tests/Classes/ObjectManagerUnitTest.inc b/Magento/Tests/Classes/ObjectManagerUnitTest.inc new file mode 100644 index 00000000..7492f011 --- /dev/null +++ b/Magento/Tests/Classes/ObjectManagerUnitTest.inc @@ -0,0 +1,23 @@ +_objectManager->get(\Magento\Sales\Model\Order::class); + +$this->_om->get(\Magento\Sales\Model\Order::class); + +$this->om->create(\Magento\Sales\Model\Order::class); + +$shipment = $this->objectManager->create(\Magento\Sales\Model\Order::class); + +$productModel = $this->_giftWrapHelper->getObjectManager() + ->create(\Magento\Sales\Model\Order::class); + +$order = $block->getObjectManager()->create(\Magento\Sales\Model\Order::class); + +$order = $block->getObjectManager()->get(\Magento\Sales\Model\Order::class); + +$om = $block->get(\Magento\Sales\Model\Order::class); + +$om = $objectManager::get(\Magento\Sales\Model\Order::class); + +$objectManager = \Magento\Framework\App\ObjectManager::getInstance(); +$product = $objectManager->create(\Magento\Sales\Model\Order::class); diff --git a/Magento/Tests/Classes/ObjectManagerUnitTest.php b/Magento/Tests/Classes/ObjectManagerUnitTest.php new file mode 100644 index 00000000..4190f8f6 --- /dev/null +++ b/Magento/Tests/Classes/ObjectManagerUnitTest.php @@ -0,0 +1,40 @@ + 1, + 5 => 1, + 7 => 1, + 9 => 1, + 12 => 1, + 14 => 1, + 16 => 1, + 20 => 1, + 23 => 1, + ]; + } +} diff --git a/Magento/Tests/Exceptions/NamespaceUnitTest.php b/Magento/Tests/Exceptions/NamespaceUnitTest.php index d52742a6..e8bcfe94 100644 --- a/Magento/Tests/Exceptions/NamespaceUnitTest.php +++ b/Magento/Tests/Exceptions/NamespaceUnitTest.php @@ -18,9 +18,6 @@ class NamespaceUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 9 => 1, - 10 => 1, - 48 => 1, ]; } @@ -29,6 +26,10 @@ public function getErrorList() */ public function getWarningList() { - return []; + return [ + 9 => 1, + 10 => 1, + 48 => 1, + ]; } } diff --git a/Magento/Tests/NamingConvention/ReservedWordsUnitTest.inc b/Magento/Tests/NamingConvention/ReservedWordsUnitTest.inc new file mode 100644 index 00000000..8a165739 --- /dev/null +++ b/Magento/Tests/NamingConvention/ReservedWordsUnitTest.inc @@ -0,0 +1,22 @@ + 1, + 4 => 1, + 6 => 1, + 8 => 1, + 10 => 1, + 12 => 1, + 14 => 1, + 16 => 1, + 18 => 1, + 20 => 1, + 22 => 1, + ]; + } + + /** + * @inheritdoc + */ + public function getWarningList() + { + return []; + } +} diff --git a/Magento/Tests/PHP/ShortEchoSyntaxUnitTest.inc b/Magento/Tests/PHP/ShortEchoSyntaxUnitTest.inc new file mode 100644 index 00000000..b254ebc0 --- /dev/null +++ b/Magento/Tests/PHP/ShortEchoSyntaxUnitTest.inc @@ -0,0 +1,5 @@ + + + + + diff --git a/Magento/Tests/PHP/ShortEchoSyntaxUnitTest.php b/Magento/Tests/PHP/ShortEchoSyntaxUnitTest.php new file mode 100644 index 00000000..512c2a9e --- /dev/null +++ b/Magento/Tests/PHP/ShortEchoSyntaxUnitTest.php @@ -0,0 +1,32 @@ + 1, + ]; + } +} diff --git a/Magento/Tests/Strings/RegExUnitTest.inc b/Magento/Tests/Strings/ExecutableRegExUnitTest.inc similarity index 100% rename from Magento/Tests/Strings/RegExUnitTest.inc rename to Magento/Tests/Strings/ExecutableRegExUnitTest.inc diff --git a/Magento/Tests/Strings/RegExUnitTest.php b/Magento/Tests/Strings/ExecutableRegExUnitTest.php similarity index 84% rename from Magento/Tests/Strings/RegExUnitTest.php rename to Magento/Tests/Strings/ExecutableRegExUnitTest.php index 6ec3b53e..1c2e0f51 100644 --- a/Magento/Tests/Strings/RegExUnitTest.php +++ b/Magento/Tests/Strings/ExecutableRegExUnitTest.php @@ -8,9 +8,9 @@ use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; /** - * Class RegExUnitTest + * Class ExecutableRegExUnitTest */ -class RegExUnitTest extends AbstractSniffUnitTest +class ExecutableRegExUnitTest extends AbstractSniffUnitTest { /** * @inheritdoc diff --git a/Magento/Tests/Translation/ConstantUsageUnitTest.inc b/Magento/Tests/Translation/ConstantUsageUnitTest.inc index 6fbec171..89560092 100644 --- a/Magento/Tests/Translation/ConstantUsageUnitTest.inc +++ b/Magento/Tests/Translation/ConstantUsageUnitTest.inc @@ -1,19 +1,52 @@ 1, + 7 => 1, 9 => 1, - 11 => 1, 12 => 1, 15 => 1, + 17 => 1, + 19 => 1, + 21 => 1, + 24 => 1, ]; } } diff --git a/Magento/ruleset.xml b/Magento/ruleset.xml index 112b031f..10bd7805 100644 --- a/Magento/ruleset.xml +++ b/Magento/ruleset.xml @@ -60,9 +60,15 @@ 10 + + 6 + 8 + + 8 + 8 warning @@ -73,14 +79,24 @@ 8 + + + + + + 6 + 10 + + 6 + 6 - - 8 + + 10 6 @@ -91,12 +107,21 @@ 10 + + 8 + 10 + + 8 + 8 + + 8 + 8 @@ -113,7 +138,7 @@ *.phtml 8 - + 10 @@ -126,7 +151,7 @@ 6 - + 6 @@ -137,6 +162,7 @@ 8 warning + 10 diff --git a/composer.json b/composer.json index 6500684e..6c27724c 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "type": "phpcodesniffer-standard", "require": { - "php": ">=5.5.0", + "php": ">=5.6.0", "squizlabs/php_codesniffer": "~3.3.0" }, "require-dev": {