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/squizlabs/php_codesniffer/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php \PHP_CodeSniffer\Standards\Squiz\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

1 call to FunctionCommentSniff::processParams()
FunctionCommentSniff::process in vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php
Processes this test, when one of its tokens is encountered.

File

vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php, line 489

Class

FunctionCommentSniff
Parses and verifies the doc comments for functions. Largely copied from PHP_CodeSniffer\Standards\Squiz\Sniffs\Commenting\FunctionCommentSniff.

Namespace

Drupal\Sniffs\Commenting

Code

protected function processParams(File $phpcsFile, $stackPtr, $commentStart) {
    $tokens = $phpcsFile->getTokens();
    $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);
            $typeLen = strlen($matches[1]);
            $type = trim($matches[1]);
            $typeSpace = $typeLen - strlen($type);
            $typeLen = strlen($type);
            if ($typeLen > $maxType) {
                $maxType = $typeLen;
            }
            // If there is more than one word then it is a comment that should be
            // on the next line.
            if (isset($matches[4]) === true && ($typeLen > 0 || preg_match('/[^\\s]+[\\s]+[^\\s]+/', $matches[4]) === 1)) {
                $comment = $matches[4];
                $error = 'Parameter comment must be on the next line';
                $fix = $phpcsFile->addFixableError($error, $tag + 2, 'ParamCommentNewLine');
                if ($fix === true) {
                    $parts = $matches;
                    unset($parts[0]);
                    $parts[3] = "\n *   ";
                    $phpcsFile->fixer
                        ->replaceToken($tag + 2, implode('', $parts));
                }
            }
            if (isset($matches[2]) === true) {
                $var = $matches[2];
            }
            else {
                $var = '';
            }
            if (substr($var, -1) === '.') {
                $error = 'Doc comment parameter name "%s" must not end with a dot';
                $fix = $phpcsFile->addFixableError($error, $tag + 2, 'ParamNameDot', [
                    $var,
                ]);
                if ($fix === true) {
                    $content = $type . ' ' . substr($var, 0, -1);
                    $phpcsFile->fixer
                        ->replaceToken($tag + 2, $content);
                }
                // Continue with the next parameter to avoid confusing
                // overlapping errors further down.
                continue;
            }
            $varLen = strlen($var);
            if ($varLen > $maxVar) {
                $maxVar = $varLen;
            }
            // Any strings until the next tag belong to this comment.
            if (isset($tokens[$commentStart]['comment_tags'][$pos + 1]) === true) {
                // Ignore code tags and include them within this comment.
                $skipTags = [
                    '@code',
                    '@endcode',
                    '@link',
                ];
                $skipPos = $pos;
                while (isset($tokens[$commentStart]['comment_tags'][$skipPos + 1]) === true) {
                    $skipPos++;
                    if (in_array($tokens[$tokens[$commentStart]['comment_tags'][$skipPos]]['content'], $skipTags) === false && $tokens[$tokens[$commentStart]['comment_tags'][$skipPos]]['column'] === $tokens[$tag]['column']) {
                        break;
                    }
                }
                if ($tokens[$tokens[$commentStart]['comment_tags'][$skipPos]]['column'] === $tokens[$tag]['column'] + 2) {
                    $end = $tokens[$commentStart]['comment_closer'];
                }
                else {
                    $end = $tokens[$commentStart]['comment_tags'][$skipPos];
                }
            }
            else {
                $end = $tokens[$commentStart]['comment_closer'];
            }
            
            //end if
            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 = strlen($tokens[$i - 1]['content']);
                        // There can be @code or @link tags within an @param comment.
                        if ($tokens[$i - 2]['code'] === T_DOC_COMMENT_TAG) {
                            $indent = 0;
                            if ($tokens[$i - 3]['code'] === T_DOC_COMMENT_WHITESPACE) {
                                $indent = strlen($tokens[$i - 3]['content']);
                            }
                        }
                    }
                    $comment .= ' ' . $tokens[$i]['content'];
                    $commentLines[] = [
                        'comment' => $tokens[$i]['content'],
                        'token' => $i,
                        'indent' => $indent,
                    ];
                    if ($indent < 3) {
                        $error = 'Parameter comment indentation must be 3 spaces, found %s spaces';
                        $fix = $phpcsFile->addFixableError($error, $i, 'ParamCommentIndentation', [
                            $indent,
                        ]);
                        if ($fix === true) {
                            $phpcsFile->fixer
                                ->replaceToken($i - 1, '   ');
                        }
                    }
                }
                
                //end if
            }
            
            //end for
            // The first line of the comment must be indented no more than 3
            // spaces, the following lines can be more so we only check the first
            // line.
            if (empty($commentLines[0]['indent']) === false && $commentLines[0]['indent'] > 3) {
                $error = 'Parameter comment indentation must be 3 spaces, found %s spaces';
                $fix = $phpcsFile->addFixableError($error, $commentLines[0]['token'] - 1, 'ParamCommentIndentation', [
                    $commentLines[0]['indent'],
                ]);
                if ($fix === true) {
                    $phpcsFile->fixer
                        ->replaceToken($commentLines[0]['token'] - 1, '   ');
                }
            }
            if ($comment === '') {
                $error = 'Missing parameter comment';
                $phpcsFile->addError($error, $tag, 'MissingParamComment');
                $commentLines[] = [
                    'comment' => '',
                ];
            }
            
            //end if
            $variableArguments = false;
            // Allow the "..." @param doc for a variable number of parameters.
            // This could happen with type defined as @param array ... or
            // without type defined as @param ...
            if ($tokens[$tag + 2]['content'] === '...' || substr($tokens[$tag + 2]['content'], -3) === '...' && count(explode(' ', $tokens[$tag + 2]['content'])) === 2) {
                $variableArguments = true;
            }
            if ($typeLen === 0 && $variableArguments === false) {
                $error = 'Missing parameter type';
                // If there is just one word as comment at the end of the line
                // then this is probably the data type. Move it before the
                // variable name.
                if (isset($matches[4]) === true && preg_match('/[^\\s]+[\\s]+[^\\s]+/', $matches[4]) === 0) {
                    $fix = $phpcsFile->addFixableError($error, $tag, 'MissingParamType');
                    if ($fix === true) {
                        $phpcsFile->fixer
                            ->replaceToken($tag + 2, $matches[4] . ' ' . $var);
                    }
                }
                else {
                    $phpcsFile->addError($error, $tag, 'MissingParamType');
                }
            }
            if (empty($matches[2]) === true && $variableArguments === false) {
                $error = 'Missing parameter name';
                $phpcsFile->addError($error, $tag, 'MissingParamName');
            }
        }
        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 = [];
    $checkPos = 0;
    foreach ($params as $pos => $param) {
        if ($param['var'] === '') {
            continue;
        }
        $foundParams[] = $param['var'];
        // If the type is empty, the whole line is empty.
        if ($param['type'] === '') {
            continue;
        }
        // Make sure the param name is correct.
        $matched = false;
        // Parameter documentation can be omitted for some parameters, so we have
        // to search the rest for a match.
        $realName = '<undefined>';
        while (isset($realParams[$checkPos]) === true) {
            $realName = $realParams[$checkPos]['name'];
            if ($realName === $param['var'] || $realParams[$checkPos]['pass_by_reference'] === true && '&' . $realName === $param['var'] || $realParams[$checkPos]['variable_length'] === true && '...' . $realName === $param['var']) {
                $matched = true;
                break;
            }
            $checkPos++;
        }
        // Support variadic arguments.
        if (preg_match('/(\\s+)\\.{3}$/', $param['type'], $matches) === 1) {
            $param['type_space'] = strlen($matches[1]);
            $param['type'] = preg_replace('/\\s+\\.{3}$/', '', $param['type']);
        }
        // Check the param type value. This could also be multiple parameter
        // types separated by '|'.
        $typeNames = explode('|', $param['type']);
        $suggestedNames = [];
        foreach ($typeNames as $i => $typeName) {
            $suggestedNames[] = static::suggestType($typeName);
        }
        $suggestedType = implode('|', $suggestedNames);
        if (preg_match('/\\s/', $param['type']) === 1) {
            // Do not check PHPStan types that contain any kind of brackets.
            // See https://phpstan.org/writing-php-code/phpdoc-types#general-arrays .
            if (preg_match('/[<\\[\\{\\(]/', $param['type']) === 0) {
                $error = 'Parameter type "%s" must not contain spaces';
                $data = [
                    $param['type'],
                ];
                $phpcsFile->addError($error, $param['tag'], 'ParamTypeSpaces', $data);
            }
        }
        else {
            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) {
                    $content = $suggestedType;
                    $content .= str_repeat(' ', $param['type_space']);
                    $content .= $param['var'];
                    $phpcsFile->fixer
                        ->replaceToken($param['tag'] + 2, $content);
                }
            }
        }
        
        //end if
        // Check number of spaces after the type.
        $spaces = 1;
        if ($param['type_space'] !== $spaces) {
            $error = 'Expected %s spaces after parameter type; %s found';
            $data = [
                $spaces,
                $param['type_space'],
            ];
            $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamType', $data);
            if ($fix === true) {
                $phpcsFile->fixer
                    ->beginChangeset();
                $content = $param['type'];
                $content .= str_repeat(' ', $spaces);
                $content .= $param['var'];
                $content .= str_repeat(' ', $param['var_space']);
                // At this point there is no description expected in the
                // param line so no need to append 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;
                    }
                    $newIndent = max($param['commentLines'][$lineNum]['indent'] + $spaces - $param['type_space'], 0);
                    $phpcsFile->fixer
                        ->replaceToken($param['commentLines'][$lineNum]['token'] - 1, str_repeat(' ', $newIndent));
                }
                $phpcsFile->fixer
                    ->endChangeset();
            }
            
            //end if
        }
        
        //end if
        if ($matched === false) {
            if ($checkPos >= $pos) {
                $code = 'ParamNameNoMatch';
                $data = [
                    $param['var'],
                    $realName,
                ];
                $error = 'Doc comment for parameter %s does not match ';
                if (strtolower($param['var']) === strtolower($realName)) {
                    $error .= 'case of ';
                    $code = 'ParamNameNoCaseMatch';
                }
                $error .= 'actual variable name %s';
                $phpcsFile->addError($error, $param['tag'], $code, $data);
                // Reset the parameter position to check for following
                // parameters.
                $checkPos = $pos - 1;
            }
            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
        }
        
        //end if
        $checkPos++;
        if ($param['comment'] === '') {
            continue;
        }
        // Param comments must start with a capital letter and end with the full stop.
        if (isset($param['commentLines'][0]['comment']) === true) {
            $firstChar = $param['commentLines'][0]['comment'];
        }
        else {
            $firstChar = $param['comment'];
        }
        if (preg_match('|\\p{Lu}|u', $firstChar) === 0) {
            $error = 'Parameter comment must start with a capital letter';
            if (isset($param['commentLines'][0]['token']) === true) {
                $commentToken = $param['commentLines'][0]['token'];
            }
            else {
                $commentToken = $param['tag'];
            }
            $phpcsFile->addError($error, $commentToken, 'ParamCommentNotCapital');
        }
        $lastChar = substr($param['comment'], -1);
        if (in_array($lastChar, [
            '.',
            '!',
            '?',
            ')',
        ]) === false) {
            $error = 'Parameter comment must end with a full stop';
            if (empty($param['commentLines']) === true) {
                $commentToken = $param['tag'] + 2;
            }
            else {
                $lastLine = end($param['commentLines']);
                $commentToken = $lastLine['token'];
            }
            // Don't show an error if the end of the comment is in a code
            // example.
            if ($this->isInCodeExample($phpcsFile, $commentToken, $param['tag']) === false) {
                $fix = $phpcsFile->addFixableError($error, $commentToken, 'ParamCommentFullStop');
                if ($fix === true) {
                    // Add a full stop as the last character of the comment.
                    $phpcsFile->fixer
                        ->addContent($commentToken, '.');
                }
            }
        }
    }
    
    //end foreach
    // Missing parameters only apply to methods and not function because on
    // functions it is allowed to leave out param comments for form constructors
    // for example.
    // It is also allowed to omit param tags completely, in which case we don't
    // throw errors. Only throw errors if param comments exists but are
    // incomplete on class methods.
    if ($tokens[$stackPtr]['level'] > 0 && empty($foundParams) === false) {
        foreach ($realParams as $realParam) {
            $realParamKeyName = $realParam['name'];
            if (in_array($realParamKeyName, $foundParams) === false && ($realParam['pass_by_reference'] === true && in_array("&{$realParamKeyName}", $foundParams) === true || $realParam['variable_length'] === true && in_array("...{$realParamKeyName}", $foundParams) === true) === false) {
                $error = 'Parameter %s is not described in comment';
                $phpcsFile->addError($error, $commentStart, 'ParamMissingDefinition', [
                    $realParam['name'],
                ]);
            }
        }
    }
    
    //end if
}
RSS feed
Powered by Drupal