Each parameter is in the following format:
0 => array(
'name' => '$var', // The variable name.
'pass_by_reference' => false, // Passed by reference.
'type_hint' => string, // Type hint for array or custom type
)
Parameters with default values have an additional array index of
'default' with the value of the default as a string. public getMethodParameters ( integer $stackPtr ) : array | ||
$stackPtr | integer | The position in the stack of the T_FUNCTION token to acquire the parameters for. |
Résultat | array |
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) { if (in_array($param, $this->forbiddenParameterNames) === true) { $error = "[PHP 5.4] {$param} is not a valid parameter name."; $phpcsFile->addError($error, $stackPtr); } } }
/** * Verify default value parsing. * * @return void */ public function testDefaultValues() { $expected = array(); $expected[0] = array('name' => '$var1', 'default' => '1', 'pass_by_reference' => false, 'type_hint' => ''); $expected[1] = array('name' => '$var2', 'default' => "'value'", 'pass_by_reference' => false, 'type_hint' => ''); $start = $this->_phpcsFile->numTokens - 1; $function = $this->_phpcsFile->findPrevious(T_COMMENT, $start, null, false, '/* testDefaultValues */'); $found = $this->_phpcsFile->getMethodParameters($function + 2); $this->assertSame($expected, $found); }
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $paramsInfo = $phpcsFile->getMethodParameters($stackPtr); // Check each param to see if it has a type hint we need to change foreach ($paramsInfo as $paramInfo) { if (isset($paramInfo['type_hint'])) { $classNamePtr = $paramInfo['type_hint']; $className = $tokens[$classNamePtr]['content']; $varPtr = $paramInfo['var']; $this->_variableTypes->setVariableType($varPtr, $className, $phpcsFile, null, $stackPtr); } } }
public function process(CodeSnifferFile $file, $stackPtr) { $methodName = $file->getDeclarationName($stackPtr); if ($methodName !== '__construct' && strpos($methodName, 'set') !== 0) { return; } $methodParameters = $file->getMethodParameters($stackPtr); foreach ($methodParameters as $methodParameter) { $this->verifyParameterName($file, $stackPtr, $methodName, $methodParameter); } $propertyAssignments = $this->getPropertyAssignments($file, $stackPtr); foreach ($methodParameters as $methodParameter) { $this->verifyPropertyName($file, $stackPtr, $methodName, $methodParameter, $propertyAssignments); } }
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $functionName = $phpcsFile->getDeclarationName($stackPtr); if ($this->startsWith($functionName, $this->prefixes, $this->exclude)) { $paramsQty = count($phpcsFile->getMethodParameters($stackPtr)); if ($paramsQty < self::PARAMS_QTY) { $phpcsFile->addWarning('Plugin ' . $functionName . ' function must have at least two parameters.', $stackPtr); } $tokens = $phpcsFile->getTokens(); $hasReturn = false; foreach ($tokens as $currToken) { if ($currToken['code'] == T_RETURN && isset($currToken['conditions'][$stackPtr])) { $hasReturn = true; break; } } if (!$hasReturn) { $phpcsFile->addError('Plugin ' . $functionName . ' function must return value.', $stackPtr); } } }
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); // Find the actual name of the class if ($tokens[$stackPtr]['code'] == T_FUNCTION) { // Get information about the function parameters $paramsInfo = $phpcsFile->getMethodParameters($stackPtr); // Check each param to see if it has a type hint we need to change foreach ($paramsInfo as $paramInfo) { if (isset($paramInfo['type_hint'])) { $classNamePtr = $paramInfo['type_hint']; $this->checkClassNamePtr($classNamePtr, $phpcsFile); } } } else { if ($tokens[$stackPtr]['code'] == T_PAAMAYIM_NEKUDOTAYIM) { $classNamePtr = $phpcsFile->findPrevious(T_STRING, $stackPtr); $this->checkClassNamePtr($classNamePtr, $phpcsFile); } else { $classNamePtr = $phpcsFile->findNext(T_STRING, $stackPtr); $this->checkClassNamePtr($classNamePtr, $phpcsFile); } } }
/** * Process the function parameter comments. * * @param int $commentStart The position in the stack where * the comment started. * * @return void */ protected function processParams($commentStart) { $realParams = $this->currentFile->getMethodParameters($this->_functionToken); $params = $this->commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { // There must be an empty line before the parameter block. if (substr_count($params[0]->getWhitespaceBefore(), $this->currentFile->eolChar) < 2) { $error = 'There must be an empty line before the parameter block'; $errorPos = $params[0]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParams'); } $lastParm = count($params) - 1; if (substr_count($params[$lastParm]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) { $error = 'Last parameter comment requires a blank newline after it'; $errorPos = $params[$lastParm]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingAfterParams'); } $checkPos = 0; foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; // Make sure that there is only one space before the var type. if ($param->getWhitespaceBeforeType() !== ' ') { $error = 'Expected 1 space before variable type'; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParamType'); } // Make sure they are in the correct order, // and have the correct name. $pos = $param->getPosition(); $paramName = '[ UNKNOWN ]'; if ($param->getVarName() !== '') { $paramName = $param->getVarName(); } // Make sure the names of the parameter comment matches the // actual parameter. $matched = false; // Parameter documentation can be ommitted for some parameters, so // we have to search the rest for a match. while (isset($realParams[$checkPos]) === true) { $realName = $realParams[$checkPos]['name']; $expectedParamName = $realName; $isReference = $realParams[$checkPos]['pass_by_reference']; // Append ampersand to name if passing by reference. if ($isReference === true && substr($paramName, 0, 1) === '&') { $expectedParamName = '&' . $realName; } if ($expectedParamName === $paramName) { $matched = true; break; } $checkPos++; } if ($matched === false && $paramName !== '...') { if ($checkPos >= $pos) { $code = 'ParamNameNoMatch'; $data = array($paramName, $realParams[$pos - 1]['name'], $pos); $error = 'Doc comment for var %s does not match '; if (strtolower($paramName) === strtolower($realParams[$pos - 1]['name'])) { $error .= 'case of '; $code = 'ParamNameNoCaseMatch'; } $error .= 'actual variable name %s at position %s'; $this->currentFile->addError($error, $errorPos, $code, $data); // Reset the parameter position to check for following // parameters. $checkPos = $pos - 1; } else { // We must have an extra parameter comment. $error = 'Superfluous doc comment at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'ExtraParamComment'); } //end if } //end if $checkPos++; if ($param->getVarName() === '') { $error = 'Missing parameter name at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'MissingParamName'); } if ($param->getType() === '') { $error = 'Missing parameter type at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'MissingParamType'); } if (in_array($param->getType(), array('unknown_type', '<type>', 'type')) === true) { $error = 'Expected a valid @param data type, but found %s'; $data = array($param->getType()); $this->currentFile->addError($error, $errorPos, 'InvalidParamType', $data); } if (isset($this->invalidTypes[$param->getType()]) === true) { $error = 'Invalid @param data type, expected %s but found %s'; $data = array($this->invalidTypes[$param->getType()], $param->getType()); $this->currentFile->addError($error, $errorPos, 'InvalidParamTypeName', $data); } if ($paramComment === '') { $error = 'Missing comment for param "%s" at position %s'; $data = array($paramName, $pos); $this->currentFile->addError($error, $errorPos, 'MissingParamComment', $data); } else { if (substr_count($param->getWhitespaceBeforeComment(), $this->currentFile->eolChar) !== 1) { $error = 'Parameter comment must be on the next line at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'ParamCommentNewLine'); } else { if (substr_count($param->getWhitespaceBeforeComment(), ' ') !== 3) { $error = 'Parameter comment indentation must be 2 additional spaces at position ' . $pos; $this->currentFile->addError($error, $errorPos + 1, 'ParamCommentIndentation'); } } } } //end foreach } //end if $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } }
/** * Checks the number of method arguments. * * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * @param string $methodName The name of the method. * @param int $countArgs Number of required arguments. * * @return void */ private function _checkArguments(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $methodName, $countArgs) { $methodArguments = $phpcsFile->getMethodParameters($stackPtr); // Warning for non-public declaration if ($countArgs !== count($methodArguments)) { $error = sprintf('Wrong argument count of magic method "%s"! Must take exactly %d argument(s).', $methodName, $countArgs); $phpcsFile->addError($error, $stackPtr); } }
/** * Process the function parameter comments. * * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); $params = array(); $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 = array(); if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { $matches = array(); 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 (isset($matches[4]) === true) { $error = 'Parameter comment must be on the next line'; $phpcsFile->addError($error, $tag + 2, 'ParamCommentNewLine'); } $var = isset($matches[2]) ? $matches[2] : ''; $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) { $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 = strlen($tokens[$i - 1]['content']); } $comment .= ' ' . $tokens[$i]['content']; $commentLines[] = array('comment' => $tokens[$i]['content'], 'token' => $i, 'indent' => $indent); if ($indent < 3) { $error = 'Parameter comment indentation must be 3 spaces, found %s spaces'; $phpcsFile->addError($error, $i, 'ParamCommentIndentation', array($indent)); } } } if ($comment == '') { $error = 'Missing parameter comment'; $phpcsFile->addError($error, $tag, 'MissingParamComment'); $commentLines[] = array('comment' => ''); } //end if // Allow the "..." @param doc for a variable number of parameters. if (isset($matches[2]) === false && $tokens[$tag + 2]['content'] !== '...') { if ($tokens[$tag + 2]['content'][0] === '$' || $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[] = array('tag' => $tag, 'type' => $type, 'var' => $var, 'comment' => $comment, 'commentLines' => $commentLines, 'type_space' => $typeSpace, 'var_space' => $varSpace); } //end foreach $realParams = $phpcsFile->getMethodParameters($stackPtr); $foundParams = array(); $checkPos = 0; foreach ($params as $pos => $param) { // If the type is empty, the whole line is empty. if ($param['type'] === '') { continue; } if ($param['var'] === '') { continue; } // Make sure the param name is correct. $matched = false; // Parameter documentation can be ommitted for some parameters, so // we have to search the rest for a match. while (isset($realParams[$checkPos]) === true) { $realName = $realParams[$checkPos]['name']; if ($realName === $param['var'] || $realParams[$checkPos]['pass_by_reference'] === true && '&' . $realName === $param['var']) { $matched = true; break; } $checkPos++; } // Check the param type value. $typeNames = explode('|', $param['type']); foreach ($typeNames as $typeName) { $suggestedName = $this->suggestType($typeName); if ($typeName !== $suggestedName) { $error = 'Expected "%s" but found "%s" for parameter type'; $data = array($suggestedName, $typeName); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data); if ($fix === true && $phpcsFile->fixer->enabled === true) { $content = $suggestedName; $content .= str_repeat(' ', $param['type_space']); $content .= $param['var']; $content .= str_repeat(' ', $param['var_space']); $content .= $param['commentLines'][0]['comment']; $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); } } else { if (count($typeNames) === 1) { // Check type hint for array and custom type. $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false) { $suggestedTypeHint = 'array'; } else { if (strpos($suggestedName, 'callable') !== false) { $suggestedTypeHint = 'callable'; } else { if (in_array($typeName, $this->allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } } } if ($suggestedTypeHint !== '' && isset($realParams[$checkPos]) === true) { $typeHint = $realParams[$checkPos]['type_hint']; if ($typeHint === '') { $error = 'Type hint "%s" missing for %s'; $data = array($suggestedTypeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'TypeHintMissing', $data); } else { if ($typeHint !== $suggestedTypeHint) { // The type hint could be fully namespaced, so we check // for the part after the last "\". $name_parts = explode('\\', $suggestedTypeHint); $last_part = end($name_parts); if ($last_part !== $typeHint) { $error = 'Expected type hint "%s"; found "%s" for %s'; $data = array($last_part, $typeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data); } } } } else { if ($suggestedTypeHint === '' && isset($realParams[$checkPos]) === true) { $typeHint = $realParams[$checkPos]['type_hint']; if ($typeHint !== '') { $error = 'Unknown type hint "%s" found for %s'; $data = array($typeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'InvalidTypeHint', $data); } } } //end if } } //end if } //end foreach $foundParams[] = $param['var']; // Check number of spaces after the type. $spaces = 1; if ($param['type_space'] !== $spaces) { $error = 'Expected %s spaces after parameter type; %s found'; $data = array($spaces, $param['type_space']); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamType', $data); if ($fix === true && $phpcsFile->fixer->enabled === true) { $phpcsFile->fixer->beginChangeset(); $content = $param['type']; $content .= str_repeat(' ', $spaces); $content .= $param['var']; $content .= str_repeat(' ', $param['var_space']); $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; } $newIndent = $param['commentLines'][$lineNum]['indent'] + $spaces - $param['type_space']; $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 = array($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. $firstChar = $param['commentLines'][0]['comment']; if (preg_match('|\\p{Lu}|u', $firstChar) === 0) { $error = 'Parameter comment must start with a capital letter'; $phpcsFile->addError($error, $param['commentLines'][0]['token'], 'ParamCommentNotCapital'); } $lastChar = substr($param['comment'], -1); if ($lastChar !== '.') { $error = 'Parameter comment must end with a full stop'; $lastLine = end($param['commentLines']); $phpcsFile->addError($error, $lastLine['token'], 'ParamCommentFullStop'); } } //end foreach }
/** * Process the function parameter comments. * * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); $params = array(); $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 = ''; if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { $matches = array(); 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 (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]; // 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) { $comment .= ' ' . $tokens[$i]['content']; } } } else { $error = 'Missing parameter comment'; $phpcsFile->addError($error, $tag, 'MissingParamComment'); } } 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[] = array('tag' => $tag, 'type' => $type, 'var' => $var, 'comment' => $comment, 'type_space' => $typeSpace, 'var_space' => $varSpace); } //end foreach $realParams = $phpcsFile->getMethodParameters($stackPtr); $foundParams = array(); foreach ($params as $pos => $param) { if ($param['var'] === '') { continue; } $foundParams[] = $param['var']; // Check number of spaces after the type. $spaces = $maxType - strlen($param['type']) + 1; if ($param['type_space'] !== $spaces) { $error = 'Expected %s spaces after parameter type; %s found'; $data = array($spaces, $param['type_space']); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamType', $data); if ($fix === true && $phpcsFile->fixer->enabled === true) { $content = $param['type']; $content .= str_repeat(' ', $spaces); $content .= $param['var']; $content .= str_repeat(' ', $param['var_space']); $content .= $param['comment']; $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); } } // Make sure the param name is correct. if (isset($realParams[$pos]) === true) { $realName = $realParams[$pos]['name']; // Append ampersand to name if passing by reference. if ($realParams[$pos]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $param['var']) { $code = 'ParamNameNoMatch'; $data = array($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); } } 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. $spaces = $maxVar - strlen($param['var']) + 1; if ($param['var_space'] !== $spaces) { $error = 'Expected %s spaces after parameter name; %s found'; $data = array($spaces, $param['var_space']); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamName', $data); if ($fix === true && $phpcsFile->fixer->enabled === true) { $content = $param['type']; $content .= str_repeat(' ', $param['type_space']); $content .= $param['var']; $content .= str_repeat(' ', $spaces); $content .= $param['comment']; $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); } } } //end foreach $realNames = array(); foreach ($realParams as $realParam) { if ($realParam['pass_by_reference'] === true) { $realParam['name'] = '&' . $realParam['name']; } $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); } }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $token = $tokens[$stackPtr]; // Skip broken function declarations. if (isset($token['scope_opener']) === false || isset($token['parenthesis_opener']) === false) { return; } $params = array(); foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) { $params[$param['name']] = $stackPtr; } $next = ++$token['scope_opener']; $end = --$token['scope_closer']; $foundContent = false; for (; $next <= $end; ++$next) { $token = $tokens[$next]; $code = $token['code']; // Ignorable tokens. if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === true) { continue; } if ($foundContent === false) { // A throw statement as the first content indicates an interface method. if ($code === T_THROW) { return; } // A return statement as the first content indicates an interface method. if ($code === T_RETURN) { $tmp = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $next + 1, null, true); if ($tmp === false) { return; } // There is a return. if ($tokens[$tmp]['code'] === T_SEMICOLON) { return; } $tmp = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $tmp + 1, null, true); if ($tmp !== false && $tokens[$tmp]['code'] === T_SEMICOLON) { // There is a return <token>. return; } } //end if } //end if $foundContent = true; if ($code === T_VARIABLE && isset($params[$token['content']]) === true) { unset($params[$token['content']]); } else { if ($code === T_DOLLAR) { $nextToken = $phpcsFile->findNext(T_WHITESPACE, $next + 1, null, true); if ($tokens[$nextToken]['code'] === T_OPEN_CURLY_BRACKET) { $nextToken = $phpcsFile->findNext(T_WHITESPACE, $nextToken + 1, null, true); if ($tokens[$nextToken]['code'] === T_STRING) { $varContent = '$' . $tokens[$nextToken]['content']; if (isset($params[$varContent]) === true) { unset($params[$varContent]); } } } } else { if ($code === T_DOUBLE_QUOTED_STRING || $code === T_START_HEREDOC || $code === T_START_NOWDOC) { // Tokenize strings that can contain variables. // Make sure the string is re-joined if it occurs over multiple lines. $validTokens = array(T_HEREDOC, T_NOWDOC, T_END_HEREDOC, T_END_NOWDOC, T_DOUBLE_QUOTED_STRING); $validTokens = array_merge($validTokens, PHP_CodeSniffer_Tokens::$emptyTokens); $content = $token['content']; for ($i = $next + 1; $i <= $end; $i++) { if (in_array($tokens[$i]['code'], $validTokens) === true) { $content .= $tokens[$i]['content']; $next++; } else { break; } } $stringTokens = token_get_all(sprintf('<?php %s;?>', $content)); foreach ($stringTokens as $stringPtr => $stringToken) { if (is_array($stringToken) === false) { continue; } $varContent = ''; if ($stringToken[0] === T_DOLLAR_OPEN_CURLY_BRACES) { $varContent = '$' . $stringTokens[$stringPtr + 1][1]; } else { if ($stringToken[0] === T_VARIABLE) { $varContent = $stringToken[1]; } } if ($varContent !== '' && isset($params[$varContent]) === true) { unset($params[$varContent]); } } } } } //end if } //end for if ($foundContent === true && count($params) > 0) { foreach ($params as $paramName => $position) { $error = 'The method parameter %s is never used'; $data = array($paramName); $phpcsFile->addWarning($error, $position, 'Found', $data); } } }
/** * Process the function parameter comments. * * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); $params = array(); $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 = array(); if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { $matches = array(); 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 (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 ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { $indent = 0; if ($tokens[$i - 1]['code'] === T_DOC_COMMENT_WHITESPACE) { $indent = strlen($tokens[$i - 1]['content']); } $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' => ''); } //end if } 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[] = array('tag' => $tag, 'type' => $type, 'var' => $var, 'comment' => $comment, 'commentLines' => $commentLines, 'type_space' => $typeSpace, 'var_space' => $varSpace); } //end foreach $realParams = $phpcsFile->getMethodParameters($stackPtr); $foundParams = array(); 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']); foreach ($typeNames as $typeName) { $suggestedName = PHP_CodeSniffer::suggestType($typeName); if ($typeName !== $suggestedName) { $error = 'Expected "%s" but found "%s" for parameter type'; $data = array($suggestedName, $typeName); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data); if ($fix === true) { $content = $suggestedName; $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); } } else { if (count($typeNames) === 1) { // Check type hint for array and custom type. $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false) { $suggestedTypeHint = 'array'; } else { if (strpos($suggestedName, 'callable') !== false) { $suggestedTypeHint = 'callable'; } else { if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } } } if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true) { $getType = substr($tokens[$tag + 2]['content'], 0, strlen($suggestedTypeHint)); $typeHint = $realParams[$pos]['type_hint']; if ($getType !== $suggestedTypeHint) { if ($typeHint === '') { $error = 'Type hint "%s" missing for %s'; $data = array($suggestedTypeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'TypeHintMissing', $data); } else { if ($typeHint !== substr($suggestedTypeHint, strlen($typeHint) * -1)) { $error = 'Expected type hint "%s"; found "%s" for %s'; $data = array($suggestedTypeHint, $typeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data); } } } } else { if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) { $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); } } } //end if } } //end if } //end foreach if ($param['var'] === '') { continue; } $foundParams[] = $param['var']; // Check number of spaces after the type. $spaces = $maxType - strlen($param['type']) + 1; if ($param['type_space'] !== $spaces) { $error = 'Expected %s spaces after parameter type; %s found'; $data = array($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']); $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; } $newIndent = $param['commentLines'][$lineNum]['indent'] + $spaces - $param['type_space']; $phpcsFile->fixer->replaceToken($param['commentLines'][$lineNum]['token'] - 1, str_repeat(' ', $newIndent)); } $phpcsFile->fixer->endChangeset(); } //end if } //end if // Make sure the param name is correct. if (isset($realParams[$pos]) === true) { $realName = $realParams[$pos]['name']; if ($realName !== $param['var']) { $code = 'ParamNameNoMatch'; $data = array($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); } } 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. $spaces = $maxVar - strlen($param['var']) + 1; if ($param['var_space'] !== $spaces) { $error = 'Expected %s spaces after parameter name; %s found'; $data = array($spaces, $param['var_space']); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamName', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $content = $param['type']; $content .= str_repeat(' ', $param['type_space']); $content .= $param['var']; $content .= str_repeat(' ', $spaces); $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; } $newIndent = $param['commentLines'][$lineNum]['indent'] + $spaces - $param['var_space']; $phpcsFile->fixer->replaceToken($param['commentLines'][$lineNum]['token'] - 1, str_repeat(' ', $newIndent)); } $phpcsFile->fixer->endChangeset(); } //end if } //end if // Param comments must start with a capital letter and end with the full stop. $firstChar = $param['comment'][0]; if (preg_match('|\\p{Lu}|u', $firstChar) === 0) { $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 = 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); } }
/** * Process the function parameter comments. * * @param int $commentStart The position in the stack where * the comment started. * @param int $commentEnd The position in the stack where * the comment ended. * * @return void */ protected function processParams($commentStart, $commentEnd) { $realParams = $this->currentFile->getMethodParameters($this->_functionToken); $params = $this->commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { if (substr_count($params[count($params) - 1]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) { $error = 'Last parameter comment requires a blank newline after it'; $errorPos = $params[count($params) - 1]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingAfterParams'); } // Parameters must appear immediately after the comment. if ($params[0]->getOrder() !== 2) { $error = 'Parameters must appear immediately after the comment'; $errorPos = $params[0]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParams'); } $previousParam = null; $spaceBeforeVar = 10000; $spaceBeforeComment = 10000; $longestType = 0; $longestVar = 0; foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; // Make sure that there is only one space before the var type. if ($param->getWhitespaceBeforeType() !== ' ') { $error = 'Expected 1 space before variable type'; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParamType'); } $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); if ($spaceCount < $spaceBeforeVar) { $spaceBeforeVar = $spaceCount; $longestType = $errorPos; } $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); if ($spaceCount < $spaceBeforeComment && $paramComment !== '') { $spaceBeforeComment = $spaceCount; $longestVar = $errorPos; } // Make sure they are in the correct order, and have the correct name. $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; if ($previousParam !== null) { $previousName = $previousParam->getVarName() !== '' ? $previousParam->getVarName() : 'UNKNOWN'; // Check to see if the parameters align properly. if ($param->alignsVariableWith($previousParam) === false) { $error = 'The variable names for parameters %s (%s) and %s (%s) do not align'; $data = array($previousName, $pos - 1, $paramName, $pos); $this->currentFile->addError($error, $errorPos, 'ParameterNamesNotAligned', $data); } if ($param->alignsCommentWith($previousParam) === false) { $error = 'The comments for parameters %s (%s) and %s (%s) do not align'; $data = array($previousName, $pos - 1, $paramName, $pos); $this->currentFile->addError($error, $errorPos, 'ParameterCommentsNotAligned', $data); } } // Variable must be one of the supported standard type. $typeNames = explode('|', $param->getType()); foreach ($typeNames as $typeName) { $suggestedName = PHP_CodeSniffer::suggestType($typeName); if ($typeName !== $suggestedName) { $error = 'Expected "%s"; found "%s" for %s at position %s'; $data = array($suggestedName, $typeName, $paramName, $pos); $this->currentFile->addError($error, $errorPos, 'IncorrectParamVarName', $data); } else { if (count($typeNames) === 1) { // Check type hint for array and custom type. $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false) { $suggestedTypeHint = 'array'; } else { if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } } if ($suggestedTypeHint !== '' && isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint === '') { $error = 'Type hint "%s" missing for %s at position %s'; $data = array($suggestedTypeHint, $paramName, $pos); $this->currentFile->addError($error, $commentEnd + 2, 'TypeHintMissing', $data); } else { if ($typeHint !== $suggestedTypeHint) { $error = 'Expected type hint "%s"; found "%s" for %s at position %s'; $data = array($suggestedTypeHint, $typeHint, $paramName, $pos); $this->currentFile->addError($error, $commentEnd + 2, 'IncorrectTypeHint', $data); } } } else { if ($suggestedTypeHint === '' && isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint !== '') { $error = 'Unknown type hint "%s" found for %s at position %s'; $data = array($typeHint, $paramName, $pos); $this->currentFile->addError($error, $commentEnd + 2, 'InvalidTypeHint', $data); } } } } } //end if } //end foreach // Make sure the names of the parameter comment matches the // actual parameter. if (isset($realParams[$pos - 1]) === true) { $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; // Append ampersand to name if passing by reference. if ($realParams[$pos - 1]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $paramName) { $code = 'ParamNameNoMatch'; $data = array($paramName, $realName, $pos); $error = 'Doc comment for var %s does not match '; if (strtolower($paramName) === strtolower($realName)) { $error .= 'case of '; $code = 'ParamNameNoCaseMatch'; } $error .= 'actual variable name %s at position %s'; $this->currentFile->addError($error, $errorPos, $code, $data); } } else { if (substr($paramName, -4) !== ',...') { // We must have an extra parameter comment. $error = 'Superfluous doc comment at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'ExtraParamComment'); } } if ($param->getVarName() === '') { $error = 'Missing parameter name at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'MissingParamName'); } if ($param->getType() === '') { $error = 'Missing type at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'MissingParamType'); } if ($paramComment === '') { $error = 'Missing comment for param "%s" at position %s'; $data = array($paramName, $pos); $this->currentFile->addError($error, $errorPos, 'MissingParamComment', $data); } else { // Param comments must start with a capital letter and // end with the full stop. $firstChar = $paramComment[0]; if (preg_match('|[A-Z]|', $firstChar) === 0) { $error = 'Param comment must start with a capital letter'; $this->currentFile->addError($error, $errorPos, 'ParamCommentNotCapital'); } $lastChar = $paramComment[strlen($paramComment) - 1]; if ($lastChar !== '.') { $error = 'Param comment must end with a full stop'; $this->currentFile->addError($error, $errorPos, 'ParamCommentFullStop'); } } $previousParam = $param; } //end foreach if ($spaceBeforeVar !== 1 && $spaceBeforeVar !== 10000 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest type'; $this->currentFile->addError($error, $longestType, 'SpacingAfterLongType'); } if ($spaceBeforeComment !== 1 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest variable name'; $this->currentFile->addError($error, $longestVar, 'SpacingAfterLongName'); } } //end if $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report missing comments. $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $error = 'Doc comment for "%s" missing'; $data = array($neededParam); $this->currentFile->addError($error, $errorPos, 'MissingParamTag', $data); } }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $token = $tokens[$stackPtr]; // Skip broken function declarations. if (isset($token['scope_opener']) === false || isset($token['parenthesis_opener']) === false) { return; } $params = array(); foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) { $params[$param['name']] = $stackPtr; } $next = ++$token['scope_opener']; $end = --$token['scope_closer']; $emptyBody = true; for (; $next <= $end; ++$next) { $token = $tokens[$next]; $code = $token['code']; // Ingorable tokens. if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === true) { continue; } else { if ($code === T_THROW && $emptyBody === true) { // Throw statement and an empty body indicate an interface method. return; } else { if ($code === T_RETURN && $emptyBody === true) { // Return statement and an empty body indicate an interface method. $tmp = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $next + 1, null, true); if ($tmp === false) { return; } // There is a return. if ($tokens[$tmp]['code'] === T_SEMICOLON) { return; } $tmp = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $tmp + 1, null, true); // There is a return <token>. if ($tmp !== false && $tokens[$tmp]['code'] === T_SEMICOLON) { return; } } } } //end if $emptyBody = false; if ($code === T_VARIABLE && isset($params[$token['content']]) === true) { unset($params[$token['content']]); } else { if ($code === T_DOUBLE_QUOTED_STRING || $code === T_HEREDOC) { // Tokenize strings that can contain variables. // Make sure the string is re-joined if it occurs over multiple lines. $string = $token['content']; for ($i = $next + 1; $i <= $end; $i++) { if ($tokens[$i]['code'] === $code) { $string .= $tokens[$i]['content']; $next++; } } $strTokens = token_get_all(sprintf('<?php %s;?>', $string)); foreach ($strTokens as $tok) { if (is_array($tok) === false || $tok[0] !== T_VARIABLE) { continue; } if (isset($params[$tok[1]]) === true) { unset($params[$tok[1]]); } } } } //end if } //end for if ($emptyBody === false && count($params) > 0) { foreach ($params as $paramName => $position) { $error = 'The method parameter %s is never used'; $data = array($paramName); $phpcsFile->addWarning($error, $position, 'Found', $data); } } }
/** * Process the function parameter comments. * * @param int $commentStart The position in the stack where * the comment started. * * @return void */ protected function processParams($commentStart, $commentEnd, $tagElements) { $realParams = $this->currentFile->getMethodParameters($this->_functionToken); $params = $this->commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { $lastParm = count($params) - 1; if (substr_count($params[$lastParm]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) { $error = 'Last parameter comment requires a blank newline after it'; $errorPos = $params[$lastParm]->getLine() + $commentStart; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.20') . $error, $errorPos); } // Parameters must appear immediately after the comment. if ($params[0]->getOrder() !== 2) { $error = 'Parameters must appear immediately after the comment'; $errorPos = $params[0]->getLine() + $commentStart; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.21') . $error, $errorPos); } $previousParam = null; $spaceBeforeVar = 10000; $spaceBeforeComment = 10000; $longestType = 0; $longestVar = 0; foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; // Check type $r = $this->checkType($param->getType(), $this->allowedParamTypes, 'param'); if (true !== $r) { $this->currentFile->addError($this->getReqPrefix('REQ.PHP.3.5.15') . $r, $errorPos); } // Make sure that there is only one space before the var type. if ($param->getWhitespaceBeforeType() !== ' ') { $error = 'Expected 1 space before variable type'; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.22') . $error, $errorPos); } $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); if ($spaceCount < $spaceBeforeVar) { $spaceBeforeVar = $spaceCount; $longestType = $errorPos; } $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); if ($spaceCount < $spaceBeforeComment && $paramComment !== '') { $spaceBeforeComment = $spaceCount; $longestVar = $errorPos; } // Make sure they are in the correct order, // and have the correct name. $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; if ($previousParam !== null) { $previousName = $previousParam->getVarName() !== '' ? $previousParam->getVarName() : 'UNKNOWN'; // Check to see if the parameters align properly. if ($param->alignsVariableWith($previousParam) === false) { $error = 'The variable names for parameters ' . $previousName . ' (' . ($pos - 1) . ') and ' . $paramName . ' (' . $pos . ') do not align'; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.23') . $error, $errorPos); } if ($param->alignsCommentWith($previousParam) === false) { $error = 'The comments for parameters ' . $previousName . ' (' . ($pos - 1) . ') and ' . $paramName . ' (' . $pos . ') do not align'; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.24') . $error, $errorPos); } } //end if // Make sure the names of the parameter comment matches the // actual parameter. if (isset($realParams[$pos - 1]) === true) { $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; // Append ampersand to name if passing by reference. if ($realParams[$pos - 1]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $param->getVarName()) { $error = 'Doc comment var "' . $paramName; $error .= '" does not match actual variable name "' . $realName; $error .= '" at position ' . $pos; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.25') . $error, $errorPos); } if (isset($realParams[$pos - 1]['default']) && !preg_match('/ OPTIONAL$/Ss', $paramComment)) { $this->currentFile->addError($this->getReqPrefix('REQ.PHP.3.5.18') . 'Переменная "' . $paramName . '" опциональная, но служебного тэга OPTIONAL ее комментарий не имеет', $errorPos); } } else { // We must have an extra parameter comment. $error = 'Superfluous doc comment at position ' . $pos; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.27') . $error, $errorPos); } if ($param->getVarName() === '') { $error = 'Missing parameter name at position ' . $pos; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.26') . $error, $errorPos); } if ($param->getType() === '') { $error = 'Missing type at position ' . $pos; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.26') . $error, $errorPos); } if ($paramComment === '') { $error = 'Missing comment for param "' . $paramName . '" at position ' . $pos; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.26') . $error, $errorPos); } elseif (preg_match('/^[a-z]/Ss', trim($paramComment))) { $error = 'Комментарий параметра "' . $paramName . '" начинается с маленькой буквы'; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.5.8') . $error, $errorPos); } $this->checkForDefaultValue($paramName, 'param', $errorPos); $this->checkForDefaultValue($paramComment, 'param', $errorPos); $previousParam = $param; } //end foreach if ($spaceBeforeVar !== 1 && $spaceBeforeVar !== 10000 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest type'; $this->currentFile->addError($this->getReqPrefix('?') . $error, $longestType); } if ($spaceBeforeComment !== 1 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest variable name'; $this->currentFile->addError($this->getReqPrefix('?') . $error, $longestVar); } } //end if $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report and missing comments. $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $error = 'Doc comment for "' . $neededParam . '" missing'; $this->currentFile->addError($this->getReqPrefix('REQ.PHP.4.1.27') . $error, $errorPos); } }
/** * Process the function parameter comments * * @param integer $commentStart The position in the stack where the comment started * @param integer $commentEnd The position in the stack where the comment ended * @return void */ protected function _processParams($commentStart, $commentEnd) { $realParams = $this->_currentFile->getMethodParameters($this->_functionToken); $params = $this->_commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { $isSpecialMethod = ($this->_methodName === '__construct' or $this->_methodName === '__destruct'); if (substr_count($params[count($params) - 1]->getWhitespaceAfter(), $this->_currentFile->eolChar) !== 1 and $isSpecialMethod === false) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; $this->_currentFile->addEvent('EMPTY_LINE_LAST_PARAMETER_FUNCTION_COMMENT', array(), $errorPos + 1); } // Parameters must appear immediately after the comment if ($params[0]->getOrder() !== 2) { $errorPos = $params[0]->getLine() + $commentStart; $this->_currentFile->addEvent('PARAMETER_AFTER_COMMENT_FUNCTION_COMMENT', array(), $errorPos); } $previousParam = null; $spaceBeforeVar = 10000; $spaceBeforeComment = 10000; $longestType = 0; $longestVar = 0; if (count($this->_commentParser->getThrows()) !== 0) { $isSpecialMethod = false; } foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; if ($isSpecialMethod === true and $param->getWhitespaceBeforeType() !== ' ') { $this->_currentFile->addEvent('ONE_SPACE_VARIABLE_FUNCTION_COMMENT', array(), $errorPos); } $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); if ($spaceCount < $spaceBeforeVar) { $spaceBeforeVar = $spaceCount; $longestType = $errorPos; } $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); if ($spaceCount < $spaceBeforeComment and $paramComment !== '') { $spaceBeforeComment = $spaceCount; $longestVar = $errorPos; } // Make sure they are in the correct order, and have the correct name $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; if ($previousParam !== null) { $previousName = $previousParam->getVarName() !== '' ? $previousParam->getVarName() : 'UNKNOWN'; // Check to see if the parameters align properly if ($param->alignsVariableWith($previousParam) === false) { $this->_currentFile->addEvent('VARIABLES_NAMES_NOT_ALIGN_FUNCTION_COMMENT', array('previousname' => $previousName, 'previousnamepos' => $pos - 1, 'paramname' => $paramName, 'paramnamepos' => $pos), $errorPos); } if ($param->alignsCommentWith($previousParam) === false) { $this->_currentFile->addEvent('COMMENTS_NOT_ALIGN_FUNCTION_COMMENT', array('previousname' => $previousName, 'previousnamepos' => $pos - 1, 'paramname' => $paramName, 'paramnamepos' => $pos), $errorPos); } } // Variable must be one of the supported standard type $typeNames = explode('|', $param->getType()); foreach ($typeNames as $typeName) { $suggestedName = PHP_CodeSniffer::suggestType($typeName); if ($typeName !== $suggestedName) { $this->_currentFile->addEvent('EXPECTED_FOUND_FUNCTION_COMMENT', array('suggestedname' => $suggestedName, 'paramname' => $paramName, 'typename' => $paramName, 'paramnamepos' => $pos), $errorPos); continue; } if (count($typeNames) !== 1) { continue; } // Check type hint for array and custom type $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false) { $suggestedTypeHint = 'array'; } else { if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } } if ($suggestedTypeHint !== '' and isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint === '') { $this->_currentFile->addEvent('TYPEHINT_MISSING_FUNCTION_COMMENT', array('suggestedtypehint' => $suggestedTypeHint, 'paramname' => $paramName, 'paramnamepos' => $pos), $commentEnd + 2); } else { if ($typeHint !== $suggestedTypeHint) { $this->_currentFile->addEvent('EXPECTED_TYPEHINT_FOUND_FUNCTION_COMMENT', array('suggestedtypehint' => $suggestedTypeHint, 'typehint' => $typeHint, 'paramname' => $paramName, 'paramnamepos' => $pos), $commentEnd + 2); } } } else { if ($suggestedTypeHint === '' and isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint !== '') { $this->_currentFile->addEvent('UNKNOW_TYPEHINT_FOUND_FUNCTION_COMMENT', array('typehint' => $typeHint, 'paramname' => $paramName, 'paramnamepos' => $pos), $commentEnd + 2); } } } } // Make sure the names of the parameter comment matches the // actual parameter if (isset($realParams[$pos - 1]) === true) { $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; // Append ampersand to name if passing by reference if ($realParams[$pos - 1]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $param->getVarName()) { $this->_currentFile->addEvent('DOCCOMMENT_NOT_MATCH_FUNCTION_COMMENT', array('paramname' => $paramName, 'realname' => $realName, 'paramnamepos' => $pos), $errorPos); } } else { // We must have an extra parameter comment $this->_currentFile->addEvent('SUPERFLUOUS_DOCCOMMENT_FUNCTION_COMMENT', array('doccommentpos' => $pos), $errorPos); } if ($param->getVarName() === '') { $this->_currentFile->addEvent('MISSING_PARAMETER_FUNCTION_COMMENT', array('parameterpos' => $pos), $errorPos); } if ($param->getType() === '') { $this->_currentFile->addEvent('MISSING_TYPE_FUNCTION_COMMENT', array('typepos' => $pos), $errorPos); } if ($paramComment === '') { $this->_currentFile->addEvent('MISSING_COMMENT_PARAM_FUNCTION_COMMENT', array('paramname' => $paramName, 'paramnamepos' => $pos), $errorPos); } else { // Check if optional params include (Optional) within their description $functionBegin = $this->_currentFile->findNext(array(T_FUNCTION), $commentStart); $functionName = $this->_currentFile->findNext(array(T_STRING), $functionBegin); $openBracket = $this->_tokens[$functionBegin]['parenthesis_opener']; $closeBracket = $this->_tokens[$functionBegin]['parenthesis_closer']; $nextParam = $this->_currentFile->findNext(T_VARIABLE, $openBracket + 1, $closeBracket); while ($nextParam !== false) { $nextToken = $this->_currentFile->findNext(T_WHITESPACE, $nextParam + 1, $closeBracket + 1, true); if ($nextToken === false and $this->_tokens[$nextParam + 1]['code'] === T_CLOSE_PARENTHESIS) { break; } $nextCode = $this->_tokens[$nextToken]['code']; $arg = $this->_tokens[$nextParam]['content']; if ($nextCode === T_EQUAL and $paramName === $arg) { if (substr($paramComment, 0, 11) !== '(Optional) ') { $this->_currentFile->addEvent('OPTIONAL_PARAM_START_FUNCTION_COMMENT', array('paramname' => $paramName), $errorPos); } } $nextParam = $this->_currentFile->findNext(T_VARIABLE, $nextParam + 1, $closeBracket); } } $previousParam = $param; } if ($spaceBeforeVar !== 1 and $spaceBeforeVar !== 10000 and $spaceBeforeComment !== 10000) { $this->_currentFile->addEvent('ONE_SPACE_LONGEST_TYPE_FUNCTION_COMMENT', array(), $longestType); } if ($spaceBeforeComment !== 1 and $spaceBeforeComment !== 10000) { $this->_currentFile->addEvent('ONE_SPACE_LONGEST_VARIABLE_FUNCTION_COMMENT', array(), $longestVar); } } $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report missing comments $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $this->_currentFile->addEvent('DOCCOMMENT_MISSING_FUNCTION_COMMENT', array('param' => $neededParam), $errorPos); } }
/** * Process the function parameter comments * * @param integer $commentStart The position in the stack where the comment started * @param integer $commentEnd The position in the stack where the comment ended * @return void */ protected function _processParams($commentStart, $commentEnd) { $realParams = $this->_currentFile->getMethodParameters($this->_functionToken); $params = $this->_commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { $isSpecialMethod = ($this->_methodName === '__construct' or $this->_methodName === '__destruct'); if (substr_count($params[count($params) - 1]->getWhitespaceAfter(), $this->_currentFile->eolChar) !== 1 and $isSpecialMethod === false) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; $this->_currentFile->addError("No empty line after last parameter comment allowed", $errorPos + 1, 'EmptyLineLastParameterFunctionComment'); } // Parameters must appear immediately after the comment if ($params[0]->getOrder() !== 2) { $errorPos = $params[0]->getLine() + $commentStart; $this->_currentFile->addError("Parameters must appear immediately after the comment", $errorPos, 'ParameterAfterCommentFunctionComment'); } $previousParam = null; $spaceBeforeVar = 10000; $spaceBeforeComment = 10000; $longestType = 0; $longestVar = 0; if (count($this->_commentParser->getThrows()) !== 0) { $isSpecialMethod = false; } foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; if ($isSpecialMethod === true and $param->getWhitespaceBeforeType() !== ' ') { $this->_currentFile->addError("Expected 1 space before variable type", $errorPos, 'OneSpaceVariableFunctionComment'); } $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); if ($spaceCount < $spaceBeforeVar) { $spaceBeforeVar = $spaceCount; $longestType = $errorPos; } $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); if ($spaceCount < $spaceBeforeComment and $paramComment !== '') { $spaceBeforeComment = $spaceCount; $longestVar = $errorPos; } // Make sure they are in the correct order, and have the correct name $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; if ($previousParam !== null) { $previousName = $previousParam->getVarName() !== '' ? $previousParam->getVarName() : 'UNKNOWN'; // Check to see if the parameters align properly if ($param->alignsVariableWith($previousParam) === false) { $this->_currentFile->addError("The variable names for parameters {$previousName} (" . ($pos - 1) . ") and {$paramName} ({$pos}) do not align", $errorPos, 'VariablesNamesNotAlignFunctionComment'); } if ($param->alignsCommentWith($previousParam) === false) { $this->_currentFile->addError("The comments for parameters {$previousName} (" . ($pos - 1) . ") and {$paramName} ({$pos}) do not align", $errorPos, 'CommentsNotAlignFunctionComment'); } } // Variable must be one of the supported standard type $typeNames = explode('|', $param->getType()); foreach ($typeNames as $typeName) { $suggestedName = PHP_CodeSniffer::suggestType($typeName); if ($typeName !== $suggestedName) { $this->_currentFile->addError("Expected {$suggestedName} found {$typeName} for {$paramName} at position {$pos}", $errorPos, 'ExpectedFoundFunctionComment'); continue; } if (count($typeNames) !== 1) { continue; } // Check type hint for array and custom type $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false) { $suggestedTypeHint = 'array'; } else { if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } } if ($suggestedTypeHint !== '' and isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint === '') { $this->_currentFile->addError("Type hint {$suggestedTypeHint} missing for {$paramName} at position {$pos}", $commentEnd + 2, 'TypehintMissingFunctionComment'); } else { if ($typeHint !== $suggestedTypeHint) { $this->_currentFile->addError("Expected type hint {$suggestedTypeHint} found {$typeHint} for {$paramName} at position {$pos}", $commentEnd + 2, 'ExpectedTypehintFoundFunctionComment'); } } } else { if ($suggestedTypeHint === '' and isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint !== '') { $this->_currentFile->addError("Unknown type hint {$typeHint} found for {$paramName} at position {$pos}", $commentEnd + 2, 'UnknowTypehintFoundFunctionComment'); } } } } // Make sure the names of the parameter comment matches the // actual parameter if (isset($realParams[$pos - 1]) === true) { $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; // Append ampersand to name if passing by reference if ($realParams[$pos - 1]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $param->getVarName()) { $this->_currentFile->addError("Doc comment var {$paramName} does not match actual variable name {$realName} at position {$pos}", $errorPos, 'DoccommentNotMatchFunctionComment'); } } else { // We must have an extra parameter comment $this->_currentFile->addError("Superfluous doc comment at position {$pos}", $errorPos, 'SuperfluousDoccommentFunctionComment'); } if ($param->getVarName() === '') { $this->_currentFile->addError("Missing parameter name at position {$pos}", $errorPos, 'MissingParameterFunctionComment'); } if ($param->getType() === '') { $this->_currentFile->addError("Missing type at position {$pos}", $errorPos, 'MissingTypeFunctionComment'); } if ($paramComment === '') { $this->_currentFile->addError("Missing comment for param {$paramName} at position {$pos}", $errorPos, 'MissingCommentParamFunctionComment'); } else { // Check if optional params include (Optional) within their description $functionBegin = $this->_currentFile->findNext(array(T_FUNCTION), $commentStart); $functionName = $this->_currentFile->findNext(array(T_STRING), $functionBegin); $openBracket = $this->_tokens[$functionBegin]['parenthesis_opener']; $closeBracket = $this->_tokens[$functionBegin]['parenthesis_closer']; $nextParam = $this->_currentFile->findNext(T_VARIABLE, $openBracket + 1, $closeBracket); while ($nextParam !== false) { $nextToken = $this->_currentFile->findNext(T_WHITESPACE, $nextParam + 1, $closeBracket + 1, true); if ($nextToken === false and $this->_tokens[$nextParam + 1]['code'] === T_CLOSE_PARENTHESIS) { break; } $nextCode = $this->_tokens[$nextToken]['code']; $arg = $this->_tokens[$nextParam]['content']; if ($nextCode === T_EQUAL and $paramName === $arg) { if (substr($paramComment, 0, 11) !== '(Optional) ') { $this->_currentFile->addError("Le commentaire pour le parametre {$paramName} doit commencer avec \"(Optional)\"", $errorPos, 'OptionalParamStartFunctionComment'); } } $nextParam = $this->_currentFile->findNext(T_VARIABLE, $nextParam + 1, $closeBracket); } } $previousParam = $param; } if ($spaceBeforeVar !== 1 and $spaceBeforeVar !== 10000 and $spaceBeforeComment !== 10000) { $this->_currentFile->addError("1 espace attendu après la définition du type pour les paramètres", $longestType, 'OneSpaceLongestTypeFunctionComment'); } if ($spaceBeforeComment !== 1 and $spaceBeforeComment !== 10000) { $this->_currentFile->addError("1 espace attendu après le nom de la variable pour les paramètres", $longestVar, 'OneSpaceLongestTypeFunctionComment'); } } $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report missing comments $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $this->_currentFile->addError("Commentaire de fonction manquant ({$neededParam})", $errorPos, 'DoccommentMissingFunctionComment'); } }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $token = $tokens[$stackPtr]; // Skip broken function declarations. if (isset($token['scope_opener']) === false || isset($token['parenthesis_opener']) === false) { return; } $params = array(); foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) { $params[$param['name']] = $stackPtr; } $next = ++$token['scope_opener']; $end = --$token['scope_closer']; $emptyBody = true; for (; $next <= $end; ++$next) { $token = $tokens[$next]; $code = $token['code']; // Ingorable tokens. if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === true) { continue; } else { if ($code === T_THROW && $emptyBody === true) { // Throw statement and an empty body indicate an interface method. return; } else { if ($code === T_RETURN && $emptyBody === true) { // Return statement and an empty body indicate an interface method. $tmp = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $next + 1, null, true); if ($tmp === false) { return; } // There is a return. if ($tokens[$tmp] === T_SEMICOLON) { return; } $tmp = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $tmp + 1, null, true); // There is a return <token>. if ($tmp !== false && $tokens[$tmp] === T_SEMICOLON) { return; } } } } //end if $emptyBody = false; if ($code === T_VARIABLE && isset($params[$token['content']]) === true) { unset($params[$token['content']]); } else { if ($code === T_DOUBLE_QUOTED_STRING) { // Tokenize double quote string. $strTokens = token_get_all(sprintf('<?php %s;?>', $token['content'])); foreach ($strTokens as $tok) { foreach (array_keys($params) as $par) { if (is_array($tok) && strpos($tok[1], $par) !== false) { unset($params[$par]); } } if (is_array($tok) === false || $tok[0] !== T_VARIABLE) { continue; } if (isset($params[$tok[1]]) === true) { unset($params[$tok[1]]); } } } } //end if } //end for if ($emptyBody === false && count($params) > 0) { foreach ($params as $paramName => $position) { $error = 'El parámetro ' . $paramName . ' nunca es usado'; $phpcsFile->addWarning($error, $position); } } }
/** * @param PHP_CodeSniffer_File $phpcsFile * @param $stackPtr * * @return bool */ private function functionHasParams(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { return (bool) $phpcsFile->getMethodParameters($stackPtr); }
/** * Process the code * * @return void * @param PHP_CodeSniffer_File $phpcsFile The file. * @param unknown_type $stackPtr The stackpointer. */ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { // @todo no empty lines after { and before } // get the tokens $tokens = $phpcsFile->getTokens(); $current = $tokens[$stackPtr]; $lines = file($phpcsFile->getFilename()); $next = $tokens[$stackPtr + 1]; if ($next['content'] != ' ') { $phpcsFile->addError('After "function" we expect exactly one space.', $stackPtr); } if (isset($current['scope_opener'])) { // check if { is on next line if ($tokens[$current['scope_opener']]['line'] - $current['line'] != 1) { $phpcsFile->addError('The opening brace of a function/method should be placed on the line below the signature.', $stackPtr); } if ($tokens[$current['scope_opener']]['column'] != $tokens[$current['scope_closer']]['column']) { $phpcsFile->addError('The opening and closing brace should be indentend equaly.', $stackPtr); } // find class $class = $phpcsFile->findPrevious(T_CLASS, $stackPtr); // class found? if ($class != false) { // get next function inside the class $nextFunction = $phpcsFile->findNext(T_FUNCTION, $stackPtr + 1, $tokens[$class]['scope_closer']); // not last method? if ($nextFunction != false && isset($tokens[$stackPtr - 2]) && in_array($tokens[$stackPtr - 2]['code'], array(T_PRIVATE, T_PUBLIC, T_PROTECTED, T_STATIC))) { if (!($lines[$tokens[$current['scope_closer']]['line']] == "\n" && $lines[$tokens[$current['scope_closer']]['line'] + 1] == "\n" && trim($lines[$tokens[$current['scope_closer']]['line'] + 2]) != '')) { $phpcsFile->addError('After a function/method we expect 2 empty lines.', $stackPtr); } } } } // find comment if ($phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, $stackPtr, $stackPtr - 7) === false) { $phpcsFile->addError('We expect a function/method to have PHPDoc-documentation.', $stackPtr); } if (isset($tokens[$stackPtr - 2]) && in_array($tokens[$stackPtr - 2]['code'], array(T_PRIVATE, T_PUBLIC, T_PROTECTED, T_STATIC))) { // get comment $startComment = (int) $phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, null, null, '/**' . "\n"); $endComment = (int) $phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, null, null, ' */'); $parameters = $phpcsFile->getMethodParameters($stackPtr); $hasReturn = false; $returnFirst = false; $paramCounter = 0; for ($i = $startComment; $i <= $endComment; $i++) { // package if (trim(substr($tokens[$i]['content'], 0, 11)) == '* @return') { // reset $hasReturn = true; // find part $content = trim(substr($tokens[$i]['content'], strrpos($tokens[$i]['content'], "\t"))); // validate syntax if (substr($tokens[$i]['content'], 11, 1) != "\t") { $phpcsFile->addError('After "@return" there should be at least one tab.', $i); } $returnFirst = true; } if (trim(substr($tokens[$i]['content'], 0, 10)) == '* @param') { if (!$returnFirst && $phpcsFile->getDeclarationName($stackPtr) !== '__construct') { $phpcsFile->addError('We expect "@return" before "@param".', $i); } // find part $content = trim(substr($tokens[$i]['content'], strpos($tokens[$i]['content'], "\t", 2))); $type = substr($content, 0, strpos($content, ' ')); $varName = trim(substr($content, strpos($content, ' ') + 1)); if (substr_count($varName, "\t") > 0) { if (substr($varName, -1, 1) != '.' && substr($varName, -1, 1) != '?') { $phpcsFile->addWarning('Shouldn\'t the description have a "." on the end?', $i); } $varName = trim(substr($varName, 0, strpos($varName, "\t"))); } else { $phpcsFile->addWarning('Shouldn\'t the parameter be documented?', $i); } // check if it match if (!preg_match('/(.*)\\s\\$(.*)(\\t(.*))?$/', $content)) { $phpcsFile->addError('Wrong sequence, we expect "@param" in the following way: @param[tab]<type>[space]<varname>([tab]<documentation>).', $i); } if (!isset($parameters[$paramCounter]['name']) || $parameters[$paramCounter]['name'] != $varName) { $phpcsFile->addError('We expect the variablename used in the PHPDoc to be the same as the one used in the parameterlist.', $i); } if (isset($parameters[$paramCounter]['default']) && $parameters[$paramCounter]['default'] != '') { if (substr_count($type, '[optional]') == 0) { $phpcsFile->addError('We expect optional parameters to have "[optional]" just after the type.', $i); } if ($parameters[$paramCounter]['type_hint'] != '' && $type != $parameters[$paramCounter]['type_hint'] . '[optional]') { $phpcsFile->addError('The type in the PHPDoc doesn\'t match the typehinting in the signature.', $i); } } elseif (isset($parameters[$paramCounter]['type_hint']) && $parameters[$paramCounter]['type_hint'] != '' && $type != $parameters[$paramCounter]['type_hint']) { $phpcsFile->addError('The type in the PHPDoc doesn\'t match the typehinting in the signature.', $i); } // increment $paramCounter++; } if (trim(substr($tokens[$i]['content'], 0, 15)) == '* @deprecated') { // find part $content = trim(substr($tokens[$i]['content'], strpos($tokens[$i]['content'], "\t", 2))); if ($content != '') { if (substr($content, -1, 1) != '.' && substr($content, -1, 1) != '?') { $phpcsFile->addWarning('Shouldn\'t the explanation have a "." on the end?', $i); } } else { $phpcsFile->addWarning('Shouldn\'t there be an explanation?', $i); } } } // incorrect number of parameters if ($paramCounter != count($parameters)) { $phpcsFile->addError('We expect all parameters to be in the PHPDoc.', $stackPtr); } } // cleanup unset($tokens); unset($current); unset($lines); unset($next); }
/** * Process the function parameter comments. * * @param int $commentStart The position in the stack where * the comment started. * * @return void */ protected function processParams($commentStart) { $realParams = $this->currentFile->getMethodParameters($this->_functionToken); $params = $this->commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { $lastParm = count($params) - 1; if (substr_count($params[$lastParm]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) { $error = 'Last parameter comment requires a blank newline after it'; $errorPos = $params[$lastParm]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos); } // Parameters must appear immediately after the comment. if ($params[0]->getOrder() !== 2) { $error = 'Parameters must appear immediately after the comment'; $errorPos = $params[0]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos); } $previousParam = null; $spaceBeforeVar = 10000; $spaceBeforeComment = 10000; $longestType = 0; $longestVar = 0; foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; // Make sure that there is only one space before the var type. if ($param->getWhitespaceBeforeType() !== ' ') { $error = 'Expected 1 space before variable type'; $this->currentFile->addError($error, $errorPos); } $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); if ($spaceCount < $spaceBeforeVar) { $spaceBeforeVar = $spaceCount; $longestType = $errorPos; } $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); if ($spaceCount < $spaceBeforeComment && $paramComment !== '') { $spaceBeforeComment = $spaceCount; $longestVar = $errorPos; } // Make sure they are in the correct order, // and have the correct name. $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; if ($previousParam !== null) { $previousName = $previousParam->getVarName() !== '' ? $previousParam->getVarName() : 'UNKNOWN'; // Check to see if the parameters align properly. if ($param->alignsVariableWith($previousParam) === false) { $error = 'The variable names for parameters ' . $previousName . ' (' . ($pos - 1) . ') and ' . $paramName . ' (' . $pos . ') do not align'; $this->currentFile->addError($error, $errorPos); } if ($param->alignsCommentWith($previousParam) === false) { $error = 'The comments for parameters ' . $previousName . ' (' . ($pos - 1) . ') and ' . $paramName . ' (' . $pos . ') do not align'; $this->currentFile->addError($error, $errorPos); } } //end if // Make sure the names of the parameter comment matches the // actual parameter. if (isset($realParams[$pos - 1]) === true) { $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; // Append ampersand to name if passing by reference. if ($realParams[$pos - 1]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $param->getVarName()) { $error = 'Doc comment var "' . $paramName; $error .= '" does not match actual variable name "' . $realName; $error .= '" at position ' . $pos; $this->currentFile->addError($error, $errorPos); } } else { // We must have an extra parameter comment. $error = 'Superfluous doc comment at position ' . $pos; $this->currentFile->addError($error, $errorPos); } if ($param->getVarName() === '') { $error = 'Missing parameter name at position ' . $pos; $this->currentFile->addError($error, $errorPos); } if ($param->getType() === '') { $error = 'Missing type at position ' . $pos; $this->currentFile->addError($error, $errorPos); } if ($paramComment === '') { $error = 'Missing comment for param "' . $paramName . '" at position ' . $pos; $this->currentFile->addError($error, $errorPos); } $previousParam = $param; } //end foreach if ($spaceBeforeVar !== 1 && $spaceBeforeVar !== 10000 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest type'; $this->currentFile->addError($error, $longestType); } if ($spaceBeforeComment !== 1 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest variable name'; $this->currentFile->addError($error, $longestVar); } } //end if $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report and missing comments. $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $error = 'Doc comment for "' . $neededParam . '" missing'; $this->currentFile->addError($error, $errorPos); } }
/** * Process the function parameter comments. * * @param int $commentStart The position in the stack where * the comment started. * * @return void */ protected function processParams($commentStart) { $realParams = $this->currentFile->getMethodParameters($this->_functionToken); $params = $this->commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { $lastParm = count($params) - 1; if (substr_count($params[$lastParm]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 1) { $error = 'Last parameter comment must not a blank newline after it'; $errorPos = $params[$lastParm]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingAfterParams'); } // Parameters must appear immediately after the comment. if ($params[0]->getOrder() !== 2) { $error = 'Parameters must appear immediately after the comment'; $errorPos = $params[0]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParams'); } $previousParam = null; foreach ($params as $param) { $errorPos = $param->getLine() + $commentStart; // Make sure they are in the correct order, // and have the correct name. $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; // Make sure the names of the parameter comment matches the // actual parameter. if (isset($realParams[$pos - 1]) === true) { // Make sure that there are only tabs used to intend the var type. if ($this->isTabUsedToIntend($param->getWhitespaceBeforeType())) { $error = 'Spaces must be used to indent the variable type; tabs are not allowed'; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParamType'); } // Make sure that there are only tabs used to intend the var comment. if ($this->isTabUsedToIntend($param->getWhiteSpaceBeforeComment())) { $error = 'Spaces must be used to indent the variable comment; tabs are not allowed'; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParamComment'); } // Make sure that there are only tabs used to intend the var name. if ($param->getVarName() && $this->isTabUsedToIntend($param->getWhiteSpaceBeforeVarName())) { $error = 'Spaces must be used to indent the variable name; tabs are not allowed'; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParamName'); } $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; // Append ampersand to name if passing by reference. if ($realParams[$pos - 1]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $paramName) { $code = 'ParamNameNoMatch'; $data = array($paramName, $realName, $pos); $error = 'Doc comment for var %s does not match '; if (strtolower($paramName) === strtolower($realName)) { $error .= 'case of '; $code = 'ParamNameNoCaseMatch'; } $error .= 'actual variable name %s at position %s'; $this->currentFile->addError($error, $errorPos, $code, $data); } } else { // Throw an error if we found a parameter in comment but not in the parameter list of the function $error = 'The paramter "' . $paramName . '" at position ' . $pos . ' is superfluous, because this parameter was not found in parameter list.'; $this->currentFile->addError($error, $errorPos, 'SuperFluous.ParamComment'); } if ($param->getVarName() === '') { $error = 'Missing parameter name at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'MissingParamName'); } if ($param->getType() === '') { $error = 'Missing type at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'MissingParamType'); } } } $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report and missing comments. $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $error = 'Doc comment for "%s" missing'; $data = array($neededParam); $this->currentFile->addError($error, $errorPos, 'MissingParamTag', $data); } }
/** * Process the function parameter comments. * * @param PHP_CodeSniffer_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. * * @throws \PHP_CodeSniffer_Exception * @return void */ protected function processParams(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); $params = array(); foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { if ($tokens[$tag]['content'] !== '@param') { continue; } $type = ''; $typeSpace = 0; $isVarIntendByTab = false; $var = ''; $varSpace = 0; $isCommentIndentByTab = false; $comment = ''; $commentLines = array(); if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { $matches = array(); preg_match('/([^$&]+)(?:((?:\\$|&)[^\\s]+)(?:(\\s+)(.*))?)?/', $tokens[$tag + 2]['content'], $matches); $typeLen = strlen($matches[1]); $type = trim($matches[1]); $typeSpace = $typeLen - strlen($type); $isVarIntendByTab = $this->isTabUsedToIntend($matches[1]); if (isset($matches[2]) === true) { $var = $matches[2]; if (isset($matches[4]) === true) { $varSpace = strlen($matches[3]); $isCommentIndentByTab = $this->isTabUsedToIntend($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 ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { $indent = 0; if ($tokens[$i - 1]['code'] === T_DOC_COMMENT_WHITESPACE) { $indent = strlen($tokens[$i - 1]['content']); } $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' => ''); } //end if } 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[] = array('tag' => $tag, 'type' => $type, 'var' => $var, 'comment' => $comment, 'commentLines' => $commentLines, 'type_space' => $typeSpace, 'var_tab_indent' => $isVarIntendByTab, 'var_space' => $varSpace, 'comment_tab_indent' => $isCommentIndentByTab); } //end foreach $realParams = $phpcsFile->getMethodParameters($stackPtr); $foundParams = array(); 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']); foreach ($typeNames as $typeName) { $suggestedName = self::suggestType($typeName); if ($suggestedName !== '\\TYPO3\\CMS\\Extbase\\Persistence\\ObjectStorage' && $typeName !== $suggestedName) { $error = 'Expected "%s" but found "%s" for parameter type'; $data = array($suggestedName, $typeName); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data); if ($fix === true) { $content = $suggestedName; $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); } } else { if (count($typeNames) === 1) { // Check type hint for array and custom type. $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false) { $suggestedTypeHint = 'array'; } else { if (strpos($suggestedName, 'callable') !== false) { $suggestedTypeHint = 'callable'; // TODO: AllowedTypes are the long version but we only allow the short version. } else { if (in_array($typeName, self::$allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } } } if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true) { $typeHint = $realParams[$pos]['type_hint']; if ($typeHint === '') { $error = 'Type hint "%s" missing for %s'; $data = array($suggestedTypeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'TypeHintMissing', $data); } else { if ($typeHint !== substr($suggestedTypeHint, strlen($typeHint) * -1)) { $error = 'Expected type hint "%s"; found "%s" for %s'; $data = array($suggestedTypeHint, $typeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data); } } } else { if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) { $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); } } } //end if } } //end if } //end foreach if ($param['var'] === '') { continue; } $foundParams[] = $param['var']; // Make sure the param name is correct. if (isset($realParams[$pos]) === true) { $realName = $realParams[$pos]['name']; if ($realName !== $param['var']) { $code = 'ParamNameNoMatch'; $data = array($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); } } 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; } // Param comments should start with a capital letter. $firstChar = $param['comment'][0]; if (preg_match('|\\p{Lu}|u', $firstChar) === 0) { $error = 'Parameter comment should start with a capital letter'; $phpcsFile->addWarning($error, $param['tag'], 'ParamCommentNotCapital'); } } //end foreach $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); } }
/** * Process the function parameter comments. * * @param integer $commentStart The position in the stack where the comment started * @param integer $commentEnd The position in the stack where the comment ended * @return void */ protected function processParams($commentStart, $commentEnd) { $realParams = $this->currentFile->getMethodParameters($this->_functionToken); $params = $this->commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { if (substr_count($params[count($params) - 1]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 1) { $error = 'No empty line after last parameter comment allowed'; $errorPos = $params[count($params) - 1]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos + 1); } // Parameters must appear immediately after the comment. if ($params[0]->getOrder() !== 2) { $error = 'Parameters must appear immediately after the comment'; $errorPos = $params[0]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos); } $previousParam = null; $spaceBeforeVar = 10000; $spaceBeforeComment = 10000; $longestType = 0; $longestVar = 0; foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; // Make sure that there is only one space before the var type. if ($param->getWhitespaceBeforeType() !== ' ') { $error = 'Expected 2 space before variable type'; $this->currentFile->addError($error, $errorPos); } $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); if ($spaceCount < $spaceBeforeVar) { $spaceBeforeVar = $spaceCount; $longestType = $errorPos; } $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); if ($spaceCount < $spaceBeforeComment && $paramComment !== '') { $spaceBeforeComment = $spaceCount; $longestVar = $errorPos; } // Make sure they are in the correct order, and have the correct name. $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; if ($previousParam !== null) { $previousName = $previousParam->getVarName() !== '' ? $previousParam->getVarName() : 'UNKNOWN'; // Check to see if the parameters align properly. if ($param->alignsVariableWith($previousParam) === false) { $error = 'The variable names for parameters ' . $previousName . ' (' . ($pos - 1) . ') and ' . $paramName . ' (' . $pos . ') do not align'; $this->currentFile->addError($error, $errorPos); } if ($param->alignsCommentWith($previousParam) === false) { $error = 'The comments for parameters ' . $previousName . ' (' . ($pos - 1) . ') and ' . $paramName . ' (' . $pos . ') do not align'; $this->currentFile->addError($error, $errorPos); } } // Variable must be one of the supported standard type. $typeNames = explode('|', $param->getType()); foreach ($typeNames as $typeName) { $suggestedName = PHP_CodeSniffer::suggestType($typeName); if ($typeName !== $suggestedName) { $error = "Expected \"{$suggestedName}\"; found \"{$typeName}\" for {$paramName} at position {$pos}"; $this->currentFile->addError($error, $errorPos); } else { if (count($typeNames) === 1) { // Check type hint for array and custom type. $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false) { $suggestedTypeHint = 'array'; } else { if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } } if ($suggestedTypeHint !== '' && isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint === '') { $error = "Type hint \"{$suggestedTypeHint}\" missing for {$paramName} at position {$pos}"; $this->currentFile->addError($error, $commentEnd + 2); } else { if ($typeHint !== $suggestedTypeHint) { $error = "Expected type hint \"{$suggestedTypeHint}\"; found \"{$typeHint}\" for {$paramName} at position {$pos}"; $this->currentFile->addError($error, $commentEnd + 2); } } } else { if ($suggestedTypeHint === '' && isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint !== '') { $error = "Unknown type hint \"{$typeHint}\" found for {$paramName} at position {$pos}"; $this->currentFile->addError($error, $commentEnd + 2); } } } } } } // Make sure the names of the parameter comment matches the // actual parameter. if (isset($realParams[$pos - 1]) === true) { $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; // Append ampersand to name if passing by reference. if ($realParams[$pos - 1]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $param->getVarName()) { $error = 'Doc comment var "' . $paramName; $error .= '" does not match actual variable name "' . $realName; $error .= '" at position ' . $pos; $this->currentFile->addError($error, $errorPos); } } else { // We must have an extra parameter comment. $error = 'Superfluous doc comment at position ' . $pos; $this->currentFile->addError($error, $errorPos); } if ($param->getVarName() === '') { $error = 'Missing parameter name at position ' . $pos; $this->currentFile->addError($error, $errorPos); } if ($param->getType() === '') { $error = 'Missing type at position ' . $pos; $this->currentFile->addError($error, $errorPos); } if ($paramComment === '') { $error = 'Missing comment for param "' . $paramName . '" at position ' . $pos; $this->currentFile->addError($error, $errorPos); } else { // Param comments must start with a capital letter and // end with the full stop. $firstChar = $paramComment[0]; if (preg_match('|[A-Z]|', $firstChar) === 0) { $error = 'Param comment must start with a capital letter'; $this->currentFile->addError($error, $errorPos); } } $previousParam = $param; } if ($spaceBeforeVar !== 1 && $spaceBeforeVar !== 10000 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest type'; $this->currentFile->addError($error, $longestType); } if ($spaceBeforeComment !== 1 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest variable name'; $this->currentFile->addError($error, $longestVar); } } $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report missing comments. $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $error = 'Doc comment for "' . $neededParam . '" missing'; $this->currentFile->addError($error, $errorPos); } }
/** * Process the function parameter comments * * @param integer $commentStart The position in the stack where the comment started * @param integer $commentEnd The position in the stack where the comment ended * @return void */ protected function _processParams($commentStart, $commentEnd) { $realParams = $this->_currentFile->getMethodParameters($this->_functionToken); $params = $this->_commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { $isSpecialMethod = ($this->_methodName === '__construct' or $this->_methodName === '__destruct'); if (substr_count($params[count($params) - 1]->getWhitespaceAfter(), $this->_currentFile->eolChar) !== 1 and $isSpecialMethod === false) { $error = 'No empty line after last parameter comment allowed'; $errorPos = $params[count($params) - 1]->getLine() + $commentStart; $this->_currentFile->addError($error, $errorPos + 1); } // Parameters must appear immediately after the comment if ($params[0]->getOrder() !== 2) { $error = 'Parameters must appear immediately after the comment'; $errorPos = $params[0]->getLine() + $commentStart; $this->_currentFile->addError($error, $errorPos); } $previousParam = null; $spaceBeforeVar = 10000; $spaceBeforeComment = 10000; $longestType = 0; $longestVar = 0; if (count($this->_commentParser->getThrows()) !== 0) { $isSpecialMethod = false; } foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; // Make sure that there is only one or two space before the var type if ($isSpecialMethod === false and $param->getWhitespaceBeforeType() !== ' ') { $error = 'Expected 2 spaces before variable type'; $this->_currentFile->addError($error, $errorPos); } if ($isSpecialMethod === true and $param->getWhitespaceBeforeType() !== ' ') { $error = 'Expected 1 space before variable type'; $this->_currentFile->addError($error, $errorPos); } $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); if ($spaceCount < $spaceBeforeVar) { $spaceBeforeVar = $spaceCount; $longestType = $errorPos; } $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); if ($spaceCount < $spaceBeforeComment and $paramComment !== '') { $spaceBeforeComment = $spaceCount; $longestVar = $errorPos; } // Make sure they are in the correct order, and have the correct name $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; if ($previousParam !== null) { $previousName = $previousParam->getVarName() !== '' ? $previousParam->getVarName() : 'UNKNOWN'; // Check to see if the parameters align properly if ($param->alignsVariableWith($previousParam) === false) { $error = 'The variable names for parameters ' . $previousName . ' (' . ($pos - 1) . ') and ' . $paramName . ' (' . $pos . ') do not align'; $this->_currentFile->addError($error, $errorPos); } if ($param->alignsCommentWith($previousParam) === false) { $error = 'The comments for parameters ' . $previousName . ' (' . ($pos - 1) . ') and ' . $paramName . ' (' . $pos . ') do not align'; $this->_currentFile->addError($error, $errorPos); } } // Variable must be one of the supported standard type $typeNames = explode('|', $param->getType()); foreach ($typeNames as $typeName) { $suggestedName = PHP_CodeSniffer::suggestType($typeName); if ($typeName !== $suggestedName) { $error = "Expected \"{$suggestedName}\"; found \"{$typeName}\" for {$paramName} at position {$pos}"; $this->_currentFile->addError($error, $errorPos); continue; } if (count($typeNames) !== 1) { continue; } // Check type hint for array and custom type $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false) { $suggestedTypeHint = 'array'; } else { if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } } if ($suggestedTypeHint !== '' and isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint === '') { $error = "Type hint \"{$suggestedTypeHint}\" missing for {$paramName} at position {$pos}"; $this->_currentFile->addError($error, $commentEnd + 2); } else { if ($typeHint !== $suggestedTypeHint) { $error = "Expected type hint \"{$suggestedTypeHint}\"; found \"{$typeHint}\"" . " for {$paramName} at position {$pos}"; $this->_currentFile->addError($error, $commentEnd + 2); } } } else { if ($suggestedTypeHint === '' and isset($realParams[$pos - 1]) === true) { $typeHint = $realParams[$pos - 1]['type_hint']; if ($typeHint !== '') { $error = "Unknown type hint \"{$typeHint}\" found for {$paramName} at position {$pos}"; $this->_currentFile->addError($error, $commentEnd + 2); } } } } // Make sure the names of the parameter comment matches the // actual parameter if (isset($realParams[$pos - 1]) === true) { $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; // Append ampersand to name if passing by reference if ($realParams[$pos - 1]['pass_by_reference'] === true) { $realName = '&' . $realName; } if ($realName !== $param->getVarName()) { $error = 'Doc comment var "' . $paramName; $error .= '" does not match actual variable name "' . $realName; $error .= '" at position ' . $pos; $this->_currentFile->addError($error, $errorPos); } } else { // We must have an extra parameter comment $error = 'Superfluous doc comment at position ' . $pos; $this->_currentFile->addError($error, $errorPos); } if ($param->getVarName() === '') { $error = 'Missing parameter name at position ' . $pos; $this->_currentFile->addError($error, $errorPos); } if ($param->getType() === '') { $error = 'Missing type at position ' . $pos; $this->_currentFile->addError($error, $errorPos); } if ($paramComment === '') { $error = 'Missing comment for param "' . $paramName . '" at position ' . $pos; $this->_currentFile->addError($error, $errorPos); } else { // Param comments must start with a capital letter $firstChar = $paramComment[0]; if (preg_match('|[A-Z]|', $firstChar) === 0 and $firstChar !== '(') { $error = 'Param comment must start with a capital letter'; $this->_currentFile->addError($error, $errorPos); } // Check if optional params include (Optional) within their description $functionBegin = $this->_currentFile->findNext(array(T_FUNCTION), $commentStart); $functionName = $this->_currentFile->findNext(array(T_STRING), $functionBegin); $openBracket = $this->_tokens[$functionBegin]['parenthesis_opener']; $closeBracket = $this->_tokens[$functionBegin]['parenthesis_closer']; $nextParam = $this->_currentFile->findNext(T_VARIABLE, $openBracket + 1, $closeBracket); while ($nextParam !== false) { $nextToken = $this->_currentFile->findNext(T_WHITESPACE, $nextParam + 1, $closeBracket + 1, true); if ($nextToken === false and $this->_tokens[$nextParam + 1]['code'] === T_CLOSE_PARENTHESIS) { break; } $nextCode = $this->_tokens[$nextToken]['code']; $arg = $this->_tokens[$nextParam]['content']; if ($nextCode === T_EQUAL and $paramName === $arg) { if (substr($paramComment, 0, 11) !== '(Optional) ') { $error = "Optional param comment for '{$paramName}' must start with '(Optional)'"; $this->_currentFile->addError($error, $errorPos); } else { if (preg_match('|[A-Z]|', $paramComment[11]) === 0) { $error = 'Param comment must start with a capital letter'; $this->_currentFile->addError($error, $errorPos); } } } $nextParam = $this->_currentFile->findNext(T_VARIABLE, $nextParam + 1, $closeBracket); } } $previousParam = $param; } if ($spaceBeforeVar !== 1 and $spaceBeforeVar !== 10000 and $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest type'; $this->_currentFile->addError($error, $longestType); } if ($spaceBeforeComment !== 1 and $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest variable name'; $this->_currentFile->addError($error, $longestVar); } } $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report missing comments $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $error = 'Doc comment for "' . $neededParam . '" missing'; $this->_currentFile->addError($error, $errorPos); } }
/** * Process a @param comment . * * @param File $phpcsFile * @param int $stackPtr * @param int $commentStart */ 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 = ''; 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 (isset($matches[2]) === true) { $var = str_replace('...', '', $matches[2]); $varLen = strlen($var); if ($varLen > $maxVar) { $maxVar = $varLen; } if (isset($matches[4]) === true) { $varSpace = strlen($matches[3]); $comment = $matches[4]; // 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) { $comment .= ' ' . $tokens[$i]['content']; } } } //$error = 'Missing parameter comment'; //$phpcsFile->addError($error, $tag, 'MissingParamComment'); } else { $error = 'Missing parameter name'; $phpcsFile->addError($error, $tag, 'MissingParamName'); } } else { $error = 'Missing parameter type'; $phpcsFile->addError($error, $tag, 'MissingParamType'); } $params[] = ['tag' => $tag, 'type' => $type, 'var' => $var, 'comment' => $comment, 'type_space' => $typeSpace, 'var_space' => $varSpace]; } $realParams = $phpcsFile->getMethodParameters($stackPtr); $foundParams = []; foreach ($params as $pos => $param) { if ($param['var'] === '') { continue; } if (array_key_exists('type', $param)) { $types = explode('|', $param['type']); foreach ($types as $type) { $this->checkParamType($type, $param['tag'], $phpcsFile); } } $foundParams[] = $param['var']; // 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) { $content = $param['type']; $content .= str_repeat(' ', $spaces); $content .= $param['var']; $content .= str_repeat(' ', $param['var_space']); $content .= $param['comment']; $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); } } // Make sure the param name is correct. if (isset($realParams[$pos]) === true) { $realName = $realParams[$pos]['name']; if ($realName !== $param['var']) { $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); } } else { if (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. $spaces = 1; if ($param['var_space'] !== $spaces) { $error = 'Expected %s spaces after parameter name; %s found'; $data = [$spaces, $param['var_space']]; $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamName', $data); if ($fix === true) { $content = $param['type']; $content .= str_repeat(' ', $param['type_space']); $content .= $param['var']; $content .= str_repeat(' ', $spaces); $content .= $param['comment']; $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); } } } }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $token = $tokens[$stackPtr]; // Skip function without body. if (isset($token['scope_opener']) === false) { return; } // Get function name. $methodName = $phpcsFile->getDeclarationName($stackPtr); // Get all parameters from method signature. $signature = array(); foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) { $signature[] = $param['name']; } $next = ++$token['scope_opener']; $end = --$token['scope_closer']; for (; $next <= $end; ++$next) { $code = $tokens[$next]['code']; if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === true) { continue; } else { if ($code === T_RETURN) { continue; } } break; } // Any token except 'parent' indicates correct code. if ($tokens[$next]['code'] !== T_PARENT) { return; } // Find next non empty token index, should be double colon. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $next + 1, null, true); // Skip for invalid code. if ($next === false || $tokens[$next]['code'] !== T_DOUBLE_COLON) { return; } // Find next non empty token index, should be the function name. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $next + 1, null, true); // Skip for invalid code or other method. if ($next === false || $tokens[$next]['content'] !== $methodName) { return; } // Find next non empty token index, should be the open parenthesis. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $next + 1, null, true); // Skip for invalid code. if ($next === false || $tokens[$next]['code'] !== T_OPEN_PARENTHESIS) { return; } $validParameterTypes = array(T_VARIABLE, T_LNUMBER, T_CONSTANT_ENCAPSED_STRING); $parameters = array(''); $parenthesisCount = 1; $count = count($tokens); for (++$next; $next < $count; ++$next) { $code = $tokens[$next]['code']; if ($code === T_OPEN_PARENTHESIS) { ++$parenthesisCount; } else { if ($code === T_CLOSE_PARENTHESIS) { --$parenthesisCount; } else { if ($parenthesisCount === 1 && $code === T_COMMA) { $parameters[] = ''; } else { if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === false) { $parameters[count($parameters) - 1] .= $tokens[$next]['content']; } } } } if ($parenthesisCount === 0) { break; } } //end for $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $next + 1, null, true); if ($next === false || $tokens[$next]['code'] !== T_SEMICOLON) { return; } // Check rest of the scope. for (++$next; $next <= $end; ++$next) { $code = $tokens[$next]['code']; // Skip for any other content. if (in_array($code, PHP_CodeSniffer_Tokens::$emptyTokens) === false) { return; } } $parameters = array_map('trim', $parameters); $parameters = array_filter($parameters); if (count($parameters) === count($signature) && $parameters === $signature) { $phpcsFile->addWarning('Useless method overriding detected', $stackPtr); } }
/** * Process the code * * @param PHP_CodeSniffer_File $phpcsFile * @param unknown_type $stackPtr */ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $current = $tokens[$stackPtr]; $lines = file($phpcsFile->getFilename()); $next = $tokens[$stackPtr + 1]; // function whitespaces if ($next['content'] != ' ') { $phpcsFile->addError('After "function" we expect exactly one space.', $stackPtr); } if (isset($current['scope_opener'])) { // check if { is on next line if ($tokens[$current['scope_opener']]['line'] - $current['line'] != 1) { $phpcsFile->addError('The opening brace of a function/method should be placed on the line below the signature.', $stackPtr); } // braces indentation if ($tokens[$current['scope_opener']]['column'] != $tokens[$current['scope_closer']]['column']) { $phpcsFile->addError('The opening and closing brace should be indentend equally.', $stackPtr); } // find class $class = $phpcsFile->findPrevious(T_CLASS, $stackPtr); // class found? if ($class != false) { // get next function inside the class $nextFunction = $phpcsFile->findNext(T_FUNCTION, $stackPtr + 1, $tokens[$class]['scope_closer']); // not last method? if ($nextFunction != false && isset($tokens[$stackPtr - 2]) && in_array($tokens[$stackPtr - 2]['code'], array(T_PRIVATE, T_PUBLIC, T_PROTECTED, T_STATIC))) { if (!($lines[$tokens[$current['scope_closer']]['line']] == "\n" && trim($lines[$tokens[$current['scope_closer']]['line'] + 1]) != '')) { $phpcsFile->addError('After a function/method we expect 1 blank line.', $stackPtr); } } } } // find comment if ($phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, $stackPtr, $stackPtr - 7) === false) { $phpcsFile->addError('We expect a function/method to have PHPDoc-documentation.', $stackPtr); } if (isset($tokens[$stackPtr - 2]) && in_array($tokens[$stackPtr - 2]['code'], array(T_PRIVATE, T_PUBLIC, T_PROTECTED, T_STATIC))) { // get comment $startComment = (int) $phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, null, null, '/**' . "\n"); $endComment = (int) $phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, null, null, ' */'); $parameters = $phpcsFile->getMethodParameters($stackPtr); $hasReturn = false; $paramCounter = 0; for ($i = $startComment; $i <= $endComment; $i++) { if (trim(substr($tokens[$i]['content'], 0, 11)) == '* @return') { $hasReturn = true; // find part $content = trim(substr($tokens[$i]['content'], strrpos($tokens[$i]['content'], ' '))); // validate syntax if (substr($tokens[$i]['content'], 11, 1) != ' ') { $phpcsFile->addError('After "@return" there should be exactly one space.', $i); } // @return should be last if ($paramCounter == 0 && count($parameters) != 0) { $phpcsFile->addError('"@return" needs to be placed after the last "@param"', $i); } } if (trim(substr($tokens[$i]['content'], 0, 10)) == '* @param') { // find part $content = trim(substr($tokens[$i]['content'], strpos($tokens[$i]['content'], '* ') + 2)); $pieces = explode(' ', trim(str_replace('@param', '', $content)), 4); $type = $pieces[0]; $variable = $pieces[1]; $description = isset($pieces[2]) ? $pieces[2] : null; // validate type if ($type != trim($type)) { $phpcsFile->addError('Something wrong with the type in the PHPDoc. We expect @param[space]<type>...', $i); } // validate variable if ($variable !== trim($variable)) { $phpcsFile->addError('Something wrong with the variable in the PHPDoc. We expect @param[space]<type>[space]<variable>...', $i); } /* * @todo add validation: * - description ends with a dot (if filled in) * - compare parameters to method * - optional parameters should be like that in the method and phpdoc */ // check if it match /*if(!preg_match('/(.*)\s\$(.*)(\t(.*))?$/', $content)) $phpcsFile->addError('Wrong sequence, we expect "@param" in the following way: @param[tab]<type>[space]<varname>([tab]<documentation>).', $i); if(!isset($parameters[$paramCounter]['name']) || $parameters[$paramCounter]['name'] != $varName) { $phpcsFile->addError('We expect the variablename used in the PHPDoc to be the same as the one used in the parameterlist.', $i); } if(isset($parameters[$paramCounter]['default']) && $parameters[$paramCounter]['default'] != '') { if(substr_count($type, '[optional]') == 0) $phpcsFile->addError('We expect optional parameters to have "[optional]" just after the type.', $i); if($parameters[$paramCounter]['type_hint'] != '' && $type != $parameters[$paramCounter]['type_hint'] .'[optional]') $phpcsFile->addError('The type in the PHPDoc doesn\'t match the typehinting in the signature.', $i); } elseif(isset($parameters[$paramCounter]['type_hint']) && $parameters[$paramCounter]['type_hint'] != '' && $type != $parameters[$paramCounter]['type_hint']) $phpcsFile->addError('The type in the PHPDoc doesn\'t match the typehinting in the signature.', $i); */ // increment $paramCounter++; } } // incorrect number of parameters if ($paramCounter != count($parameters)) { $phpcsFile->addError('We expect all parameters to be in the PHPDoc.', $stackPtr); } } unset($tokens); unset($current); unset($lines); unset($next); }
/** * Process the function parameter comments. * * @param int $commentStart The position in the stack where * the comment started. * * @return void */ protected function processParams($commentStart) { $realParams = $this->currentFile->getMethodParameters($this->_functionToken); $params = $this->commentParser->getParams(); $foundParams = array(); if (empty($params) === false) { $lastParm = count($params) - 1; if (substr_count($params[$lastParm]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) { $error = 'Last parameter comment requires a blank newline after it'; $errorPos = $params[$lastParm]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingAfterParams'); } // Parameters must appear immediately after the comment. if ($params[0]->getOrder() !== 2) { $error = 'Parameters must appear immediately after the comment'; $errorPos = $params[0]->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParams'); } $previousParam = null; $spaceBeforeVar = 10000; $spaceBeforeComment = 10000; $longestType = 0; $longestVar = 0; foreach ($params as $param) { $paramComment = trim($param->getComment()); $errorPos = $param->getLine() + $commentStart; // Make sure that there is only one space before the var type. if ($param->getWhitespaceBeforeType() !== ' ') { $error = 'Expected 1 space before variable type'; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParamType'); } $spaceCount = substr_count($param->getWhitespaceBeforeVarName(), ' '); if ($spaceCount < $spaceBeforeVar) { $spaceBeforeVar = $spaceCount; $longestType = $errorPos; } $spaceCount = substr_count($param->getWhitespaceBeforeComment(), ' '); if ($spaceCount < $spaceBeforeComment && $paramComment !== '') { $spaceBeforeComment = $spaceCount; $longestVar = $errorPos; } // Make sure they are in the correct order, // and have the correct name. $pos = $param->getPosition(); $paramName = $param->getVarName() !== '' ? $param->getVarName() : '[ UNKNOWN ]'; if ($previousParam !== null) { $previousName = $previousParam->getVarName() !== '' ? $previousParam->getVarName() : 'UNKNOWN'; // Check to see if the parameters align properly. if ($param->alignsVariableWith($previousParam) === false) { $error = 'The variable names for parameters %s (%s) and %s (%s) do not align'; $data = array($previousName, $pos - 1, $paramName, $pos); $this->currentFile->addError($error, $errorPos, 'ParameterNamesNotAligned', $data); } if ($param->alignsCommentWith($previousParam) === false) { $error = 'The comments for parameters %s (%s) and %s (%s) do not align'; $data = array($previousName, $pos - 1, $paramName, $pos); $this->currentFile->addError($error, $errorPos, 'ParameterCommentsNotAligned', $data); } } //end if // Make sure the names of the parameter comment matches the // actual parameter. if (isset($realParams[$pos - 1]) === true) { $realName = $realParams[$pos - 1]['name']; $foundParams[] = $realName; if ($realName !== $paramName) { $code = 'ParamNameNoMatch'; $data = array($paramName, $realName, $pos); $error = 'Doc comment for var %s does not match '; if (strtolower($paramName) === strtolower($realName)) { $error .= 'case of '; $code = 'ParamNameNoCaseMatch'; } $error .= 'actual variable name %s at position %s'; $this->currentFile->addError($error, $errorPos, $code, $data); } } else { // We must have an extra parameter comment. $error = 'Superfluous doc comment at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'ExtraParamComment'); } if ($param->getVarName() === '') { $error = 'Missing parameter name at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'MissingParamName'); } if ($param->getType() === '') { $error = 'Missing type at position ' . $pos; $this->currentFile->addError($error, $errorPos, 'MissingParamType'); } if ($paramComment === '') { $error = 'Missing comment for param "%s" at position %s'; $data = array($paramName, $pos); $this->currentFile->addError($error, $errorPos, 'MissingParamComment', $data); } $previousParam = $param; } //end foreach if ($spaceBeforeVar !== 1 && $spaceBeforeVar !== 10000 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest type'; $this->currentFile->addError($error, $longestType, 'SpacingAfterLongType'); } if ($spaceBeforeComment !== 1 && $spaceBeforeComment !== 10000) { $error = 'Expected 1 space after the longest variable name'; $this->currentFile->addError($error, $longestVar, 'SpacingAfterLongName'); } } //end if $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report and missing comments. $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { if (count($params) !== 0) { $errorPos = $params[count($params) - 1]->getLine() + $commentStart; } else { $errorPos = $commentStart; } $error = 'Doc comment for "%s" missing'; $data = array($neededParam); $this->currentFile->addError($error, $errorPos, 'MissingParamTag', $data); } }
/** * Process the function parameter comments. * * Extends PEAR.Commenting.FunctionComment.processReturn to enforce correct alignment of the doc block. * * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. * @param integer $stackPtr The position of the current token in the stack passed in $tokens. * @param integer $commentStart The position in the stack where the comment started. * * @return void * * @todo Reinstate the check that params come after the function's comment and has a blank line before them * @todo Reinstate the check that there is a blank line after all params are declared */ protected function processParams(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); $params = array(); $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 = ''; if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { $matches = array(); 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 (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]; // 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) { $comment .= ' ' . $tokens[$i]['content']; } } } else { $error = 'Missing parameter comment'; $phpcsFile->addError($error, $tag, 'MissingParamComment'); } } 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, 'type_space' => $typeSpace, 'var_space' => $varSpace, 'align_space' => $tokens[$tag + 1]['content']); } $realParams = $phpcsFile->getMethodParameters($stackPtr); $foundParams = array(); $previousParam = null; foreach ($params as $pos => $param) { if ($param['var'] === '') { continue; } $foundParams[] = $param['var']; // Joomla change: There must be 3 spaces after the @param tag to make it line up with the @return tag if ($param['align_space'] !== ' ') { $error = 'Expected 3 spaces before variable type, found %s'; $spaces = strlen($param['align_space']); $data = array($spaces); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'BeforeParamType', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = 0; $i < $spaces; $i++) { $phpcsFile->fixer->replaceToken($param['tag'] + 1, ''); } $phpcsFile->fixer->addContent($param['tag'], ' '); $phpcsFile->fixer->endChangeset(); } } // Make sure the param name is correct. if (isset($realParams[$pos]) === true) { $realName = $realParams[$pos]['name']; if ($realName !== $param['var']) { $code = 'ParamNameNoMatch'; $data = array($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); } } 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; } // Joomla change: Enforces alignment of the param variables and comments if ($previousParam !== null) { $previousName = $previousParam['var'] !== '' ? $previousParam['var'] : 'UNKNOWN'; // Check to see if the parameters align properly. if (!$this->paramVarsAlign($param, $previousParam)) { $error = 'The variable names for parameters %s and %s do not align'; $data = array($previousName, $param['var']); $phpcsFile->addError($error, $param['tag'], 'ParameterNamesNotAligned', $data); } // Check to see if the comments align properly. if (!$this->paramCommentsAlign($param, $previousParam)) { $error = 'The comments for parameters %s and %s do not align'; $data = array($previousName, $param['var']); $phpcsFile->addError($error, $param['tag'], 'ParameterCommentsNotAligned', $data); } } $previousParam = $param; } $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); } }