public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $scope = $phpcsFile->getDeclarationName($stackPtr); $class = $phpcsFile->getDeclarationName($phpcsFile->findPrevious([T_CLASS, T_INTERFACE], $stackPtr)); $tokens = $phpcsFile->getTokens(); $function = $tokens[$stackPtr]; if (!isset($function['scope_opener'])) { // No scope means it's an abstract/interface function declaration return; } $scopeTypes = [T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_SWITCH, T_WHILE]; $functionStartPtr = $function['scope_opener']; $functionEndPtr = $function['scope_closer']; $ptr = $functionStartPtr; // var_dump($class, $scope); while ($ptr = $phpcsFile->findNext($scopeTypes, $ptr + 1, $functionEndPtr)) { $token = $tokens[$ptr]; if ($token['code'] != T_IF && $token['code'] != T_ELSE && $token['code'] != T_ELSEIF) { $this->getBlankLineCountBefore($phpcsFile, $tokens, $functionStartPtr, $ptr - 1); $this->getBlankLineCountAfter($phpcsFile, $tokens, $token['scope_closer'] + 1, $functionEndPtr); } elseif ($token['code'] == T_IF) { $this->getBlankLineCountBefore($phpcsFile, $tokens, $functionStartPtr, $ptr); $this->getBlankLineCountAfter($phpcsFile, $tokens, $token['scope_closer'] + 1, $functionEndPtr, [T_ELSE, T_ELSEIF]); } elseif ($token['code'] == T_ELSEIF) { $this->getBlankLineCountBefore($phpcsFile, $tokens, $functionStartPtr, $ptr, [T_CLOSE_CURLY_BRACKET]); $this->getBlankLineCountAfter($phpcsFile, $tokens, $token['scope_closer'] + 1, $functionEndPtr, [T_ELSE, T_ELSEIF]); } elseif ($token['code'] == T_ELSE) { $this->getBlankLineCountBefore($phpcsFile, $tokens, $functionStartPtr, $ptr, [T_CLOSE_CURLY_BRACKET]); $this->getBlankLineCountAfter($phpcsFile, $tokens, $token['scope_closer'] + 1, $functionEndPtr); } } }
/** * Processes the tokens within the scope. * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param int $stackPtr The position where this token was * found. * @param int $currScope The position of the current scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $methodName = $phpcsFile->getDeclarationName($stackPtr); if ($methodName === null) { // Ignore closures. return; } // Ignore magic methods. if (preg_match('|^__|', $methodName) !== 0) { $magicPart = strtolower(substr($methodName, 2)); if (isset($this->magicMethods[$magicPart]) === true || isset($this->methodsDoubleUnderscore[$magicPart]) === true) { return; } } $testName = ltrim($methodName, '_'); if (PHP_CodeSniffer::isCamelCaps($testName, false, true, false) === false) { $error = 'Method name "%s" is not in camel caps format'; $className = $phpcsFile->getDeclarationName($currScope); $errorData = array($className . '::' . $methodName); $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $errorData); $phpcsFile->recordMetric($stackPtr, 'CamelCase method name', 'no'); } else { $phpcsFile->recordMetric($stackPtr, 'CamelCase method name', 'yes'); } }
/** * Processes the tokens within the scope. * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param int $stackPtr The position where this token was * found. * @param int $currScope The position of the current scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $methodName = $phpcsFile->getDeclarationName($stackPtr); if ($methodName === null) { // Ignore closures. return; } $className = $phpcsFile->getDeclarationName($currScope); $errorData = array($className . '::' . $methodName); // Is this a magic method. IE. is prefixed with "__". if (preg_match('|^__|', $methodName) !== 0) { $magicPart = substr($methodName, 2); if (in_array($magicPart, $this->magicMethods) === false) { $error = 'Method name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; $phpcsFile->addError($error, $stackPtr, 'MethodDoubleUnderscore', $errorData); } return; } $methodProps = $phpcsFile->getMethodProperties($stackPtr); $scope = $methodProps['scope']; $scopeSpecified = $methodProps['scope_specified']; // Methods should not contain underscores. if (strpos($methodName, '_') !== false) { if ($scopeSpecified === true) { $error = '%s method name "%s" is not in lowerCamel format, it must not contain underscores'; $data = array(ucfirst($scope), $errorData[0]); $phpcsFile->addError($error, $stackPtr, 'ScopeNotLowerCamel', $data); } else { $error = 'Method name "%s" is not in lowerCamel format, it must not contain underscores'; $phpcsFile->addError($error, $stackPtr, 'NotLowerCamel', $errorData); } } }
/** * 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 token stack. * * @return void */ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); if (isset($tokens[$stackPtr]['scope_closer']) === false) { return; } $className = $phpcsFile->getDeclarationName($stackPtr); $errorData = array(strtolower($tokens[$stackPtr]['content'])); $nextClass = $phpcsFile->findNext(array(T_CLASS, T_INTERFACE, T_TRAIT), $tokens[$stackPtr]['scope_closer'] + 1); if ($nextClass !== false) { $nextClassName = $phpcsFile->getDeclarationName($nextClass); $extends = $phpcsFile->findExtendedClassName($nextClass); if ($className == $nextClassName && $extends != $className && substr($extends, -1 * (strlen($className) + 1)) == '\\' . $className) { // $nextClassName wraps $className in global namespace (probably) $phpcsFile->recordMetric($stackPtr, 'One class per file', 'yes'); } else { $error = 'Each %s must be in a file by itself'; $phpcsFile->addError($error, $nextClass, 'MultipleClasses', $errorData); $phpcsFile->recordMetric($stackPtr, 'One class per file', 'no'); } } else { $phpcsFile->recordMetric($stackPtr, 'One class per file', 'yes'); } if (version_compare(PHP_VERSION, '5.3.0') >= 0) { $namespace = $phpcsFile->findNext(array(T_NAMESPACE, T_CLASS, T_INTERFACE, T_TRAIT), 0); if ($tokens[$namespace]['code'] !== T_NAMESPACE) { $error = 'Each %s must be in a namespace of at least one level (a top-level vendor name)'; $phpcsFile->addWarning($error, $stackPtr, 'MissingNamespace', $errorData); $phpcsFile->recordMetric($stackPtr, 'Class defined in namespace', 'no'); } else { $phpcsFile->recordMetric($stackPtr, 'Class defined in namespace', 'yes'); } } }
/** * Processes this test when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile The current file being scanned. * @param int $stackPtr The position of the current token * in the stack passed in $tokens. * @param int $currScope A pointer to the start of the scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $className = $phpcsFile->getDeclarationName($currScope); $methodName = $phpcsFile->getDeclarationName($stackPtr); if (strcasecmp($methodName, $className) === 0) { $error = 'PHP4 style constructors are not allowed; use "__construct()" instead'; $phpcsFile->addError($error, $stackPtr, 'OldStyle'); } else { if (strcasecmp($methodName, '__construct') !== 0) { // Not a constructor. return; } } $tokens = $phpcsFile->getTokens(); $parentClassName = $phpcsFile->findExtendedClassName($currScope); if ($parentClassName === false) { return; } $endFunctionIndex = $tokens[$stackPtr]['scope_closer']; $startIndex = $stackPtr; while ($doubleColonIndex = $phpcsFile->findNext(array(T_DOUBLE_COLON), $startIndex, $endFunctionIndex)) { if ($tokens[$doubleColonIndex + 1]['code'] === T_STRING && $tokens[$doubleColonIndex + 1]['content'] === $parentClassName) { $error = 'PHP4 style calls to parent constructors are not allowed; use "parent::__construct()" instead'; $phpcsFile->addError($error, $doubleColonIndex + 1, 'OldStyleCall'); } $startIndex = $doubleColonIndex + 1; } }
protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $className = $phpcsFile->getDeclarationName($currScope); $methodName = $phpcsFile->getDeclarationName($stackPtr); if (!PHP_CodeSniffer::isCamelCaps($methodName, false, true, false)) { $error = $className . '::' . $methodName . '() name is not in camel caps format'; $phpcsFile->addError($error, $stackPtr); } }
/** * Processes the tokens within the scope. * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param int $stackPtr The position where this token was * found. * @param int $currScope The position of the current scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $className = $phpcsFile->getDeclarationName($currScope); $methodName = $phpcsFile->getDeclarationName($stackPtr); // Is this a magic method. IE. is prefixed with "__". if (preg_match('|^__|', $methodName) !== 0) { $magicPart = substr($methodName, 2); if (in_array($magicPart, $this->_magicMethods) === false) { $error = "Method name \"{$className}::{$methodName}\" is invalid; only PHP magic methods should be prefixed with a double underscore"; $phpcsFile->addError($error, $stackPtr); } return; } // PHP4 constructors are allowed to break our rules. if ($methodName === $className) { return; } // PHP4 destructors are allowed to break our rules. if ($methodName === '_' . $className) { return; } $methodProps = $phpcsFile->getMethodProperties($stackPtr); $isPublic = $methodProps['scope'] === 'public' ? true : false; $scope = $methodProps['scope']; $scopeSpecified = $methodProps['scope_specified']; // If it's a private method, it must have an underscore on the front. if ($isPublic === false && $methodName[0] !== '_') { $error = ucfirst($scope) . " method name \"{$className}::{$methodName}\" must be prefixed with an underscore"; $phpcsFile->addError($error, $stackPtr); return; } // If it's not a private method, it must not have an underscore on the front. if ($isPublic === true && $scopeSpecified === true && $methodName[0] === '_') { $error = "Public method name \"{$className}::{$methodName}\" must not be prefixed with an underscore"; $phpcsFile->addError($error, $stackPtr); return; } // If the scope was specified on the method, then the method must be // camel caps and an underscore should be checked for. If it wasn't // specified, treat it like a public method and remove the underscore // prefix if there is one because we cant determine if it is private or // public. $testMethodName = $methodName; if ($scopeSpecified === false && $methodName[0] === '_') { $testMethodName = substr($methodName, 1); } if (PHP_CodeSniffer::isCamelCaps($testMethodName, false, $isPublic, false) === false) { if ($scopeSpecified === true) { $error = ucfirst($scope) . " method name \"{$className}::{$methodName}\" is not in camel caps format"; } else { $error = "Method name \"{$className}::{$methodName}\" is not in camel caps format"; } $phpcsFile->addError($error, $stackPtr); return; } }
/** * Processes the tokens within the scope. * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param integer $stackPtr The position where this token was found. * @param integer $currScope The position of the current scope. * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $methodName = $phpcsFile->getDeclarationName($stackPtr); if ($methodName === null) { // Ignore closures. return; } $className = $phpcsFile->getDeclarationName($currScope); $errorData = array($className . '::' . $methodName); // PHP4 constructors are allowed to break our rules. if ($methodName === $className) { return; } // PHP4 destructors are allowed to break our rules. if ($methodName === '_' . $className) { return; } // Ignore magic methods if (preg_match('/^__(' . implode('|', $this->_magicMethods) . ')$/', $methodName)) { return; } $methodProps = $phpcsFile->getMethodProperties($stackPtr); if ($methodProps['scope_specified'] === false) { // Let another sniffer take care of that return; } $isPublic = $methodProps['scope'] === 'public'; $isProtected = $methodProps['scope'] === 'protected'; $isPrivate = $methodProps['scope'] === 'private'; $scope = $methodProps['scope']; if ($isPublic === true) { if ($methodName[0] === '_') { $error = 'Public method name "%s" must not be prefixed with underscore'; $phpcsFile->addError($error, $stackPtr, 'PublicWithUnderscore', $errorData); return; } // Underscored public methods in controller are allowed to break our rules. if (substr($className, -10) === 'Controller') { return; } // Underscored public methods in shells are allowed to break our rules. if (substr($className, -5) === 'Shell') { return; } // Underscored public methods in tasks are allowed to break our rules. if (substr($className, -4) === 'Task') { return; } } elseif ($isPrivate === true) { $filename = $phpcsFile->getFilename(); $warning = 'Private method name "%s" in CakePHP core is discouraged'; $phpcsFile->addWarning($warning, $stackPtr, 'PrivateMethodInCore', $errorData); } }
/** * Processes this test when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile The current file being scanned. * @param int $stackPtr The position of the current token * in the stack passed in $tokens. * @param int $currScope A pointer to the start of the scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $methodName = $phpcsFile->getDeclarationName($stackPtr); $className = $phpcsFile->getDeclarationName($currScope); $isPhp4Constructor = strcasecmp($methodName, $className) === 0; $isPhp5Constructor = strcasecmp($methodName, '__construct') === 0; if ($this->php5Constructors != '0') { if ($isPhp4Constructor) { $error = "PHP4 style constructors are not allowed; use \"__construct\" instead"; $phpcsFile->addError($error, $stackPtr); } } else { if ($isPhp5Constructor) { $error = "PHP5 style constructors are not allowed; use \"{$className}\" instead"; $phpcsFile->addError($error, $stackPtr); } } if (!$isPhp4Constructor && !$isPhp5Constructor) { return; } $tokens = $phpcsFile->getTokens(); $parentClassName = $phpcsFile->findExtendedClassName($currScope); $wrongConstructor = ''; // prepares the error message and wrong constructor if ($this->php5Constructors != '0') { $error = 'PHP4 style calls to parent constructors are not allowed.'; $error = "{$error} Please use \"parent::__construct\" instead."; if (false !== $parentClassName) { $wrongConstructor = $parentClassName; } // Else $wrongConstructor will be empty // and the test expression will always be false. // It doesn't check that no parent method should be called // when no parent class is defined. } else { $error = 'PHP5 style calls to parent constructors are not allowed.'; if (false !== $parentClassName) { $error = "{$error} Please use \"parent::{$parentClassName}\" instead."; } $wrongConstructor = '__construct'; } // looks for the use of a wrong constructor. $endFunctionIndex = $tokens[$stackPtr]['scope_closer']; $doubleColonIndex = $phpcsFile->findNext(array(T_DOUBLE_COLON), $stackPtr, $endFunctionIndex); while ($doubleColonIndex) { if ($tokens[$doubleColonIndex + 1]['code'] === T_STRING && $tokens[$doubleColonIndex + 1]['content'] === $wrongConstructor) { $phpcsFile->addError($error, $doubleColonIndex + 1); } $doubleColonIndex = $phpcsFile->findNext(array(T_DOUBLE_COLON), $doubleColonIndex + 1, $endFunctionIndex); } }
/** * Override parent function to allow custom code validation * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param int $stackPtr The position where this token was * found. * @param int $currScope The position of the current scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $methodName = $phpcsFile->getDeclarationName($stackPtr); if ($methodName === null) { // Ignore closures. return; } $className = $phpcsFile->getDeclarationName($currScope); // Is this a magic method. IE. is prefixed with "__". if (preg_match('|^__|', $methodName) !== 0) { $magicPart = substr($methodName, 2); if (in_array($magicPart, $this->magicMethods) === false) { $error = "Method name \"{$className}::{$methodName}\" is invalid; only PHP magic methods should be prefixed with a double underscore"; $phpcsFile->addError($error, $stackPtr); } return; } // PHP4 constructors are allowed to break our rules. if ($methodName === $className) { return; } // PHP4 destructors are allowed to break our rules. if ($methodName === '_' . $className) { return; } $methodProps = $phpcsFile->getMethodProperties($stackPtr); $isPublic = $methodProps['scope'] === 'private' ? false : true; $scope = $methodProps['scope']; $scopeSpecified = $methodProps['scope_specified']; // If it's a private method, it must have an underscore on the front. if ($isPublic === false && $methodName[0] !== '_') { $error = "Private method name \"{$className}::{$methodName}\" must be prefixed with an underscore"; $phpcsFile->addError($error, $stackPtr); return; } // We want to allow private and protected methods to start with an underscore $testMethodName = $methodName; if (($scopeSpecified === false || $scope == 'protected') && $methodName[0] === '_') { $testMethodName = substr($methodName, 1); } if (PHP_CodeSniffer::isCamelCaps($testMethodName, false, $isPublic, false) === false) { if ($scopeSpecified === true) { $error = ucfirst($scope) . " method name \"{$className}::{$methodName}\" is not in camel caps format"; } else { $error = "Method name \"{$className}::{$methodName}\" is not in camel caps format"; } $phpcsFile->addError($error, $stackPtr); return; } }
/** * Processes the tokens within the scope. * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param int $stackPtr The position where this token was * found. * @param int $currScope The position of the current scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $className = $phpcsFile->getDeclarationName($currScope); $methodName = $phpcsFile->getDeclarationName($stackPtr); // Is this a magic method. IE. is prefixed with "__". if (preg_match('|^__|', $methodName) !== 0) { $magicPart = substr($methodName, 2); if (in_array($magicPart, $this->_magicMethods) === false) { $error = "Method name \"{$className}::{$methodName}\" is invalid; only PHP magic methods should be prefixed with a double underscore"; $phpcsFile->addError($error, $stackPtr, 'MethodDoubleUnderscore'); } return; } // PHP4 constructors are allowed to break our rules. if ($methodName === $className) { return; } // PHP4 destructors are allowed to break our rules. if ($methodName === '_' . $className) { return; } // If this is a child class, it may have to use camelCase. if ($phpcsFile->findExtendedClassName($currScope) || $this->findImplementedInterfaceName($currScope, $phpcsFile)) { return; } $methodProps = $phpcsFile->getMethodProperties($stackPtr); $scope = $methodProps['scope']; $scopeSpecified = $methodProps['scope_specified']; if ($methodProps['scope'] === 'private') { $isPublic = false; } else { $isPublic = true; } // If the scope was specified on the method, then the method must be // camel caps and an underscore should be checked for. If it wasn't // specified, treat it like a public method and remove the underscore // prefix if there is one because we can't determine if it is private or // public. $testMethodName = $methodName; if ($scopeSpecified === false && $methodName[0] === '_') { $testMethodName = substr($methodName, 1); } if (strtolower($testMethodName) !== $testMethodName) { $suggested = preg_replace('/([A-Z])/', '_$1', $methodName); $suggested = strtolower($suggested); $suggested = str_replace('__', '_', $suggested); $error = "Function name \"{$methodName}\" is in camel caps format, try '" . $suggested . "'"; $phpcsFile->addError($error, $stackPtr, 'FunctionNameInvalid'); } }
/** * Processes the tokens within the scope. * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param int $stackPtr The position where this token was * found. * @param int $currScope The position of the current scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $methodName = $phpcsFile->getDeclarationName($stackPtr); if ($methodName === null) { // Ignore closures. return; } $testName = ltrim($methodName, '_'); if (PHP_CodeSniffer::isCamelCaps($testName, false, true, false) === false) { $error = 'Method name "%s" is not in camel caps format'; $className = $phpcsFile->getDeclarationName($currScope); $errorData = array($className . '::' . $methodName); $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $errorData); } }
/** * 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(\PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $find = PHP_CodeSniffer_Tokens::$methodPrefixes; $find[] = T_WHITESPACE; $isTagFound = false; $functionName = ''; try { $functionName = $phpcsFile->getDeclarationName($stackPtr); } catch (PHP_CodeSniffer_Exception $e) { error_log($e->getMessage()); } // Process only test* functions if (substr($functionName, 0, strlen(self::TEST_FUNCTION_PREFIX)) !== self::TEST_FUNCTION_PREFIX) { return; } $commentEnd = $phpcsFile->findPrevious($find, $stackPtr - 1, null, true); if (array_key_exists('comment_opener', $tokens[$commentEnd])) { foreach ($tokens[$tokens[$commentEnd]['comment_opener']]['comment_tags'] as $tag) { if ($tokens[$tag]['content'] === self::ANNOTATION_COVERS) { $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd); $isTagFound = true; if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) { $error = sprintf('Content missing for %s tag in test method comment', self::ANNOTATION_COVERS); $phpcsFile->addError($error, $tag, 'EmptyCoversTag'); } } } } if ($isTagFound === false) { $error = sprintf('Missing %s tag in test method comment', self::ANNOTATION_COVERS); $phpcsFile->addError($error, $stackPtr, 'MissingCoversTag'); } }
/** * 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 processReturn(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); // Skip constructor and destructor. $methodName = $phpcsFile->getDeclarationName($stackPtr); $isSpecialMethod = $methodName === '__construct' || $methodName === '__destruct'; $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'); } elseif (!$this->functionHasReturnStatement($phpcsFile, $stackPtr)) { $error = 'Function return type is set, but function has no return statement'; $phpcsFile->addError($error, $return, 'InvalidNoReturn'); } } elseif ($this->functionHasReturnStatement($phpcsFile, $stackPtr)) { $error = 'Missing @return tag in function comment.'; $phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn'); } //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(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $tokens = $phpcsFile->getTokens(); $methodName = $phpcsFile->getDeclarationName($stackPtr); if ($methodName === null) { // Ignore closures. return; } $modifier = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$scopeModifiers, $stackPtr); if ($modifier === false || $tokens[$modifier]['line'] !== $tokens[$stackPtr]['line']) { $error = 'Visibility must be declared on method "%s"'; $data = array($methodName); $previous = $phpcsFile->findPrevious(array(T_WHITESPACE), $stackPtr - 1, null, true); // Only correct the trivial cases for now if (!$modifier && $tokens[$modifier]['line'] === $tokens[$stackPtr]['line']) { $phpcsFile->addFixableError($error, $stackPtr, 'Missing', $data); $visbility = 'public'; $name = substr($tokens[$stackPtr]['content'], 1); $totalUnderscores = 0; while (strpos($name, '_') === 0) { $totalUnderscores++; $name = substr($name, 1); } if ($totalUnderscores > 1) { $visbility = 'private'; } elseif ($totalUnderscores > 0) { $visbility = 'protected'; } } else { $phpcsFile->addError($error, $stackPtr, 'Missing', $data); } //TODO } }
/** * 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'); } }
/** * Processes the tokens outside the scope. * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param int $stackPtr The position where this token was found. */ protected function processTokenOutsideScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $functionName = $phpcsFile->getDeclarationName($stackPtr); if ($functionName === null) { // Ignore closures. return; } $errorData = [$functionName]; // Is this a magic function. i.e., it is prefixed with "__". if (preg_match('|^__|', $functionName) !== 0) { $magicPart = strtolower(substr($functionName, 2)); if (isset($this->magicFunctions[$magicPart]) === false) { $error = 'Function name "%s" is invalid; only PHP magic methods' . ' should be prefixed with a double underscore'; $phpcsFile->addError($error, $stackPtr, 'FunctionDoubleUnderscore', $errorData); } return; } // Ignore first underscore in functions prefixed with "_". $functionName = ltrim($functionName, '_'); if (preg_match('/^[a-z][_a-z]*$/', $functionName) === false) { $error = 'Function name "%s" is not in snake case format'; $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $errorData); $phpcsFile->recordMetric($stackPtr, 'snake_case function name', 'no'); } else { $phpcsFile->recordMetric($stackPtr, 'snake_case method name', 'yes'); } }
/** * Called when one of the token types that this sniff is listening for * is found. * * The stackPtr variable indicates where in the stack the token was found. * A sniff can acquire information this token, along with all the other * tokens within the stack by first acquiring the token stack: * * <code> * $tokens = $phpcsFile->getTokens(); * echo 'Encountered a '.$tokens[$stackPtr]['type'].' token'; * echo 'token information: '; * print_r($tokens[$stackPtr]); * </code> * * If the sniff discovers an anomaly in the code, they can raise an error * by calling addError() on the PHP_CodeSniffer_File object, specifying an error * message and the position of the offending token: * * <code> * $phpcsFile->addError('Encountered an error', $stackPtr); * </code> * * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the * token was found. * @param int $stackPtr The position in the PHP_CodeSniffer * file's token stack where the token * was found. * * @return void|int Optionally returns a stack pointer. The sniff will not be * called again on the current file until the returned stack * pointer is reached. Return (count($tokens) + 1) to skip * the rest of the file. */ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $className = $phpcsFile->getDeclarationName($stackPtr); if (substr($className, -9) !== 'Interface') { $phpcsFile->addError('Interface name "%s" must be suffixed with "Interface"', $stackPtr, '', [$className]); } }
/** * 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(PHP_CodeSniffer_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(PHP_CodeSniffer_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 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(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $tokens = $phpcsFile->getTokens(); $className = $stackPtr - 1; if ($tokens[$className]['code'] === T_SELF) { if (strtolower($tokens[$className]['content']) !== $tokens[$className]['content']) { $error = 'Must use "self::" for local static member reference; found "' . $tokens[$className]['content'] . '::"'; $phpcsFile->addError($error, $className); return; } } else { if ($tokens[$className]['code'] === T_STRING) { // Make sure this is another class reference. $declarationName = $phpcsFile->getDeclarationName($currScope); if ($declarationName === $tokens[$className]['content']) { // Class name is the same as the current class. $error = 'Must use "self::" for local static member reference'; $phpcsFile->addError($error, $className); return; } } } if ($tokens[$stackPtr - 1]['code'] === T_WHITESPACE) { $found = strlen($tokens[$stackPtr - 1]['content']); $error = "Expected 0 spaces before double colon; {$found} found"; $phpcsFile->addError($error, $className); } if ($tokens[$stackPtr + 1]['code'] === T_WHITESPACE) { $found = strlen($tokens[$stackPtr + 1]['content']); $error = "Expected 0 spaces after double colon; {$found} found"; $phpcsFile->addError($error, $className); } }
/** * Process the return comment of this function comment. * * @param int $commentStart The position in the stack where the comment started. * @param int $commentEnd The position in the stack where the comment ended. * * @return void */ protected function processReturn($commentStart, $commentEnd) { // Skip constructor and destructor. $className = ''; if ($this->_classToken !== null) { $className = $this->currentFile->getDeclarationName($this->_classToken); $className = strtolower(ltrim($className, '_')); } $methodName = strtolower(ltrim($this->_methodName, '_')); $isSpecialMethod = $this->_methodName === '__construct' || $this->_methodName === '__destruct'; if ($isSpecialMethod === false && $methodName !== $className) { // Report missing return tag. if ($this->commentParser->getReturn() === null) { $error = 'Missing @return tag in function comment'; $this->currentFile->addError($error, $commentEnd, 'MissingReturn'); } else { if (trim($this->commentParser->getReturn()->getRawContent()) === '') { $error = '@return tag is empty in function comment'; $errorPos = $commentStart + $this->commentParser->getReturn()->getLine(); $this->currentFile->addError($error, $errorPos, 'EmptyReturn'); } else { if (substr_count($this->commentParser->getReturn()->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) { $error = 'Return comment requires a blank newline after it'; $errorPos = $this->commentParser->getReturn()->getLine() + $commentStart; $this->currentFile->addError($error, $errorPos, 'SpacingAfterReturn'); } } } } }
/** * Process the return comment of this function comment. * * @param int $commentStart The position in the stack where the comment started. * @param int $commentEnd The position in the stack where the comment ended. * * @return void */ protected function processReturn($commentStart, $commentEnd) { // Skip constructor and destructor. $className = ''; if ($this->_classToken !== null) { $className = $this->currentFile->getDeclarationName($this->_classToken); $className = strtolower(ltrim($className, '_')); } $methodName = strtolower(ltrim($this->_methodName, '_')); if ($methodName === '') { $methodName = $this->_methodName; } $isSpecialMethod = $this->_methodName === '__construct' || $this->_methodName === '__destruct'; if ($isSpecialMethod === false && $methodName !== $className) { // Report missing return tag. if ($this->commentParser->getReturn() === null) { $error = 'Missing @return tag in function comment'; $this->currentFile->addError($error, $commentEnd, 'MissingReturn'); } else { if (trim($this->commentParser->getReturn()->getRawContent()) === '') { $error = '@return tag is empty in function comment'; $errorPos = $commentStart + $this->commentParser->getReturn()->getLine(); $this->currentFile->addError($error, $errorPos, 'EmptyReturn'); } } } }
/** * Process the return comment of this function comment. * * @param int $commentStart The position in the stack where the comment started. * @param int $commentEnd The position in the stack where the comment ended. * * @return void */ protected function processReturn($commentStart, $commentEnd) { // Skip constructor and destructor. $className = ''; if ($this->_classToken !== null) { $className = $this->currentFile->getDeclarationName($this->_classToken); $className = strtolower(ltrim($className, '_')); } $methodName = strtolower(ltrim($this->_methodName, '_')); $isSpecialMethod = $this->_methodName === '__construct' || $this->_methodName === '__destruct'; if ($isSpecialMethod === false && $methodName !== $className) { $return = $this->commentParser->getReturn(); if ($return !== null) { $errorPos = $commentStart + $this->commentParser->getReturn()->getLine(); if (trim($return->getRawContent()) === '') { $error = '@return tag is empty in function comment'; $this->currentFile->addError($error, $errorPos, 'EmptyReturn'); return; } $comment = $return->getComment(); $commentWhitespace = $return->getWhitespaceBeforeComment(); if (substr_count($return->getWhitespaceBeforeValue(), $this->currentFile->eolChar) > 0) { $error = 'Data type of return value is missing'; $this->currentFile->addError($error, $errorPos, 'MissingReturnType'); // Treat the value as part of the comment. $comment = $return->getValue() . ' ' . $comment; $commentWhitespace = $return->getWhitespaceBeforeValue(); } else { if ($return->getWhitespaceBeforeValue() !== ' ') { $error = 'Expected 1 space before return type'; $this->currentFile->addError($error, $errorPos, 'SpacingBeforeReturnType'); } } if (strpos($return->getValue(), '$') !== false) { $error = '@return data type must not contain "$"'; $this->currentFile->addError($error, $errorPos, '$InReturnType'); } if (in_array($return->getValue(), array('unknown_type', '<type>', 'type')) === true) { $error = 'Expected a valid @return data type, but found %s'; $data = array($return->getValue()); $this->currentFile->addError($error, $errorPos, 'InvalidReturnType', $data); } if (trim($comment) === '') { $error = 'Missing comment for @return statement'; $this->currentFile->addError($error, $errorPos, 'MissingReturnComment'); } else { if (substr_count($commentWhitespace, $this->currentFile->eolChar) !== 1) { $error = 'Return comment must be on the next line'; $this->currentFile->addError($error, $errorPos, 'ReturnCommentNewLine'); } else { if (substr_count($commentWhitespace, ' ') !== 3) { $error = 'Return comment indentation must be 2 additional spaces'; $this->currentFile->addError($error, $errorPos + 1, 'ParamCommentIndentation'); } } } } } }
/** * 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) { $name = strtolower($phpcsFile->getDeclarationName($stackPtr)); if (in_array($name, $this->newReservedWords) === true) { $error = "[PHP 5.4] {$name} is now a reserved word."; $phpcsFile->addError($error, $stackPtr); } }
/** * 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 (empty($tokens[$stackPtr]['conditions']) === true) { $functionName = $phpcsFile->getDeclarationName($stackPtr); $error = "Consider putting global function \"{$functionName}\" in a static class"; $phpcsFile->addWarning($error, $stackPtr); } }
/** * 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(); if (empty($tokens[$stackPtr]['conditions']) === true) { $functionName = $phpcsFile->getDeclarationName($stackPtr); $error = 'Global functions are not allowed. Put the global function ' . $functionName . ' in a static class'; $phpcsFile->addError($error, $stackPtr); } }
/** * Called when one of the token types that this sniff is listening for * is found. * * The stackPtr variable indicates where in the stack the token was found. * A sniff can acquire information this token, along with all the other * tokens within the stack by first acquiring the token stack: * * <code> * $tokens = $phpcsFile->getTokens(); * echo 'Encountered a '.$tokens[$stackPtr]['type'].' token'; * echo 'token information: '; * print_r($tokens[$stackPtr]); * </code> * * If the sniff discovers an anomaly in the code, they can raise an error * by calling addError() on the PHP_CodeSniffer_File object, specifying an error * message and the position of the offending token: * * <code> * $phpcsFile->addError('Encountered an error', $stackPtr); * </code> * * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the * token was found. * @param int $stackPtr The position in the PHP_CodeSniffer * file's token stack where the token * was found. * * @return void|int Optionally returns a stack pointer. The sniff will not be * called again on the current file until the returned stack * pointer is reached. Return (count($tokens) + 1) to skip * the rest of the file. */ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); if ($tokens[$stackPtr - 2]['code'] === T_ABSTRACT) { $className = $phpcsFile->getDeclarationName($stackPtr); if (substr($className, 0, 8) !== 'Abstract') { $phpcsFile->addError('Abstract class name "%s" must be prefixed with "Abstract"', $stackPtr, '', [$className]); } } }
/** * 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 ($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); 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: '; $error .= $tokens[$stackPtr]['content']; $error .= ' missing opening or closing brace'; $phpcsFile->addWarning($error, $stackPtr); 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) { $phpcsFile->addError($error, $closingBracket); return; } if (rtrim($tokens[$closingBracket + 1]['content']) !== $comment) { $phpcsFile->addError($error, $closingBracket); return; } }
/** * Processes the tokens within the scope. * * @param PHP_CodeSniffer_File $phpcsFile The file being processed. * @param int $stackPtr The position where this token was * found. * @param int $currScope The position of the current scope. * * @return void */ protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) { $methodName = $phpcsFile->getDeclarationName($stackPtr); if ($methodName === null) { // Ignore closures. return; } // Ignore magic methods. $magicPart = strtolower(substr($methodName, 2)); if (in_array($magicPart, array_merge($this->magicMethods, $this->methodsDoubleUnderscore)) !== false) { return; } $testName = ltrim($methodName, '_'); if (PHP_CodeSniffer::isCamelCaps($testName, false, true, false) === false) { $error = 'Method name "%s" is not in camel caps format'; $className = $phpcsFile->getDeclarationName($currScope); $errorData = array($className . '::' . $methodName); $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $errorData); } }
/** * 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 (empty($tokens[$stackPtr]['conditions']) === true) { $functionName = $phpcsFile->getDeclarationName($stackPtr); // Special exception for __autoload as it needs to be global. if ($functionName !== '__autoload') { $error = "Consider putting global function \"{$functionName}\" in a static class"; $phpcsFile->addWarning($error, $stackPtr); } } }