Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
425 / 425
FunctionCommentSniff
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
6 / 6
124
100.00% covered (success)
100.00%
425 / 425
 register
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 process
100.00% covered (success)
100.00%
1 / 1
16
100.00% covered (success)
100.00%
41 / 41
 processReturn
100.00% covered (success)
100.00%
1 / 1
31
100.00% covered (success)
100.00%
98 / 98
 processThrows
100.00% covered (success)
100.00%
1 / 1
13
100.00% covered (success)
100.00%
49 / 49
 processParams
100.00% covered (success)
100.00%
1 / 1
59
100.00% covered (success)
100.00%
225 / 225
 replaceParamComment
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
11 / 11
<?php
/**
 * This file was copied from PHP_CodeSniffer before being modified
 * File: Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php
 * From repository: https://github.com/squizlabs/PHP_CodeSniffer
 *
 * Parses and verifies the doc comments for functions.
 *
 * PHP version 5
 *
 * @category PHP
 * @package PHP_CodeSniffer
 * @author Greg Sherwood <gsherwood@squiz.net>
 * @author Marc McIntyre <mmcintyre@squiz.net>
 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD-3-Clause
 * @link http://pear.php.net/package/PHP_CodeSniffer
 */
namespace MediaWiki\Sniffs\Commenting;
use MediaWiki\Sniffs\PHPUnit\PHPUnitTestTrait;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
class FunctionCommentSniff implements Sniff {
    use DocumentationTypeTrait;
    use PHPUnitTestTrait;
    /**
     * Standard class methods that
     * don't require documentation
     */
    private const SKIP_STANDARD_METHODS = [
        '__toString',
        '__destruct',
        '__sleep',
        '__wakeup',
        '__clone',
        '__invoke',
        '__call',
        '__callStatic',
        '__get',
        '__set',
        '__isset',
        '__unset',
        '__serialize',
        '__unserialize',
        '__set_state',
        '__debugInfo',
    ];
    /**
     * @inheritDoc
     */
    public function register() : array {
        return [ T_FUNCTION ];
    }
    /**
     * Processes this test, when one of its tokens is encountered.
     *
     * @param File $phpcsFile The file being scanned.
     * @param int $stackPtr The position of the current token in the stack passed in $tokens.
     *
     * @return void
     */
    public function process( File $phpcsFile, $stackPtr ) {
        $funcName = $phpcsFile->getDeclarationName( $stackPtr );
        if ( $funcName === null || in_array( $funcName, self::SKIP_STANDARD_METHODS ) ) {
            // Don't require documentation for an obvious method
            return;
        }
        $tokens = $phpcsFile->getTokens();
        $find = Tokens::$methodPrefixes;
        $find[] = T_WHITESPACE;
        $commentEnd = $phpcsFile->findPrevious( $find, $stackPtr - 1, null, true );
        if ( $tokens[$commentEnd]['code'] === T_COMMENT ) {
            // Inline comments might just be closing comments for
            // control structures or functions instead of function comments
            // using the wrong comment type. If there is other code on the line,
            // assume they relate to that code.
            $prev = $phpcsFile->findPrevious( $find, $commentEnd - 1, null, true );
            if ( $prev !== false && $tokens[$prev]['line'] === $tokens[$commentEnd]['line'] ) {
                $commentEnd = $prev;
            }
        }
        if ( $tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG
            && $tokens[$commentEnd]['code'] !== T_COMMENT
        ) {
            // Don't require documentation for functions with no parameters, except getters
            if ( ( substr( $funcName, 0, 3 ) === 'get' || $phpcsFile->getMethodParameters( $stackPtr ) )
                && !$this->isTestFile( $phpcsFile, $stackPtr )
            ) {
                $methodProps = $phpcsFile->getMethodProperties( $stackPtr );
                $phpcsFile->addError(
                    'Missing function doc comment',
                    $stackPtr,
                    // Messages used: MissingDocumentationPublic, MissingDocumentationProtected,
                    // MissingDocumentationPrivate
                    'MissingDocumentation' . ucfirst( $methodProps['scope'] )
                );
            }
            return;
        }
        if ( $tokens[$commentEnd]['code'] === T_COMMENT ) {
            $phpcsFile->addError(
                'You must use "/**" style comments for a function comment',
                $stackPtr,
                'WrongStyle'
            );
            return;
        }
        if ( $tokens[$commentEnd]['line'] !== $tokens[$stackPtr]['line'] - 1 ) {
            $phpcsFile->addError(
                'There must be no blank lines after the function comment',
                $commentEnd,
                'SpacingAfter'
            );
        }
        $commentStart = $tokens[$commentEnd]['comment_opener'];
        foreach ( $tokens[$commentStart]['comment_tags'] as $tag ) {
            $tagText = $tokens[$tag]['content'];
            if ( strcasecmp( $tagText, '@inheritDoc' ) === 0 || $tagText === '@deprecated' ) {
                // No need to validate deprecated functions or those that inherit
                // their documentation
                return;
            }
        }
        $this->processReturn( $phpcsFile, $stackPtr, $commentStart );
        $this->processThrows( $phpcsFile, $commentStart );
        $this->processParams( $phpcsFile, $stackPtr, $commentStart );
    }
    /**
     * Process the return comment of this function comment.
     *
     * @param 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.
     */
    protected function processReturn( File $phpcsFile, int $stackPtr, int $commentStart ) : void {
        $tokens = $phpcsFile->getTokens();
        // Skip constructors
        if ( $phpcsFile->getDeclarationName( $stackPtr ) === '__construct' ) {
            return;
        }
        $found = false;
        // if function has body (not abstract or part of interface)
        if ( isset( $tokens[$stackPtr]['scope_opener'] ) ) {
            $endFunction = $tokens[$stackPtr]['scope_closer'];
            for ( $i = $endFunction - 1; $i > $stackPtr; $i-- ) {
                $token = $tokens[$i];
                if ( isset( $token['scope_condition'] ) && (
                    $tokens[$token['scope_condition']]['code'] === T_CLOSURE ||
                    $tokens[$token['scope_condition']]['code'] === T_FUNCTION ||
                    $tokens[$token['scope_condition']]['code'] === T_ANON_CLASS
                ) ) {
                    // Skip to the other side of the closure/inner function and continue
                    $i = $token['scope_condition'];
                    continue;
                }
                if ( $token['code'] === T_RETURN ||
                    $token['code'] === T_YIELD ||
                    $token['code'] === T_YIELD_FROM
                ) {
                    if ( isset( $tokens[$i + 1] ) && $tokens[$i + 1]['code'] === T_SEMICOLON ) {
                        // This is a `return;` so it doesn't need documentation
                        continue;
                    }
                    $found = true;
                    break;
                }
            }
        }
        // If a return type is provided, there should be a @return
        $returnType = $phpcsFile->getMethodProperties( $stackPtr )['return_type'];
        if ( $returnType !== '' && $returnType !== 'void' ) {
            $found = true;
        }
        if ( !$found ) {
            return;
        }
        $returnPtr = null;
        foreach ( $tokens[$commentStart]['comment_tags'] as $ptr ) {
            if ( $tokens[$ptr]['content'] !== '@return' ) {
                continue;
            }
            if ( $returnPtr ) {
                $phpcsFile->addError( 'Only 1 @return tag is allowed in a function comment', $ptr, 'DuplicateReturn' );
                return;
            }
            $returnPtr = $ptr;
        }
        if ( $returnPtr !== null ) {
            $retTypeSpacingPtr = $returnPtr + 1;
            // Check spaces before type
            if ( $tokens[$retTypeSpacingPtr]['code'] === T_DOC_COMMENT_WHITESPACE ) {
                $expectedSpaces = 1;
                $currentSpaces = strlen( $tokens[$retTypeSpacingPtr]['content'] );
                if ( $currentSpaces !== $expectedSpaces ) {
                    $fix = $phpcsFile->addFixableWarning(
                        'Expected %s spaces before return type; %s found',
                        $retTypeSpacingPtr,
                        'SpacingBeforeReturnType',
                        [ $expectedSpaces, $currentSpaces ]
                    );
                    if ( $fix ) {
                        $phpcsFile->fixer->replaceToken( $retTypeSpacingPtr, ' ' );
                    }
                }
            }
            $retTypePtr = $returnPtr + 2;
            $content = '';
            if (