From a4703de6e9d5d28ca2367763e376fe2796399bb1 Mon Sep 17 00:00:00 2001 From: Jay McPartland Date: Mon, 20 Oct 2025 15:40:47 +0100 Subject: [PATCH] Add fixers for function comments - Capitalisation of first letter in param/throws/return descriptions - Terminating full stop in param/throws/return descriptions --- .../Commenting/FunctionCommentStandard.xml | 296 +++++ .../Commenting/FunctionCommentSniff.php | 806 ++++++++++++ .../Commenting/FunctionCommentUnitTest.inc | 1160 +++++++++++++++++ .../FunctionCommentUnitTest.inc.fixed | 1160 +++++++++++++++++ .../Commenting/FunctionCommentUnitTest.php | 206 +++ BigBiteDocs/ruleset.xml | 10 - composer.json | 3 +- 7 files changed, 3629 insertions(+), 12 deletions(-) create mode 100644 BigBiteDocs/Docs/Commenting/FunctionCommentStandard.xml create mode 100644 BigBiteDocs/Sniffs/Commenting/FunctionCommentSniff.php create mode 100644 BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.inc create mode 100644 BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.inc.fixed create mode 100644 BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.php diff --git a/BigBiteDocs/Docs/Commenting/FunctionCommentStandard.xml b/BigBiteDocs/Docs/Commenting/FunctionCommentStandard.xml new file mode 100644 index 0000000..975e01c --- /dev/null +++ b/BigBiteDocs/Docs/Commenting/FunctionCommentStandard.xml @@ -0,0 +1,296 @@ + + + + + + + /** + * Short description here. + * + * @return void + */ +function foo() +{ +} + ]]> + + + + + + + + Short description here. + * + * @return void + */ +function foo() +{ +} + ]]> + + + + * Short description here. + * + * @return void + */ +function foo() +{ +} + ]]> + + + + + + * Long description here. + * + * @return void + */ +function foo() +{ +} + ]]> + + + + * + * Long description here. + * + * + * @return void + */ +function foo() +{ +} + ]]> + + + + + + * @return void + */ +function foo() +{ +} + ]]> + + + + * + * @return void + */ +function foo() +{ +} + ]]> + + + + + When something goes wrong with foo. + */ +function foo() +{ +} + ]]> + + + @throws + */ +function foo() +{ +} + ]]> + + + + + @return void + */ +function foo() +{ +} + ]]> + + + + + + + + $foo Foo parameter. + * @param string $bar Bar parameter. + * @return void + */ +function foo($foo, $bar) +{ +} + ]]> + + + $qux Bar parameter. + * @return void + */ +function foo($foo, $bar) +{ +} + ]]> + + + + + $foo Foo parameter. + * @param string $bar Bar parameter. + * @return void + */ +function foo($foo, $bar) +{ +} + ]]> + + + $bar Bar parameter. + * @param string $foo Foo parameter. + * @return void + */ +function foo($foo, $bar) +{ +} + ]]> + + + + + Foo parameter. + * @param string $bar Bar parameter. + * @return void + */ +function foo($foo, $bar) +{ +} + ]]> + + + foo parameter. + * @param string $bar bar parameter. + * @return void + */ +function foo($foo, $bar) +{ +} + ]]> + + + + + . + * @param string $bar Bar parameter. + * @return void + */ +function foo($foo, $bar) +{ +} + ]]> + + + + * @param string $bar bar parameter + * @return void + */ +function foo($foo, $bar) +{ +} + ]]> + + + diff --git a/BigBiteDocs/Sniffs/Commenting/FunctionCommentSniff.php b/BigBiteDocs/Sniffs/Commenting/FunctionCommentSniff.php new file mode 100644 index 0000000..52be848 --- /dev/null +++ b/BigBiteDocs/Sniffs/Commenting/FunctionCommentSniff.php @@ -0,0 +1,806 @@ + + */ + protected static $allowedTypes = array( + 'array', + 'bool', + 'float', + 'int', + 'mixed', + 'object', + 'string', + 'resource', + 'callable', + ); + + /** + * The current PHP version. + * + * @var int|string|null + */ + private $phpVersion = null; + + /** + * Process the return comment of this function comment. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * @param int $commentStart The position in the stack where the comment started. + * + * @return void + */ + protected function processReturn( File $phpcsFile, $stackPtr, $commentStart ) { + $tokens = $phpcsFile->getTokens(); + $return = null; + + if ( true === $this->skipIfInheritdoc ) { + if ( true === $this->checkInheritdoc( $phpcsFile, $stackPtr, $commentStart ) ) { + return; + } + } + + foreach ( $tokens[ $commentStart ]['comment_tags'] as $tag ) { + if ( '@return' === $tokens[ $tag ]['content'] ) { + if ( null !== $return ) { + $error = 'Only 1 @return tag is allowed in a function comment'; + $phpcsFile->addError( $error, $tag, 'DuplicateReturn' ); + return; + } + + $return = $tag; + } + } + + // Skip constructor and destructor. + $methodName = $phpcsFile->getDeclarationName( $stackPtr ); + $isSpecialMethod = in_array( $methodName, $this->specialMethods, true ); + + if ( null !== $return ) { + $content = $tokens[ ( $return + 2 ) ]['content']; + if ( empty( $content ) === true || T_DOC_COMMENT_STRING !== $tokens[ ( $return + 2 ) ]['code'] ) { + $error = 'Return type missing for @return tag in function comment'; + $phpcsFile->addError( $error, $return, 'MissingReturnType' ); + } else { + // Support both a return type and a description. + preg_match( '`^((?:\|?(?:array\([^\)]*\)|[\\\\a-z0-9\[\]]+))*)( .*)?`i', $content, $returnParts ); + if ( isset( $returnParts[1] ) === false ) { + return; + } + + $returnType = $returnParts[1]; + + // Check return type (can be multiple, separated by '|'). + $typeNames = explode( '|', $returnType ); + $suggestedNames = array(); + foreach ( $typeNames as $typeName ) { + $suggestedName = $this->suggestType( $typeName ); + if ( in_array( $suggestedName, $suggestedNames, true ) === false ) { + $suggestedNames[] = $suggestedName; + } + } + + $suggestedType = implode( '|', $suggestedNames ); + if ( $returnType !== $suggestedType ) { + $error = 'Expected "%s" but found "%s" for function return type'; + $data = array( + $suggestedType, + $returnType, + ); + + $fix = $phpcsFile->addFixableError( $error, $return, 'InvalidReturn', $data ); + + if ( true === $fix ) { + $replacement = $suggestedType; + if ( empty( $returnParts[2] ) === false ) { + $replacement .= $returnParts[2]; + } + + $phpcsFile->fixer->replaceToken( ( $return + 2 ), $replacement ); + unset( $replacement ); + } + } + + // If the return type is void, make sure there is + // no return statement in the function. + if ( 'void' === $returnType ) { + if ( isset( $tokens[ $stackPtr ]['scope_closer'] ) === true ) { + $endToken = $tokens[ $stackPtr ]['scope_closer']; + for ( $returnToken = $stackPtr; $returnToken < $endToken; $returnToken++ ) { + if ( T_CLOSURE === $tokens[ $returnToken ]['code'] + || T_ANON_CLASS === $tokens[ $returnToken ]['code'] + ) { + $returnToken = $tokens[ $returnToken ]['scope_closer']; + continue; + } + + if ( T_RETURN === $tokens[ $returnToken ]['code'] + || T_YIELD === $tokens[ $returnToken ]['code'] + || T_YIELD_FROM === $tokens[ $returnToken ]['code'] + ) { + break; + } + } + + if ( $returnToken !== $endToken ) { + // If the function is not returning anything, just + // exiting, then there is no problem. + $semicolon = $phpcsFile->findNext( T_WHITESPACE, ( $returnToken + 1 ), null, true ); + if ( T_SEMICOLON !== $tokens[ $semicolon ]['code'] ) { + $error = 'Function return type is void, but function contains return statement'; + $phpcsFile->addError( $error, $return, 'InvalidReturnVoid' ); + } + } + } + } elseif ( 'mixed' !== $returnType + && 'never' !== $returnType + && in_array( 'void', $typeNames, true ) === false + ) { + // If return type is not void, never, or mixed, there needs to be a + // return statement somewhere in the function that returns something. + if ( isset( $tokens[ $stackPtr ]['scope_closer'] ) === true ) { + $endToken = $tokens[ $stackPtr ]['scope_closer']; + for ( $returnToken = $stackPtr; $returnToken < $endToken; $returnToken++ ) { + if ( T_CLOSURE === $tokens[ $returnToken ]['code'] + || T_ANON_CLASS === $tokens[ $returnToken ]['code'] + ) { + $returnToken = $tokens[ $returnToken ]['scope_closer']; + continue; + } + + if ( T_RETURN === $tokens[ $returnToken ]['code'] + || T_YIELD === $tokens[ $returnToken ]['code'] + || T_YIELD_FROM === $tokens[ $returnToken ]['code'] + ) { + break; + } + } + + if ( $returnToken === $endToken ) { + $error = 'Function return type is not void, but function has no return statement'; + $phpcsFile->addError( $error, $return, 'InvalidNoReturn' ); + } else { + $semicolon = $phpcsFile->findNext( T_WHITESPACE, ( $returnToken + 1 ), null, true ); + if ( T_SEMICOLON === $tokens[ $semicolon ]['code'] ) { + $error = 'Function return type is not void, but function is returning void here'; + $phpcsFile->addError( $error, $returnToken, 'InvalidReturnNotVoid' ); + } + } + } + } + } + } else { + if ( true === $isSpecialMethod ) { + return; + } + + $error = 'Missing @return tag in function comment'; + $phpcsFile->addError( $error, $tokens[ $commentStart ]['comment_closer'], 'MissingReturn' ); + } + } + + /** + * Process any throw tags that this function comment has. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * @param int $commentStart The position in the stack where the comment started. + * + * @return void + */ + protected function processThrows( File $phpcsFile, $stackPtr, $commentStart ) { + $tokens = $phpcsFile->getTokens(); + + if ( true === $this->skipIfInheritdoc ) { + if ( true === $this->checkInheritdoc( $phpcsFile, $stackPtr, $commentStart ) ) { + return; + } + } + + foreach ( $tokens[ $commentStart ]['comment_tags'] as $pos => $tag ) { + if ( '@throws' !== $tokens[ $tag ]['content'] ) { + continue; + } + + $exception = null; + $comment = null; + if ( T_DOC_COMMENT_STRING === $tokens[ ( $tag + 2 ) ]['code'] ) { + $matches = array(); + preg_match( '/([^\s]+)(?:\s+(.*))?/', $tokens[ ( $tag + 2 ) ]['content'], $matches ); + $exception = $matches[1]; + if ( true === isset( $matches[2] ) && '' !== trim( $matches[2] ) ) { + $comment = $matches[2]; + } + } + + if ( null === $exception ) { + $error = 'Exception type and comment missing for @throws tag in function comment'; + $phpcsFile->addError( $error, $tag, 'InvalidThrows' ); + } elseif ( null === $comment ) { + $error = 'Comment missing for @throws tag in function comment'; + $phpcsFile->addError( $error, $tag, 'EmptyThrows' ); + } else { + // Any strings until the next tag belong to this comment. + if ( true === isset( $tokens[ $commentStart ]['comment_tags'][ ( $pos + 1 ) ] ) ) { + $end = $tokens[ $commentStart ]['comment_tags'][ ( $pos + 1 ) ]; + } else { + $end = $tokens[ $commentStart ]['comment_closer']; + } + + for ( $i = ( $tag + 3 ); $i < $end; $i++ ) { + if ( T_DOC_COMMENT_STRING === $tokens[ $i ]['code'] ) { + $comment .= ' ' . $tokens[ $i ]['content']; + } + } + + $comment = trim( $comment ); + + // Starts with a capital letter and ends with a fullstop. + $firstChar = $comment[0]; + if ( strtoupper( $firstChar ) !== $firstChar ) { + $error = '@throws tag comment must start with a capital letter'; + $fix = $phpcsFile->addFixableError( $error, ( $tag + 2 ), 'ThrowsNotCapital' ); + + if ( true === $fix ) { + $oldContent = $comment; + $newContent = ucfirst( $oldContent ); + $replacement = str_replace( $oldContent, $newContent, $tokens[ ( $tag + 2 ) ]['content'] ); + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( ( $tag + 2 ), $replacement ); + $phpcsFile->fixer->endChangeset(); + return; + } + } + + $lastChar = substr( $comment, -1 ); + if ( '.' !== $lastChar ) { + $error = '@throws tag comment must end with a full stop'; + $fix = $phpcsFile->addFixableError( $error, ( $tag + 2 ), 'ThrowsNoFullStop' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent( ( $tag + 2 ), '.' ); + $phpcsFile->fixer->endChangeset(); + } + } + } + } + } + + /** + * Process the function parameter comments. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * @param int $commentStart The position in the stack where the comment started. + * + * @return void + */ + protected function processParams( File $phpcsFile, $stackPtr, $commentStart ) { + if ( null === $this->phpVersion ) { + $this->phpVersion = Config::getConfigData( 'php_version' ); + + if ( null === $this->phpVersion ) { + $this->phpVersion = PHP_VERSION_ID; + } + } + + $tokens = $phpcsFile->getTokens(); + + if ( true === $this->skipIfInheritdoc ) { + if ( true === $this->checkInheritdoc( $phpcsFile, $stackPtr, $commentStart ) ) { + return; + } + } + + $params = array(); + $maxType = 0; + $maxVar = 0; + + foreach ( $tokens[ $commentStart ]['comment_tags'] as $pos => $tag ) { + if ( '@param' !== $tokens[ $tag ]['content'] ) { + continue; + } + + $type = ''; + $typeSpace = 0; + $var = ''; + $varSpace = 0; + $comment = ''; + $commentLines = array(); + if ( T_DOC_COMMENT_STRING === $tokens[ ( $tag + 2 ) ]['code'] ) { + $matches = array(); + preg_match( '/([^$&.]+)(?:((?:\.\.\.)?(?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/', $tokens[ ( $tag + 2 ) ]['content'], $matches ); + + if ( empty( $matches ) === false ) { + $typeLen = strlen( $matches[1] ); + $type = trim( $matches[1] ); + $typeSpace = ( $typeLen - strlen( $type ) ); + $typeLen = strlen( $type ); + if ( $typeLen > $maxType ) { + $maxType = $typeLen; + } + } + + if ( isset( $matches[2] ) === true ) { + $var = $matches[2]; + $varLen = strlen( $var ); + if ( $varLen > $maxVar ) { + $maxVar = $varLen; + } + + if ( isset( $matches[4] ) === true ) { + $varSpace = strlen( $matches[3] ); + $comment = $matches[4]; + $commentLines[] = array( + 'comment' => $comment, + 'token' => ( $tag + 2 ), + 'indent' => $varSpace, + ); + + // Any strings until the next tag belong to this comment. + if ( isset( $tokens[ $commentStart ]['comment_tags'][ ( $pos + 1 ) ] ) === true ) { + $end = $tokens[ $commentStart ]['comment_tags'][ ( $pos + 1 ) ]; + } else { + $end = $tokens[ $commentStart ]['comment_closer']; + } + + for ( $i = ( $tag + 3 ); $i < $end; $i++ ) { + if ( T_DOC_COMMENT_STRING === $tokens[ $i ]['code'] ) { + $indent = 0; + if ( T_DOC_COMMENT_WHITESPACE === $tokens[ ( $i - 1 ) ]['code'] ) { + $indent = $tokens[ ( $i - 1 ) ]['length']; + } + + $comment .= ' ' . $tokens[ $i ]['content']; + $commentLines[] = array( + 'comment' => $tokens[ $i ]['content'], + 'token' => $i, + 'indent' => $indent, + ); + } + } + } else { + $error = 'Missing parameter comment'; + $phpcsFile->addError( $error, $tag, 'MissingParamComment' ); + $commentLines[] = array( 'comment' => '' ); + } + } elseif ( '$' === $tokens[ ( $tag + 2 ) ]['content'][0] ) { + $error = 'Missing parameter type'; + $phpcsFile->addError( $error, $tag, 'MissingParamType' ); + } else { + $error = 'Missing parameter name'; + $phpcsFile->addError( $error, $tag, 'MissingParamName' ); + } + } else { + $error = 'Missing parameter type'; + $phpcsFile->addError( $error, $tag, 'MissingParamType' ); + } + + $params[] = array( + 'tag' => $tag, + 'type' => $type, + 'var' => $var, + 'comment' => $comment, + 'commentLines' => $commentLines, + 'type_space' => $typeSpace, + 'var_space' => $varSpace, + ); + } + + $realParams = $phpcsFile->getMethodParameters( $stackPtr ); + $foundParams = array(); + + // We want to use ... for all variable length arguments, so added + // this prefix to the variable name so comparisons are easier. + foreach ( $realParams as $pos => $param ) { + if ( true === $param['variable_length'] ) { + $realParams[ $pos ]['name'] = '...' . $realParams[ $pos ]['name']; + } + } + + foreach ( $params as $pos => $param ) { + // If the type is empty, the whole line is empty. + if ( '' === $param['type'] ) { + continue; + } + + // Check the param type value. + $typeNames = explode( '|', $param['type'] ); + $suggestedTypeNames = array(); + + foreach ( $typeNames as $typeName ) { + if ( '' === $typeName ) { + continue; + } + + // Strip nullable operator. + if ( '?' === $typeName[0] ) { + $typeName = substr( $typeName, 1 ); + } + + $suggestedName = $this->suggestType( $typeName ); + $suggestedTypeNames[] = $suggestedName; + + if ( count( $typeNames ) > 1 ) { + continue; + } + + // Check type hint for array and custom type. + $suggestedTypeHint = ''; + if ( false !== strpos( $suggestedName, 'array' ) || '[]' === substr( $suggestedName, -2 ) ) { + $suggestedTypeHint = 'array'; + } elseif ( false !== strpos( $suggestedName, 'callable' ) ) { + $suggestedTypeHint = 'callable'; + } elseif ( false !== strpos( $suggestedName, 'callback' ) ) { + $suggestedTypeHint = 'callable'; + } elseif ( false === in_array( $suggestedName, $this::$allowedTypes, true ) ) { + $suggestedTypeHint = $suggestedName; + } + + if ( $this->phpVersion >= 70000 ) { + if ( 'string' === $suggestedName ) { + $suggestedTypeHint = 'string'; + } elseif ( 'int' === $suggestedName || 'integer' === $suggestedName ) { + $suggestedTypeHint = 'int'; + } elseif ( 'float' === $suggestedName ) { + $suggestedTypeHint = 'float'; + } elseif ( 'bool' === $suggestedName || 'boolean' === $suggestedName ) { + $suggestedTypeHint = 'bool'; + } + } + + if ( $this->phpVersion >= 70200 ) { + if ( 'object' === $suggestedName ) { + $suggestedTypeHint = 'object'; + } + } + + if ( $this->phpVersion >= 80000 ) { + if ( 'mixed' === $suggestedName ) { + $suggestedTypeHint = 'mixed'; + } + } + + if ( '' !== $suggestedTypeHint && true === isset( $realParams[ $pos ] ) && '' !== $param['var'] ) { + $typeHint = $realParams[ $pos ]['type_hint']; + + // Remove namespace prefixes when comparing. + $compareTypeHint = substr( $suggestedTypeHint, ( strlen( $typeHint ) * -1 ) ); + + if ( '' === $typeHint ) { + $error = 'Type hint "%s" missing for %s'; + $data = array( + $suggestedTypeHint, + $param['var'], + ); + + $errorCode = 'TypeHintMissing'; + if ( 'string' === $suggestedTypeHint + || 'int' === $suggestedTypeHint + || 'float' === $suggestedTypeHint + || 'bool' === $suggestedTypeHint + ) { + $errorCode = 'Scalar' . $errorCode; + } + + $phpcsFile->addError( $error, $stackPtr, $errorCode, $data ); + } elseif ( $typeHint !== $compareTypeHint && '?' !== $typeHint . $compareTypeHint ) { + $error = 'Expected type hint "%s"; found "%s" for %s'; + $data = array( + $suggestedTypeHint, + $typeHint, + $param['var'], + ); + $phpcsFile->addError( $error, $stackPtr, 'IncorrectTypeHint', $data ); + } + } elseif ( '' === $suggestedTypeHint && true === isset( $realParams[ $pos ] ) ) { + $typeHint = $realParams[ $pos ]['type_hint']; + if ( '' !== $typeHint ) { + $error = 'Unknown type hint "%s" found for %s'; + $data = array( + $typeHint, + $param['var'], + ); + $phpcsFile->addError( $error, $stackPtr, 'InvalidTypeHint', $data ); + } + } + } + + $suggestedType = implode( '|', $suggestedTypeNames ); + if ( $param['type'] !== $suggestedType ) { + $error = 'Expected "%s" but found "%s" for parameter type'; + $data = array( + $suggestedType, + $param['type'], + ); + + $fix = $phpcsFile->addFixableError( $error, $param['tag'], 'IncorrectParamVarName', $data ); + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + + $content = $suggestedType; + $content .= str_repeat( ' ', $param['type_space'] ); + $content .= $param['var']; + $content .= str_repeat( ' ', $param['var_space'] ); + if ( isset( $param['commentLines'][0] ) === true ) { + $content .= $param['commentLines'][0]['comment']; + } + + $phpcsFile->fixer->replaceToken( ( $param['tag'] + 2 ), $content ); + + // Fix up the indent of additional comment lines. + foreach ( $param['commentLines'] as $lineNum => $line ) { + if ( 0 === $lineNum || 0 === $param['commentLines'][ $lineNum ]['indent'] ) { + continue; + } + + $diff = ( strlen( $param['type'] ) - strlen( $suggestedType ) ); + $newIndent = ( $param['commentLines'][ $lineNum ]['indent'] - $diff ); + + $phpcsFile->fixer->replaceToken( + ( $param['commentLines'][ $lineNum ]['token'] - 1 ), + str_repeat( ' ', $newIndent ) + ); + } + + $phpcsFile->fixer->endChangeset(); + } + } + + if ( '' === $param['var'] ) { + continue; + } + + $foundParams[] = $param['var']; + + // Check number of spaces after the type. + $this->checkSpacingAfterParamType( $phpcsFile, $param, $maxType ); + + // Make sure the param name is correct. + if ( true === isset( $realParams[ $pos ] ) ) { + $realName = $realParams[ $pos ]['name']; + $paramVarName = $param['var']; + + if ( '&' === $param['var'][0] ) { + // Even when passed by reference, the variable name in $realParams does not have + // a leading '&'. This sniff will accept both '&$var' and '$var' in these cases. + $paramVarName = substr( $param['var'], 1 ); + + // This makes sure that the 'MissingParamTag' check won't throw a false positive. + $foundParams[ ( count( $foundParams ) - 1 ) ] = $paramVarName; + + if ( true !== $realParams[ $pos ]['pass_by_reference'] && $realName === $paramVarName ) { + // Don't complain about this unless the param name is otherwise correct. + $error = 'Doc comment for parameter %s is prefixed with "&" but parameter is not passed by reference'; + $code = 'ParamNameUnexpectedAmpersandPrefix'; + $data = array( $paramVarName ); + + // We're not offering an auto-fix here because we can't tell if the docblock + // is wrong, or the parameter should be passed by reference. + $phpcsFile->addError( $error, $param['tag'], $code, $data ); + } + } + + if ( $realName !== $paramVarName ) { + $code = 'ParamNameNoMatch'; + $data = array( + $paramVarName, + $realName, + ); + + $error = 'Doc comment for parameter %s does not match '; + if ( strtolower( $paramVarName ) === strtolower( $realName ) ) { + $error .= 'case of '; + $code = 'ParamNameNoCaseMatch'; + } + + $error .= 'actual variable name %s'; + + $phpcsFile->addError( $error, $param['tag'], $code, $data ); + } + } elseif ( substr( $param['var'], -4 ) !== ',...' ) { + // We must have an extra parameter comment. + $error = 'Superfluous parameter comment'; + $phpcsFile->addError( $error, $param['tag'], 'ExtraParamComment' ); + } + + if ( '' === $param['comment'] ) { + continue; + } + + // Check number of spaces after the var name. + $this->checkSpacingAfterParamName( $phpcsFile, $param, $maxVar ); + + // Param comments must start with a capital letter and end with a full stop. + if ( 1 === preg_match( '/^(\p{Ll}|\P{L})/u', $param['comment'] ) ) { + $error = 'Parameter comment must start with a capital letter'; + + $firstCharOrd = ord( $param['comment'][0] ); + + // Char is lowercase ASCII letter - we can fix it. + if ( $firstCharOrd >= 97 && $firstCharOrd <= 122 ) { + $fix = $phpcsFile->addFixableError( $error, $param['tag'], 'ParamCommentNotCapital', array(), 0 ); + + if ( true === $fix ) { + $tokenToFix = ( $param['tag'] + 2 ); + $oldContent = $param['commentLines'][0]['comment']; + $newContent = ucfirst( $oldContent ); + $replacement = str_replace( $oldContent, $newContent, $tokens[ $tokenToFix ]['content'] ); + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $tokenToFix, $replacement ); + $phpcsFile->fixer->endChangeset(); + } + } else { + // Char is not lowercase ASCII letter - user must manually fix. + $phpcsFile->addError( $error, $param['tag'], 'ParamCommentNotCapital', array(), 0 ); + } + } + + $lastChar = substr( $param['comment'], -1 ); + if ( '.' !== $lastChar ) { + $error = 'Parameter comment must end with a full stop'; + $fix = $phpcsFile->addFixableError( $error, $param['tag'], 'ParamCommentFullStop', array(), 0 ); + + if ( true === $fix ) { + $lineCount = count( $param['commentLines'] ); + + if ( 1 === $lineCount ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent( ( $param['tag'] + 2 ), '.' ); + $phpcsFile->fixer->endChangeset(); + } else { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent( $param['commentLines'][ ( $lineCount - 1 ) ]['token'], '.' ); + $phpcsFile->fixer->endChangeset(); + } + } + } + } + + $realNames = array(); + foreach ( $realParams as $realParam ) { + $realNames[] = $realParam['name']; + } + + // Report missing comments. + $diff = array_diff( $realNames, $foundParams ); + foreach ( $diff as $neededParam ) { + $error = 'Doc comment for parameter "%s" missing'; + $data = array( $neededParam ); + $phpcsFile->addError( $error, $commentStart, 'MissingParamTag', $data ); + } + } + + /** + * Returns a valid variable type for param/var tags. + * + * If type is not one of the standard types, it must be a custom type. + * Returns the correct type name suggestion if type name is invalid. + * + * @param string $varType The variable type to process. + * + * @return string + */ + protected function suggestType( $varType ) { + if ( '' === $varType ) { + return ''; + } + + // A valid type. + if ( in_array( $varType, self::$allowedTypes, true ) === true ) { + return $varType; + } + + $lowerVarType = strtolower( $varType ); + switch ( $lowerVarType ) { + case 'bool': + case 'boolean': + return 'bool'; + case 'double': + case 'real': + case 'float': + return 'float'; + case 'int': + case 'integer': + return 'int'; + case 'array()': + case 'array': + return 'array'; + } + + // A valid type, but not lower cased. + if ( in_array( $lowerVarType, self::$allowedTypes, true ) === true ) { + return $lowerVarType; + } + + // Old-style array declaration. + if ( strpos( $lowerVarType, 'array(' ) !== false ) { + // Valid array declaration: + // array, array, array. + $matches = array(); + $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i'; + + if ( preg_match( $pattern, $varType, $matches ) !== 0 ) { + $type1 = ''; + if ( isset( $matches[1] ) === true ) { + $type1 = $matches[1]; + } + + $type2 = ''; + if ( isset( $matches[3] ) === true ) { + $type2 = $matches[3]; + } + + $type1 = $this->suggestType( $type1 ); + $type2 = $this->suggestType( $type2 ); + if ( '' !== $type2 ) { + $type2 = ',' . $type2; + } + + return "array<$type1$type2>"; + } + + return 'array'; + } + + // New style array declaration. + if ( strpos( $lowerVarType, 'array<' ) !== false ) { + // Valid array declaration: + // array, array, array. + $matches = array(); + $pattern = '/^array\<\s*([^\s^,]*)(\s*,\s*(.*))?\s*\>/i'; + + if ( preg_match( $pattern, $varType, $matches ) !== 0 ) { + $type1 = ''; + if ( isset( $matches[1] ) === true ) { + $type1 = $matches[1]; + } + + $type2 = ''; + if ( isset( $matches[3] ) === true ) { + $type2 = $matches[3]; + } + + $type1 = $this->suggestType( $type1 ); + $type2 = $this->suggestType( $type2 ); + if ( '' !== $type2 ) { + $type2 = ',' . $type2; + } + + return "array<$type1$type2>"; + } + + return 'array'; + } + + // Must be a custom type name. + return $varType; + } +} diff --git a/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.inc b/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.inc new file mode 100644 index 0000000..cca1a4d --- /dev/null +++ b/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.inc @@ -0,0 +1,1160 @@ +MyClass) + */ +public function caseSensitive($a1, $a2, $a3, arRay $a4, $a5, $a6, myclas $a7) +{ + +}//end caseSensitive() + + +/** + * More type hint check for custom type and array. + * + * @param array $a1 Comment here. + * @param array $a2 Comment here. + * @param MyClass $a3 Comment here. + * @param MyClass $a4 Comment here. + * + * @return array(int => MyClass) + */ +public function typeHint(MyClass $a1, $a2, myclass $a3, $a4) +{ + return (3 => 'myclass obj'); + +}//end typeHint() + + +/** + * Mixed variable type separated by a '|'. + * + * @param array|string $a1 Comment here. + * @param mixed $a2 Comment here. + * @param string|array $a3 Comment here. + * @param MyClass|integer $a4 Comment here. + * + * @return boolean + */ +public function mixedType($a1, $a2, $a3, $a4) +{ + return true; + +}//end mixedType() + + +/** + * Array type. + * + * @param array(MyClass) $a1 OK. + * @param array() $a2 Invalid type. + * @param array( $a3 Typo. + * @param array(integer) $a4 Use 'array' instead. + * @param array(integer => integer) $a5 Use 'array' instead. + * @param array(integer => bool) $a6 Use 'array' instead. + * @param aRRay $a7 Use 'array' instead. + * @param string $a8 String with unknown type hint. + * + * @return integer + */ +public function mixedArrayType($a1, $a2, array $a3, $a4, $a5, $a6, $a7, unknownTypeHint $a8) +{ + return 1; + +}//end mixedArrayType() + + +/** + */ +function empty1() +{ +}//end empty1() + + +/** + * + */ +function empty2() +{ +}//end empty2() + + +/** + * + * + * + */ +function empty3() +{ +}//end empty3 + + +/** + * @return boolean + */ +public function missingShortDescriptionInFunctionComment() +{ + return true; + +}//end missingShortDescriptionInFunctionComment() + + +class Another_Class +{ + + /** + * Destructor should not include a return tag. + * + * @return void + */ + function __destruct() + { + return; + } + + /** + * Constructor should not include a return tag. + * + * @return void + */ + function __construct() + { + return; + } + +}//end class + + +/** + * Comment param alignment test. + * + * @param string $varrr1 Comment1.. + * @param string $vr2 Comment2. + * @param string $var3 Comment3.. + * + * @return void + */ +public static function paramAlign($varrr1, $vr2, $var3) +{ + +}//end paramAlign() + + +/** + * Comment. + * + * @param string $id Comment. + * @param array $design Comment. + * + * @return void + */ +public static function paint($id, array $design) +{ + +}//end paint() + + +/** + * Adds specified class name to class attribute of this widget. + * + * @since 4.0.0 + * @return string + */ +public function myFunction() +{ + if ($condition === FALSE) { + echo 'hi'; + } + +}//end myFunction() + + +/** + * Adds specified class name to class attribute of this widget. + * + * @return string + */ +public function myFunction() +{ + if ($condition === FALSE) { + echo 'hi'; + return; + } + + return 'blah'; + +}//end myFunction() + + +/** + * Adds specified class name to class attribute of this widget. + * + * @return string + */ +public function myFunction() +{ + if ($condition === FALSE) { + echo 'hi'; + } + + return 'blah'; + +}//end myFunction() + +/** + * Test function. + * + * @param string $arg1 An argument + * + * @access public + * @return bool + */ + +echo $blah; + +function myFunction($arg1) {} + +class MyClass() { + /** + * An abstract function. + * + * @return string[] + */ + abstract final protected function myFunction(); +} + +/** + * Comment. + * + * @param mixed $test An argument. + * + * @return mixed + */ +function test($test) +{ + if ($test === TRUE) { + return; + } + + return $test; + +}//end test() + + +/** Comment. + * + * @return mixed + * + */ +function test() +{ + +}//end test() + +/** + * Comment. + * + * @param \other\ns\item $test An argument. + * + * @return mixed + */ +function test(\other\ns\item $test) +{ + return $test; + +}//end test() + +/** + * Comment. + * + * @param item $test An argument. + * + * @return mixed + */ +function test(\other\ns\item $test) +{ + return $test; + +}//end test() + +/** + * Comment. + * + * @param \first\ns\item $test An argument. + * + * @return mixed + */ +function test(\first\ns\item $test = \second\ns::CONSTANT) +{ + return $test; + +}//end test() + +/** + * Comment. + * + * @param \first\item $test An argument. + * + * @return mixed + */ +function test(\first\ns\item $test = \second\ns::CONSTANT) +{ + return $test; + +}//end test() + +// Closures should be ignored. +preg_replace_callback( + '~-([a-z])~', + function ($match) { + return strtoupper($match[1]); + }, + 'hello-world' +); + +$callback = function ($bar) use ($foo) + { + $bar += $foo; + }; + +/** + * Comment should end with '*', not '**' before the slash. + **/ +function test123() { + +} + +/** + * Cant use resource for type hint. + * + * @param resource $test An argument. + * + * @return mixed + */ +function test($test) +{ + return $test; + +}//end test() + +/** + * Variable number of args. + * + * @param string $a1 Comment here. + * @param string $a2 Comment here. + * @param string $a2,... Comment here. + * + * @return boolean + */ +public function variableArgs($a1, $a2) +{ + return true; + +}//end variableArgs() + +/** + * Contains closure. + * + * @return void + */ +public function containsClosure() +{ + function ($e) { + return new Event($e); + }, + +}//end containsClosure() + +/** + * 这是一条测试评论. + * + * @return void + */ +public function test() +{ + +}//end variableArgs() + +/** + * Uses callable. + * + * @param callable $cb Test parameter. + * + * @return void + */ +public function usesCallable(callable $cb) { + $cb(); +}//end usesCallable() + +/** + * Creates a map of tokens => line numbers for each token. + * + * @param array $tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character + * to use for splitting strings. + * + * @return void + * @throws Exception If something really bad + * happens while doing foo. + */ +public function foo(array &$tokens, $tokenizer, $eolChar) +{ + +}//end foo() + +/** + * Some description. + * + * @param \Vendor\Package\SomeClass $someclass Some class. + * @param \OtherVendor\Package\SomeClass2 $someclass2 Some class. + * @param \OtherVendor\Package\SomeClass2 $someclass3 Some class. + * + * @return void + */ +public function foo(SomeClass $someclass, \OtherVendor\Package\SomeClass2 $someclass2, SomeClass3 $someclass3) +{ +} + +/** + * Gettext. + * + * @return string + */ +public function _() { + return $foo; +} + +class Baz { + /** + * The PHP5 constructor + * + * No return tag + */ + public function __construct() { + + } +} + +/** + * Test + * + * @return void + * @throws E + */ +function myFunction() {} + +/** + * Yield test + * + * @return int + */ +function yieldTest() +{ + for ($i = 0; $i < 5; $i++) { + yield $i; + } +} + +/** + * Yield test + * + * @return void + */ +function yieldTest() +{ + for ($i = 0; $i < 5; $i++) { + yield $i; + } +} + +/** + * Using "array" as a type hint should satisfy a specified array parameter type. + * + * @param MyClass[] $values An array of MyClass objects. + * + * @return void + */ +public function specifiedArray(array $values) { + +}// end specifiedArray() + +/** + * Using "callable" as a type hint should satisfy a "callback" parameter type. + * + * @param callback $cb A callback. + * + * @return void + */ +public function callableCallback(callable $cb) { + +}// end callableCallback() + +/** + * PHP7 type hints. + * + * @param string $name1 Comment. + * @param int $name2 Comment. + * @param float $name3 Comment. + * @param boolean $name4 Comment. + * + * @return void + */ +public function myFunction (string $name1, int $name2, float $name3, bool $name4) { +} + +/** + * Variadic function. + * + * @param string $name1 Comment. + * @param string ...$name2 Comment. + * + * @return void + */ +public function myFunction(string $name1, string ...$name2) { +} + + +/** + * Variadic function. + * + * @param string $name1 Comment. + * @param string $name2 Comment. + * + * @return void + */ +public function myFunction(string $name1, string ...$name2) { +} + +/** + * Return description function + correct type. + * + * @return int This is a description. + */ +public function myFunction() { + return 5; +} + +/** + * Return description function + incorrect type. + * + * @return integer This is a description. + */ +public function myFunction() { + return 5; +} + +/** + * Return description function + no return. + * + * @return void This is a description. + */ +public function myFunction() { +} + +/** + * Return description function + mixed return. + * + * @return mixed This is a description. + */ +public function myFunction() { +} + +/** + * Function comment. + * + * @param $bar + * Comment here. + * @param ... + * Additional arguments here. + * + * @return + * Return value + * + */ +function foo($bar) { +} + +/** + * Do something. + * + * @return void + */ +public function someFunc(): void +{ + $class = new class + { + /** + * Do something. + * + * @return string + */ + public function getString(): string + { + return 'some string'; + } + }; +} + +/** + * Return description function + mixed return types. + * + * @return boolean|integer This is a description. + */ +function returnTypeWithDescriptionA() +{ + return 5; + +}//end returnTypeWithDescriptionA() + + +/** + * Return description function + mixed return types. + * + * @return real|bool This is a description. + */ +function returnTypeWithDescriptionB() +{ + return 5; + +}//end returnTypeWithDescriptionB() + + +/** + * Return description function + lots of different mixed return types. + * + * @return int|object|string[]|real|double|float|bool|array(int=>MyClass)|callable And here we have a description + */ +function returnTypeWithDescriptionC() +{ + return 5; + +}//end returnTypeWithDescriptionC() + + +/** + * Return description function + lots of different mixed return types. + * + * @return array(int=>bool)|\OtherVendor\Package\SomeClass2|MyClass[]|void And here we have a description + */ +function returnTypeWithDescriptionD() +{ + +}//end returnTypeWithDescriptionD() + +/** + * Yield from test + * + * @return int[] + */ +function yieldFromTest() +{ + yield from foo(); +} + +/** + * Audio + * + * Generates an audio element to embed sounds + * + * @param mixed $src Either a source string or + * an array of sources. + * @param mixed $unsupportedMessage The message to display + * if the media tag is not supported by the browser. + * @param mixed $attributes HTML attributes. + * @return string + */ +function audio( + $src, + $unsupportedMessage = '', + $attributes = '', +) +{ + return 'test'; +} + +/** + * Test function + * + * @return array + */ +function returnArrayWithClosure() +{ + function () { + return; + }; + + return []; +} + +/** + * Test function + * + * @return array + */ +function returnArrayWithAnonymousClass() +{ + new class { + /** + * @return void + */ + public function test() { + return; + } + }; + + return []; +} + +/** + * @return void + */ +function returnVoidWithClosure() +{ + function () { + return 1; + }; +} + +/** + * @return void + */ +function returnVoidWithAnonymousClass() +{ + new class { + /** + * @return int + */ + public function test() + { + return 1; + } + }; +} + +class TestReturnVoid +{ + /** + * @return void + */ + public function test() + { + function () { + return 4; + }; + } +} + +/** + * Comment here. + * + * @param int $a This is A. + * @param ?array $b This is B. + * + * @return void + */ +public static function foo(?int $a, ?array $b) {} + +/** + * Comment here. + * + * @param object $a This is A. + * @param object $b This is B. + * + * @return void + */ +public function foo(object $a, ?object $b) {} + +/** + * Prepares given PHP class method for later code building. + * + * @param int $foo Comment. + * - Additional comment. + * + * @return void + */ +function foo($foo) {} + +/** + * {@inheritDoc} + */ +public function foo($a, $b) {} + +// phpcs:set BigBiteDocs.Commenting.FunctionComment skipIfInheritdoc true + +/** + * {@inheritDoc} + */ +public function foo($a, $b) {} + +/** + * Foo. + * + * @param mixed $a Comment. + * + * @return mixed + */ +public function foo(mixed $a): mixed {} + +// phpcs:set BigBiteDocs.Commenting.FunctionComment specialMethods[] +class Bar { + /** + * The PHP5 constructor + */ + public function __construct() { + + } +} + +// phpcs:set BigBiteDocs.Commenting.FunctionComment specialMethods[] ignored +/** + * Should be ok + */ +public function ignored() { + +} + +// phpcs:set BigBiteDocs.Commenting.FunctionComment specialMethods[] __construct,__destruct + +/** + * @return void + * @throws Exception If any other error occurs. */ +function throwCommentOneLine() {} + +/** + * When two adjacent pipe symbols are used (by mistake), the sniff should not throw a PHP Fatal error + * + * @param stdClass||null $object While invalid, this should not throw a PHP Fatal error. + * @return void + */ +function doublePipeFatalError(?stdClass $object) {} + +/** + * Test for passing variables by reference + * + * This sniff treats the '&' as optional for parameters passed by reference, but + * forbidden for parameters which are not passed by reference. + * + * Because mismatches may be in either direction, we cannot auto-fix these. + * + * @param string $foo A string passed in by reference. + * @param string &$bar A string passed in by reference. + * @param string $baz A string NOT passed in by reference. + * @param string &$qux A string NOT passed in by reference. + * @param string &$case1 A string passed in by reference with a case mismatch. + * @param string &$CASE2 A string NOT passed in by reference, also with a case mismatch. + * + * @return void + */ +public function variablesPassedByReference(&$foo, &$bar, $baz, $qux, &$CASE1, $case2) +{ + return; +} + +/** + * Test for param tag containing ref, but param in declaration not being by ref. + * + * @param string &$foo This should be flagged as (only) ParamNameUnexpectedAmpersandPrefix. + * @param string &$bar This should be flagged as (only) ParamNameNoMatch. + * @param string &$baz This should be flagged as (only) ParamNameNoCaseMatch. + * + * @return void + */ +function passedByRefMismatch($foo, $bra, $BAZ) { + return; +} + +/** + * Test variable case + * + * @param string $foo This parameter is lowercase. + * @param string $BAR This parameter is UPPERCASE. + * @param string $BazQux This parameter is TitleCase. + * @param string $corgeGrault This parameter is camelCase. + * @param string $GARPLY This parameter should be in lowercase. + * @param string $waldo This parameter should be in TitleCase. + * @param string $freD This parameter should be in UPPERCASE. + * @param string $PLUGH This parameter should be in TitleCase. + * + * @return void + */ +public function variableCaseTest( + $foo, + $BAR, + $BazQux, + $corgeGrault, + $garply, + $Waldo, + $FRED, + $PluGh +) { + return; +} + +/** + * Test variable order mismatch + * + * @param string $foo This is the third parameter. + * @param string $bar This is the first parameter. + * @param string $baz This is the second parameter. + * + * @return void + */ +public function variableOrderMismatch($bar, $baz, $foo) { + return; +} + +/** + * @return never + */ +function foo() {} + +/** + * @param $noTypeNoComment + * @return void + */ +function paramVariation1($noTypeNoComment): void {} + +/** + * @param $noTypeWithComment This parameter has no type specified. + * @return void + */ +function paramVariation2($noTypeWithComment): void {} + +/** + * @param int $hasTypeNoComment + * @return void + */ +function paramVariation3($hasTypeNoComment): void {} + +/** + * @param int $hasTypehasComment This parameter has type. + * @return void + */ +function paramVariation4($hasTypehasComment): void {} diff --git a/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.inc.fixed new file mode 100644 index 0000000..f60caf2 --- /dev/null +++ b/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -0,0 +1,1160 @@ + + */ +public function caseSensitive($a1, $a2, $a3, arRay $a4, $a5, $a6, myclas $a7) +{ + +}//end caseSensitive() + + +/** + * More type hint check for custom type and array. + * + * @param array $a1 Comment here. + * @param array $a2 Comment here. + * @param MyClass $a3 Comment here. + * @param MyClass $a4 Comment here. + * + * @return array + */ +public function typeHint(MyClass $a1, $a2, myclass $a3, $a4) +{ + return (3 => 'myclass obj'); + +}//end typeHint() + + +/** + * Mixed variable type separated by a '|'. + * + * @param array|string $a1 Comment here. + * @param mixed $a2 Comment here. + * @param string|array $a3 Comment here. + * @param MyClass|int $a4 Comment here. + * + * @return bool + */ +public function mixedType($a1, $a2, $a3, $a4) +{ + return true; + +}//end mixedType() + + +/** + * Array type. + * + * @param array $a1 OK. + * @param array $a2 Invalid type. + * @param array $a3 Typo. + * @param array $a4 Use 'array' instead. + * @param array $a5 Use 'array' instead. + * @param array $a6 Use 'array' instead. + * @param array $a7 Use 'array' instead. + * @param string $a8 String with unknown type hint. + * + * @return int + */ +public function mixedArrayType($a1, $a2, array $a3, $a4, $a5, $a6, $a7, unknownTypeHint $a8) +{ + return 1; + +}//end mixedArrayType() + + +/** + */ +function empty1() +{ +}//end empty1() + + +/** + * + */ +function empty2() +{ +}//end empty2() + + +/** + * + * + * + */ +function empty3() +{ +}//end empty3 + + +/** + * @return bool + */ +public function missingShortDescriptionInFunctionComment() +{ + return true; + +}//end missingShortDescriptionInFunctionComment() + + +class Another_Class +{ + + /** + * Destructor should not include a return tag. + * + * @return void + */ + function __destruct() + { + return; + } + + /** + * Constructor should not include a return tag. + * + * @return void + */ + function __construct() + { + return; + } + +}//end class + + +/** + * Comment param alignment test. + * + * @param string $varrr1 Comment1.. + * @param string $vr2 Comment2. + * @param string $var3 Comment3.. + * + * @return void + */ +public static function paramAlign($varrr1, $vr2, $var3) +{ + +}//end paramAlign() + + +/** + * Comment. + * + * @param string $id Comment. + * @param array $design Comment. + * + * @return void + */ +public static function paint($id, array $design) +{ + +}//end paint() + + +/** + * Adds specified class name to class attribute of this widget. + * + * @since 4.0.0 + * @return string + */ +public function myFunction() +{ + if ($condition === FALSE) { + echo 'hi'; + } + +}//end myFunction() + + +/** + * Adds specified class name to class attribute of this widget. + * + * @return string + */ +public function myFunction() +{ + if ($condition === FALSE) { + echo 'hi'; + return; + } + + return 'blah'; + +}//end myFunction() + + +/** + * Adds specified class name to class attribute of this widget. + * + * @return string + */ +public function myFunction() +{ + if ($condition === FALSE) { + echo 'hi'; + } + + return 'blah'; + +}//end myFunction() + +/** + * Test function. + * + * @param string $arg1 An argument + * + * @access public + * @return bool + */ + +echo $blah; + +function myFunction($arg1) {} + +class MyClass() { + /** + * An abstract function. + * + * @return string[] + */ + abstract final protected function myFunction(); +} + +/** + * Comment. + * + * @param mixed $test An argument. + * + * @return mixed + */ +function test($test) +{ + if ($test === TRUE) { + return; + } + + return $test; + +}//end test() + + +/** Comment. + * + * @return mixed + * + */ +function test() +{ + +}//end test() + +/** + * Comment. + * + * @param \other\ns\item $test An argument. + * + * @return mixed + */ +function test(\other\ns\item $test) +{ + return $test; + +}//end test() + +/** + * Comment. + * + * @param item $test An argument. + * + * @return mixed + */ +function test(\other\ns\item $test) +{ + return $test; + +}//end test() + +/** + * Comment. + * + * @param \first\ns\item $test An argument. + * + * @return mixed + */ +function test(\first\ns\item $test = \second\ns::CONSTANT) +{ + return $test; + +}//end test() + +/** + * Comment. + * + * @param \first\item $test An argument. + * + * @return mixed + */ +function test(\first\ns\item $test = \second\ns::CONSTANT) +{ + return $test; + +}//end test() + +// Closures should be ignored. +preg_replace_callback( + '~-([a-z])~', + function ($match) { + return strtoupper($match[1]); + }, + 'hello-world' +); + +$callback = function ($bar) use ($foo) + { + $bar += $foo; + }; + +/** + * Comment should end with '*', not '**' before the slash. + **/ +function test123() { + +} + +/** + * Cant use resource for type hint. + * + * @param resource $test An argument. + * + * @return mixed + */ +function test($test) +{ + return $test; + +}//end test() + +/** + * Variable number of args. + * + * @param string $a1 Comment here. + * @param string $a2 Comment here. + * @param string $a2,... Comment here. + * + * @return bool + */ +public function variableArgs($a1, $a2) +{ + return true; + +}//end variableArgs() + +/** + * Contains closure. + * + * @return void + */ +public function containsClosure() +{ + function ($e) { + return new Event($e); + }, + +}//end containsClosure() + +/** + * 这是一条测试评论. + * + * @return void + */ +public function test() +{ + +}//end variableArgs() + +/** + * Uses callable. + * + * @param callable $cb Test parameter. + * + * @return void + */ +public function usesCallable(callable $cb) { + $cb(); +}//end usesCallable() + +/** + * Creates a map of tokens => line numbers for each token. + * + * @param array $tokens The array of tokens to process. + * @param object $tokenizer The tokenizer being used to process this file. + * @param string $eolChar The EOL character + * to use for splitting strings. + * + * @return void + * @throws Exception If something really bad + * happens while doing foo. + */ +public function foo(array &$tokens, $tokenizer, $eolChar) +{ + +}//end foo() + +/** + * Some description. + * + * @param \Vendor\Package\SomeClass $someclass Some class. + * @param \OtherVendor\Package\SomeClass2 $someclass2 Some class. + * @param \OtherVendor\Package\SomeClass2 $someclass3 Some class. + * + * @return void + */ +public function foo(SomeClass $someclass, \OtherVendor\Package\SomeClass2 $someclass2, SomeClass3 $someclass3) +{ +} + +/** + * Gettext. + * + * @return string + */ +public function _() { + return $foo; +} + +class Baz { + /** + * The PHP5 constructor + * + * No return tag + */ + public function __construct() { + + } +} + +/** + * Test + * + * @return void + * @throws E + */ +function myFunction() {} + +/** + * Yield test + * + * @return int + */ +function yieldTest() +{ + for ($i = 0; $i < 5; $i++) { + yield $i; + } +} + +/** + * Yield test + * + * @return void + */ +function yieldTest() +{ + for ($i = 0; $i < 5; $i++) { + yield $i; + } +} + +/** + * Using "array" as a type hint should satisfy a specified array parameter type. + * + * @param MyClass[] $values An array of MyClass objects. + * + * @return void + */ +public function specifiedArray(array $values) { + +}// end specifiedArray() + +/** + * Using "callable" as a type hint should satisfy a "callback" parameter type. + * + * @param callback $cb A callback. + * + * @return void + */ +public function callableCallback(callable $cb) { + +}// end callableCallback() + +/** + * PHP7 type hints. + * + * @param string $name1 Comment. + * @param int $name2 Comment. + * @param float $name3 Comment. + * @param bool $name4 Comment. + * + * @return void + */ +public function myFunction (string $name1, int $name2, float $name3, bool $name4) { +} + +/** + * Variadic function. + * + * @param string $name1 Comment. + * @param string ...$name2 Comment. + * + * @return void + */ +public function myFunction(string $name1, string ...$name2) { +} + + +/** + * Variadic function. + * + * @param string $name1 Comment. + * @param string $name2 Comment. + * + * @return void + */ +public function myFunction(string $name1, string ...$name2) { +} + +/** + * Return description function + correct type. + * + * @return int This is a description. + */ +public function myFunction() { + return 5; +} + +/** + * Return description function + incorrect type. + * + * @return int This is a description. + */ +public function myFunction() { + return 5; +} + +/** + * Return description function + no return. + * + * @return void This is a description. + */ +public function myFunction() { +} + +/** + * Return description function + mixed return. + * + * @return mixed This is a description. + */ +public function myFunction() { +} + +/** + * Function comment. + * + * @param $bar + * Comment here. + * @param ... + * Additional arguments here. + * + * @return + * Return value + * + */ +function foo($bar) { +} + +/** + * Do something. + * + * @return void + */ +public function someFunc(): void +{ + $class = new class + { + /** + * Do something. + * + * @return string + */ + public function getString(): string + { + return 'some string'; + } + }; +} + +/** + * Return description function + mixed return types. + * + * @return bool|int This is a description. + */ +function returnTypeWithDescriptionA() +{ + return 5; + +}//end returnTypeWithDescriptionA() + + +/** + * Return description function + mixed return types. + * + * @return float|bool This is a description. + */ +function returnTypeWithDescriptionB() +{ + return 5; + +}//end returnTypeWithDescriptionB() + + +/** + * Return description function + lots of different mixed return types. + * + * @return int|object|string[]|float|bool|array|callable And here we have a description + */ +function returnTypeWithDescriptionC() +{ + return 5; + +}//end returnTypeWithDescriptionC() + + +/** + * Return description function + lots of different mixed return types. + * + * @return array|\OtherVendor\Package\SomeClass2|MyClass[]|void And here we have a description + */ +function returnTypeWithDescriptionD() +{ + +}//end returnTypeWithDescriptionD() + +/** + * Yield from test + * + * @return int[] + */ +function yieldFromTest() +{ + yield from foo(); +} + +/** + * Audio + * + * Generates an audio element to embed sounds + * + * @param mixed $src Either a source string or + * an array of sources. + * @param mixed $unsupportedMessage The message to display + * if the media tag is not supported by the browser. + * @param mixed $attributes HTML attributes. + * @return string + */ +function audio( + $src, + $unsupportedMessage = '', + $attributes = '', +) +{ + return 'test'; +} + +/** + * Test function + * + * @return array + */ +function returnArrayWithClosure() +{ + function () { + return; + }; + + return []; +} + +/** + * Test function + * + * @return array + */ +function returnArrayWithAnonymousClass() +{ + new class { + /** + * @return void + */ + public function test() { + return; + } + }; + + return []; +} + +/** + * @return void + */ +function returnVoidWithClosure() +{ + function () { + return 1; + }; +} + +/** + * @return void + */ +function returnVoidWithAnonymousClass() +{ + new class { + /** + * @return int + */ + public function test() + { + return 1; + } + }; +} + +class TestReturnVoid +{ + /** + * @return void + */ + public function test() + { + function () { + return 4; + }; + } +} + +/** + * Comment here. + * + * @param int $a This is A. + * @param array $b This is B. + * + * @return void + */ +public static function foo(?int $a, ?array $b) {} + +/** + * Comment here. + * + * @param object $a This is A. + * @param object $b This is B. + * + * @return void + */ +public function foo(object $a, ?object $b) {} + +/** + * Prepares given PHP class method for later code building. + * + * @param int $foo Comment. + * - Additional comment. + * + * @return void + */ +function foo($foo) {} + +/** + * {@inheritDoc} + */ +public function foo($a, $b) {} + +// phpcs:set BigBiteDocs.Commenting.FunctionComment skipIfInheritdoc true + +/** + * {@inheritDoc} + */ +public function foo($a, $b) {} + +/** + * Foo. + * + * @param mixed $a Comment. + * + * @return mixed + */ +public function foo(mixed $a): mixed {} + +// phpcs:set BigBiteDocs.Commenting.FunctionComment specialMethods[] +class Bar { + /** + * The PHP5 constructor + */ + public function __construct() { + + } +} + +// phpcs:set BigBiteDocs.Commenting.FunctionComment specialMethods[] ignored +/** + * Should be ok + */ +public function ignored() { + +} + +// phpcs:set BigBiteDocs.Commenting.FunctionComment specialMethods[] __construct,__destruct + +/** + * @return void + * @throws Exception If any other error occurs. */ +function throwCommentOneLine() {} + +/** + * When two adjacent pipe symbols are used (by mistake), the sniff should not throw a PHP Fatal error + * + * @param stdClass|null $object While invalid, this should not throw a PHP Fatal error. + * @return void + */ +function doublePipeFatalError(?stdClass $object) {} + +/** + * Test for passing variables by reference + * + * This sniff treats the '&' as optional for parameters passed by reference, but + * forbidden for parameters which are not passed by reference. + * + * Because mismatches may be in either direction, we cannot auto-fix these. + * + * @param string $foo A string passed in by reference. + * @param string &$bar A string passed in by reference. + * @param string $baz A string NOT passed in by reference. + * @param string &$qux A string NOT passed in by reference. + * @param string &$case1 A string passed in by reference with a case mismatch. + * @param string &$CASE2 A string NOT passed in by reference, also with a case mismatch. + * + * @return void + */ +public function variablesPassedByReference(&$foo, &$bar, $baz, $qux, &$CASE1, $case2) +{ + return; +} + +/** + * Test for param tag containing ref, but param in declaration not being by ref. + * + * @param string &$foo This should be flagged as (only) ParamNameUnexpectedAmpersandPrefix. + * @param string &$bar This should be flagged as (only) ParamNameNoMatch. + * @param string &$baz This should be flagged as (only) ParamNameNoCaseMatch. + * + * @return void + */ +function passedByRefMismatch($foo, $bra, $BAZ) { + return; +} + +/** + * Test variable case + * + * @param string $foo This parameter is lowercase. + * @param string $BAR This parameter is UPPERCASE. + * @param string $BazQux This parameter is TitleCase. + * @param string $corgeGrault This parameter is camelCase. + * @param string $GARPLY This parameter should be in lowercase. + * @param string $waldo This parameter should be in TitleCase. + * @param string $freD This parameter should be in UPPERCASE. + * @param string $PLUGH This parameter should be in TitleCase. + * + * @return void + */ +public function variableCaseTest( + $foo, + $BAR, + $BazQux, + $corgeGrault, + $garply, + $Waldo, + $FRED, + $PluGh +) { + return; +} + +/** + * Test variable order mismatch + * + * @param string $foo This is the third parameter. + * @param string $bar This is the first parameter. + * @param string $baz This is the second parameter. + * + * @return void + */ +public function variableOrderMismatch($bar, $baz, $foo) { + return; +} + +/** + * @return never + */ +function foo() {} + +/** + * @param $noTypeNoComment + * @return void + */ +function paramVariation1($noTypeNoComment): void {} + +/** + * @param $noTypeWithComment This parameter has no type specified. + * @return void + */ +function paramVariation2($noTypeWithComment): void {} + +/** + * @param int $hasTypeNoComment + * @return void + */ +function paramVariation3($hasTypeNoComment): void {} + +/** + * @param int $hasTypehasComment This parameter has type. + * @return void + */ +function paramVariation4($hasTypehasComment): void {} diff --git a/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.php b/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.php new file mode 100644 index 0000000..3c9bb20 --- /dev/null +++ b/BigBiteDocs/Tests/Commenting/FunctionCommentUnitTest.php @@ -0,0 +1,206 @@ + + */ + public function getErrorList( $testFile = '' ) { + $errors = array( + 5 => 1, + 10 => 2, + 12 => 1, + 13 => 3, + 14 => 1, + 15 => 1, + 28 => 1, + 43 => 1, + 76 => 1, + 87 => 1, + 103 => 1, + 109 => 1, + 112 => 1, + 122 => 1, + 123 => 2, + 124 => 3, + 125 => 1, + 126 => 1, + 137 => 2, + 138 => 2, + 139 => 2, + 143 => 2, + 155 => 1, + 159 => 1, + 166 => 1, + 173 => 1, + 183 => 1, + 190 => 2, + 193 => 2, + 196 => 1, + 199 => 2, + 210 => 1, + 211 => 1, + 222 => 1, + 223 => 1, + 224 => 1, + 225 => 1, + 226 => 1, + 227 => 1, + 230 => 2, + 232 => 2, + 246 => 1, + 248 => 4, + 261 => 1, + 263 => 1, + 275 => 1, + 276 => 1, + 277 => 1, + 278 => 1, + 279 => 1, + 280 => 1, + 281 => 1, + 284 => 1, + 286 => 7, + 294 => 1, + 302 => 1, + 312 => 1, + 319 => 1, + 358 => 1, + 359 => 2, + 372 => 1, + 373 => 1, + 387 => 1, + 407 => 1, + 441 => 1, + 500 => 1, + 526 => 1, + 548 => 1, + 573 => 1, + 641 => 1, + 669 => 1, + 688 => 1, + 725 => 1, + 744 => 1, + 748 => 1, + 767 => 1, + 789 => 1, + 792 => 1, + 794 => 1, + 797 => 1, + 828 => 1, + 840 => 1, + 852 => 1, + 864 => 1, + 886 => 1, + 888 => 1, + 890 => 1, + 978 => 1, + 982 => 2, + 992 => 1, + 997 => 1, + 1004 => 2, + 1006 => 1, + 1029 => 1, + 1053 => 1, + 1058 => 2, + 1069 => 1, + 1070 => 1, + 1071 => 1, + 1080 => 2, + 1083 => 1, + 1084 => 1, + 1085 => 1, + 1093 => 4, + 1100 => 1, + 1101 => 1, + 1102 => 1, + 1103 => 1, + 1123 => 1, + 1124 => 1, + 1125 => 1, + 1138 => 1, + 1139 => 1, + 1144 => 1, + 1145 => 1, + 1151 => 1, + ); + + // Scalar type hints only work from PHP 7 onwards. + if ( PHP_VERSION_ID >= 70000 ) { + $errors[17] = 3; + $errors[128] = 1; + $errors[143] = 3; + $errors[161] = 2; + $errors[201] = 1; + $errors[232] = 7; + $errors[363] = 3; + $errors[377] = 1; + $errors[575] = 2; + $errors[627] = 1; + $errors[1002] = 1; + $errors[1075] = 6; + $errors[1089] = 3; + $errors[1107] = 8; + $errors[1129] = 3; + $errors[1154] = 1; + $errors[1160] = 1; + } else { + $errors[729] = 4; + $errors[740] = 2; + $errors[752] = 2; + $errors[982] = 1; + } + + // Object type hints only work from PHP 7.2 onwards. + if ( PHP_VERSION_ID >= 70200 ) { + $errors[627] = 2; + } else { + $errors[992] = 2; + } + + // Mixed type hints only work from PHP 8.0 onwards. + if ( PHP_VERSION_ID >= 80000 ) { + $errors[265] = 1; + $errors[459] = 1; + $errors[893] = 3; + } else { + $errors[1023] = 1; + } + + return $errors; + } + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() { + return array(); + } +} diff --git a/BigBiteDocs/ruleset.xml b/BigBiteDocs/ruleset.xml index 76746fc..cd91056 100644 --- a/BigBiteDocs/ruleset.xml +++ b/BigBiteDocs/ruleset.xml @@ -29,16 +29,6 @@ - - - - - - - - - - diff --git a/composer.json b/composer.json index ac477e2..c51c9b0 100644 --- a/composer.json +++ b/composer.json @@ -76,8 +76,7 @@ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf ." ], "test": [ - "@php ./vendor/bin/phpunit --filter BigBite ./vendor/squizlabs/php_codesniffer/tests/AllTests.php", - "@php ./vendor/bin/phpunit --filter BigBiteDocs ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" + "@php ./vendor/bin/phpunit --filter BigBite ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" ], "analyse": [ "./vendor/bin/phpstan"