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

Full property visibility/staticness review & fixing of custom property merging #841

Merged
merged 15 commits into from Feb 25, 2017

Conversation

jrfnl
Copy link
Member

@jrfnl jrfnl commented Feb 11, 2017

Primarily, this PR is a review of all sniff class properties and their handling.

I've broken up the PR into a number of isolated commits for easier review & having a clearer history for the various affected files.

tl;dr

This PR fixes/addresses a number of issues:

Pre-amble

Sniff properties which are public can be overruled via a custom ruleset.
Sniff properties should therefore only be public if that is the intended behaviour. Otherwise a more restrictive visibility should be used.

Changes to the visibility of properties may break backward compatibility for some users, but only if they were doing_it_wrong in the first place, so I'm not concerned about this.

Properties set via the ruleset will be handled by PHPCS as follows:

  • Properties with the type=array attribute - will be converted to arrays. PHPCS has the ability to handle key/value arrays if the property is set using the following syntax: key=>value,key2=>value2
  • All other properties will be treated as strings.
  • If the string value of a property is true or false, the property value will be cast to a boolean value before it's set.
  • Setting a property in a ruleset will overrule the previously set value. This also means that if two rulesets have conflicting values for a property, the value received from the last ruleset parsed by PHPCS will be the value used.

What this PR changes/fixes

  • I've reviewed every single sniff class property for visibility & static vs non-static and changed them where appropriate.
  • For the properties where custom lists are being merged with pre-set lists, I've added a helper function to handle the merging.
    This helper function is a little bit "magical".
    • It will correctly handle custom properties passed as comma delimited strings as well as arrays. Basically being forgiving to end-users who forget to set the type=array bit when adding a property in a custom ruleset.
    • If anything else is passed it will disregard the value of the custom property.
    • If a base array to merge the custom property with is passed, it expects the base array to be set up as value => true to allow for using isset() instead of in_array(). Only one existing array needed to be changed to allow for this $test_class_whitelist.
    • When merging a custom array with a base array, the custom array will be flipped, so the values will be added as keys to be in line with the above (can be turned off by passing false as the third param).
    • Any custom keys added to the base array through the merge will be given the value false. This has no impact when using isset(), but allows us to filter out anything added for a previous custom property value if the custom property would be overruled inline in a file. This in turn allows us to unit test the custom properties.
    • For more detail, see the function documentation
  • I've implemented use of this new utility function where appropriate.
  • For the custom array property merging issues, most sniffs used to add the custom values to the static pre-set arrays and only added these once for efficiency, using a $added_custom_properties setting to keep track of whether the custom properties had been merged in. Some of these static pre-set properties however would be used by several sniffs and could therefore be using a "contaminated" value. Also, the fact that the custom properties were only merged in once, prevented changing these values inline using the @codingStandardsChangeSetting syntax, which prevented unit testing them.
    I've fixed this by:
    • Turning the $added_custom_properties property into an array to keep track of the previous merged in value(s) instead of being a true/false toggle.
    • This allows us to avoid merging the same values time & time again (efficiency), while still allowing the value to be changed on the fly. This minimizes the performance impact of the change, while allowing more flexibility and greater compliance with the default capabilities of PHPCS.

Additional notes:

  • The PHPCS 2.x minimum requirement is PHP 5.1. The new utility function however uses the array_fill_keys() function which was only introduced in PHP 5.2. This means that the minimum PHP requirement for WPCS has to go up to PHP 5.2. Considering that's an ancient version, I don't think this will cause any issues. Just to be sure, I have added this info to the requirements section of the readme.
  • I've corrected the default value of one property - was float, should have been string ($minimum_supported_version in WP/DeprecatedFunctions). This property is used in a version_compare(), so string is the expected value type anyway and would be the type if the property is overruled from the ruleset as well.
  • Reviewed the internal handling of properties.
    • ControlStructureSpacing / blank_line_check and blank_line_after_check: PHPCS handles the casting to boolean already. For the compares, made sure they are done against "our" defaults, so that even if incorrectly set, the additional boolean cast is not necessary.
    • ControlStructureSpacing / spaces_before_closure_open_paren: moved the cast to int up instead of doing it three times.
  • Implemented use of the exclude property in the WPCS rulesets, includes correcting an invalid (outdated) exclusion in the Extra ruleset.
  • Removed the $supportedTokenizers property in the WordPress_Sniffs_WhiteSpace_ControlStructureSpacingSniff as it was set to the default value used by PHPCS anyway.
  • I've moved the custom list merging to a separate function in the relevant sniffs.
  • Where appropriate, I've moved the merging of custom properties to a later point in the code flow to only do so if the properties are needed.
  • CSRF/Nonce sniff: This sniff uses the is_only_sanitized() method which calls the is_sanitized() method which uses the sanitization function lists, but there was no way to enhance them. Added the relevant custom properties to enable enhancing them for that specific sniff.
  • DirectDatabaseQuery sniff: This sniff did so far not extend the WP_Sniff class, but should do so within the new structure. Changed the sniff to leverage the properties of the WP_Sniff class as well.

The PR includes unit tests for all custom properties which weren't unit tested before now.

Question:

  • Does anyone remember whether there was a reason to make the CSRF/Nonce sniff $errorForSuperGlobals and $warnForSuperGlobals properties public ? That was the one set of properties I was in doubt about changing the visibility for.

Open issue/question:

What should be the value for the exclude property in the overall WordPress ruleset for the WordPress.PHP.DiscouragedPHPFunctions sniff ? Or should nothing be excluded ?

The Extra ruleset excludes create_function, the VIP ruleset excludes obfuscation. Based on that, I would suggest adding them both to the WordPress ruleset to maintain the same behaviour as before.

Technical details:

Previously, the excludes were done via the <exclude.. syntax, not by using the exclude property. As the WordPress ruleset includes both these ruleset, both excludes where set for the overall ruleset.
With this PR, the excludes will be done via the exclude property instead. The properties in ruleset are not cumulative, but overrule each other. That means that in effect the WordPress ruleset would now only exclude the obfuscation group as that's the value set in the last ruleset included (VIP).
Setting the exclude property to both from within the WordPress ruleset will maintain the old behaviour.
However, a case could be made to exclude nothing if the overall WordPress ruleset is used, which is why I'm making a point of putting this up for discussion.

Closes #765
Closes #839

…set with an existing array.

This method is specifically intended to handle PHPCS custom properties.

This function allows for resetting as follows:
* If a custom property is changed during a PHPCS run, the base array will be reset to its original value before the new custom property value is merged in.

* If both a `$base` as well as a `$custom` parameter are passed and the `$custom` property value is boolean (`false`/`true`), the $base property will be returned.
This is especially useful during unit testing where you want to be able to add some custom functions and then reset to the original base array afterwards. This is now possible by passing `false`.

* Sometimes you want to just take advantage of the handling of comma delimited lists this function provides and overwrite the original value with the value returned by this function.
That's why the `$base` parameter is optional and defaults to an empty array. If it was not, you could only add to the `$base` array and never overwrite it properly.
* Change standard WP function list properties from static to non-static and change visibility to protected.
* Change `$addedCustomFunctions` property to non-static and change to array format.
* Split off the list merging to a separate function
* Use new `merge_custom_array()` utility function
* Move the list merging to a later point in time just before the lists are used. No need to do this earlier.
* Add two more custom function properties. This sniff uses the `is_only_sanitized()` method which calls the `is_sanitized()` method which uses the sanitization function lists, but there was no way to enhance them.
* Change standard WP function list properties from static to non-static and change visibility to protected.
* Change `$addedCustomFunctions` property to non-static, change visibility to protected and change to array format.
* Split off the list merging to a separate function
* Use new `merge_custom_array()` utility function
* Move the list merging to a later point in time just before the lists are used. No need to do this earlier.
* Change standard WP function list properties from static to non-static and change visibility to protected.
* Change `$addedCustomFunctions` property to non-static, change visibility to protected and change to array format.
* Split off the list merging to a separate function
* Use new `merge_custom_array()` utility function
* Extend the WordPress_Sniff
* Revert the addition of the private list properties which was part of an earlier version of this fix (unreleased).
* Change standard WP function list properties from static to non-static and change visibility to protected.
* Change `$addedCustomFunctions` to array format.
* Change `$methods` property to non-static.
* Split off the list merging to a separate function
* Use new `merge_custom_array()` utility function
* Move the list merging to a later point in time just before the lists are used. No need to do this earlier.
* Change visibility for `$whitelisted_mixed_case_member_var_names` property to `protected`.
* Copy in `merge_custom_array()` as this class extends an upstream sniff and therefore can't use the version include in `WordPress_Sniff`.
* Use new `merge_custom_array()` utility function
* Move the list merging to a later point in time just before the lists are used. No need to do this earlier.
Sniff properties which are `public` can be overruled via a custom ruleset.
Sniff properties should therefore only be `public` if that is the intended behaviour. Otherwise a more restrictive visibility should be used.

