/** * 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) { $tokens = $phpcsFile->getTokens(); $methodName = $phpcsFile->getDeclarationName($stackPtr); if ($methodName === null) { // Ignore closures. return; } if ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true) { // Ignore nested functions. return; } $modifier = null; for ($i = $stackPtr - 1; $i > 0; $i--) { if ($tokens[$i]['line'] < $tokens[$stackPtr]['line']) { break; } else { if (isset(Tokens::$scopeModifiers[$tokens[$i]['code']]) === true) { $modifier = $i; break; } } } if ($modifier === null) { $error = 'Visibility must be declared on method "%s"'; $data = array($methodName); $phpcsFile->addError($error, $stackPtr, '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 */ protected function processVariable(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $varName = ltrim($tokens[$stackPtr]['content'], '$'); $phpReservedVars = array('_SERVER', '_GET', '_POST', '_REQUEST', '_SESSION', '_ENV', '_COOKIE', '_FILES', 'GLOBALS', 'http_response_header', 'HTTP_RAW_POST_DATA', 'php_errormsg'); // If it's a php reserved var, then its ok. if (in_array($varName, $phpReservedVars) === true) { return; } $objOperator = $phpcsFile->findNext(array(T_WHITESPACE), $stackPtr + 1, null, true); if ($tokens[$objOperator]['code'] === T_OBJECT_OPERATOR) { // Check to see if we are using a variable from an object. $var = $phpcsFile->findNext(array(T_WHITESPACE), $objOperator + 1, null, true); if ($tokens[$var]['code'] === T_STRING) { $bracket = $phpcsFile->findNext(array(T_WHITESPACE), $var + 1, null, true); if ($tokens[$bracket]['code'] !== T_OPEN_PARENTHESIS) { $objVarName = $tokens[$var]['content']; // There is no way for us to know if the var is public or // private, so we have to ignore a leading underscore if there is // one and just check the main part of the variable name. $originalVarName = $objVarName; if (substr($objVarName, 0, 1) === '_') { $objVarName = substr($objVarName, 1); } if (Common::isCamelCaps($objVarName, false, true, false) === false) { $error = 'Variable "%s" is not in valid camel caps format'; $data = array($originalVarName); $phpcsFile->addError($error, $var, 'NotCamelCaps', $data); } } //end if } //end if } //end if // There is no way for us to know if the var is public or private, // so we have to ignore a leading underscore if there is one and just // check the main part of the variable name. $originalVarName = $varName; if (substr($varName, 0, 1) === '_') { $objOperator = $phpcsFile->findPrevious(array(T_WHITESPACE), $stackPtr - 1, null, true); if ($tokens[$objOperator]['code'] === T_DOUBLE_COLON) { // The variable lives within a class, and is referenced like // this: MyClass::$_variable, so we don't know its scope. $inClass = true; } else { $inClass = $phpcsFile->hasCondition($stackPtr, array(T_CLASS, T_INTERFACE, T_TRAIT)); } if ($inClass === true) { $varName = substr($varName, 1); } } if (Common::isCamelCaps($varName, false, true, false) === false) { $error = 'Variable "%s" is not in valid camel caps format'; $data = array($originalVarName); $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $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]['scope_closer']) === false) { // Probably an interface method. return; } $closeBrace = $tokens[$stackPtr]['scope_closer']; $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, $closeBrace - 1, null, true); // Special case for empty JS functions. if ($phpcsFile->tokenizerType === 'JS' && $prevContent === $tokens[$stackPtr]['scope_opener']) { // In this case, the opening and closing brace must be // right next to each other. if ($tokens[$stackPtr]['scope_closer'] !== $tokens[$stackPtr]['scope_opener'] + 1) { $error = 'The opening and closing braces of empty functions must be directly next to each other; e.g., function () {}'; $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBetween'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = $tokens[$stackPtr]['scope_opener'] + 1; $i < $closeBrace; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } } return; } $nestedFunction = false; if ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true || isset($tokens[$stackPtr]['nested_parenthesis']) === true) { $nestedFunction = true; } $braceLine = $tokens[$closeBrace]['line']; $prevLine = $tokens[$prevContent]['line']; $found = $braceLine - $prevLine - 1; $afterKeyword = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true); $beforeKeyword = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); if ($nestedFunction === true) { if ($found < 0) { $error = 'Closing brace of nested function must be on a new line'; $fix = $phpcsFile->addFixableError($error, $closeBrace, 'ContentBeforeClose'); if ($fix === true) { $phpcsFile->fixer->addNewlineBefore($closeBrace); } } else { if ($found > 0) { $error = 'Expected 0 blank lines before closing brace of nested function; %s found'; $data = array($found); $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBeforeNestedClose', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $changeMade = false; for ($i = $prevContent + 1; $i < $closeBrace; $i++) { // Try and maintain indentation. if ($tokens[$i]['line'] === $braceLine - 1) { break; } $phpcsFile->fixer->replaceToken($i, ''); $changeMade = true; } // Special case for when the last content contains the newline // token as well, like with a comment. if ($changeMade === false) { $phpcsFile->fixer->replaceToken($prevContent + 1, ''); } $phpcsFile->fixer->endChangeset(); } //end if } } //end if } else { if ($found !== 1) { if ($found < 0) { $found = 0; } $error = 'Expected 1 blank line before closing function brace; %s found'; $data = array($found); $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBeforeClose', $data); if ($fix === true) { if ($found > 1) { $phpcsFile->fixer->beginChangeset(); for ($i = $prevContent + 1; $i < $closeBrace - 1; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->replaceToken($i, $phpcsFile->eolChar); $phpcsFile->fixer->endChangeset(); } else { // Try and maintain indentation. if ($tokens[$closeBrace - 1]['code'] === T_WHITESPACE) { $phpcsFile->fixer->addNewlineBefore($closeBrace - 1); } else { $phpcsFile->fixer->addNewlineBefore($closeBrace); } } } } //end if } //end if }
/** * 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]['parenthesis_opener']) === true && isset($tokens[$stackPtr]['parenthesis_closer']) === true) { $parenOpener = $tokens[$stackPtr]['parenthesis_opener']; $parenCloser = $tokens[$stackPtr]['parenthesis_closer']; if ($tokens[$parenOpener + 1]['code'] === T_WHITESPACE) { $gap = $tokens[$parenOpener + 1]['length']; if ($gap === 0) { $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', 'newline'); $gap = 'newline'; } else { $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', $gap); } $error = 'Expected 0 spaces after opening bracket; %s found'; $data = array($gap); $fix = $phpcsFile->addFixableError($error, $parenOpener + 1, 'SpacingAfterOpenBrace', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($parenOpener + 1, ''); } } else { $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', 0); } if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line'] && $tokens[$parenCloser - 1]['code'] === T_WHITESPACE) { $gap = $tokens[$parenCloser - 1]['length']; $error = 'Expected 0 spaces before closing bracket; %s found'; $data = array($gap); $fix = $phpcsFile->addFixableError($error, $parenCloser - 1, 'SpaceBeforeCloseBrace', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($parenCloser - 1, ''); } if ($gap === 0) { $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', 'newline'); } else { $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', $gap); } } else { $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', 0); } } //end if if (isset($tokens[$stackPtr]['scope_closer']) === false) { return; } $scopeOpener = $tokens[$stackPtr]['scope_opener']; $scopeCloser = $tokens[$stackPtr]['scope_closer']; for ($firstContent = $scopeOpener + 1; $firstContent < $phpcsFile->numTokens; $firstContent++) { if ($tokens[$firstContent]['code'] !== T_WHITESPACE) { break; } } // We ignore spacing for some structures that tend to have their own rules. $ignore = array(T_FUNCTION => true, T_CLASS => true, T_INTERFACE => true, T_TRAIT => true, T_DOC_COMMENT_OPEN_TAG => true); if (isset($ignore[$tokens[$firstContent]['code']]) === false && $tokens[$firstContent]['line'] >= $tokens[$scopeOpener]['line'] + 2) { $error = 'Blank line found at start of control structure'; $fix = $phpcsFile->addFixableError($error, $scopeOpener, 'SpacingAfterOpen'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $i = $scopeOpener + 1; while ($tokens[$i]['line'] !== $tokens[$firstContent]['line']) { $phpcsFile->fixer->replaceToken($i, ''); $i++; } $phpcsFile->fixer->addNewline($scopeOpener); $phpcsFile->fixer->endChangeset(); } } if ($firstContent !== $scopeCloser) { $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, $scopeCloser - 1, null, true); $lastNonEmptyContent = $phpcsFile->findPrevious(Tokens::$emptyTokens, $scopeCloser - 1, null, true); $checkToken = $lastContent; if (isset($tokens[$lastNonEmptyContent]['scope_condition']) === true) { $checkToken = $tokens[$lastNonEmptyContent]['scope_condition']; } if (isset($ignore[$tokens[$checkToken]['code']]) === false && $tokens[$lastContent]['line'] <= $tokens[$scopeCloser]['line'] - 2) { $errorToken = $scopeCloser; for ($i = $scopeCloser - 1; $i > $lastContent; $i--) { if ($tokens[$i]['line'] < $tokens[$scopeCloser]['line']) { $errorToken = $i; break; } } $error = 'Blank line found at end of control structure'; $fix = $phpcsFile->addFixableError($error, $errorToken, 'SpacingBeforeClose'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $i = $scopeCloser - 1; for ($i = $scopeCloser - 1; $i > $lastContent; $i--) { if ($tokens[$i]['line'] === $tokens[$scopeCloser]['line']) { continue; } if ($tokens[$i]['line'] === $tokens[$lastContent]['line']) { break; } $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } } //end if } //end if $trailingContent = $phpcsFile->findNext(T_WHITESPACE, $scopeCloser + 1, null, true); if ($tokens[$trailingContent]['code'] === T_COMMENT) { // Special exception for code where the comment about // an ELSE or ELSEIF is written between the control structures. $nextCode = $phpcsFile->findNext(Tokens::$emptyTokens, $scopeCloser + 1, null, true); if ($tokens[$nextCode]['code'] === T_ELSE || $tokens[$nextCode]['code'] === T_ELSEIF) { $trailingContent = $nextCode; } } //end if if ($tokens[$trailingContent]['code'] === T_ELSE) { if ($tokens[$stackPtr]['code'] === T_IF) { // IF with ELSE. return; } } if ($tokens[$trailingContent]['code'] === T_WHILE && $tokens[$stackPtr]['code'] === T_DO) { // DO with WHILE. return; } if ($tokens[$trailingContent]['code'] === T_CLOSE_TAG) { // At the end of the script or embedded code. return; } if (isset($tokens[$trailingContent]['scope_condition']) === true && $tokens[$trailingContent]['scope_condition'] !== $trailingContent && isset($tokens[$trailingContent]['scope_opener']) === true && $tokens[$trailingContent]['scope_opener'] !== $trailingContent) { // Another control structure's closing brace. $owner = $tokens[$trailingContent]['scope_condition']; if ($tokens[$owner]['code'] === T_FUNCTION) { // The next content is the closing brace of a function // so normal function rules apply and we can ignore it. return; } if ($tokens[$owner]['code'] === T_CLOSURE && ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true || isset($tokens[$stackPtr]['nested_parenthesis']) === true)) { return; } if ($tokens[$trailingContent]['line'] !== $tokens[$scopeCloser]['line'] + 1) { $error = 'Blank line found after control structure'; $fix = $phpcsFile->addFixableError($error, $scopeCloser, 'LineAfterClose'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $i = $scopeCloser + 1; while ($tokens[$i]['line'] !== $tokens[$trailingContent]['line']) { $phpcsFile->fixer->replaceToken($i, ''); $i++; } $phpcsFile->fixer->addNewline($scopeCloser); $phpcsFile->fixer->endChangeset(); } } } else { if ($tokens[$trailingContent]['code'] !== T_ELSE && $tokens[$trailingContent]['code'] !== T_ELSEIF && $tokens[$trailingContent]['code'] !== T_CATCH && $tokens[$trailingContent]['line'] === $tokens[$scopeCloser]['line'] + 1) { $error = 'No blank line found after control structure'; $fix = $phpcsFile->addFixableError($error, $scopeCloser, 'NoLineAfterClose'); if ($fix === true) { $phpcsFile->fixer->addNewline($scopeCloser); } } } //end if }
/** * 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(); $constName = $tokens[$stackPtr]['content']; // If this token is in a heredoc, ignore it. if ($phpcsFile->hasCondition($stackPtr, T_START_HEREDOC) === true) { return; } // Special case for PHP 5.5 class name resolution. if (strtolower($constName) === 'class' && $tokens[$stackPtr - 1]['code'] === T_DOUBLE_COLON) { return; } // Special case for PHPUnit. if ($constName === 'PHPUnit_MAIN_METHOD') { return; } // If the next non-whitespace token after this token // is not an opening parenthesis then it is not a function call. for ($openBracket = $stackPtr + 1; $openBracket < $phpcsFile->numTokens; $openBracket++) { if ($tokens[$openBracket]['code'] !== T_WHITESPACE) { break; } } if ($openBracket === $phpcsFile->numTokens) { return; } if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) { $functionKeyword = $phpcsFile->findPrevious(array(T_WHITESPACE, T_COMMA, T_COMMENT, T_STRING, T_NS_SEPARATOR), $stackPtr - 1, null, true); if ($tokens[$functionKeyword]['code'] !== T_CONST) { return; } // This is a class constant. if (strtoupper($constName) !== $constName) { if (strtolower($constName) === $constName) { $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'lower'); } else { $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'mixed'); } $error = 'Class constants must be uppercase; expected %s but found %s'; $data = array(strtoupper($constName), $constName); $phpcsFile->addError($error, $stackPtr, 'ClassConstantNotUpperCase', $data); } else { $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'upper'); } return; } //end if if (strtolower($constName) !== 'define') { return; } /* This may be a "define" function call. */ // Make sure this is not a method call. $prev = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR || $tokens[$prev]['code'] === T_DOUBLE_COLON) { return; } // The next non-whitespace token must be the constant name. $constPtr = $phpcsFile->findNext(T_WHITESPACE, $openBracket + 1, null, true); if ($tokens[$constPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING) { return; } $constName = $tokens[$constPtr]['content']; // Check for constants like self::CONSTANT. $prefix = ''; $splitPos = strpos($constName, '::'); if ($splitPos !== false) { $prefix = substr($constName, 0, $splitPos + 2); $constName = substr($constName, $splitPos + 2); } // Strip namesspace from constant like /foo/bar/CONSTANT. $splitPos = strrpos($constName, '\\'); if ($splitPos !== false) { $prefix = substr($constName, 0, $splitPos + 1); $constName = substr($constName, $splitPos + 1); } if (strtoupper($constName) !== $constName) { if (strtolower($constName) === $constName) { $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'lower'); } else { $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'mixed'); } $error = 'Constants must be uppercase; expected %s but found %s'; $data = array($prefix . strtoupper($constName), $prefix . $constName); $phpcsFile->addError($error, $stackPtr, 'ConstantNotUpperCase', $data); } else { $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'upper'); } }
/** * 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 ($tokens[$stackPtr]['code'] === T_FUNCTION) { $methodProps = $phpcsFile->getMethodProperties($stackPtr); // Abstract methods do not require a closing comment. if ($methodProps['is_abstract'] === true) { return; } // Closures do not require a closing comment. if ($methodProps['is_closure'] === true) { return; } // If this function is in an interface then we don't require // a closing comment. if ($phpcsFile->hasCondition($stackPtr, T_INTERFACE) === true) { return; } if (isset($tokens[$stackPtr]['scope_closer']) === false) { $error = 'Possible parse error: non-abstract method defined as abstract'; $phpcsFile->addWarning($error, $stackPtr, 'Abstract'); return; } $decName = $phpcsFile->getDeclarationName($stackPtr); $comment = '//end ' . $decName . '()'; } else { if ($tokens[$stackPtr]['code'] === T_CLASS) { $comment = '//end class'; } else { $comment = '//end interface'; } } //end if if (isset($tokens[$stackPtr]['scope_closer']) === false) { $error = 'Possible parse error: %s missing opening or closing brace'; $data = array($tokens[$stackPtr]['content']); $phpcsFile->addWarning($error, $stackPtr, 'MissingBrace', $data); return; } $closingBracket = $tokens[$stackPtr]['scope_closer']; if ($closingBracket === null) { // Possible inline structure. Other tests will handle it. return; } $error = 'Expected ' . $comment; if (isset($tokens[$closingBracket + 1]) === false || $tokens[$closingBracket + 1]['code'] !== T_COMMENT) { $next = $phpcsFile->findNext(T_WHITESPACE, $closingBracket + 1, null, true); if (rtrim($tokens[$next]['content']) === $comment) { // The comment isn't really missing; it is just in the wrong place. $fix = $phpcsFile->addFixableError($error . ' directly after closing brace', $closingBracket, 'Misplaced'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = $closingBracket + 1; $i < $next; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } // Just in case, because indentation fixes can add indents onto // these comments and cause us to be unable to fix them. $phpcsFile->fixer->replaceToken($next, $comment . $phpcsFile->eolChar); $phpcsFile->fixer->endChangeset(); } } else { $fix = $phpcsFile->addFixableError($error, $closingBracket, 'Missing'); if ($fix === true) { $phpcsFile->fixer->replaceToken($closingBracket, '}' . $comment . $phpcsFile->eolChar); } } return; } //end if if (rtrim($tokens[$closingBracket + 1]['content']) !== $comment) { $fix = $phpcsFile->addFixableError($error, $closingBracket, 'Incorrect'); if ($fix === true) { $phpcsFile->fixer->replaceToken($closingBracket + 1, $comment . $phpcsFile->eolChar); } return; } }
/** * 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(); if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) { /* Check for start of file whitespace. */ if ($phpcsFile->tokenizerType !== 'PHP') { // The first token is always the open tag inserted when tokenizsed // and the second token is always the first piece of content in // the file. If the second token is whitespace, there was // whitespace at the start of the file. if ($tokens[$stackPtr + 1]['code'] !== T_WHITESPACE) { return; } if ($phpcsFile->fixer->enabled === true) { $stackPtr = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true); } } else { // If it's the first token, then there is no space. if ($stackPtr === 0) { return; } for ($i = $stackPtr - 1; $i >= 0; $i--) { // If we find something that isn't inline html then there is something previous in the file. if ($tokens[$i]['type'] !== 'T_INLINE_HTML') { return; } // If we have ended up with inline html make sure it isn't just whitespace. $tokenContent = trim($tokens[$i]['content']); if ($tokenContent !== '') { return; } } } //end if $fix = $phpcsFile->addFixableError('Additional whitespace found at start of file', $stackPtr, 'StartFile'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = 0; $i < $stackPtr; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } } else { if ($tokens[$stackPtr]['code'] === T_CLOSE_TAG) { /* Check for end of file whitespace. */ if ($phpcsFile->tokenizerType === 'PHP') { if (isset($tokens[$stackPtr + 1]) === false) { // The close PHP token is the last in the file. return; } for ($i = $stackPtr + 1; $i < $phpcsFile->numTokens; $i++) { // If we find something that isn't inline HTML then there // is more to the file. if ($tokens[$i]['type'] !== 'T_INLINE_HTML') { return; } // If we have ended up with inline html make sure it // isn't just whitespace. $tokenContent = trim($tokens[$i]['content']); if (empty($tokenContent) === false) { return; } } } else { // The last token is always the close tag inserted when tokenized // and the second last token is always the last piece of content in // the file. If the second last token is whitespace, there was // whitespace at the end of the file. $stackPtr--; // The pointer is now looking at the last content in the file and // not the fake PHP end tag the tokenizer inserted. if ($tokens[$stackPtr]['code'] !== T_WHITESPACE) { return; } // Allow a single newline at the end of the last line in the file. if ($tokens[$stackPtr - 1]['code'] !== T_WHITESPACE && $tokens[$stackPtr]['content'] === $phpcsFile->eolChar) { return; } if ($phpcsFile->fixer->enabled === true) { $prev = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true); $stackPtr = $prev + 1; } } //end if $fix = $phpcsFile->addFixableError('Additional whitespace found at end of file', $stackPtr, 'EndFile'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = $stackPtr + 1; $i < $phpcsFile->numTokens; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } } else { /* Check for end of line whitespace. */ // Ignore whitespace that is not at the end of a line. if (isset($tokens[$stackPtr + 1]['line']) === true && $tokens[$stackPtr + 1]['line'] === $tokens[$stackPtr]['line']) { return; } // Ignore blank lines if required. if ($this->ignoreBlankLines === true && $tokens[$stackPtr - 1]['line'] !== $tokens[$stackPtr]['line']) { return; } $tokenContent = rtrim($tokens[$stackPtr]['content'], $phpcsFile->eolChar); if (empty($tokenContent) === false) { if ($tokenContent !== rtrim($tokenContent)) { $fix = $phpcsFile->addFixableError('Whitespace found at end of line', $stackPtr, 'EndLine'); if ($fix === true) { $phpcsFile->fixer->replaceToken($stackPtr, rtrim($tokenContent) . $phpcsFile->eolChar); } } } else { if ($tokens[$stackPtr - 1]['content'] !== rtrim($tokens[$stackPtr - 1]['content']) && $tokens[$stackPtr - 1]['line'] === $tokens[$stackPtr]['line']) { $fix = $phpcsFile->addFixableError('Whitespace found at end of line', $stackPtr - 1, 'EndLine'); if ($fix === true) { $phpcsFile->fixer->replaceToken($stackPtr - 1, rtrim($tokens[$stackPtr - 1]['content'])); } } } /* Check for multiple blank lines in a function. */ if (($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true) && $tokens[$stackPtr - 1]['line'] < $tokens[$stackPtr]['line'] && $tokens[$stackPtr - 2]['line'] === $tokens[$stackPtr - 1]['line']) { // This is an empty line and the line before this one is not // empty, so this could be the start of a multiple empty // line block. $next = $phpcsFile->findNext(T_WHITESPACE, $stackPtr, null, true); $lines = $tokens[$next]['line'] - $tokens[$stackPtr]['line']; if ($lines > 1) { $error = 'Functions must not contain multiple empty lines in a row; found %s empty lines'; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'EmptyLines', array($lines)); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $i = $stackPtr; while ($tokens[$i]['line'] !== $tokens[$next]['line']) { $phpcsFile->fixer->replaceToken($i, ''); $i++; } $phpcsFile->fixer->addNewlineBefore($i); $phpcsFile->fixer->endChangeset(); } } } //end if } } //end if }