Skip to content

Commit

Permalink
Merge pull request #623 from WordPress-Coding-Standards/WPCS/feature/…
Browse files Browse the repository at this point in the history
…issue-507-soap-methods

Update `ValidFunctionNames` Sniff to allow for non-valid names in extended classes.
  • Loading branch information
JDGrimes authored Jul 25, 2016
2 parents 8058cff + 833feea commit 4bf32d1
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 56 deletions.
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 <[email protected]>
*/

/**
* 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 <[email protected]>
*/
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

0 comments on commit 4bf32d1

Please sign in to comment.