Changes to the visibility of properties *may* break backward compatibility for some users, but only if they were **_doing_it_wrong_** in the first place, so I'm not concerned about this.

Notes:
* Changed the visibility of some properties which shouldn't be changeable through the ruleset from `public` to `protected`.
* Corrected the default value of one property - was float, should have been string. This property is used in a `version_compare()`, so string is the expected value type anyway.
* Reviewed the internal handling of properties.
       - ControlStructureSpacing / `blank_line_check` and `blank_line_after_check`: PHPCS handles the casting to boolean already. For the compares, made sure they are done against "our" defaults, so that even if incorrectly set, the additional boolean cast is not necessary.
       - ControlStructureSpacing / `spaces_before_closure_open_paren`: moved the cast to int up instead of doing it three times.
 * Implemented use of the `exclude` property in the WPCS rulesets, includes correcting an invalid (outdated) exclusion in the `Extra` ruleset.
 * Removed the `$supportedTokenizers` property in the `WordPress_Sniffs_WhiteSpace_ControlStructureSpacingSniff` as it was set to the default value used by PHPCS anyway.
@grappler
Copy link
Member

grappler commented Feb 11, 2017

Why do we need to change from <exclude.. syntax to the exclude property?

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.

Looks good.

Yes, I did a performance check. The merge_custom_array() method might add a slight time hit. Potentially also a < 1 MB memory increase (2%) from these changes. I'm not worried by it. 😄

@@ -182,3 +182,27 @@ _deprecated_hook( 'some_filter', '1.3.0', esc_html__( 'The $arg is deprecated.'
_deprecated_hook( "filter_{$context}", '1.3.0', __( 'The $arg is deprecated.' ), sprintf( __( 'Some parsed message %s', $variable ) ) ); // Bad.

echo add_filter( get_the_excerpt( get_the_ID() ) ; // Bad, but ignored as code contains a parse error.


Copy link
Contributor

Choose a reason for hiding this comment

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

Extra blank line here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

*
* @var array
*/
public $errorForSuperGlobals = array( '$_POST', '$_FILE' );
protected $errorForSuperGlobals = array( '$_POST', '$_FILE' );
Copy link
Contributor

Choose a reason for hiding this comment

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

I originally made these public so that they could be modified in custom rulesets. I don't know if anybody is using that feature, but it was intentional. The reason for letting them be completely overridden rather than just merging custom settings with the default lists is so that values could be removed and not just added. I haven't actually used that feature though, and I doubt anybody else has, so it is fine with me if we change these to protected.

Copy link
Member Author

Choose a reason for hiding this comment

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

Got it. Thanks for explaining the history. I'm open to leaving these public and adding them to the upcoming wiki page if you prefer.

@jrfnl
Copy link
Member Author

jrfnl commented Feb 11, 2017

Why do we need to change from <exclude.. syntax to the exclude property?

I should probably have changed this already in #814. The errorcodes for these specific messages have changed, so the "old" codes won't work anymore.
I could have changed it over to the "new" codes, but especially for obfuscation that would mean adding a lot more codes.
I also figured that leading by example with using the property would be a good thing.

@@ -6,4 +6,10 @@
<rule ref="WordPress-Docs"/>
<rule ref="WordPress-Extra"/>
<rule ref="WordPress-VIP"/>

<rule ref="WordPress.PHP.DiscouragedPHPFunctions">
Copy link
Member

Choose a reason for hiding this comment

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

I would add a comment here explaining why we are adding an exception or referencing to that they have been excluded from their respective rulesets.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. Done.

@jrfnl
Copy link
Member Author

jrfnl commented Feb 25, 2017

@GaryJones Have you got an opinion on this PR or should I remove you as a reviewer ?

@GaryJones
Copy link
Member

Have you got an opinion on this PR or should I remove you as a reviewer ?

Reviewed and approved.

@jrfnl
Copy link
Member Author

jrfnl commented Feb 25, 2017

@GaryJones Great! Thanks.

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.

The future of the WordPress_Sniff class
4 participants