/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile The current file being processed. * @param int $stackPtr The position of the current token in the * stack passed in $tokens. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); if (isset($tokens[$stackPtr]['scope_opener']) === false) { $error = 'Possible parse error: %s missing opening or closing brace'; $data = array($tokens[$stackPtr]['content']); $phpcsFile->addWarning($error, $stackPtr, 'MissingBrace', $data); return; } // Determine the name of the class or interface. Note that we cannot // simply look for the first T_STRING because a class name // starting with the number will be multiple tokens. $opener = $tokens[$stackPtr]['scope_opener']; $nameStart = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, $opener, true); $nameEnd = $phpcsFile->findNext(T_WHITESPACE, $nameStart, $opener); if ($nameEnd === false) { $name = $tokens[$nameStart]['content']; } else { $name = trim($phpcsFile->getTokensAsString($nameStart, $nameEnd - $nameStart)); } // Check for camel caps format. $valid = Common::isCamelCaps($name, true, true, false); if ($valid === false) { $type = ucfirst($tokens[$stackPtr]['content']); $error = '%s name "%s" is not in camel caps format'; $data = array($type, $name); $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $data); $phpcsFile->recordMetric($stackPtr, 'CamelCase class name', 'no'); } else { $phpcsFile->recordMetric($stackPtr, 'CamelCase class name', 'yes'); } }
/** * 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(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $prevType = $tokens[$stackPtr - 1]['code']; if (isset(Tokens::$emptyTokens[$prevType]) === false) { return; } $nonSpace = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 2, null, true); if ($tokens[$nonSpace]['code'] === T_SEMICOLON) { // Empty statement. return; } $expected = $tokens[$nonSpace]['content'] . ';'; $found = $phpcsFile->getTokensAsString($nonSpace, $stackPtr - $nonSpace) . ';'; $error = 'Space found before semicolon; expected "%s" but found "%s"'; $data = array($expected, $found); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Incorrect', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = $stackPtr - 1; $i > $nonSpace; $i--) { $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } }
/** * Processes this sniff, when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile The current file being checked. * @param int $stackPtr The position of the current token in the * stack passed in $tokens. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); // PHP 5.4 introduced a shorthand array declaration syntax, so we need // to ignore the these type of array declarations because this sniff is // only dealing with array usage. if ($tokens[$stackPtr]['code'] === T_OPEN_SQUARE_BRACKET) { $openBracket = $stackPtr; } else { if (isset($tokens[$stackPtr]['bracket_opener']) === false) { return; } $openBracket = $tokens[$stackPtr]['bracket_opener']; } $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, $openBracket - 1, null, true); if ($tokens[$prev]['code'] === T_EQUAL) { return; } // Square brackets can not have a space before them. $prevType = $tokens[$stackPtr - 1]['code']; if (isset(Tokens::$emptyTokens[$prevType]) === true) { $nonSpace = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 2, null, true); $expected = $tokens[$nonSpace]['content'] . $tokens[$stackPtr]['content']; $found = $phpcsFile->getTokensAsString($nonSpace, $stackPtr - $nonSpace) . $tokens[$stackPtr]['content']; $error = 'Space found before square bracket; expected "%s" but found "%s"'; $data = array($expected, $found); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceBeforeBracket', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($stackPtr - 1, ''); } } // Open square brackets can't ever have spaces after them. if ($tokens[$stackPtr]['code'] === T_OPEN_SQUARE_BRACKET) { $nextType = $tokens[$stackPtr + 1]['code']; if (isset(Tokens::$emptyTokens[$nextType]) === true) { $nonSpace = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 2, null, true); $expected = $tokens[$stackPtr]['content'] . $tokens[$nonSpace]['content']; $found = $phpcsFile->getTokensAsString($stackPtr, $nonSpace - $stackPtr + 1); $error = 'Space found after square bracket; expected "%s" but found "%s"'; $data = array($expected, $found); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterBracket', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($stackPtr + 1, ''); } } } }
/** * Processes this sniff, 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(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $varName = $tokens[$stackPtr]['content']; if ($varName !== '$_REQUEST' && $varName !== '$_GET' && $varName !== '$_POST' && $varName !== '$_FILES') { return; } // The only place these super globals can be accessed directly is // in the getRequestData() method of the Security class. $inClass = false; foreach ($tokens[$stackPtr]['conditions'] as $i => $type) { if ($tokens[$i]['code'] === T_CLASS) { $className = $phpcsFile->findNext(T_STRING, $i); $className = $tokens[$className]['content']; if (strtolower($className) === 'security') { $inClass = true; } else { // We don't have nested classes. break; } } else { if ($inClass === true && $tokens[$i]['code'] === T_FUNCTION) { $funcName = $phpcsFile->findNext(T_STRING, $i); $funcName = $tokens[$funcName]['content']; if (strtolower($funcName) === 'getrequestdata') { // This is valid. return; } else { // We don't have nested functions. break; } } } //end if } //end foreach // If we get to here, the super global was used incorrectly. // First find out how it is being used. $globalName = strtolower(substr($varName, 2)); $usedVar = ''; $openBracket = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true); if ($tokens[$openBracket]['code'] === T_OPEN_SQUARE_BRACKET) { $closeBracket = $tokens[$openBracket]['bracket_closer']; $usedVar = $phpcsFile->getTokensAsString($openBracket + 1, $closeBracket - $openBracket - 1); } $type = 'SuperglobalAccessed'; $error = 'The %s super global must not be accessed directly; use Security::getRequestData('; $data = array($varName); if ($usedVar !== '') { $type .= 'WithVar'; $error .= '%s, \'%s\''; $data[] = $usedVar; $data[] = $globalName; } $error .= ') instead'; $phpcsFile->addError($error, $stackPtr, $type, $data); }
/** * Processes this sniff, 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(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); // Make sure it is an API function. We know this by the doc comment. $commentEnd = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr); $commentStart = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $commentEnd - 1); $comment = $phpcsFile->getTokensAsString($commentStart, $commentEnd - $commentStart); if (strpos($comment, '* @api') === false) { return; } // Find all the vars passed in as we are only interested in comparisons // to NULL for these specific variables. $foundVars = array(); $open = $tokens[$stackPtr]['parenthesis_opener']; $close = $tokens[$stackPtr]['parenthesis_closer']; for ($i = $open + 1; $i < $close; $i++) { if ($tokens[$i]['code'] === T_VARIABLE) { $foundVars[$tokens[$i]['content']] = true; } } if (empty($foundVars) === true) { return; } $start = $tokens[$stackPtr]['scope_opener']; $end = $tokens[$stackPtr]['scope_closer']; for ($i = $start + 1; $i < $end; $i++) { if ($tokens[$i]['code'] !== T_VARIABLE || isset($foundVars[$tokens[$i]['content']]) === false) { continue; } $operator = $phpcsFile->findNext(T_WHITESPACE, $i + 1, null, true); if ($tokens[$operator]['code'] !== T_IS_IDENTICAL && $tokens[$operator]['code'] !== T_IS_NOT_IDENTICAL) { continue; } $nullValue = $phpcsFile->findNext(T_WHITESPACE, $operator + 1, null, true); if ($tokens[$nullValue]['code'] !== T_NULL) { continue; } $error = 'Values submitted via Ajax requests should not be compared directly to NULL; use empty() instead'; $phpcsFile->addWarning($error, $nullValue, 'Found'); } //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(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $namespace = ''; $findTokens = array(T_CLASS, T_INTERFACE, T_NAMESPACE, T_CLOSE_TAG); $stackPtr = $phpcsFile->findNext($findTokens, $stackPtr + 1); while ($stackPtr !== false) { if ($tokens[$stackPtr]['code'] === T_CLOSE_TAG) { // We can stop here. The sniff will continue from the next open // tag when PHPCS reaches that token, if there is one. return; } // Keep track of what namespace we are in. if ($tokens[$stackPtr]['code'] === T_NAMESPACE) { $nsEnd = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING, T_WHITESPACE), $stackPtr + 1, null, true); $namespace = trim($phpcsFile->getTokensAsString($stackPtr + 1, $nsEnd - $stackPtr - 1)); $stackPtr = $nsEnd; } else { $nameToken = $phpcsFile->findNext(T_STRING, $stackPtr); $name = $tokens[$nameToken]['content']; if ($namespace !== '') { $name = $namespace . '\\' . $name; } $compareName = strtolower($name); if (isset($this->foundClasses[$compareName]) === true) { $type = strtolower($tokens[$stackPtr]['content']); $file = $this->foundClasses[$compareName]['file']; $line = $this->foundClasses[$compareName]['line']; $error = 'Duplicate %s name "%s" found; first defined in %s on line %s'; $data = array($type, $name, $file, $line); $phpcsFile->addWarning($error, $stackPtr, 'Found', $data); } else { $this->foundClasses[$compareName] = array('file' => $phpcsFile->getFilename(), 'line' => $tokens[$stackPtr]['line']); } } //end if $stackPtr = $phpcsFile->findNext($findTokens, $stackPtr + 1); } //end while }
/** * Processes the tokens that this sniff is interested in. * * @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. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); // Some styles look like shorthand but are not actually a set of 4 sizes. $style = strtolower($tokens[$stackPtr]['content']); if (isset($this->excludeStyles[$style]) === true) { return; } // Get the whole style content. $end = $phpcsFile->findNext(T_SEMICOLON, $stackPtr + 1); $origContent = $phpcsFile->getTokensAsString($stackPtr + 1, $end - $stackPtr - 1); $origContent = trim($origContent, ': '); // Account for a !important annotation. $content = $origContent; if (substr($content, -10) === '!important') { $content = substr($content, 0, -10); $content = trim($content); } // Check if this style value is a set of numbers with optional prefixes. $content = preg_replace('/\\s+/', ' ', $content); $values = array(); $num = preg_match_all('/([0-9]+)([a-zA-Z]{2}\\s+|%\\s+|\\s+)/', $content . ' ', $values, PREG_SET_ORDER); // Only interested in styles that have multiple sizes defined. if ($num < 2) { return; } // Rebuild the content we matched to ensure we got everything. $matched = ''; foreach ($values as $value) { $matched .= $value[0]; } if ($content !== trim($matched)) { return; } if ($num === 3) { $expected = trim($content . ' ' . $values[1][1] . $values[1][2]); $error = 'Shorthand syntax not allowed here; use %s instead'; $data = array($expected); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NotAllowed', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); if (substr($origContent, -10) === '!important') { $expected .= ' !important'; } $next = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 2, null, true); $phpcsFile->fixer->replaceToken($next, $expected); for ($next++; $next < $end; $next++) { $phpcsFile->fixer->replaceToken($next, ''); } $phpcsFile->fixer->endChangeset(); } return; } //end if if ($num === 2) { if ($values[0][0] !== $values[1][0]) { // Both values are different, so it is already shorthand. return; } } else { if ($values[0][0] !== $values[2][0] || $values[1][0] !== $values[3][0]) { // Can't shorthand this. return; } } if ($values[0][0] === $values[1][0]) { // All values are the same. $expected = $values[0][0]; } else { $expected = $values[0][0] . ' ' . $values[1][0]; } $expected = preg_replace('/\\s+/', ' ', trim($expected)); $error = 'Size definitions must use shorthand if available; expected "%s" but found "%s"'; $data = array($expected, $content); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NotUsed', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); if (substr($origContent, -10) === '!important') { $expected .= ' !important'; } $next = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 2, null, true); $phpcsFile->fixer->replaceToken($next, $expected); for ($next++; $next < $end; $next++) { $phpcsFile->fixer->replaceToken($next, ''); } $phpcsFile->fixer->endChangeset(); } }
/** * Processes the function tokens within the class. * * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found. * @param int $stackPtr The position where the token was found. * @param int $currScope The current scope opener token. * * @return void */ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) { // Is this the first throw token within the current function scope? // If so, we have to validate other throw tokens within the same scope. $previousThrow = $phpcsFile->findPrevious(T_THROW, $stackPtr - 1, $currScope); if ($previousThrow !== false) { return; } $tokens = $phpcsFile->getTokens(); $find = Tokens::$methodPrefixes; $find[] = T_WHITESPACE; $commentEnd = $phpcsFile->findPrevious($find, $currScope - 1, null, true); if ($tokens[$commentEnd]['code'] === T_COMMENT) { // Function is using the wrong type of comment. return; } if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG && $tokens[$commentEnd]['code'] !== T_COMMENT) { // Function doesn't have a doc comment. return; } $currScopeEnd = $tokens[$currScope]['scope_closer']; // Find all the exception type token within the current scope. $throwTokens = array(); $currPos = $stackPtr; $foundThrows = false; while ($currPos < $currScopeEnd && $currPos !== false) { if ($phpcsFile->hasCondition($currPos, T_CLOSURE) === false) { $foundThrows = true; /* If we can't find a NEW, we are probably throwing a variable, so we ignore it, but they still need to provide at least one @throws tag, even through we don't know the exception class. */ $nextToken = $phpcsFile->findNext(T_WHITESPACE, $currPos + 1, null, true); if ($tokens[$nextToken]['code'] === T_NEW) { $currException = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), $currPos, $currScopeEnd, false, null, true); if ($currException !== false) { $endException = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), $currException + 1, $currScopeEnd, true, null, true); if ($endException === false) { $throwTokens[] = $tokens[$currException]['content']; } else { $throwTokens[] = $phpcsFile->getTokensAsString($currException, $endException - $currException); } } //end if } //end if } //end if $currPos = $phpcsFile->findNext(T_THROW, $currPos + 1, $currScopeEnd); } //end while if ($foundThrows === false) { return; } // Only need one @throws tag for each type of exception thrown. $throwTokens = array_unique($throwTokens); $throwTags = array(); $commentStart = $tokens[$commentEnd]['comment_opener']; foreach ($tokens[$commentStart]['comment_tags'] as $tag) { if ($tokens[$tag]['content'] !== '@throws') { continue; } if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { $exception = $tokens[$tag + 2]['content']; $space = strpos($exception, ' '); if ($space !== false) { $exception = substr($exception, 0, $space); } $throwTags[$exception] = true; } } if (empty($throwTags) === true) { $error = 'Missing @throws tag in function comment'; $phpcsFile->addError($error, $commentEnd, 'Missing'); return; } else { if (empty($throwTokens) === true) { // If token count is zero, it means that only variables are being // thrown, so we need at least one @throws tag (checked above). // Nothing more to do. return; } } // Make sure @throws tag count matches throw token count. $tokenCount = count($throwTokens); $tagCount = count($throwTags); if ($tokenCount !== $tagCount) { $error = 'Expected %s @throws tag(s) in function comment; %s found'; $data = array($tokenCount, $tagCount); $phpcsFile->addError($error, $commentEnd, 'WrongNumber', $data); return; } foreach ($throwTokens as $throw) { if (isset($throwTags[$throw]) === false) { $error = 'Missing @throws tag for "%s" exception'; $data = array($throw); $phpcsFile->addError($error, $commentEnd, 'Missing', $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(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); if (isset($tokens[$stackPtr + 1]) === false) { return; } // Single space after the keyword. $found = 1; if ($tokens[$stackPtr + 1]['code'] !== T_WHITESPACE) { $found = 0; } else { if ($tokens[$stackPtr + 1]['content'] !== ' ') { if (strpos($tokens[$stackPtr + 1]['content'], $phpcsFile->eolChar) !== false) { $found = 'newline'; } else { $found = strlen($tokens[$stackPtr + 1]['content']); } } } if ($found !== 1) { $error = 'Expected 1 space after %s keyword; %s found'; $data = array(strtoupper($tokens[$stackPtr]['content']), $found); $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data); if ($fix === true) { if ($found === 0) { $phpcsFile->fixer->addContent($stackPtr, ' '); } else { $phpcsFile->fixer->replaceToken($stackPtr + 1, ' '); } } } // Single space after closing parenthesis. if (isset($tokens[$stackPtr]['parenthesis_closer']) === true && isset($tokens[$stackPtr]['scope_opener']) === true) { $closer = $tokens[$stackPtr]['parenthesis_closer']; $opener = $tokens[$stackPtr]['scope_opener']; $content = $phpcsFile->getTokensAsString($closer + 1, $opener - $closer - 1); if ($content !== ' ') { $error = 'Expected 1 space after closing parenthesis; found %s'; if (trim($content) === '') { $found = strlen($content); } else { $found = '"' . str_replace($phpcsFile->eolChar, '\\n', $content) . '"'; } $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceAfterCloseParenthesis', array($found)); if ($fix === true) { if ($closer === $opener - 1) { $phpcsFile->fixer->addContent($closer, ' '); } else { $phpcsFile->fixer->beginChangeset(); $phpcsFile->fixer->addContent($closer, ' ' . $tokens[$opener]['content']); $phpcsFile->fixer->replaceToken($opener, ''); if ($tokens[$opener]['line'] !== $tokens[$closer]['line']) { $next = $phpcsFile->findNext(T_WHITESPACE, $opener + 1, null, true); if ($tokens[$next]['line'] !== $tokens[$opener]['line']) { for ($i = $opener + 1; $i < $next; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } } } $phpcsFile->fixer->endChangeset(); } } } //end if } //end if // Single newline after opening brace. if (isset($tokens[$stackPtr]['scope_opener']) === true) { $opener = $tokens[$stackPtr]['scope_opener']; for ($next = $opener + 1; $next < $phpcsFile->numTokens; $next++) { $code = $tokens[$next]['code']; if ($code === T_WHITESPACE || $code === T_INLINE_HTML && trim($tokens[$next]['content']) === '') { continue; } // Skip all empty tokens on the same line as the opener. if ($tokens[$next]['line'] === $tokens[$opener]['line'] && (isset(Tokens::$emptyTokens[$code]) === true || $code === T_CLOSE_TAG)) { continue; } // We found the first bit of a code, or a comment on the // following line. break; } //end for if ($tokens[$next]['line'] === $tokens[$opener]['line']) { $error = 'Newline required after opening brace'; $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterOpenBrace'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = $opener + 1; $i < $next; $i++) { if (trim($tokens[$i]['content']) !== '') { break; } // Remove whitespace. $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->addContent($opener, $phpcsFile->eolChar); $phpcsFile->fixer->endChangeset(); } } //end if } else { if ($tokens[$stackPtr]['code'] === T_WHILE) { // Zero spaces after parenthesis closer. $closer = $tokens[$stackPtr]['parenthesis_closer']; $found = 0; if ($tokens[$closer + 1]['code'] === T_WHITESPACE) { if (strpos($tokens[$closer + 1]['content'], $phpcsFile->eolChar) !== false) { $found = 'newline'; } else { $found = strlen($tokens[$closer + 1]['content']); } } if ($found !== 0) { $error = 'Expected 0 spaces before semicolon; %s found'; $data = array($found); $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceBeforeSemicolon', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($closer + 1, ''); } } } } //end if // Only want to check multi-keyword structures from here on. if ($tokens[$stackPtr]['code'] === T_DO) { if (isset($tokens[$stackPtr]['scope_closer']) === false) { return; } $closer = $tokens[$stackPtr]['scope_closer']; } else { if ($tokens[$stackPtr]['code'] === T_ELSE || $tokens[$stackPtr]['code'] === T_ELSEIF || $tokens[$stackPtr]['code'] === T_CATCH) { $closer = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true); if ($closer === false || $tokens[$closer]['code'] !== T_CLOSE_CURLY_BRACKET) { return; } } else { return; } } // Single space after closing brace. $found = 1; if ($tokens[$closer + 1]['code'] !== T_WHITESPACE) { $found = 0; } else { if ($tokens[$closer + 1]['content'] !== ' ') { if (strpos($tokens[$closer + 1]['content'], $phpcsFile->eolChar) !== false) { $found = 'newline'; } else { $found = strlen($tokens[$closer + 1]['content']); } } } if ($found !== 1) { $error = 'Expected 1 space after closing brace; %s found'; $data = array($found); $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceAfterCloseBrace', $data); if ($fix === true) { if ($found === 0) { $phpcsFile->fixer->addContent($closer, ' '); } else { $phpcsFile->fixer->replaceToken($closer + 1, ' '); } } } }
/** * Processes the pattern and verifies the code at $stackPtr. * * @param array $patternInfo Information about the pattern used * for checking, which includes are * parsed token representation of the * pattern. * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the * token occurred. * @param int $stackPtr The position in the tokens stack where * the listening token type was found. * * @return array */ protected function processPattern($patternInfo, File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $pattern = $patternInfo['pattern']; $patternCode = $patternInfo['pattern_code']; $errors = array(); $found = ''; $ignoreTokens = array(T_WHITESPACE); if ($this->ignoreComments === true) { $ignoreTokens = array_merge($ignoreTokens, Tokens::$commentTokens); } $origStackPtr = $stackPtr; $hasError = false; if ($patternInfo['listen_pos'] > 0) { $stackPtr--; for ($i = $patternInfo['listen_pos'] - 1; $i >= 0; $i--) { if ($pattern[$i]['type'] === 'token') { if ($pattern[$i]['token'] === T_WHITESPACE) { if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { $found = $tokens[$stackPtr]['content'] . $found; } // Only check the size of the whitespace if this is not // the first token. We don't care about the size of // leading whitespace, just that there is some. if ($i !== 0) { if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) { $hasError = true; } } } else { // Check to see if this important token is the same as the // previous important token in the pattern. If it is not, // then the pattern cannot be for this piece of code. $prev = $phpcsFile->findPrevious($ignoreTokens, $stackPtr, null, true); if ($prev === false || $tokens[$prev]['code'] !== $pattern[$i]['token']) { return false; } // If we skipped past some whitespace tokens, then add them // to the found string. $tokenContent = $phpcsFile->getTokensAsString($prev + 1, $stackPtr - $prev - 1); $found = $tokens[$prev]['content'] . $tokenContent . $found; if (isset($pattern[$i - 1]) === true && $pattern[$i - 1]['type'] === 'skip') { $stackPtr = $prev; } else { $stackPtr = $prev - 1; } } //end if } else { if ($pattern[$i]['type'] === 'skip') { // Skip to next piece of relevant code. if ($pattern[$i]['to'] === 'parenthesis_closer') { $to = 'parenthesis_opener'; } else { $to = 'scope_opener'; } // Find the previous opener. $next = $phpcsFile->findPrevious($ignoreTokens, $stackPtr, null, true); if ($next === false || isset($tokens[$next][$to]) === false) { // If there was not opener, then we must be // using the wrong pattern. return false; } if ($to === 'parenthesis_opener') { $found = '{' . $found; } else { $found = '(' . $found; } $found = '...' . $found; // Skip to the opening token. $stackPtr = $tokens[$next][$to] - 1; } else { if ($pattern[$i]['type'] === 'string') { $found = 'abc'; } else { if ($pattern[$i]['type'] === 'newline') { if ($this->ignoreComments === true && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) { $startComment = $phpcsFile->findPrevious(Tokens::$commentTokens, $stackPtr - 1, null, true); if ($tokens[$startComment]['line'] !== $tokens[$startComment + 1]['line']) { $startComment++; } $tokenContent = $phpcsFile->getTokensAsString($startComment, $stackPtr - $startComment + 1); $found = $tokenContent . $found; $stackPtr = $startComment - 1; } if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) { $found = $tokens[$stackPtr]['content'] . $found; // This may just be an indent that comes after a newline // so check the token before to make sure. If it is a newline, we // can ignore the error here. if ($tokens[$stackPtr - 1]['content'] !== $phpcsFile->eolChar && ($this->ignoreComments === true && isset(Tokens::$commentTokens[$tokens[$stackPtr - 1]['code']]) === false)) { $hasError = true; } else { $stackPtr--; } } else { $found = 'EOL' . $found; } } else { $found = $tokens[$stackPtr]['content'] . $found; $hasError = true; } //end if if ($hasError === false && $pattern[$i - 1]['type'] !== 'newline') { // Make sure they only have 1 newline. $prev = $phpcsFile->findPrevious($ignoreTokens, $stackPtr - 1, null, true); if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) { $hasError = true; } } } } } } //end if } //end for } //end if $stackPtr = $origStackPtr; $lastAddedStackPtr = null; $patternLen = count($pattern); for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) { if (isset($tokens[$stackPtr]) === false) { break; } if ($pattern[$i]['type'] === 'token') { if ($pattern[$i]['token'] === T_WHITESPACE) { if ($this->ignoreComments === true) { // If we are ignoring comments, check to see if this current // token is a comment. If so skip it. if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) { continue; } // If the next token is a comment, the we need to skip the // current token as we should allow a space before a // comment for readability. if (isset($tokens[$stackPtr + 1]) === true && isset(Tokens::$commentTokens[$tokens[$stackPtr + 1]['code']]) === true) { continue; } } $tokenContent = ''; if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { if (isset($pattern[$i + 1]) === false) { // This is the last token in the pattern, so just compare // the next token of content. $tokenContent = $tokens[$stackPtr]['content']; } else { // Get all the whitespace to the next token. $next = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr, null, true); $tokenContent = $phpcsFile->getTokensAsString($stackPtr, $next - $stackPtr); $lastAddedStackPtr = $stackPtr; $stackPtr = $next; } //end if if ($stackPtr !== $lastAddedStackPtr) { $found .= $tokenContent; } } else { if ($stackPtr !== $lastAddedStackPtr) { $found .= $tokens[$stackPtr]['content']; $lastAddedStackPtr = $stackPtr; } } //end if if (isset($pattern[$i + 1]) === true && $pattern[$i + 1]['type'] === 'skip') { // The next token is a skip token, so we just need to make // sure the whitespace we found has *at least* the // whitespace required. if (strpos($tokenContent, $pattern[$i]['value']) !== 0) { $hasError = true; } } else { if ($tokenContent !== $pattern[$i]['value']) { $hasError = true; } } } else { // Check to see if this important token is the same as the // next important token in the pattern. If it is not, then // the pattern cannot be for this piece of code. $next = $phpcsFile->findNext($ignoreTokens, $stackPtr, null, true); if ($next === false || $tokens[$next]['code'] !== $pattern[$i]['token']) { // The next important token did not match the pattern. return false; } if ($lastAddedStackPtr !== null) { if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) && isset($tokens[$next]['scope_condition']) === true && $tokens[$next]['scope_condition'] > $lastAddedStackPtr) { // This is a brace, but the owner of it is after the current // token, which means it does not belong to any token in // our pattern. This means the pattern is not for us. return false; } if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS) && isset($tokens[$next]['parenthesis_owner']) === true && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr) { // This is a bracket, but the owner of it is after the current // token, which means it does not belong to any token in // our pattern. This means the pattern is not for us. return false; } } //end if // If we skipped past some whitespace tokens, then add them // to the found string. if ($next - $stackPtr > 0) { $hasComment = false; for ($j = $stackPtr; $j < $next; $j++) { $found .= $tokens[$j]['content']; if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) { $hasComment = true; } } // If we are not ignoring comments, this additional // whitespace or comment is not allowed. If we are // ignoring comments, there needs to be at least one // comment for this to be allowed. if ($this->ignoreComments === false || $this->ignoreComments === true && $hasComment === false) { $hasError = true; } // Even when ignoring comments, we are not allowed to include // newlines without the pattern specifying them, so // everything should be on the same line. if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { $hasError = true; } } //end if if ($next !== $lastAddedStackPtr) { $found .= $tokens[$next]['content']; $lastAddedStackPtr = $next; } if (isset($pattern[$i + 1]) === true && $pattern[$i + 1]['type'] === 'skip') { $stackPtr = $next; } else { $stackPtr = $next + 1; } } //end if } else { if ($pattern[$i]['type'] === 'skip') { if ($pattern[$i]['to'] === 'unknown') { $next = $phpcsFile->findNext($pattern[$i + 1]['token'], $stackPtr); if ($next === false) { // Couldn't find the next token, so we must // be using the wrong pattern. return false; } $found .= '...'; $stackPtr = $next; } else { // Find the previous opener. $next = $phpcsFile->findPrevious(Tokens::$blockOpeners, $stackPtr); if ($next === false || isset($tokens[$next][$pattern[$i]['to']]) === false) { // If there was not opener, then we must // be using the wrong pattern. return false; } $found .= '...'; if ($pattern[$i]['to'] === 'parenthesis_closer') { $found .= ')'; } else { $found .= '}'; } // Skip to the closing token. $stackPtr = $tokens[$next][$pattern[$i]['to']] + 1; } //end if } else { if ($pattern[$i]['type'] === 'string') { if ($tokens[$stackPtr]['code'] !== T_STRING) { $hasError = true; } if ($stackPtr !== $lastAddedStackPtr) { $found .= 'abc'; $lastAddedStackPtr = $stackPtr; } $stackPtr++; } else { if ($pattern[$i]['type'] === 'newline') { // Find the next token that contains a newline character. $newline = 0; for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) { if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) { $newline = $j; break; } } if ($newline === 0) { // We didn't find a newline character in the rest of the file. $next = $phpcsFile->numTokens - 1; $hasError = true; } else { if ($this->ignoreComments === false) { // The newline character cannot be part of a comment. if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) { $hasError = true; } } if ($newline === $stackPtr) { $next = $stackPtr + 1; } else { // Check that there were no significant tokens that we // skipped over to find our newline character. $next = $phpcsFile->findNext($ignoreTokens, $stackPtr, null, true); if ($next < $newline) { // We skipped a non-ignored token. $hasError = true; } else { $next = $newline + 1; } } } //end if if ($stackPtr !== $lastAddedStackPtr) { $found .= $phpcsFile->getTokensAsString($stackPtr, $next - $stackPtr); $diff = $next - $stackPtr; $lastAddedStackPtr = $next - 1; } $stackPtr = $next; } } } } //end if } //end for if ($hasError === true) { $error = $this->prepareError($found, $patternCode); $errors[$origStackPtr] = $error; } return $errors; }