Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function call parameter related utility functions and abstract class #815

Merged
merged 4 commits into from
Jan 23, 2017
Merged
Show file tree
Hide file tree
Changes from 3 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
104 changes: 104 additions & 0 deletions WordPress/AbstractFunctionParameterSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php
/**
* WordPress Coding Standard.
*
* @package WPCS\WordPressCodingStandards
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
* @license https://opensource.org/licenses/MIT MIT
*/

/**
* Advises about parameters used in function calls.
*
* @package WPCS\WordPressCodingStandards
*
* @since 0.11.0
*/
abstract class WordPress_AbstractFunctionParameterSniff extends WordPress_AbstractFunctionRestrictionsSniff {

/**
* The group name for this group of functions.
*
* Intended to be overruled in the child class.
*
* @var string
*/
protected $group_name = 'restricted_parameters';

/**
* Functions this sniff is looking for. Should be defined in the child class.
*
* @var array The only requirement for this array is that the top level
* array keys are the names of the functions you're looking for.
* Other than that, the array can have arbitrary content depending on your needs.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the text not need to be all on the same height? e.g.

	/**
	 * @var array The only requirement for this array is that the top level
	 *            array keys are the names of the functions you're looking for.
	 *            Other than that, the array can have arbitrary content depending
	 *            on your needs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

*/
protected $target_functions = array();

/**
* Groups of function to restrict.
*
* @return array
*/
public function getGroups() {
if ( empty( $this->target_functions ) ) {
return array();
}

return array(
$this->group_name => array(
'functions' => array_keys( $this->target_functions ),
),
);
}

/**
* Process a matched token.
*
* @param int $stackPtr The position of the current token in the stack.
* @param array $group_name The name of the group which was matched.
* @param string $matched_content The token content (function name) which was matched.
*
* @return void
*/
public function process_matched_token( $stackPtr, $group_name, $matched_content ) {

$parameters = $this->get_function_call_parameters( $stackPtr );

if ( empty( $parameters ) ) {
$this->process_no_parameters( $stackPtr, $group_name, $matched_content );
} else {
$this->process_parameters( $stackPtr, $group_name, $matched_content, $parameters );
}
}

/**
* Process the parameters of a matched function.
*
* This method has to be made concrete in child classes.
*
* @param int $stackPtr The position of the current token in the stack.
* @param array $group_name The name of the group which was matched.
* @param string $matched_content The token content (function name) which was matched.
* @param array $parameters Array with information about the parameters.
*
* @return void
*/
abstract public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters );

/**
* Process the function if no parameters were found.
*
* Default to doing nothing. Can be overloaded in child classes to handle functions
* were parameters are expected, but none found.
*
* @param int $stackPtr The position of the current token in the stack.
* @param array $group_name The name of the group which was matched.
* @param string $matched_content The token content (function name) which was matched.
*
* @return void
*/
public function process_no_parameters( $stackPtr, $group_name, $matched_content ) {
return;
}

} // End class.
220 changes: 220 additions & 0 deletions WordPress/Sniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -1291,4 +1291,224 @@ protected function get_interpolated_variables( $string ) {
return $variables;
}

/**
* Checks if a function call has parameters.
*
* Expects to be passed the T_STRING stack pointer for the function call.
* If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
*
* Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, it
* will detect whether the array has values or is empty.
*
* @link https://github.com/wimg/PHPCompatibility/issues/120
* @link https://github.com/wimg/PHPCompatibility/issues/152
*
* @since 0.11.0
*
* @param int $stackPtr The position of the function call token.
*
* @return bool
*/
public function does_function_call_have_parameters( $stackPtr ) {

// Check for the existence of the token.
if ( false === isset( $this->tokens[ $stackPtr ] ) ) {
return false;
}

// Is this one of the tokens this function handles ?
if ( false === in_array( $this->tokens[ $stackPtr ]['code'], array( T_STRING, T_ARRAY, T_OPEN_SHORT_ARRAY ), true ) ) {
return false;
}

$next_non_empty = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true );

// Deal with short array syntax.
if ( 'T_OPEN_SHORT_ARRAY' === $this->tokens[ $stackPtr ]['type'] ) {
if ( false === isset( $this->tokens[ $stackPtr ]['bracket_closer'] ) ) {
return false;
}

if ( $next_non_empty === $this->tokens[ $stackPtr ]['bracket_closer'] ) {
// No parameters.
return false;
} else {
return true;
}
}

// Deal with function calls & long arrays.
// Next non-empty token should be the open parenthesis.
if ( false === $next_non_empty && T_OPEN_PARENTHESIS !== $this->tokens[ $next_non_empty ]['code'] ) {
return false;
}

if ( false === isset( $this->tokens[ $next_non_empty ]['parenthesis_closer'] ) ) {
return false;
}

$close_parenthesis = $this->tokens[ $next_non_empty ]['parenthesis_closer'];
$next_next_non_empty = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $next_non_empty + 1 ), ( $close_parenthesis + 1 ), true );

if ( $next_next_non_empty === $close_parenthesis ) {
// No parameters.
return false;
}

return true;
}

/**
* Count the number of parameters a function call has been passed.
*
* Expects to be passed the T_STRING stack pointer for the function call.
* If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
*
* Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
* it will return the number of values in the array.
*
* @link https://github.com/wimg/PHPCompatibility/issues/111
* @link https://github.com/wimg/PHPCompatibility/issues/114
* @link https://github.com/wimg/PHPCompatibility/issues/151
*
* @since 0.11.0
*
* @param int $stackPtr The position of the function call token.
*
* @return int
*/
public function get_function_call_parameter_count( $stackPtr ) {
if ( false === $this->does_function_call_have_parameters( $stackPtr ) ) {
return 0;
}

return count( $this->get_function_call_parameters( $stackPtr ) );
}

