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

Conversation

jrfnl
Copy link
Member

@jrfnl jrfnl commented Jan 22, 2017

Now #795 has been merged, it was possible to create an abstract function parameter class which builds upon the logic for determining function calls already contained in WordPress_AbstractFunctionRestrictionsSniff.

This PR introduces the abstract and a number of related utility functions.

Utility functions

I initially introduced these utility functions in the PHPCompatibility standard were they are also individually unit tested.
They have been in use there for a number of months and proven useful & accurate.

The functions are:

  • does_function_call_have_parameters( $stackPtr ) - returns bool
  • get_function_call_parameter_count( $stackPtr ) - returns int
  • get_function_call_parameters( $stackPtr ) - returns array, key: 1-indexed parameter position, value: array with start - int stack pointer of param start position, end - int stack pointer of param end position and raw - trimmed raw content for the passed parameter
  • get_function_call_parameter( $stackPtr, $param_offset ) - returns array or false if no parameter was found at the specified offset

The functions expect to be passed a T_STRING stack pointer to a function call.

And while the function name might not indicate this, each of these functions can also be used with array stack pointers (T_ARRAY, T_OPEN_SHORT_ARRAY). This is a feature which was introduced after the initial naming & merge of the functions and the function documentation reflects this.

It is my intention to introduce these functions upstream in PHPCS itself at some point in the future.

New WordPress_AbstractFunctionParameterSniff class

The new WordPress_AbstractFunctionParameterSniff extends the WordPress_AbstractFunctionRestrictionsSniff class for the yes/no function call determination, retrieves the parameters used by the targetted function call using the new utility functions and then passes processing on to the child class.

The child class is expected to implement a process_parameters() method to handle the actual testing of parameters.

Optionally a process_no_parameters() method can be added to the child class to handle cases where parameters are expected, but not found.

The abstract expects a $target_functions array, but is without opinion about how that array should look, except for the requirement that the top level array keys are the function names.
Further requirements of the array are not needed as the processing of the parameters is done in the child class, allowing for great flexibility in potential use of this class.

Start using the abstract

As a last step - and to prove that the same functionality is maintained, and in some cases improved by the new abstract - I've refactored three existing sniffs which were prime candidates (duplicate logic and such) to start using the new WordPress_AbstractFunctionParameterSniff.

Fixes #745

I initially introduced these functions in the PHPCompatibility standard were they are also individually unit tested.
They have been in use there for a number of months and proven useful & accurate.
This extends the `WordPress_AbstractFunctionRestrictionsSniff` class for the yes/no function call determination, retrieves the parameters used in a function call and then passes processing on to the child class.

The child class is expected to implement a `process_parameters()` method to handle the actual testing of parameters.

Optionally a `process_no_parameters()` method can be added to the child class to handle cases where parameters are expected, but not found.

The abstract expects a `$target_functions` array, but is without opinion about how that array should look, except for the requirement that the top level array keys are the function names.
Further requirements of the array are not needed as the processing of the parameters is done in the child class, allowing for great flexibility in potential use of this class.
Copy link
Member

@grappler grappler left a comment

Choose a reason for hiding this comment

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

Thank you very much for this.This will be a great help! This fixes #745

One thought that can to me is what would happen if one of the parameters that we were checking was an anonymous function. A common place where anonymous functions are used are hooks.

*
* @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

// 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.

* @return void
*/
public function process_no_parameters( $stackPtr, $group_name, $matched_content ) {
$this->phpcsFile->addError( 'Missing arguments to %s.', $stackPtr, 'MissingArguments', array( $matched_content ) );
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need this as if there are no parameters then there will be a PHP error. #770 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

We do need this.
By far not all function calls have required parameters, so a function call without parameters can be perfectly valid code, no syntax errors, but might still need to throw an error under certain rules.

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.

The three functions that we are checking have required parameters. in_array, array_search and array_keys. Sorry I did not mean generally, just for this check.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh sorry, I was thinking the comment was about the abstract.

And true, in this case "no parameters" would also result in a PHP error, though not one which would be picked up by the Generic.PHP.Syntax sniff (not a parse error).
I added the method and the error message in this case to maintain backward compatibility. The same message was already being thrown by this sniff and I saw no compelling reason to remove it.

@jrfnl
Copy link
Member Author

jrfnl commented Jan 22, 2017

One thought that came to me is what would happen if one of the parameters that we were checking was an anonymous function. A common place where anonymous functions are used are hooks.

That works just fine, you will receive the start and end parameter and the raw content, no problem ;-)

More in-depth: The functions check for parenthesis at the correct nesting level and will skip over everything which is not at the right nesting level, so an anonymous function as a parameter will be returned and treated as one parameter.

And before you ask: The methods don't deal with parameters passed to anonymous functions, but don't need to either as that is a function declaration and you can use the PHPCS native getMethodParameters() for that.

*
* @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.

Copy link
Contributor

@JDGrimes JDGrimes left a comment

Choose a reason for hiding this comment

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

💯 I've created similar functions before, though not as robust, and I know that they can be very useful.

*
* @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.

@GaryJones GaryJones merged commit c418017 into develop Jan 23, 2017
@GaryJones GaryJones deleted the feature/function-parameter-abstract branch January 23, 2017 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants