Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 92 additions & 52 deletions WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php
Original file line number Diff line number Diff line change
@@ -1,37 +1,47 @@
<?php
/**
* Enforces WordPress function name format, based upon Squiz code.
* Enforces WordPress function name format.
*
* @category PHP
* @package PHP_CodeSniffer
* @author John Godley <john@urbangiraffe.com>
*/

/**
* Enforces WordPress function name format.
* Enforces WordPress function name and method name format, based upon Squiz code.
*
* @link https://make.wordpress.org/core/handbook/coding-standards/php/#naming-conventions
*
* Last synced with parent class July 2016 at commit 916b09a.
* @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/NamingConventions/ValidFunctionNameSniff.php
*
* @category PHP
* @package PHP_CodeSniffer
* @author John Godley <john@urbangiraffe.com>
*/
class WordPress_Sniffs_NamingConventions_ValidFunctionNameSniff extends PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff {

private $_magicMethods = array(
'construct',
'destruct',
'call',
'callStatic',
'get',
'set',
'isset',
'unset',
'sleep',
'wakeup',
'toString',
'set_state',
'clone',
'invoke',
'debugInfo',
/**
* Additional double underscore prefixed methods specific to certain PHP native extensions.
*
* Currently only handles the SoapClient Extension.
*
* @link http://php.net/manual/en/class.soapclient.php
*
* @var array <string method name> => <string class name>
*/
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',
);

/**
Expand All @@ -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()
Expand All @@ -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;
}

Expand All @@ -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()
Expand Down
75 changes: 71 additions & 4 deletions WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -21,17 +21,84 @@ 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
}

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.
}
12 changes: 12 additions & 0 deletions WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down