diff --git a/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php index feefc43c70..055afe5645 100644 --- a/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php +++ b/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -1,6 +1,6 @@ => + */ + private $methodsDoubleUnderscore = array( + 'doRequest' => 'SoapClient', + 'getFunctions' => 'SoapClient', + 'getLastRequest' => 'SoapClient', + 'getLastRequestHeaders' => 'SoapClient', + 'getLastResponse' => 'SoapClient', + 'getLastResponseHeaders' => 'SoapClient', + 'getTypes' => 'SoapClient', + 'setCookie' => 'SoapClient', + 'setLocation' => 'SoapClient', + 'setSoapHeaders' => 'SoapClient', + 'soapCall' => 'SoapClient', ); /** @@ -46,13 +56,41 @@ class WordPress_Sniffs_NamingConventions_ValidFunctionNameSniff extends PEAR_Sni protected function processTokenOutsideScope( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { $functionName = $phpcsFile->getDeclarationName( $stackPtr ); + if ( ! isset( $functionName ) ) { + // Ignore closures. + return; + } + + if ( '' === ltrim( $functionName, '_' ) ) { + // Ignore special functions. + return; + } + + // Is this a magic function ? I.e., it is prefixed with "__" ? + // Outside class scope this basically just means __autoload(). + if ( 0 === strpos( $functionName, '__' ) ) { + $magicPart = strtolower( substr( $functionName, 2 ) ); + if ( ! isset( $this->magicFunctions[ $magicPart ] ) ) { + $error = 'Function name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; + $errorData = array( $functionName ); + $phpcsFile->addError( $error, $stackPtr, 'FunctionDoubleUnderscore', $errorData ); + } + + return; + } + if ( strtolower( $functionName ) !== $functionName ) { $suggested = preg_replace( '/([A-Z])/', '_$1', $functionName ); $suggested = strtolower( $suggested ); $suggested = str_replace( '__', '_', $suggested ); - - $error = "Function name \"$functionName\" is in camel caps format, try '{$suggested}'"; - $phpcsFile->addError( $error, $stackPtr, 'FunctionNameInvalid' ); + $suggested = trim( $suggested, '_' ); + + $error = 'Function name "%s" is not in snake case format, try "%s"'; + $errorData = array( + $functionName, + $suggested, + ); + $phpcsFile->addError( $error, $stackPtr, 'FunctionNameInvalid', $errorData ); } } // end processTokenOutsideScope() @@ -68,17 +106,17 @@ protected function processTokenOutsideScope( PHP_CodeSniffer_File $phpcsFile, $s * @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 ( 0 === strpos( $methodName, '__' ) ) { - $magicPart = substr( $methodName, 2 ); - if ( false === in_array( $magicPart, $this->_magicMethods, true ) ) { - $error = "Method name \"$className::$methodName\" is invalid; only PHP magic methods should be prefixed with a double underscore"; - $phpcsFile->addError( $error, $stackPtr, 'MethodDoubleUnderscore' ); - } + if ( ! isset( $methodName ) ) { + // Ignore closures. + return; + } + $className = $phpcsFile->getDeclarationName( $currScope ); + + // Ignore special functions. + if ( '' === ltrim( $methodName, '_' ) ) { return; } @@ -92,38 +130,40 @@ protected function processTokenWithinScope( PHP_CodeSniffer_File $phpcsFile, $st return; } - // If this is a child class, it may have to use camelCase. - if ( $phpcsFile->findExtendedClassName( $currScope ) || $this->findImplementedInterfaceName( $currScope, $phpcsFile ) ) { + $extended = $phpcsFile->findExtendedClassName( $currScope ); + $interface = $this->findImplementedInterfaceName( $currScope, $phpcsFile ); + + // If this is a child class or interface implementation, it may have to use camelCase or double underscores. + if ( $extended || $interface ) { return; } - $methodProps = $phpcsFile->getMethodProperties( $stackPtr ); - $scope = $methodProps['scope']; - $scopeSpecified = $methodProps['scope_specified']; - - if ( 'private' === $methodProps['scope'] ) { - $isPublic = false; - } else { - $isPublic = true; - } + // Is this a magic method ? I.e. is it prefixed with "__" ? + if ( 0 === strpos( $methodName, '__' ) ) { + $magicPart = strtolower( substr( $methodName, 2 ) ); + if ( ! isset( $this->magicMethods[ $magicPart ] ) && ! isset( $this->methodsDoubleUnderscore[ $magicPart ] ) ) { + $error = 'Method name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; + $errorData = array( $className . '::' . $methodName ); + $phpcsFile->addError( $error, $stackPtr, 'MethodDoubleUnderscore', $errorData ); + } - // 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 ( false === $scopeSpecified && '_' === $methodName{0} ) { - $testMethodName = substr( $methodName, 1 ); + return; } - if ( strtolower( $testMethodName ) !== $testMethodName ) { + // Check for all lowercase. + if ( strtolower( $methodName ) !== $methodName ) { $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' ); + $suggested = trim( $suggested, '_' ); + + $error = 'Method name "%s" in class %s is not in snake case format, try "%s"'; + $errorData = array( + $methodName, + $className, + $suggested, + ); + $phpcsFile->addError( $error, $stackPtr, 'MethodNameInvalid', $errorData ); } } // end processTokenWithinScope() diff --git a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc index d66445aeee..176bcaeea3 100644 --- a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc +++ b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc @@ -6,7 +6,7 @@ function my_template_tags() {} // Good function _my_template_tags() {} // OK -function __my_template_tags() {} // OK +function __my_template_tags() {} // Bad class My_Plugin { @@ -21,6 +21,9 @@ class My_Plugin { public function __invoke() {} // OK } +/** + * Verify that CamelCase is not checked for extended classes or interfaces. + */ class Test extends WP_UnitTestCase { public function setUp() {} // OK @@ -28,10 +31,74 @@ class Test extends WP_UnitTestCase { class Foo implements ArrayAccess { function offsetSet( $key, $value ) {} // OK - function offsetUnset( $key ) {} // OK - function offsetExists( $key ) {} // OK - function offsetGet( $key ) {} // OK } + + +/** + * Verify all PHP supported magic methods. + */ +class Its_A_Kind_Of_Magic { + function __construct() {} // Ok. + function __destruct() {} // Ok. + function __call() {} // Ok. + function __callStatic() {} // Ok. + function __get() {} // Ok. + function __set() {} // Ok. + function __isset() {} // Ok. + function __unset() {} // Ok. + function __sleep() {} // Ok. + function __wakeup() {} // Ok. + function __toString() {} // Ok. + function __set_state() {} // Ok. + function __clone() {} // Ok. + function __invoke() {} // Ok. + function __debugInfo() {} // Ok. +} + +/** + * Verify SoapClient magic methods. + */ +class My_Soap extends SoapClient { + public function __doRequest() {} // Ok. + public function __getFunctions() {} // Ok. + public function __getLastRequest() {} // Ok. + public function __getLastRequestHeaders() {} // Ok. + public function __getLastResponse() {} // Ok. + public function __getLastResponseHeaders() {} // Ok. + public function __getTypes() {} // Ok. + public function __setCookie() {} // Ok. + public function __setLocation() {} // Ok. + public function __setSoapHeaders() {} // Ok. + public function __soapCall() {} // Ok. +} + +class My_Soap { + public function __doRequest() {} // Bad. + private function __getFunctions() {} // Bad. + protected function __getLastRequest() {} // Bad. + public function __getLastRequestHeaders() {} // Bad. + public function __getLastResponse() {} // Bad. + public function __getLastResponseHeaders() {} // Bad. + public function __getTypes() {} // Bad. + public function __setCookie() {} // Bad. + public function __setLocation() {} // Bad. + public function __setSoapHeaders() {} // Bad. + public function __soapCall() {} // Bad. +} + +class My_Soap extends somethingElse { + public function __doRequest() {} // Ok - as somethingElse might be extended from SoapClient again. + private function __getFunctions() {} // Ok. + protected function __getLastRequest() {} // Ok. + public function __getLastRequestHeaders() {} // Ok. + public function __getLastResponse() {} // Ok. + public function __getLastResponseHeaders() {} // Ok. + public function __getTypes() {} // Ok. + public function __setCookie() {} // Ok. + public function __setLocation() {} // Ok. + public function __setSoapHeaders() {} // Ok. + public function __soapCall() {} // Ok. +} diff --git a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php index d33b7ab8e2..c11cce2650 100644 --- a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php @@ -35,8 +35,20 @@ class WordPress_Tests_NamingConventions_ValidFunctionNameUnitTest extends Abstra public function getErrorList() { return array( 3 => 1, + 9 => 1, 13 => 1, 15 => 1, + 79 => 1, + 80 => 1, + 81 => 1, + 82 => 1, + 83 => 1, + 84 => 1, + 85 => 1, + 86 => 1, + 87 => 1, + 88 => 1, + 89 => 1, ); } // end getErrorList()