/** * 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; } } } } $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) { if (is_array($tok) === false || $tok[0] !== T_VARIABLE) { continue; } if (isset($params[$tok[1]]) === true) { unset($params[$tok[1]]); } } } } } if ($emptybody === false && count($params) > 0) { foreach ($params as $paramname => $position) { $error = 'The method parameter ' . $paramname . ' is never used'; $phpcsfile->addwarning($error, $position); } } }
/** * 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; } } $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 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->addwarning($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->addwarning($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->addwarning($error, $errorpos); } if ($param->alignsCommentWith($previousparam) === false) { $error = 'The comments for parameters ' . $previousname . ' (' . ($pos - 1) . ') and ' . $paramname . ' (' . $pos . ') do not align'; $this->currentfile->addwarning($error, $errorpos); } } // 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; } 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 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); } }