Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. FunctionCommentSniff.php

function FunctionCommentSniff::processParams

Same name in this branch
  1. 11.1.x vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php \Drupal\Sniffs\Commenting\FunctionCommentSniff::processParams()
  2. 11.1.x vendor/squizlabs/php_codesniffer/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php \PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FunctionCommentSniff::processParams()

Process the function parameter comments.

Parameters

\PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.:

int $stackPtr The position of the current token: in the stack passed in $tokens.

int $commentStart The position in the stack where the comment started.:

Return value

void

Overrides FunctionCommentSniff::processParams

File

vendor/squizlabs/php_codesniffer/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php, line 281

Class

FunctionCommentSniff

Namespace

PHP_CodeSniffer\Standards\Squiz\Sniffs\Commenting

Code

protected function processParams(File $phpcsFile, $stackPtr, $commentStart) {
    if ($this->phpVersion === null) {
        $this->phpVersion = Config::getConfigData('php_version');
        if ($this->phpVersion === null) {
            $this->phpVersion = PHP_VERSION_ID;
        }
    }
    $tokens = $phpcsFile->getTokens();
    if ($this->skipIfInheritdoc === true) {
        if ($this->checkInheritdoc($phpcsFile, $stackPtr, $commentStart) === true) {
            return;
        }
    }
    $params = [];
    $maxType = 0;
    $maxVar = 0;
    foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
        if ($tokens[$tag]['content'] !== '@param') {
            continue;
        }
        $type = '';
        $typeSpace = 0;
        $var = '';
        $varSpace = 0;
        $comment = '';
        $commentLines = [];
        if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) {
            $matches = [];
            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[] = [
                        '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 ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
                            $indent = 0;
                            if ($tokens[$i - 1]['code'] === T_DOC_COMMENT_WHITESPACE) {
                                $indent = $tokens[$i - 1]['length'];
                            }
                            $comment .= ' ' . $tokens[$i]['content'];
                            $commentLines[] = [
                                'comment' => $tokens[$i]['content'],
                                'token' => $i,
                                'indent' => $indent,
                            ];
                        }
                    }
                }
                else {
                    $error = 'Missing parameter comment';
                    $phpcsFile->addError($error, $tag, 'MissingParamComment');
                    $commentLines[] = [
                        'comment' => '',
                    ];
                }
                
                //end if
            }
            else {
                if ($tokens[$tag + 2]['content'][0] === '$') {
                    $error = 'Missing parameter type';
                    $phpcsFile->addError($error, $tag, 'MissingParamType');
                }
                else {
                    $error = 'Missing parameter name';
                    $phpcsFile->addError($error, $tag, 'MissingParamName');
                }
            }
            
            //end if
        }
        else {
            $error = 'Missing parameter type';
            $phpcsFile->addError($error, $tag, 'MissingParamType');
        }
        
        //end if
        $params[] = [
            'tag' => $tag,
            'type' => $type,
            'var' => $var,
            'comment' => $comment,
            'commentLines' => $commentLines,
            'type_space' => $typeSpace,
            'var_space' => $varSpace,
        ];
    }
    
    //end foreach
    $realParams = $phpcsFile->getMethodParameters($stackPtr);
    $foundParams = [];
    // 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 ($param['variable_length'] === true) {
            $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 = [];
        foreach ($typeNames as $typeName) {
            if ($typeName === '') {
                continue;
            }
            // Strip nullable operator.
            if ($typeName[0] === '?') {
                $typeName = substr($typeName, 1);
            }
            $suggestedName = Common::suggestType($typeName);
            $suggestedTypeNames[] = $suggestedName;
            if (count($typeNames) > 1) {
                continue;
            }
            // Check type hint for array and custom type.
            $suggestedTypeHint = '';
            if (strpos($suggestedName, 'array') !== false || substr($suggestedName, -2) === '[]') {
                $suggestedTypeHint = 'array';
            }
            else {
                if (strpos($suggestedName, 'callable') !== false) {
                    $suggestedTypeHint = 'callable';
                }
                else {
                    if (strpos($suggestedName, 'callback') !== false) {
                        $suggestedTypeHint = 'callable';
                    }
                    else {
                        if (in_array($suggestedName, Common::$allowedTypes, true) === false) {
                            $suggestedTypeHint = $suggestedName;
                        }
                    }
                }
            }
            if ($this->phpVersion >= 70000) {
                if ($suggestedName === 'string') {
                    $suggestedTypeHint = 'string';
                }
                else {
                    if ($suggestedName === 'int' || $suggestedName === 'integer') {
                        $suggestedTypeHint = 'int';
                    }
                    else {
                        if ($suggestedName === 'float') {
                            $suggestedTypeHint = 'float';
                        }
                        else {
                            if ($suggestedName === 'bool' || $suggestedName === 'boolean') {
                                $suggestedTypeHint = 'bool';
                            }
                        }
                    }
                }
            }
            if ($this->phpVersion >= 70200) {
                if ($suggestedName === 'object') {
                    $suggestedTypeHint = 'object';
                }
            }
            if ($this->phpVersion >= 80000) {
                if ($suggestedName === 'mixed') {
                    $suggestedTypeHint = 'mixed';
                }
            }
            if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true && $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 = [
                        $suggestedTypeHint,
                        $param['var'],
                    ];
                    $errorCode = 'TypeHintMissing';
                    if ($suggestedTypeHint === 'string' || $suggestedTypeHint === 'int' || $suggestedTypeHint === 'float' || $suggestedTypeHint === 'bool') {
                        $errorCode = 'Scalar' . $errorCode;
                    }
                    $phpcsFile->addError($error, $stackPtr, $errorCode, $data);
                }
                else {
                    if ($typeHint !== $compareTypeHint && $typeHint !== '?' . $compareTypeHint) {
                        $error = 'Expected type hint "%s"; found "%s" for %s';
                        $data = [
                            $suggestedTypeHint,
                            $typeHint,
                            $param['var'],
                        ];
                        $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data);
                    }
                }
                
                //end if
            }
            else {
                if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) {
                    $typeHint = $realParams[$pos]['type_hint'];
                    if ($typeHint !== '') {
                        $error = 'Unknown type hint "%s" found for %s';
                        $data = [
                            $typeHint,
                            $param['var'],
                        ];
                        $phpcsFile->addError($error, $stackPtr, 'InvalidTypeHint', $data);
                    }
                }
            }
            
            //end if
        }
        
        //end foreach
        $suggestedType = implode('|', $suggestedTypeNames);
        if ($param['type'] !== $suggestedType) {
            $error = 'Expected "%s" but found "%s" for parameter type';
            $data = [
                $suggestedType,
                $param['type'],
            ];
            $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data);
            if ($fix === true) {
                $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 ($lineNum === 0 || $param['commentLines'][$lineNum]['indent'] === 0) {
                        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();
            }
            
            //end if
        }
        
        //end if
        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 (isset($realParams[$pos]) === true) {
            $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 ($realParams[$pos]['pass_by_reference'] !== true && $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 = [
                        $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 = [
                    $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);
            }
            
            //end if
        }
        else {
            if (substr($param['var'], -4) !== ',...') {
                // We must have an extra parameter comment.
                $error = 'Superfluous parameter comment';
                $phpcsFile->addError($error, $param['tag'], 'ExtraParamComment');
            }
        }
        
        //end if
        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 (preg_match('/^(\\p{Ll}|\\P{L})/u', $param['comment']) === 1) {
            $error = 'Parameter comment must start with a capital letter';
            $phpcsFile->addError($error, $param['tag'], 'ParamCommentNotCapital');
        }
        $lastChar = substr($param['comment'], -1);
        if ($lastChar !== '.') {
            $error = 'Parameter comment must end with a full stop';
            $phpcsFile->addError($error, $param['tag'], 'ParamCommentFullStop');
        }
    }
    
    //end foreach
    $realNames = [];
    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 = [
            $neededParam,
        ];
        $phpcsFile->addError($error, $commentStart, 'MissingParamTag', $data);
    }
}
RSS feed
Powered by Drupal