Returns the position of the last non-whitespace token in a statement.
/** * Processes single-line calls. * * @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 $openBracket The position of the opening bracket * in the stack passed in $tokens. * @param array $tokens The stack of tokens that make up * the file. * * @return void */ public function isMultiLineCall(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens) { // If the first argument is on a new line, this is a multi-line // function call, even if there is only one argument. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $openBracket + 1, null, true); if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { return true; } $closeBracket = $tokens[$openBracket]['parenthesis_closer']; $end = $phpcsFile->findEndOfStatement($openBracket + 1); while ($tokens[$end]['code'] === T_COMMA) { // If the next bit of code is not on the same line, this is a // multi-line function call. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $end + 1, $closeBracket, true); if ($next === false) { return false; } if ($tokens[$next]['line'] !== $tokens[$end]['line']) { return true; } $end = $phpcsFile->findEndOfStatement($next); } // We've reached the last argument, so see if the next content // (should be the close bracket) is also on the same line. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $end + 1, $closeBracket, true); if ($next !== false && $tokens[$next]['line'] !== $tokens[$end]['line']) { return true; } return false; }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. * @param integer $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(); $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr + 1, null, true); if ($tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS) { $error = '"%s" is a statement not a function; no parentheses are required'; $data = array($tokens[$stackPtr]['content']); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'BracketsNotRequired', $data); if ($fix === true) { $end = $phpcsFile->findEndOfStatement($nextToken); $ignore = PHP_CodeSniffer_Tokens::$emptyTokens; $ignore[] = T_SEMICOLON; $closer = $phpcsFile->findPrevious($ignore, $end - 1, null, true); $phpcsFile->fixer->beginChangeset(); $phpcsFile->fixer->replaceToken($nextToken, ''); if ($tokens[$stackPtr + 1]['code'] === T_WHITESPACE) { $phpcsFile->fixer->replaceToken($stackPtr + 1, ''); } if ($tokens[$closer]['code'] === T_CLOSE_PARENTHESIS) { $phpcsFile->fixer->replaceToken($closer, ''); } $phpcsFile->fixer->addContent($stackPtr, ' '); $phpcsFile->fixer->endChangeset(); } } }
/** * Determine if this is a multi-line function declaration. * * @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 $openBracket The position of the opening bracket * in the stack passed in $tokens. * @param array $tokens The stack of tokens that make up * the file. * * @return void */ public function isMultiLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens) { $bracketsToCheck = array($stackPtr => $openBracket); // Closures may use the USE keyword and so be multi-line in this way. if ($tokens[$stackPtr]['code'] === T_CLOSURE) { $use = $phpcsFile->findNext(T_USE, $tokens[$openBracket]['parenthesis_closer'] + 1, $tokens[$stackPtr]['scope_opener']); if ($use !== false) { $open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $use + 1); if ($open !== false) { $bracketsToCheck[$use] = $open; } } } foreach ($bracketsToCheck as $stackPtr => $openBracket) { // If the first argument is on a new line, this is a multi-line // function declaration, even if there is only one argument. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $openBracket + 1, null, true); if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { return true; } $closeBracket = $tokens[$openBracket]['parenthesis_closer']; $end = $phpcsFile->findEndOfStatement($openBracket + 1); while ($tokens[$end]['code'] === T_COMMA) { // If the next bit of code is not on the same line, this is a // multi-line function declaration. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $end + 1, $closeBracket, true); if ($next === false) { continue 2; } if ($tokens[$next]['line'] !== $tokens[$end]['line']) { return true; } $end = $phpcsFile->findEndOfStatement($next); } // We've reached the last argument, so see if the next content // (should be the close bracket) is also on the same line. $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $end + 1, $closeBracket, true); if ($next !== false && $tokens[$next]['line'] !== $tokens[$end]['line']) { return true; } } //end foreach return false; }
/** * 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(); // Only check use statements in the global scope. if (empty($tokens[$stackPtr]['conditions']) === false) { return; } // Seek to the end of the statement and get the string before the semi colon. $semiColon = $phpcsFile->findEndOfStatement($stackPtr); if ($tokens[$semiColon]['code'] !== T_SEMICOLON) { return; } $classPtr = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, $semiColon - 1, null, true); // Seek along the statement to get the last part, which is the // class/interface name. while (isset($tokens[$classPtr + 1]) === true && in_array($tokens[$classPtr + 1]['code'], array(T_STRING, T_NS_SEPARATOR)) === true) { $classPtr++; } if ($tokens[$classPtr]['code'] !== T_STRING) { return; } $classUsed = $phpcsFile->findNext(T_STRING, $classPtr + 1, null, false, $tokens[$classPtr]['content']); while ($classUsed !== false) { $beforeUsage = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, $classUsed - 1, null, true); // If a backslash is used before the class name then this is some other // use statement. if ($tokens[$beforeUsage]['code'] !== T_USE && $tokens[$beforeUsage]['code'] !== T_NS_SEPARATOR) { return; } // Trait use statement within a class. if ($tokens[$beforeUsage]['code'] === T_USE && empty($tokens[$beforeUsage]['conditions']) === false) { return; } $classUsed = $phpcsFile->findNext(T_STRING, $classUsed + 1, null, false, $tokens[$classPtr]['content']); } //end while $warning = 'Unused use statement'; $fix = $phpcsFile->addFixableWarning($warning, $stackPtr, 'UnusedUse'); if ($fix === true) { // Remove the whole use statement line. $phpcsFile->fixer->beginChangeset(); for ($i = $stackPtr; $i <= $semiColon; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } // Also remove whitespace after the semicolon (new lines). while (isset($tokens[$i]) === true && $tokens[$i]['code'] === T_WHITESPACE) { $phpcsFile->fixer->replaceToken($i, ''); if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) !== false) { break; } $i++; } $phpcsFile->fixer->endChangeset(); } }
/** * @param \PHP_CodeSniffer_File $phpCsFile * @param int $stackPointer * * @return bool */ private function isFacadeNotInBridgeReturned(\PHP_CodeSniffer_File $phpCsFile, $stackPointer) { $tokens = $phpCsFile->getTokens(); $returnPointer = $phpCsFile->findNext(T_RETURN, $stackPointer); $endOfLinePointer = $phpCsFile->findEndOfStatement($returnPointer); $statementTokens = array_slice($tokens, $returnPointer, $endOfLinePointer - $returnPointer); $statement = $this->parseTokensContent($statementTokens); if (preg_match('/return \\$container->getLocator\\(\\)->(.*?)\\(\\)->facade\\(\\)/', $statement)) { return true; } return false; }
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { if ($phpcsFile->findNext(T_NAMESPACE, 0) === false) { return; } $tokens = $phpcsFile->getTokens(); $endOfTryStatement = $phpcsFile->findEndOfStatement($stackPtr); $posOfCatchVariable = $phpcsFile->findNext(T_VARIABLE, $stackPtr, $endOfTryStatement); $posOfExceptionClassName = $phpcsFile->findNext(T_STRING, $stackPtr, $posOfCatchVariable); $posOfNsSeparator = $phpcsFile->findNext(T_NS_SEPARATOR, $stackPtr, $posOfExceptionClassName); if ($posOfNsSeparator === false) { $exceptionClassName = trim($tokens[$posOfExceptionClassName]['content']); $posOfClassInUse = $phpcsFile->findNext(T_STRING, 0, $stackPtr, false, $exceptionClassName); if ($posOfClassInUse === false || $tokens[$posOfClassInUse]['level'] != 0) { $phpcsFile->addError('Namespace for "' . $exceptionClassName . '" class is not specified.', $posOfExceptionClassName); } } }
/** * @param int $stackPtr * @param PHP_CodeSniffer_File $phpcsFile * * @return array */ protected function getMethods($stackPtr, $phpcsFile) { $methods = []; $classEnd = $phpcsFile->findEndOfStatement($stackPtr); while ($stackPtr < $classEnd) { $function = $phpcsFile->findNext([T_FUNCTION], $stackPtr, null); if (false === $function) { break; } $stackPtr = $function + 1; if (in_array($phpcsFile->getDeclarationName($function), ['setUp', 'tearDown'])) { continue; } $methodProperties = $phpcsFile->getMethodProperties($function); $methods[$methodProperties['scope']][] = $function; } return $methods; }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document. * @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(); $startToken = $tokens[$stackPtr]['parenthesis_opener']; $endToken = $tokens[$stackPtr]['parenthesis_closer']; $startToken = $phpcsFile->findNext(T_AS, $startToken, $endToken); $valueToken = $phpcsFile->findNext(T_VARIABLE, $startToken, $endToken); $tmpToken = $phpcsFile->findNext(T_VARIABLE, $valueToken + 1, $endToken); // If $tmpToken is not false, the foreach loop uses $key => $value. $keyToken = false; if ($tmpToken !== false) { $keyToken = $valueToken; $valueToken = $tmpToken; unset($tmpToken); } // If there are no scope_opener & scope_closer, the developer uses no parenthesis for the loop, like // foreach($myArray as $key => $value) // echo $key . ' - ' . $value; // // In this case we have to determine the next statement / scope_opener & scope_closer on our own. if (array_key_exists('scope_opener', $tokens[$stackPtr]) === false && array_key_exists('scope_closer', $tokens[$stackPtr]) === false) { $scopeOpener = $phpcsFile->findNext(array(T_WHITESPACE), $endToken + 1, null, true, null, true); $scopeCloser = $phpcsFile->findEndOfStatement($scopeOpener); } else { $scopeOpener = $tokens[$stackPtr]['scope_opener']; $scopeCloser = $tokens[$stackPtr]['scope_closer']; } // If a $key is used in foreach loop but not used in the foreach body. if ($keyToken !== false && $phpcsFile->findNext(T_VARIABLE, $scopeOpener, $scopeCloser, false, $tokens[$keyToken]['content']) === false) { $message = 'The usage of the key variable %s is not necessary. Please remove this.'; $phpcsFile->addError($message, $stackPtr, 'KeyVariableNotNecessary', array($tokens[$keyToken]['content'])); } // If the $value is named $_ AND used in the foreach body, this variable has to be renamed. if ($tokens[$valueToken]['content'] === '$_' && $phpcsFile->findNext(T_VARIABLE, $scopeOpener, $scopeCloser, false, $tokens[$valueToken]['content']) !== false) { $message = 'The variable $_ is used in the foreach body. Please rename this variable to a more useful name.'; $phpcsFile->addError($message, $stackPtr, 'ValueVariableWrongName'); // If the $value is NOT named $_, but no one will use this in the foreach body, this variable has to be renamed. } else { if ($tokens[$valueToken]['content'] !== '$_' && $phpcsFile->findNext(T_VARIABLE, $scopeOpener, $scopeCloser, false, $tokens[$valueToken]['content']) === false) { $message = 'The variable %s is NOT used in the foreach body. Please rename this variable to $_.'; $phpcsFile->addError($message, $stackPtr, 'ValueVariableNotUsed', array($tokens[$valueToken]['content'])); } } }
/** * Is the code in the passes position a require(config.php) statement? * * @param PHP_CodeSniffer_File $file The file being scanned. * @param int $pointer The position in the stack. * @return bool true if is a config.php inclusion. */ protected function is_config_php_incluson(PHP_CodeSniffer_File $file, $pointer) { $tokens = $file->getTokens(); if ($tokens[$pointer]['code'] !== T_REQUIRE and $tokens[$pointer]['code'] !== T_REQUIRE_ONCE) { return false; } // It's a require() or require_once() statement. Is it require(config.php)? $requirecontent = $file->getTokensAsString($pointer, $file->findEndOfStatement($pointer) - $pointer); if (strpos($requirecontent, '/config.php') === false) { return false; } return true; }
/** * Processes multi-line calls. * * @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 $openBracket The position of the opening bracket * in the stack passed in $tokens. * @param array $tokens The stack of tokens that make up * the file. * * @return void */ public function processMultiLineCall(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens) { // We need to work out how far indented the function // call itself is, so we can work out how far to // indent the arguments. $start = $phpcsFile->findStartOfStatement($stackPtr); foreach (array('stackPtr', 'start') as $checkToken) { $x = ${$checkToken}; for ($i = $x - 1; $i >= 0; $i--) { if ($tokens[$i]['line'] !== $tokens[$x]['line']) { $i++; break; } } if ($i <= 0) { $functionIndent = 0; } else { if ($tokens[$i]['code'] === T_WHITESPACE) { $functionIndent = strlen($tokens[$i]['content']); } else { if ($tokens[$i]['code'] === T_CONSTANT_ENCAPSED_STRING) { $functionIndent = 0; } else { $trimmed = ltrim($tokens[$i]['content']); if ($trimmed === '') { if ($tokens[$i]['code'] === T_INLINE_HTML) { $functionIndent = strlen($tokens[$i]['content']); } else { $functionIndent = $tokens[$i]['column'] - 1; } } else { $functionIndent = strlen($tokens[$i]['content']) - strlen($trimmed); } } } } $varName = $checkToken . 'Indent'; ${$varName} = $functionIndent; } //end foreach $functionIndent = max($startIndent, $stackPtrIndent); $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $openBracket + 1, null, true); if ($tokens[$next]['line'] === $tokens[$openBracket]['line']) { $error = 'Opening parenthesis of a multi-line function call must be the last content on the line'; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'ContentAfterOpenBracket'); if ($fix === true) { $phpcsFile->fixer->addContent($openBracket, $phpcsFile->eolChar . str_repeat(' ', $functionIndent + $this->indent)); } } $closeBracket = $tokens[$openBracket]['parenthesis_closer']; $prev = $phpcsFile->findPrevious(T_WHITESPACE, $closeBracket - 1, null, true); if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) { $error = 'Closing parenthesis of a multi-line function call must be on a line by itself'; $fix = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketLine'); if ($fix === true) { $phpcsFile->fixer->addContentBefore($closeBracket, $phpcsFile->eolChar . str_repeat(' ', $functionIndent + $this->indent)); } } // Each line between the parenthesis should be indented n spaces. $lastLine = $tokens[$openBracket]['line']; $argStart = null; $argEnd = null; $inArg = false; for ($i = $openBracket + 1; $i < $closeBracket; $i++) { if ($i > $argStart && $i < $argEnd) { $inArg = true; } else { $inArg = false; } if ($tokens[$i]['line'] !== $lastLine) { $lastLine = $tokens[$i]['line']; // Ignore heredoc indentation. if (isset(PHP_CodeSniffer_Tokens::$heredocTokens[$tokens[$i]['code']]) === true) { continue; } // Ignore multi-line string indentation. if (isset(PHP_CodeSniffer_Tokens::$stringTokens[$tokens[$i]['code']]) === true && $tokens[$i]['code'] === $tokens[$i - 1]['code']) { continue; } // Ignore inline HTML. if ($tokens[$i]['code'] === T_INLINE_HTML) { continue; } // We changed lines, so this should be a whitespace indent token, but first make // sure it isn't a blank line because we don't need to check indent unless there // is actually some code to indent. if ($tokens[$i]['code'] === T_WHITESPACE) { $nextCode = $phpcsFile->findNext(T_WHITESPACE, $i + 1, $closeBracket + 1, true); if ($tokens[$nextCode]['line'] !== $lastLine) { if ($inArg === false) { $error = 'Empty lines are not allowed in multi-line function calls'; $fix = $phpcsFile->addFixableError($error, $i, 'EmptyLine'); if ($fix === true) { $phpcsFile->fixer->replaceToken($i, ''); } } continue; } } else { $nextCode = $i; } if ($tokens[$nextCode]['line'] === $tokens[$closeBracket]['line']) { // Closing brace needs to be indented to the same level // as the function call. $inArg = false; $expectedIndent = $functionIndent; } else { $expectedIndent = $functionIndent + $this->indent; } if ($tokens[$i]['code'] !== T_WHITESPACE && $tokens[$i]['code'] !== T_DOC_COMMENT_WHITESPACE) { // Just check if it is a multi-line block comment. If so, we can // calculate the indent from the whitespace before the content. if ($tokens[$i]['code'] === T_COMMENT && $tokens[$i - 1]['code'] === T_COMMENT) { $trimmed = ltrim($tokens[$i]['content']); $foundIndent = strlen($tokens[$i]['content']) - strlen($trimmed); } else { $foundIndent = 0; } } else { $foundIndent = strlen($tokens[$i]['content']); } if ($foundIndent < $expectedIndent || $inArg === false && $expectedIndent !== $foundIndent) { $error = 'Multi-line function call not indented correctly; expected %s spaces but found %s'; $data = array($expectedIndent, $foundIndent); $fix = $phpcsFile->addFixableError($error, $i, 'Indent', $data); if ($fix === true) { $padding = str_repeat(' ', $expectedIndent); if ($foundIndent === 0) { $phpcsFile->fixer->addContentBefore($i, $padding); } else { if ($tokens[$i]['code'] === T_COMMENT) { $comment = $padding . ltrim($tokens[$i]['content']); $phpcsFile->fixer->replaceToken($i, $comment); } else { $phpcsFile->fixer->replaceToken($i, $padding); } } } } //end if if ($inArg === false) { $argStart = $nextCode; $argEnd = $phpcsFile->findEndOfStatement($nextCode); } } //end if // If we are within an argument we should be ignoring commas // as these are not signaling the end of an argument. if ($inArg === false && $tokens[$i]['code'] === T_COMMA) { $next = $phpcsFile->findNext(array(T_WHITESPACE, T_COMMENT), $i + 1, $closeBracket, true); if ($next === false) { continue; } if ($this->allowMultipleArguments === false) { // Comma has to be the last token on the line. if ($tokens[$i]['line'] === $tokens[$next]['line']) { $error = 'Only one argument is allowed per line in a multi-line function call'; $fix = $phpcsFile->addFixableError($error, $next, 'MultipleArguments'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($x = $next - 1; $x > $i; $x--) { if ($tokens[$x]['code'] !== T_WHITESPACE) { break; } $phpcsFile->fixer->replaceToken($x, ''); } $phpcsFile->fixer->addContentBefore($next, $phpcsFile->eolChar . str_repeat(' ', $functionIndent + $this->indent)); $phpcsFile->fixer->endChangeset(); } } } //end if $argStart = $next; $argEnd = $phpcsFile->findEndOfStatement($next); } //end if } //end for }
/** * 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(); // We can't process SWITCH statements unless we know where they start and end. if (isset($tokens[$stackPtr]['scope_opener']) === false || isset($tokens[$stackPtr]['scope_closer']) === false) { return; } $switch = $tokens[$stackPtr]; $nextCase = $stackPtr; $caseAlignment = $switch['column'] + $this->indent; $caseCount = 0; $foundDefault = false; while (($nextCase = $phpcsFile->findNext(array(T_CASE, T_DEFAULT, T_SWITCH), $nextCase + 1, $switch['scope_closer'])) !== false) { // Skip nested SWITCH statements; they are handled on their own. if ($tokens[$nextCase]['code'] === T_SWITCH) { $nextCase = $tokens[$nextCase]['scope_closer']; continue; } if ($tokens[$nextCase]['code'] === T_DEFAULT) { $type = 'Default'; $foundDefault = true; } else { $type = 'Case'; $caseCount++; } if ($tokens[$nextCase]['content'] !== strtolower($tokens[$nextCase]['content'])) { $expected = strtolower($tokens[$nextCase]['content']); $error = strtoupper($type) . ' keyword must be lowercase; expected "%s" but found "%s"'; $data = array($expected, $tokens[$nextCase]['content']); $fix = $phpcsFile->addFixableError($error, $nextCase, $type . 'NotLower', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($nextCase, $expected); } } if ($tokens[$nextCase]['column'] !== $caseAlignment) { $error = strtoupper($type) . ' keyword must be indented ' . $this->indent . ' spaces from SWITCH keyword'; $fix = $phpcsFile->addFixableError($error, $nextCase, $type . 'Indent'); if ($fix === true) { $padding = str_repeat(' ', $caseAlignment - 1); if ($tokens[$nextCase]['column'] === 1 || $tokens[$nextCase - 1]['code'] !== T_WHITESPACE) { $phpcsFile->fixer->addContentBefore($nextCase, $padding); } else { $phpcsFile->fixer->replaceToken($nextCase - 1, $padding); } } } if ($type === 'Case' && ($tokens[$nextCase + 1]['type'] !== 'T_WHITESPACE' || $tokens[$nextCase + 1]['content'] !== ' ')) { $error = 'CASE keyword must be followed by a single space'; $fix = $phpcsFile->addFixableError($error, $nextCase, 'SpacingAfterCase'); if ($fix === true) { if ($tokens[$nextCase + 1]['type'] !== 'T_WHITESPACE') { $phpcsFile->fixer->addContent($nextCase, ' '); } else { $phpcsFile->fixer->replaceToken($nextCase + 1, ' '); } } } if (isset($tokens[$nextCase]['scope_opener']) === false) { $error = 'Possible parse error: CASE missing opening colon'; $phpcsFile->addWarning($error, $nextCase, 'MissingColon'); continue; } $opener = $tokens[$nextCase]['scope_opener']; if ($tokens[$opener - 1]['type'] === 'T_WHITESPACE') { $error = 'There must be no space before the colon in a ' . strtoupper($type) . ' statement'; $fix = $phpcsFile->addFixableError($error, $nextCase, 'SpaceBeforeColon' . $type); if ($fix === true) { $phpcsFile->fixer->replaceToken($opener - 1, ''); } } $nextBreak = $tokens[$nextCase]['scope_closer']; if ($tokens[$nextBreak]['code'] === T_BREAK || $tokens[$nextBreak]['code'] === T_RETURN || $tokens[$nextBreak]['code'] === T_CONTINUE || $tokens[$nextBreak]['code'] === T_THROW || $tokens[$nextBreak]['code'] === T_EXIT) { if ($tokens[$nextBreak]['scope_condition'] === $nextCase) { // Only need to check a couple of things once, even if the // break is shared between multiple case statements, or even // the default case. if ($tokens[$nextBreak]['column'] !== $caseAlignment) { $error = 'Case breaking statement must be indented ' . $this->indent . ' spaces from SWITCH keyword'; $fix = $phpcsFile->addFixableError($error, $nextBreak, 'BreakIndent'); if ($fix === true) { $padding = str_repeat(' ', $caseAlignment - 1); if ($tokens[$nextBreak]['column'] === 1 || $tokens[$nextBreak - 1]['code'] !== T_WHITESPACE) { $phpcsFile->fixer->addContentBefore($nextBreak, $padding); } else { $phpcsFile->fixer->replaceToken($nextBreak - 1, $padding); } } } $prev = $phpcsFile->findPrevious(T_WHITESPACE, $nextBreak - 1, $stackPtr, true); if ($tokens[$prev]['line'] !== $tokens[$nextBreak]['line'] - 1) { $error = 'Blank lines are not allowed before case breaking statements'; $phpcsFile->addError($error, $nextBreak, 'SpacingBeforeBreak'); } $nextLine = $tokens[$tokens[$stackPtr]['scope_closer']]['line']; $semicolon = $phpcsFile->findEndOfStatement($nextBreak); for ($i = $semicolon + 1; $i < $tokens[$stackPtr]['scope_closer']; $i++) { if ($tokens[$i]['type'] !== 'T_WHITESPACE') { $nextLine = $tokens[$i]['line']; break; } } if ($type === 'Case') { // Ensure the BREAK statement is followed by // a single blank line, or the end switch brace. if ($nextLine !== $tokens[$semicolon]['line'] + 2 && $i !== $tokens[$stackPtr]['scope_closer']) { $error = 'Case breaking statements must be followed by a single blank line'; $fix = $phpcsFile->addFixableError($error, $nextBreak, 'SpacingAfterBreak'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = $semicolon + 1; $i <= $tokens[$stackPtr]['scope_closer']; $i++) { if ($tokens[$i]['line'] === $nextLine) { $phpcsFile->fixer->addNewlineBefore($i); break; } if ($tokens[$i]['line'] === $tokens[$semicolon]['line']) { continue; } $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } } //end if } else { // Ensure the BREAK statement is not followed by a blank line. if ($nextLine !== $tokens[$semicolon]['line'] + 1) { $error = 'Blank lines are not allowed after the DEFAULT case\'s breaking statement'; $phpcsFile->addError($error, $nextBreak, 'SpacingAfterDefaultBreak'); } } //end if $caseLine = $tokens[$nextCase]['line']; $nextLine = $tokens[$nextBreak]['line']; for ($i = $opener + 1; $i < $nextBreak; $i++) { if ($tokens[$i]['type'] !== 'T_WHITESPACE') { $nextLine = $tokens[$i]['line']; break; } } if ($nextLine !== $caseLine + 1) { $error = 'Blank lines are not allowed after ' . strtoupper($type) . ' statements'; $phpcsFile->addError($error, $nextCase, 'SpacingAfter' . $type); } } //end if if ($tokens[$nextBreak]['code'] === T_BREAK) { if ($type === 'Case') { // Ensure empty CASE statements are not allowed. // They must have some code content in them. A comment is not enough. // But count RETURN statements as valid content if they also // happen to close the CASE statement. $foundContent = false; for ($i = $tokens[$nextCase]['scope_opener'] + 1; $i < $nextBreak; $i++) { if ($tokens[$i]['code'] === T_CASE) { $i = $tokens[$i]['scope_opener']; continue; } if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === false) { $foundContent = true; break; } } if ($foundContent === false) { $error = 'Empty CASE statements are not allowed'; $phpcsFile->addError($error, $nextCase, 'EmptyCase'); } } else { // Ensure empty DEFAULT statements are not allowed. // They must (at least) have a comment describing why // the default case is being ignored. $foundContent = false; for ($i = $tokens[$nextCase]['scope_opener'] + 1; $i < $nextBreak; $i++) { if ($tokens[$i]['type'] !== 'T_WHITESPACE') { $foundContent = true; break; } } if ($foundContent === false) { $error = 'Comment required for empty DEFAULT case'; $phpcsFile->addError($error, $nextCase, 'EmptyDefault'); } } //end if } //end if } else { if ($type === 'Default') { $error = 'DEFAULT case must have a breaking statement'; $phpcsFile->addError($error, $nextCase, 'DefaultNoBreak'); } } //end if } //end while if ($foundDefault === false) { $error = 'All SWITCH statements must contain a DEFAULT case'; $phpcsFile->addError($error, $stackPtr, 'MissingDefault'); } if ($tokens[$switch['scope_closer']]['column'] !== $switch['column']) { $error = 'Closing brace of SWITCH statement must be aligned with SWITCH keyword'; $phpcsFile->addError($error, $switch['scope_closer'], 'CloseBraceAlign'); } if ($caseCount === 0) { $error = 'SWITCH statements must contain at least one CASE statement'; $phpcsFile->addError($error, $stackPtr, 'MissingCase'); } }
/** * @param \PHP_CodeSniffer_File $phpCsFile * * @return array */ protected function parseUseStatements(\PHP_CodeSniffer_File $phpCsFile) { $useStatements = []; $tokens = $phpCsFile->getTokens(); foreach ($tokens as $id => $token) { if ($token['type'] !== 'T_USE') { continue; } $endIndex = $phpCsFile->findEndOfStatement($id); $useStatement = ''; for ($i = $id + 2; $i < $endIndex; $i++) { $useStatement .= $tokens[$i]['content']; } $useStatement = trim($useStatement); if (strpos($useStatement, ' as ') !== false) { list($useStatement, $className) = explode(' as ', $useStatement); } else { $className = $useStatement; if (strpos($useStatement, '\\') !== false) { $lastSeparator = strrpos($useStatement, '\\'); $className = substr($useStatement, $lastSeparator + 1); } } $useStatement = '\\' . ltrim($useStatement, '\\'); $useStatements[$className] = $useStatement; } return $useStatements; }
/** * @param \PHP_CodeSniffer_File $phpCsFile * @param int $stackPointer * * @return string */ protected function getNamespace(\PHP_CodeSniffer_File $phpCsFile, $stackPointer) { $namespacePosition = $phpCsFile->findPrevious(T_NAMESPACE, $stackPointer); $endOfNamespacePosition = $phpCsFile->findEndOfStatement($namespacePosition); $tokens = $phpCsFile->getTokens(); $namespaceTokens = array_splice($tokens, $namespacePosition + 2, $endOfNamespacePosition - $namespacePosition - 2); $namespace = ''; foreach ($namespaceTokens as $token) { $namespace .= $token['content']; } return $namespace; }
/** * 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(); // If this token is preceded with an "or", it only relates to one line // and should be ignored. For example: fopen() or die(). $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr - 1, null, true); if ($tokens[$prev]['code'] === T_LOGICAL_OR || $tokens[$prev]['code'] === T_BOOLEAN_OR) { return; } // Check if this token is actually part of a one-line IF or ELSE statement. for ($i = $stackPtr - 1; $i > 0; $i--) { if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS) { $i = $tokens[$i]['parenthesis_opener']; continue; } else { if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { continue; } } break; } if ($tokens[$i]['code'] === T_IF || $tokens[$i]['code'] === T_ELSE || $tokens[$i]['code'] === T_ELSEIF) { return; } if ($tokens[$stackPtr]['code'] === T_RETURN) { $next = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true); if ($tokens[$next]['code'] === T_SEMICOLON) { $next = $phpcsFile->findNext(T_WHITESPACE, $next + 1, null, true); if ($tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) { // If this is the closing brace of a function // then this return statement doesn't return anything // and is not required anyway. $owner = $tokens[$next]['scope_condition']; if ($tokens[$owner]['code'] === T_FUNCTION) { $warning = 'Empty return statement not required here'; $phpcsFile->addWarning($warning, $stackPtr, 'ReturnNotRequired'); return; } } } } if (isset($tokens[$stackPtr]['scope_opener']) === true) { $owner = $tokens[$stackPtr]['scope_condition']; if ($tokens[$owner]['code'] === T_CASE || $tokens[$owner]['code'] === T_DEFAULT) { // This token closes the scope of a CASE or DEFAULT statement // so any code between this statement and the next CASE, DEFAULT or // end of SWITCH token will not be executable. $end = $phpcsFile->findEndOfStatement($stackPtr); $next = $phpcsFile->findNext(array(T_CASE, T_DEFAULT, T_CLOSE_CURLY_BRACKET), $end + 1); if ($next !== false) { $lastLine = $tokens[$end]['line']; for ($i = $stackPtr + 1; $i < $next; $i++) { if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { continue; } $line = $tokens[$i]['line']; if ($line > $lastLine) { $type = substr($tokens[$stackPtr]['type'], 2); $warning = 'Code after %s statement cannot be executed'; $data = array($type); $phpcsFile->addWarning($warning, $i, 'Unreachable', $data); $lastLine = $line; } } } //end if // That's all we have to check for these types of statements. return; } //end if } //end if // This token may be part of an inline condition. // If we find a closing parenthesis that belongs to a condition // we should ignore this token. $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, $stackPtr - 1, null, true); if (isset($tokens[$prev]['parenthesis_owner']) === true) { $owner = $tokens[$prev]['parenthesis_owner']; $ignore = array(T_IF => true, T_ELSE => true, T_ELSEIF => true); if (isset($ignore[$tokens[$owner]['code']]) === true) { return; } } $ourConditions = array_keys($tokens[$stackPtr]['conditions']); if (empty($ourConditions) === false) { $condition = array_pop($ourConditions); if (isset($tokens[$condition]['scope_closer']) === false) { return; } // Special case for BREAK statements sitting directly inside SWITCH // statements. If we get to this point, we know the BREAK is not being // used to close a CASE statement, so it is most likely non-executable // code itself (as is the case when you put return; break; at the end of // a case). So we need to ignore this token. if ($tokens[$condition]['code'] === T_SWITCH && $tokens[$stackPtr]['code'] === T_BREAK) { return; } $closer = $tokens[$condition]['scope_closer']; // If the closer for our condition is shared with other openers, // we will need to throw errors from this token to the next // shared opener (if there is one), not to the scope closer. $nextOpener = null; for ($i = $stackPtr + 1; $i < $closer; $i++) { if (isset($tokens[$i]['scope_closer']) === true) { if ($tokens[$i]['scope_closer'] === $closer) { // We found an opener that shares the same // closing token as us. $nextOpener = $i; break; } } } //end for if ($nextOpener === null) { $end = $closer; } else { $end = $nextOpener - 1; } } else { // This token is in the global scope. if ($tokens[$stackPtr]['code'] === T_BREAK) { return; } // Throw an error for all lines until the end of the file. $end = $phpcsFile->numTokens - 1; } //end if // Find the semicolon that ends this statement, skipping // nested statements like FOR loops and closures. for ($start = $stackPtr + 1; $start < $phpcsFile->numTokens; $start++) { if ($start === $end) { break; } if ($tokens[$start]['code'] === T_OPEN_PARENTHESIS) { $start = $tokens[$start]['parenthesis_closer']; continue; } if ($tokens[$start]['code'] === T_OPEN_CURLY_BRACKET) { $start = $tokens[$start]['bracket_closer']; continue; } if ($tokens[$start]['code'] === T_SEMICOLON) { break; } } //end for $lastLine = $tokens[$start]['line']; for ($i = $start + 1; $i < $end; $i++) { if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === true || isset(PHP_CodeSniffer_Tokens::$bracketTokens[$tokens[$i]['code']]) === true) { continue; } // Skip whole functions and classes/interfaces because they are not // technically executed code, but rather declarations that may be used. if ($tokens[$i]['code'] === T_FUNCTION || $tokens[$i]['code'] === T_CLASS || $tokens[$i]['code'] === T_INTERFACE) { $i = $tokens[$i]['scope_closer']; continue; } $line = $tokens[$i]['line']; if ($line > $lastLine) { $type = substr($tokens[$stackPtr]['type'], 2); $warning = 'Code after %s statement cannot be executed'; $data = array($type); $phpcsFile->addWarning($warning, $i, 'Unreachable', $data); $lastLine = $line; } } //end for }
/** * Process the return comment of this function comment. * * @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 processReturnCore(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); $methodName = $phpcsFile->getDeclarationName($stackPtr); $isSpecialMethod = $methodName === '__construct' || $methodName === '__destruct' || 'test' === substr($methodName, 0, 4); $return = null; foreach ($tokens[$commentStart]['comment_tags'] as $tag) { if ($tokens[$tag]['content'] === '@return') { if ($return !== null) { $error = 'Only 1 @return tag is allowed in a function comment'; $phpcsFile->addError($error, $tag, 'DuplicateReturn'); return; } $return = $tag; } } if ($isSpecialMethod === true) { return; } if ($return !== null) { $content = $tokens[$return + 2]['content']; if (empty($content) === true || $tokens[$return + 2]['code'] !== T_DOC_COMMENT_STRING) { $error = 'Return type missing for @return tag in function comment'; $phpcsFile->addError($error, $return, 'MissingReturnType'); } } else { $methodOpener = $phpcsFile->findNext([T_OPEN_CURLY_BRACKET], $tokens[$commentStart]['comment_closer']); $methodClosener = $phpcsFile->findEndOfStatement($methodOpener); if ($this->checkClosureFunctions($methodOpener, $methodClosener, $phpcsFile, $tokens) === true) { return; } $error = 'Missing @return tag in function comment'; $phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn'); } }
/** * @param \PHP_CodeSniffer_File $phpCsFile * @param int $stackPointer * @param string $missingUse * * @return void */ protected function addMissingUse(\PHP_CodeSniffer_File $phpCsFile, $stackPointer, $missingUse) { $previousUsePosition = $phpCsFile->findPrevious(T_USE, $stackPointer); if ($previousUsePosition !== false) { $endOfLastUse = $phpCsFile->findEndOfStatement($previousUsePosition); $phpCsFile->fixer->addNewline($endOfLastUse); $phpCsFile->fixer->addContent($endOfLastUse, 'use ' . $missingUse . ';'); } }