1<?php 2/** 3 * PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @author Marc McIntyre <mmcintyre@squiz.net> 11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 12 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 13 * @link http://pear.php.net/package/PHP_CodeSniffer 14 */ 15 16if (class_exists('PHP_CodeSniffer_Standards_AbstractScopeSniff', true) === false) { 17 throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractScopeSniff not found'); 18} 19 20/** 21 * PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff. 22 * 23 * Ensures method names are correct depending on whether they are public 24 * or private, and that functions are named correctly. 25 * 26 * @category PHP 27 * @package PHP_CodeSniffer 28 * @author Greg Sherwood <gsherwood@squiz.net> 29 * @author Marc McIntyre <mmcintyre@squiz.net> 30 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 31 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 32 * @version Release: @package_version@ 33 * @link http://pear.php.net/package/PHP_CodeSniffer 34 */ 35class PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff extends PHP_CodeSniffer_Standards_AbstractScopeSniff 36{ 37 38 /** 39 * A list of all PHP magic methods. 40 * 41 * @var array 42 */ 43 protected $magicMethods = array( 44 'construct' => true, 45 'destruct' => true, 46 'call' => true, 47 'callstatic' => true, 48 'get' => true, 49 'set' => true, 50 'isset' => true, 51 'unset' => true, 52 'sleep' => true, 53 'wakeup' => true, 54 'tostring' => true, 55 'set_state' => true, 56 'clone' => true, 57 'invoke' => true, 58 'debuginfo' => true, 59 ); 60 61 /** 62 * A list of all PHP magic functions. 63 * 64 * @var array 65 */ 66 protected $magicFunctions = array('autoload' => true); 67 68 69 /** 70 * Constructs a PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff. 71 */ 72 public function __construct() 73 { 74 parent::__construct(array(T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT), array(T_FUNCTION), true); 75 76 }//end __construct() 77 78 79 /** 80 * Processes the tokens within the scope. 81 * 82 * @param PHP_CodeSniffer_File $phpcsFile The file being processed. 83 * @param int $stackPtr The position where this token was 84 * found. 85 * @param int $currScope The position of the current scope. 86 * 87 * @return void 88 */ 89 protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope) 90 { 91 $methodName = $phpcsFile->getDeclarationName($stackPtr); 92 if ($methodName === null) { 93 // Ignore closures. 94 return; 95 } 96 97 $className = $phpcsFile->getDeclarationName($currScope); 98 $errorData = array($className.'::'.$methodName); 99 100 // Is this a magic method. i.e., is prefixed with "__" ? 101 if (preg_match('|^__[^_]|', $methodName) !== 0) { 102 $magicPart = strtolower(substr($methodName, 2)); 103 if (isset($this->magicMethods[$magicPart]) === false) { 104 $error = 'Method name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; 105 $phpcsFile->addError($error, $stackPtr, 'MethodDoubleUnderscore', $errorData); 106 } 107 108 return; 109 } 110 111 // PHP4 constructors are allowed to break our rules. 112 if ($methodName === $className) { 113 return; 114 } 115 116 // PHP4 destructors are allowed to break our rules. 117 if ($methodName === '_'.$className) { 118 return; 119 } 120 121 $methodProps = $phpcsFile->getMethodProperties($stackPtr); 122 $scope = $methodProps['scope']; 123 $scopeSpecified = $methodProps['scope_specified']; 124 125 if ($methodProps['scope'] === 'private') { 126 $isPublic = false; 127 } else { 128 $isPublic = true; 129 } 130 131 // If it's a private method, it must have an underscore on the front. 132 if ($isPublic === false) { 133 if ($methodName{0} !== '_') { 134 $error = 'Private method name "%s" must be prefixed with an underscore'; 135 $phpcsFile->addError($error, $stackPtr, 'PrivateNoUnderscore', $errorData); 136 $phpcsFile->recordMetric($stackPtr, 'Private method prefixed with underscore', 'no'); 137 return; 138 } else { 139 $phpcsFile->recordMetric($stackPtr, 'Private method prefixed with underscore', 'yes'); 140 } 141 } 142 143 // If it's not a private method, it must not have an underscore on the front. 144 if ($isPublic === true && $scopeSpecified === true && $methodName{0} === '_') { 145 $error = '%s method name "%s" must not be prefixed with an underscore'; 146 $data = array( 147 ucfirst($scope), 148 $errorData[0], 149 ); 150 $phpcsFile->addError($error, $stackPtr, 'PublicUnderscore', $data); 151 return; 152 } 153 154 // If the scope was specified on the method, then the method must be 155 // camel caps and an underscore should be checked for. If it wasn't 156 // specified, treat it like a public method and remove the underscore 157 // prefix if there is one because we cant determine if it is private or 158 // public. 159 $testMethodName = $methodName; 160 if ($scopeSpecified === false && $methodName{0} === '_') { 161 $testMethodName = substr($methodName, 1); 162 } 163 164 if (PHP_CodeSniffer::isCamelCaps($testMethodName, false, $isPublic, false) === false) { 165 if ($scopeSpecified === true) { 166 $error = '%s method name "%s" is not in camel caps format'; 167 $data = array( 168 ucfirst($scope), 169 $errorData[0], 170 ); 171 $phpcsFile->addError($error, $stackPtr, 'ScopeNotCamelCaps', $data); 172 } else { 173 $error = 'Method name "%s" is not in camel caps format'; 174 $phpcsFile->addError($error, $stackPtr, 'NotCamelCaps', $errorData); 175 } 176 177 return; 178 } 179 180 }//end processTokenWithinScope() 181 182 183 /** 184 * Processes the tokens outside the scope. 185 * 186 * @param PHP_CodeSniffer_File $phpcsFile The file being processed. 187 * @param int $stackPtr The position where this token was 188 * found. 189 * 190 * @return void 191 */ 192 protected function processTokenOutsideScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 193 { 194 $functionName = $phpcsFile->getDeclarationName($stackPtr); 195 if ($functionName === null) { 196 // Ignore closures. 197 return; 198 } 199 200 if (ltrim($functionName, '_') === '') { 201 // Ignore special functions. 202 return; 203 } 204 205 $errorData = array($functionName); 206 207 // Is this a magic function. i.e., it is prefixed with "__". 208 if (preg_match('|^__[^_]|', $functionName) !== 0) { 209 $magicPart = strtolower(substr($functionName, 2)); 210 if (isset($this->magicFunctions[$magicPart]) === false) { 211 $error = 'Function name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; 212 $phpcsFile->addError($error, $stackPtr, 'FunctionDoubleUnderscore', $errorData); 213 } 214 215 return; 216 } 217 218 // Function names can be in two parts; the package name and 219 // the function name. 220 $packagePart = ''; 221 $camelCapsPart = ''; 222 $underscorePos = strrpos($functionName, '_'); 223 if ($underscorePos === false) { 224 $camelCapsPart = $functionName; 225 } else { 226 $packagePart = substr($functionName, 0, $underscorePos); 227 $camelCapsPart = substr($functionName, ($underscorePos + 1)); 228 229 // We don't care about _'s on the front. 230 $packagePart = ltrim($packagePart, '_'); 231 } 232 233 // If it has a package part, make sure the first letter is a capital. 234 if ($packagePart !== '') { 235 if ($functionName{0} === '_') { 236 $error = 'Function name "%s" is invalid; only private methods should be prefixed with an underscore'; 237 $phpcsFile->addError($error, $stackPtr, 'FunctionUnderscore', $errorData); 238 return; 239 } 240 241 if ($functionName{0} !== strtoupper($functionName{0})) { 242 $error = 'Function name "%s" is prefixed with a package name but does not begin with a capital letter'; 243 $phpcsFile->addError($error, $stackPtr, 'FunctionNoCapital', $errorData); 244 return; 245 } 246 } 247 248 // If it doesn't have a camel caps part, it's not valid. 249 if (trim($camelCapsPart) === '') { 250 $error = 'Function name "%s" is not valid; name appears incomplete'; 251 $phpcsFile->addError($error, $stackPtr, 'FunctionInvalid', $errorData); 252 return; 253 } 254 255 $validName = true; 256 $newPackagePart = $packagePart; 257 $newCamelCapsPart = $camelCapsPart; 258 259 // Every function must have a camel caps part, so check that first. 260 if (PHP_CodeSniffer::isCamelCaps($camelCapsPart, false, true, false) === false) { 261 $validName = false; 262 $newCamelCapsPart = strtolower($camelCapsPart{0}).substr($camelCapsPart, 1); 263 } 264 265 if ($packagePart !== '') { 266 // Check that each new word starts with a capital. 267 $nameBits = explode('_', $packagePart); 268 foreach ($nameBits as $bit) { 269 if ($bit{0} !== strtoupper($bit{0})) { 270 $newPackagePart = ''; 271 foreach ($nameBits as $bit) { 272 $newPackagePart .= strtoupper($bit{0}).substr($bit, 1).'_'; 273 } 274 275 $validName = false; 276 break; 277 } 278 } 279 } 280 281 if ($validName === false) { 282 $newName = rtrim($newPackagePart, '_').'_'.$newCamelCapsPart; 283 if ($newPackagePart === '') { 284 $newName = $newCamelCapsPart; 285 } else { 286 $newName = rtrim($newPackagePart, '_').'_'.$newCamelCapsPart; 287 } 288 289 $error = 'Function name "%s" is invalid; consider "%s" instead'; 290 $data = $errorData; 291 $data[] = $newName; 292 $phpcsFile->addError($error, $stackPtr, 'FunctionNameInvalid', $data); 293 } 294 295 }//end processTokenOutsideScope() 296 297 298}//end class 299