/**
* Get information on all parameters passed to a function call.
*
* Expects to be passed the T_STRING stack pointer for the function call.
* If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
*
* Will return an multi-dimentional array with the start token pointer, end token
* pointer and raw parameter value for all parameters. Index will be 1-based.
* If no parameters are found, will return an empty array.
*
* Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
* it will tokenize the values / key/value pairs contained in the array call.
*
* @since 0.11.0
*
* @param int $stackPtr The position of the function call token.
*
* @return array
Copy link
Member

@grappler grappler Jan 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we document the output too? Perhaps with something like:

	/**
	 * @return array {
	 *      Array of parameters. Empty array if there are no parameters.
 	 *
 	 *     @type int $position Position of the parameter. {
	 *         @type int $start Start position of the call token.
	 *         @type int $end  End position of the call token.
	 *         @type int $raw  Raw parameter content.
	 *     }
	 * }

or

	/**
	 * @return array {
	 *      Array of parameters. Empty array if there are no parameters.
 	 *
 	 *     @type int $position => { Position of the parameter.
	 *         @type int $start Start position of the call token.
	 *         @type int $end  End position of the call token.
	 *         @type int $raw  Raw parameter content.
	 *     }
	 * }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It already was documented a bit further up in the function documentation, but I've adjusted the format to a variant of what you suggested.

*/
public function get_function_call_parameters( $stackPtr ) {
if ( false === $this->does_function_call_have_parameters( $stackPtr ) ) {
return array();
}

/*
* Ok, we know we have a T_STRING, T_ARRAY or T_OPEN_SHORT_ARRAY with parameters
* and valid open & close brackets/parenthesis.
*/

// Mark the beginning and end tokens.
if ( 'T_OPEN_SHORT_ARRAY' === $this->tokens[ $stackPtr ]['type'] ) {
$opener = $stackPtr;
$closer = $this->tokens[ $stackPtr ]['bracket_closer'];

$nestedParenthesisCount = 0;
} else {
$opener = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true );
$closer = $this->tokens[ $opener ]['parenthesis_closer'];

$nestedParenthesisCount = 1;
}

// Which nesting level is the one we are interested in ?
if ( isset( $this->tokens[ $opener ]['nested_parenthesis'] ) ) {
$nestedParenthesisCount += count( $this->tokens[ $opener ]['nested_parenthesis'] );
}

$parameters = array();
$next_comma = $opener;
$param_start = ( $opener + 1 );
$cnt = 1;
while ( $next_comma = $this->phpcsFile->findNext( array( T_COMMA, $this->tokens[ $closer ]['code'], T_OPEN_SHORT_ARRAY ), ( $next_comma + 1 ), ( $closer + 1 ) ) ) {
// Ignore anything within short array definition brackets.
if ( 'T_OPEN_SHORT_ARRAY' === $this->tokens[ $next_comma ]['type']
&& ( isset( $this->tokens[ $next_comma ]['bracket_opener'] )
&& $this->tokens[ $next_comma ]['bracket_opener'] === $next_comma )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why there is an extra tab before the code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes: nested parenthesis.

&& isset( $this->tokens[ $next_comma ]['bracket_closer'] )
) {
// Skip forward to the end of the short array definition.
$next_comma = $this->tokens[ $next_comma ]['bracket_closer'];
continue;
}

// Ignore comma's at a lower nesting level.
if ( T_COMMA === $this->tokens[ $next_comma ]['code']
&& isset( $this->tokens[ $next_comma ]['nested_parenthesis'] )
&& count( $this->tokens[ $next_comma ]['nested_parenthesis'] ) !== $nestedParenthesisCount
) {
continue;
}

// Ignore closing parenthesis/bracket if not 'ours'.
if ( $this->tokens[ $next_comma ]['type'] === $this->tokens[ $closer ]['type'] && $next_comma !== $closer ) {
continue;
}

// Ok, we've reached the end of the parameter.
$parameters[ $cnt ]['start'] = $param_start;
$parameters[ $cnt ]['end'] = ( $next_comma - 1 );
$parameters[ $cnt ]['raw'] = trim( $this->phpcsFile->getTokensAsString( $param_start, ( $next_comma - $param_start ) ) );

/*
* Check if there are more tokens before the closing parenthesis.
* Prevents code like the following from setting a third parameter:
* functionCall( $param1, $param2, );
*/
$has_next_param = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $next_comma + 1 ), $closer, true, null, true );
if ( false === $has_next_param ) {
break;
}

// Prepare for the next parameter.
$param_start = ( $next_comma + 1 );
$cnt++;
} // End while().

return $parameters;
}

/**
* Get information on a specific parameter passed to a function call.
*
* Expects to be passed the T_STRING stack pointer for the function call.
* If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
*
* Will return a array with the start token pointer, end token pointer and the raw value
* of the parameter at a specific offset.
* If the specified parameter is not found, will return false.
*
* @since 0.11.0
*
* @param int $stackPtr The position of the function call token.
* @param int $param_offset The 1-based index position of the parameter to retrieve.
*
* @return array|false
*/
public function get_function_call_parameter( $stackPtr, $param_offset ) {
$parameters = $this->get_function_call_parameters( $stackPtr );

if ( false === isset( $parameters[ $param_offset ] ) ) {
return false;
}

return $parameters[ $param_offset ];
}

}
Loading