diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3fb449f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,25 @@ +# +# Exclude these files from release archives. +# This will also make them unavailable when using Composer with `--prefer-dist`. +# If you develop for WPCS using Composer, use `--prefer-source`. +# https://blog.madewithlove.be/post/gitattributes/ +# +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/.github export-ignore +/bin export-ignore +/Test export-ignore +/WordPress/Tests export-ignore + +# +# Auto detect text files and perform LF normalization +# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +# +* text=auto + +# +# The above will handle all files NOT found below +# +*.md text +*.php text +*.inc text diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..21ad809e --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,210 @@ +Hi, thank you for your interest in contributing to the WordPress Coding Standards! We look forward to working with you. + +# Reporting Bugs + +Before reporting a bug, you should check what sniff an error is coming from. +Running `phpcs` with the `-s` flag will show the name of the sniff with each error. + +Bug reports containing a minimal code sample which can be used to reproduce the issue are highly appreciated as those are most easily actionable. + +## Upstream Issues + +Since WPCS employs many sniffs that are part of PHPCS, sometimes an issue will be caused by a bug in PHPCS and not in WPCS itself. If the error message in question doesn't come from a sniff whose name starts with `WordPress`, the issue is probably a bug in PHPCS itself, and should be [reported there](https://github.com/squizlabs/PHP_CodeSniffer/issues). + +# Contributing patches and new features + +## Branches + +Ongoing development will be done in the `develop` with merges done into `master` once considered stable. + +To contribute an improvement to this project, fork the repo and open a pull request to the `develop` branch. Alternatively, if you have push access to this repo, create a feature branch prefixed by `feature/` and then open an intra-repo PR from that branch to `develop`. + +Once a commit is made to `develop`, a PR should be opened from `develop` into `master` and named "Next release". This PR will provide collaborators with a forum to discuss the upcoming stable release. + +# Considerations when writing sniffs + +## Public properties + +When writing sniffs, always remember that any `public` sniff property can be overruled via a custom ruleset by the end-user. +Only make a property `public` if that is the intended behaviour. + +When you introduce new `public` sniff properties, or your sniff extends a class from which you inherit a `public` property, please don't forget to update the [public properties wiki page](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties) with the relevant details once your PR has been merged into the `develop` branch. + +## Whitelist comments + +Sometimes, a sniff will flag code which upon further inspection by a human turns out to be OK. +If the sniff you are writing is susceptible to this, please consider adding the ability to [whitelist lines of code](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Whitelisting-code-which-flags-errors). + +To this end, the `WordPress\Sniff::has_whitelist_comment()` method was introduced. + +Example usage: +```php +namespace WordPress\Sniffs\CSRF; + +use WordPress\Sniff; + +class NonceVerificationSniff extends Sniff { + + public function process_token( $stackPtr ) { + + // Check something. + + if ( $this->has_whitelist_comment( 'CSRF', $stackPtr ) ) { + return; + } + + $this->phpcsFile->addError( ... ); + } +} +``` + +When you introduce a new whitelist comment, please don't forget to update the [whitelisting code wiki page](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Whitelisting-code-which-flags-errors) with the relevant details once your PR has been merged into the `develop` branch. + + +# Unit Testing + +## Pre-requisites +* WordPress-Coding-Standards +* PHP_CodeSniffer 2.9.x or 3.x +* PHPUnit 4.x, 5.x or 6.x + +The WordPress Coding Standards use the PHP_CodeSniffer native unit test suite for unit testing the sniffs. + +Presuming you have installed PHP_CodeSniffer and the WordPress-Coding-Standards as [noted in the README](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards#how-to-use-this), all you need now is `PHPUnit`. + +N.B.: If you installed WPCS using Composer, make sure you used `--prefer-source` or run `composer install --prefer-source` now to make sure the unit tests are available. + +If you already have PHPUnit installed on your system: Congrats, you're all set. + +If not, you can navigate to the directory where the `PHP_CodeSniffer` repo is checked out and do `composer install` to install the `dev` dependencies. +Alternatively, you can [install PHPUnit](https://phpunit.de/manual/5.7/en/installation.html) as a PHAR file. + +## Before running the unit tests + +N.B.: _If you used Composer to install the WordPress Coding Standards, you can skip this step._ + +For the unit tests to work, you need to make sure PHPUnit can find your `PHP_CodeSniffer` install. + +The easiest way to do this is to add a `phpunit.xml` file to the root of your WPCS installation and set a `PHPCS_DIR` environment variable from within this file. Make sure to adjust the path to reflect your local setup. +```xml + + + + + +``` + +## Running the unit tests + +The WordPress Coding Standards are compatible with both PHPCS 2.x as well as 3.x. This has some implications for running the unit tests. + +* Make sure you have registered the directory in which you installed WPCS with PHPCS using; + ```sh + phpcs --config-set installed_path path/to/WPCS + ``` +* Navigate to the directory in which you installed WPCS. +* To run the unit tests with PHPCS 3.x: + ```sh + phpunit --bootstrap="./Test/phpcs3-bootstrap.php" --filter WordPress /path/to/PHP_CodeSniffer/tests/AllTests.php + ``` +* To run the unit tests with PHPCS 2.x: + ```sh + phpunit --bootstrap="./Test/phpcs2-bootstrap.php" --filter WordPress ./Test/AllTests.php + ``` + +Expected output: +``` +PHPUnit 4.8.19 by Sebastian Bergmann and contributors. + +Runtime: PHP 7.1.3 with Xdebug 2.5.1 +Configuration: /WordPressCS/phpunit.xml + +.......................................................... + +Tests generated 556 unique error codes; 48 were fixable (8.63%) + +Time: 24.08 seconds, Memory: 41.75Mb + +OK (58 tests, 0 assertions) +``` + +[![asciicast](https://asciinema.org/a/98078.png)](https://asciinema.org/a/98078) + +## Unit Testing conventions + +If you look inside the `WordPress/Tests` subdirectory, you'll see the structure mimics the `WordPress/Sniffs` subdirectory structure. For example, the `WordPress/Sniffs/PHP/POSIXFunctionsSniff.php` sniff has its unit test class defined in `WordPress/Tests/PHP/POSIXFunctionsUnitTest.php` which checks the `WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc` test case file. See the file naming convention? + +Lets take a look at what's inside `POSIXFunctionsUnitTest.php`: + +```php +... +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class POSIXFunctionsUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array => + */ + public function getErrorList() { + return array( + 13 => 1, + 16 => 1, + 18 => 1, + 20 => 1, + 22 => 1, + 24 => 1, + 26 => 1, + ); + + } +... +``` + +Also note the class name convention. The method `getErrorList()` MUST return an array of line numbers indicating errors (when running `phpcs`) found in `WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc`. +If you run: + +```sh +$ cd /path-to-cloned/phpcs +$ ./bin/phpcs --standard=Wordpress -s /path/to/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc --sniffs=WordPress.PHP.POSIXFunctions +... +-------------------------------------------------------------------------------- +FOUND 7 ERRORS AFFECTING 7 LINES +-------------------------------------------------------------------------------- + 13 | ERROR | ereg() has been deprecated since PHP 5.3 and removed in PHP 7.0, + | | please use preg_match() instead. + | | (WordPress.PHP.POSIXFunctions.ereg_ereg) + 16 | ERROR | eregi() has been deprecated since PHP 5.3 and removed in PHP 7.0, + | | please use preg_match() instead. + | | (WordPress.PHP.POSIXFunctions.ereg_eregi) + 18 | ERROR | ereg_replace() has been deprecated since PHP 5.3 and removed in PHP + | | 7.0, please use preg_replace() instead. + | | (WordPress.PHP.POSIXFunctions.ereg_replace_ereg_replace) + 20 | ERROR | eregi_replace() has been deprecated since PHP 5.3 and removed in PHP + | | 7.0, please use preg_replace() instead. + | | (WordPress.PHP.POSIXFunctions.ereg_replace_eregi_replace) + 22 | ERROR | split() has been deprecated since PHP 5.3 and removed in PHP 7.0, + | | please use explode(), str_split() or preg_split() instead. + | | (WordPress.PHP.POSIXFunctions.split_split) + 24 | ERROR | spliti() has been deprecated since PHP 5.3 and removed in PHP 7.0, + | | please use explode(), str_split() or preg_split() instead. + | | (WordPress.PHP.POSIXFunctions.split_spliti) + 26 | ERROR | sql_regcase() has been deprecated since PHP 5.3 and removed in PHP + | | 7.0, please use preg_match() instead. + | | (WordPress.PHP.POSIXFunctions.ereg_sql_regcase) +-------------------------------------------------------------------------------- +.... +``` +You'll see the line number and number of ERRORs we need to return in the `getErrorList()` method. + +The `--sniffs=...` directive limits the output to the sniff you are testing. + +## Code Standards for this project + +The sniffs and test files - not test _case_ files! - for WPCS should be written such that they pass the `WordPress-Extra` and the `WordPress-Docs` code standards using the custom ruleset as found in `/bin/phpcs.xml`. diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 00000000..f6e4f498 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8142c98e..1950124e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .settings/ vendor composer.lock +phpunit.xml diff --git a/.travis.yml b/.travis.yml index 89ae2f97..c299d741 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,65 +1,100 @@ sudo: false +dist: trusty + +cache: + apt: true + language: - php php: - - 5.2 - - 5.3 - 5.4 - 5.5 - 5.6 - 7.0 - 7.1 + - 7.2 + - nightly env: - - PHPCS_BRANCH=master - - PHPCS_BRANCH=2.8.1 + # `master` is now 3.x. + - PHPCS_BRANCH=master LINT=1 + # Lowest tagged release in the 2.x series with which WPCS is compatible. + - PHPCS_BRANCH=2.9.0 matrix: fast_finish: true include: - # Run PHPCS against WPCS. I just picked to run it against 5.5. - - php: 5.5 + # Run PHPCS against WPCS. I just picked to run it against 7.0. + - php: 7.0 env: PHPCS_BRANCH=master SNIFF=1 - # Run against PHPCS 3.0. I just picked to run it against 5.6. - - php: 5.6 - env: PHPCS_BRANCH=3.0 - # Run against HHVM and PHP nightly. + addons: + apt: + packages: + - libxml2-utils + + # Run against HHVM. - php: hhvm sudo: required - dist: trusty - group: edge - env: PHPCS_BRANCH=master - - php: nightly - env: PHPCS_BRANCH=master + dist: trusty + group: edge + env: PHPCS_BRANCH=master LINT=1 + + # Test PHP 5.3 only against PHPCS 2.x as PHPCS 3.x has a minimum requirement of PHP 5.4. + - php: 5.3 + env: PHPCS_BRANCH=2.9 LINT=1 + dist: precise + # Test PHP 5.3 with short_open_tags set to On (is Off by default) + - php: 5.3 + env: PHPCS_BRANCH=2.9.0 SHORT_OPEN_TAGS=true + dist: precise + allow_failures: # Allow failures for unstable builds. - php: nightly - php: hhvm - - env: PHPCS_BRANCH=3.0 before_install: + - export XMLLINT_INDENT=" " - export PHPCS_DIR=/tmp/phpcs - export PHPUNIT_DIR=/tmp/phpunit - - export PHPCS_BIN=$(if [[ $PHPCS_BRANCH == 3.0 ]]; then echo $PHPCS_DIR/bin/phpcs; else echo $PHPCS_DIR/scripts/phpcs; fi) + - export PHPCS_BIN=$(if [[ $PHPCS_BRANCH == master ]]; then echo $PHPCS_DIR/bin/phpcs; else echo $PHPCS_DIR/scripts/phpcs; fi) - mkdir -p $PHPCS_DIR && git clone --depth 1 https://github.com/squizlabs/PHP_CodeSniffer.git -b $PHPCS_BRANCH $PHPCS_DIR - $PHPCS_BIN --config-set installed_paths $(pwd) - # Download PHPUnit 5.x for builds on PHP 7, nightly and HHVM as - # PHPCS test suite is currently not compatible with PHPUnit 6.x. - - if [[ ${TRAVIS_PHP_VERSION:0:2} != "5." ]]; then wget -P $PHPUNIT_DIR https://phar.phpunit.de/phpunit-5.7.phar && chmod +x $PHPUNIT_DIR/phpunit-5.7.phar; fi + # Download PHPUnit 5.x for builds on PHP 7, nightly and HHVM as the PHPCS + # test suite is currently not compatible with PHPUnit 6.x. + # Fixed at a very specific PHPUnit version which is also compatible with HHVM. + - if [[ ${TRAVIS_PHP_VERSION:0:2} != "5." ]]; then wget -P $PHPUNIT_DIR https://phar.phpunit.de/phpunit-5.7.17.phar && chmod +x $PHPUNIT_DIR/phpunit-5.7.17.phar; fi + # Selectively adjust the ini values for the build image to test ini value dependent sniff features. + - if [[ "$SHORT_OPEN_TAGS" == "true" ]]; then echo "short_open_tag = On" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi script: - - if find . -name "*.php" -exec php -l {} \; | grep "^[Parse error|Fatal error]"; then exit 1; fi; - - if [[ ${TRAVIS_PHP_VERSION:0:2} == "5." ]]; then phpunit --filter WordPress /tmp/phpcs/tests/AllTests.php; fi - - if [[ ${TRAVIS_PHP_VERSION:0:2} != "5." ]]; then php $PHPUNIT_DIR/phpunit-5.7.phar --filter WordPress /tmp/phpcs/tests/AllTests.php; fi + # Lint the PHP files against parse errors. + - if [[ "$LINT" == "1" ]]; then if find . -name "*.php" -exec php -l {} \; | grep "^[Parse error|Fatal error]"; then exit 1; fi; fi + # Run the unit tests. + - if [[ ${TRAVIS_PHP_VERSION:0:2} == "5." && ${PHPCS_BRANCH:0:2} == "2." ]]; then phpunit --filter WordPress $(pwd)/Test/AllTests.php; fi + - if [[ ${TRAVIS_PHP_VERSION:0:2} == "5." && ${PHPCS_BRANCH:0:2} != "2." ]]; then phpunit --filter WordPress $PHPCS_DIR/tests/AllTests.php; fi + - if [[ ${TRAVIS_PHP_VERSION:0:2} != "5." && ${PHPCS_BRANCH:0:2} == "2." ]]; then php $PHPUNIT_DIR/phpunit-5.7.17.phar --filter WordPress $(pwd)/Test/AllTests.php; fi + - if [[ ${TRAVIS_PHP_VERSION:0:2} != "5." && ${PHPCS_BRANCH:0:2} != "2." ]]; then php $PHPUNIT_DIR/phpunit-5.7.17.phar --filter WordPress $PHPCS_DIR/tests/AllTests.php; fi + # Test for fixer conflicts by running the auto-fixers of the complete WPCS over the test case files. + # This is not an exhaustive test, but should give an early indication for typical fixer conflicts. + # For the first run, the exit code will be 1 (= all fixable errors fixed). + # `travis_retry` should then kick in to run the fixer again which should now return 0 (= no fixable errors found). + # All error codes for the PHPCBF: https://github.com/squizlabs/PHP_CodeSniffer/issues/1270#issuecomment-272768413 + - if [[ "$SNIFF" == "1" ]]; then travis_retry $PHPCS_DIR/bin/phpcbf -p ./WordPress/Tests/ --standard=WordPress --extensions=inc --exclude=Generic.PHP.Syntax --report=summary; fi # WordPress Coding Standards. # @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards # @link http://pear.php.net/package/PHP_CodeSniffer/ - # -p flag: Show progress of the run. - # -s flag: Show sniff codes in all reports. - # -v flag: Print verbose output. - # -n flag: Do not print warnings. (shortcut for --warning-severity=0) - # --standard: Use WordPress as the standard. - # --extensions: Only sniff PHP files. - - if [[ "$SNIFF" == "1" ]]; then $PHPCS_DIR/scripts/phpcs -p -s -n . --standard=./bin/phpcs.xml --extensions=php; fi + - if [[ "$SNIFF" == "1" ]]; then $PHPCS_BIN . --standard=./bin/phpcs.xml --runtime-set ignore_warnings_on_exit 1; fi + # Validate the xml files. + # @link http://xmlsoft.org/xmllint.html + - if [[ "$SNIFF" == "1" ]]; then xmllint --noout ./*/ruleset.xml; fi + - if [[ "$SNIFF" == "1" ]]; then xmllint --noout ./phpcs.xml.dist.sample; fi + # Check the code-style consistency of the xml files. + - if [[ "$SNIFF" == "1" ]]; then diff -B --tabsize=4 ./WordPress/ruleset.xml <(xmllint --format "./WordPress/ruleset.xml"); fi + - if [[ "$SNIFF" == "1" ]]; then diff -B --tabsize=4 ./WordPress-Core/ruleset.xml <(xmllint --format "./WordPress-Core/ruleset.xml"); fi + - if [[ "$SNIFF" == "1" ]]; then diff -B --tabsize=4 ./WordPress-Docs/ruleset.xml <(xmllint --format "./WordPress-Docs/ruleset.xml"); fi + - if [[ "$SNIFF" == "1" ]]; then diff -B --tabsize=4 ./WordPress-Extra/ruleset.xml <(xmllint --format "./WordPress-Extra/ruleset.xml"); fi + - if [[ "$SNIFF" == "1" ]]; then diff -B --tabsize=4 ./WordPress-VIP/ruleset.xml <(xmllint --format "./WordPress-VIP/ruleset.xml"); fi + - if [[ "$SNIFF" == "1" ]]; then diff -B --tabsize=4 ./phpcs.xml.dist.sample <(xmllint --format "./phpcs.xml.dist.sample"); fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ff3e655..48278745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,169 @@ This projects adheres to [Semantic Versioning](http://semver.org/) and [Keep a C _Nothing yet._ + +## [0.14.1] - 2018-02-15 + +### Fixed +- The `WordPress.NamingConventions.PrefixAllGlobals` sniff contained a bug which could inadvertently trigger class autoloading of the project being sniffed and by extension could cause fatal errors during the PHPCS run. + +## [0.14.0] - 2017-11-01 + +### Added +- `WordPress.Arrays.MultipleStatementAlignment` sniff to the `WordPress-Core` ruleset which will align the array assignment operator for multi-item, multi-line associative arrays. + This new sniff offers four custom properties to customize its behaviour: [`ignoreNewlines`](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#array-alignment-allow-for-new-lines), [`exact`](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#array-alignment-allow-non-exact-alignment), [`maxColumn`](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#array-alignment-maximum-column) and [`alignMultilineItems`](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#array-alignment-dealing-with-multi-line-items). +- `WordPress.DB.PreparedSQLPlaceholders` sniff to the `WordPress-Core` ruleset which will analyse the placeholders passed to `$wpdb->prepare()` for their validity, check whether queries using `IN ()` and `LIKE` statements are created correctly and will check whether a correct number of replacements are passed. + This sniff should help detect queries which are impacted by the security fixes to `$wpdb->prepare()` which shipped with WP 4.8.2 and 4.8.3. + The sniff also adds a new ["PreparedSQLPlaceholders replacement count" whitelist comment](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Whitelisting-code-which-flags-errors#preparedsql-placeholders-vs-replacements) for pertinent replacement count vs placeholder mismatches. Please consider carefully whether something could be a bug when you are tempted to use the whitelist comment and if so, [report it](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues/new). +- `WordPress.PHP.DiscourageGoto` sniff to the `WordPress-Core` ruleset. +- `WordPress.PHP.RestrictedFunctions` sniff to the `WordPress-Core` ruleset which initially forbids the use of `create_function()`. + This was previous only discouraged under certain circumstances. +- `WordPress.WhiteSpace.ArbitraryParenthesesSpacing` sniff to the `WordPress-Core` ruleset which checks the spacing on the inside of arbitrary parentheses. +- `WordPress.WhiteSpace.PrecisionAlignment` sniff to the `WordPress-Core` ruleset which will throw a warning when precision alignment is detected in PHP, JS and CSS files. +- `WordPress.WhiteSpace.SemicolonSpacing` sniff to the `WordPress-Core` ruleset which will throw a (fixable) error when whitespace is found before a semi-colon, except for when the semi-colon denotes an empty `for()` condition. +- `WordPress.CodeAnalysis.AssignmentInCondition` sniff to the `WordPress-Extra` ruleset. +- `WordPress.WP.DiscouragedConstants` sniff to the `WordPress-Extra` and `WordPress-VIP` rulesets to detect usage of deprecated WordPress constants, such as `STYLESHEETPATH` and `HEADER_IMAGE`. +- Ability to pass the `minimum_supported_version` to use for the `DeprecatedFunctions`, `DeprecatedClasses` and `DeprecatedParameters` sniff in one go. You can pass a `minimum_supported_wp_version` runtime variable for this [from the command line or pass it using a `config` directive in a custom ruleset](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#setting-minimum-supported-wp-version-for-all-sniffs-in-one-go-wpcs-0140). +- `Generic.Formatting.MultipleStatementAlignment` - customized to have a `maxPadding` of `40` -, `Generic.Functions.FunctionCallArgumentSpacing` and `Squiz.WhiteSpace.ObjectOperatorSpacing` to the `WordPress-Core` ruleset. +- `Squiz.Scope.MethodScope`, `Squiz.Scope.MemberVarScope`, `Squiz.WhiteSpace.ScopeKeywordSpacing`, `PSR2.Methods.MethodDeclaration`, `Generic.Files.OneClassPerFile`, `Generic.Files.OneInterfacePerFile`, `Generic.Files.OneTraitPerFile`, `PEAR.Files.IncludingFile`, `Squiz.WhiteSpace.LanguageConstructSpacing`, `PSR2.Namespaces.NamespaceDeclaration` to the `WordPress-Extra` ruleset. +- The `is_class_constant()`, `is_class_property` and `valid_direct_scope()` utility methods to the `WordPress\Sniff` class. + +### Changed +- When passing an array property via a custom ruleset to PHP_CodeSniffer, spaces around the key/value are taken as intentional and parsed as part of the array key/value. In practice, this leads to confusion and WPCS does not expect any values which could be preceded/followed by a space, so for the WordPress Coding Standard native array properties, like `customAutoEscapedFunction`, `text_domain`, `prefixes`, WPCS will now trim whitespace from the keys/values received before use. +- The WPCS native whitelist comments used to only work when they were put on the _end of the line_ of the code they applied to. As of now, they will also be recognized when they are be put at the _end of the statement_ they apply to. +- The `WordPress.Arrays.ArrayDeclarationSpacing` sniff used to enforce all associative arrays to be multi-line. The handbook has been updated to only require this for multi-item associative arrays and the sniff has been updated accordingly. + [The original behaviour can still be enforced](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#arrays-forcing-single-item-associative-arrays-to-be-multi-line) by setting the new `allow_single_item_single_line_associative_arrays` property to `false` in a custom ruleset. +- The `WordPress.NamingConventions.PrefixAllGlobals` sniff will now allow for a limited list of WP core hooks which are intended to be called by plugins and themes. +- The `WordPress.PHP.DiscouragedFunctions` sniff used to include `create_function`. This check has been moved to the new `WordPress.PHP.RestrictedFunctions` sniff. +- The `WordPress.PHP.StrictInArray` sniff now has a separate error code `FoundNonStrictFalse` for when the `$strict` parameter has been set to `false`. This allows for excluding the warnings for that particular situation, which will normally be intentional, via a custom ruleset. +- The `WordPress.VIP.CronInterval` sniff now allows for customizing the minimum allowed cron interval by [setting a property in a custom ruleset](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#vip-croninterval-minimum-interval). +- The `WordPress.VIP.RestrictedFunctions` sniff used to prohibit the use of certain WP native functions, recommending the use of `wpcom_vip_get_term_link()`, `wpcom_vip_get_term_by()` and `wpcom_vip_get_category_by_slug()` instead, as the WP native functions were not being cached. As the results of the relevant WP native functions are cached as of WP 4.8, the advice has now been reversed i.e. use the WP native functions instead of `wpcom...` functions. +- The `WordPress.VIP.PostsPerPage` sniff now allows for customizing the `post_per_page` limit for which the sniff will trigger by [setting a property in a custom ruleset](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#vip-postsperpage-post-limit). +- The `WordPress.WP.I18n` sniff will now allow and actively encourage omitting the text-domain in I18n function calls if the text-domain passed via the `text_domain` property is `default`, i.e. the domain used by Core. + When `default` is one of several text-domains passed via the `text_domain` property, the error thrown when the domain is missing has been downgraded to a `warning`. +- The `WordPress.XSS.EscapeOutput` sniff now has a separate error code `OutputNotEscapedShortEcho` and the error message texts have been updated. +- Moved `Squiz.PHP.Eval` from the `WordPress-Extra` and `WordPress-VIP` to the `WordPress-Core` ruleset. +- Removed two sniffs from the `WordPress-VIP` ruleset which were already included via the `WordPress-Core` ruleset. +- The unit test suite is now compatible with PHPCS 3.1.0+ and PHPUnit 6.x. +- Some tidying up of the unit test case files. +- All sniffs are now also being tested against PHP 7.2 for consistent sniff results. +- An attempt is made to detect potential fixer conflicts early via a special build test. +- Various minor documentation fixes. +- Improved the Atom setup instructions in the Readme. +- Updated the unit testing information in Contributing. +- Updated the [custom ruleset example](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/blob/develop/phpcs.xml.dist.sample) for the changes contained in this release and to make it more explicit what is recommended versus example code. +- The minimum recommended version for the suggested `DealerDirect/phpcodesniffer-composer-installer` Composer plugin has gone up to `0.4.3`. This patch version fixes support for PHP 5.3. + +### Fixed +- The `WordPress.Arrays.ArrayIndentation` sniff did not correctly handle array items with multi-line strings as a value. +- The `WordPress.Arrays.ArrayIndentation` sniff did not correctly handle array items directly after an array item with a trailing comment. +- The `WordPress.Classes.ClassInstantiation` sniff will now correctly handle detection when using `new $array['key']` or `new $array[0]`. +- The `WordPress.NamingConventions.PrefixAllGlobals` sniff did not allow for arbitrary word separators in hook names. +- The `WordPress.NamingConventions.PrefixAllGlobals` sniff did not correctly recognize namespaced constants as prefixed. +- The `WordPress.PHP.StrictInArray` sniff would erronously trigger if the `true` for `$strict` was passed in uppercase. +- The `WordPress.PHP.YodaConditions` sniff could get confused over complex ternaries containing assignments. This has been remedied. +- The `WordPress.WP.PreparedSQL` sniff would erronously throw errors about comments found within a DB function call. +- The `WordPress.WP.PreparedSQL` sniff would erronously throw errors about `(int)`, `(float)` and `(bool)` casts and would also flag the subsequent variable which had been safe casted. +- The `WordPress.XSS.EscapeOutput` sniff would erronously trigger when using a fully qualified function call - including the global namespace `\` indicator - to one of the escaping functions. +- The lists of WP global variables and WP mixed case variables have been synchronized, which fixes some false positives. + + +## [0.13.1] - 2017-08-07 + +### Fixed +- Fatal error when using PHPCS 3.x with the `installed_paths` config variable set via the ruleset. + +## [0.13.0] - 2017-08-03 + +### Added +- Support for PHP_CodeSniffer 3.0.2+. The minimum required PHPCS version (2.9.0) stays the same. +- Support for the PHPCS 3 `--ignore-annotations` command line option. If you pass this option, both PHPCS native `@ignore ...` annotations as well as the WPCS specific [whitelist flags](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Whitelisting-code-which-flags-errors) will be ignored. + +### Changed +- The minimum required PHP version is now 5.3 when used in combination with PHPCS 2.x and PHP 5.4 when used in combination with PHPCS 3.x. +- The way the unit tests can be run is now slightly different for PHPCS 2.x versus 3.x. For more details, please refer to the updated information in the [Contributing Guidelines](CONTRIBUTING.md). +- Release archives will no longer contain the unit tests and other typical development files. You can still get these by using Composer with `--prefer-source` or by checking out a git clone of the repository. +- Various textual improvements to the Readme. +- Various textual improvements to the Contributing Guidelines. +- Minor internal changes. + +### Removed +- The `WordPress.Arrays.ArrayDeclaration` sniff has been deprecated. The last remaining checks this sniff contained have been moved to the `WordPress.Arrays.ArrayDeclarationSpacing` sniff. +- Work-arounds which were in place to support PHP 5.2. + +### Fixed +- A minor bug where the auto-fixer could accidentally remove a comment near an array opener. + + +## [0.12.0] - 2017-07-21 + +### Added +- A default file encoding setting to the `WordPress-Core` ruleset. All files sniffed will now be regarded as `utf-8` by default. +- `WordPress.Arrays.ArrayIndentation` sniff to the `WordPress-Core` ruleset to verify - and auto-fix - the indentation of array items and the array closer for multi-line arrays. This replaces the (partial) indentation fixing contained within the `WordPress.Array.ArrayDeclarationSpacing` sniff. +- `WordPress.Arrays.CommaAfterArrayItem` sniff to the `WordPress-Core` ruleset to enforce that each array item is followed by a comma - except for the last item in a single-line array - and checks the spacing around the comma. This replaces (and improves) the checks which were previously included in the `WordPress.Arrays.ArrayDeclaration` sniff which were causing incorrect fixes and fixer conflicts. +- `WordPress.Functions.FunctionCallSignatureNoParams` sniff to the `WordPress-Core` ruleset to verify that function calls without parameters do not have any whitespace between the parentheses. +- `WordPress.WhiteSpace.DisallowInlineTabs` to the `WordPress-Core` ruleset to verify - and auto-fix - that spaces are used for mid-line alignment. +- `WordPress.WP.CapitalPDangit` sniff to the `WordPress-Core` ruleset to - where relevant - verify that `WordPress` is spelled correctly. For misspellings in text strings and comment text, the sniff can auto-fix violations. +- `Squiz.Classes.SelfMemberReference` whitespace related checks to the `WordPress-Core` ruleset and the additional check for using `self` rather than a FQN to the `WordPress-Extra` ruleset. +- `Squiz.PHP.EmbeddedPhp` sniff to the `WordPress-Core` ruleset to check PHP code embedded within HTML blocks. +- `PSR2.ControlStructures.SwitchDeclaration` to the `WordPress-Core` ruleset to check for the correct layout of `switch` control structures. +- `WordPress.Classes.ClassInstantion` sniff to the `WordPress-Extra` ruleset to detect - and auto-fix - missing parentheses on object instantiation and superfluous whitespace in PHP and JS files. The sniff will also detect `new` being assigned by reference. +- `WordPress.CodeAnalysis.EmptyStatement` sniff to the `WordPress-Extra` ruleset to detect - and auto-fix - superfluous semi-colons and empty PHP open-close tag combinations. +- `WordPress.NamingConventions.PrefixAllGlobals` sniff to the `WordPress-Extra` ruleset to verify that all functions, classes, interfaces, traits, variables, constants and hook names which are declared/defined in the global namespace are prefixed with one of the prefixes provided via a custom property or via the command line. + To activate this sniff, [one or more allowed prefixes should be provided to the sniff](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#naming-conventions-prefix-everything-in-the-global-namespace). This can be done using a custom ruleset or via the command line. + PHP superglobals and WP global variables are exempt from variable name prefixing. Deprecated hook names will also be disregarded when non-prefixed. Back-fills for known native PHP functionality is also accounted for. + For verified exceptions, [unprefixed code can be whitelisted](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Whitelisting-code-which-flags-errors#non-prefixed-functionclassvariableconstant-in-the-global-namespace). + Code in unit test files is automatically exempt from this sniff. +- `WordPress.WP.DeprecatedClasses` sniff to the `WordPress-Extra` ruleset to detect usage of deprecated WordPress classes. +- `WordPress.WP.DeprecatedParameters` sniff to the `WordPress-Extra` ruleset to detect deprecated parameters being passed to WordPress functions with a value other than the expected default. +- The `sanitize_textarea_field()` function to the `sanitizingFunctions` list used by the `WordPress.CSRF.NonceVerification`, `WordPress.VIP.ValidatedSanitizedInput` and `WordPress.XSS.EscapeOutput` sniffs. +- The `find_array_open_closer()` utility method to the `WordPress_Sniff` class. +- Information about setting `installed_paths` using a custom ruleset to the Readme. +- Additional support links to the `composer.json` file. +- Support for Composer PHPCS plugins which sort out the `installed_paths` setting. +- Linting and code-style check of the XML ruleset files provided by WPCS. + +### Changed +- The minimum required PHP_CodeSniffer version to 2.9.0 (was 2.8.1). **Take note**: PHPCS 3.x is not (yet) supported. The next release is expected to fix that. +- Improved support for detecting issues in code using heredoc and/or nowdoc syntax. +- Improved sniff efficiency, precision and performance for a number of sniffs. +- Updated a few sniffs to take advantage of new features and fixes which are included in PHP_CodeSniffer 2.9.0. +- `WordPress.Files.Filename`: The "file name mirrors the class name prefixed with 'class'" check for PHP files containing a class will no longer be applied to typical unit test classes, i.e. for classes which extend `WP_UnitTestCase`, `PHPUnit_Framework_TestCase` and `PHPUnit\Framework\TestCase`. Additional test case base classes can be passed to the sniff using the new [`custom_test_class_whitelist` property](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#custom-unit-test-classes). +- The `WordPress.Files.FileName` sniff allows now for more theme-specific template hierarchy based file name exceptions. +- The whitelist flag for the `WordPress.VIP.SlowQuery` sniff was `tax_query` which was unintuitive. This has now been changed to `slow query` to be in line with other whitelist flags. +- The `WordPress.WhiteSpace.OperatorSpacing` sniff will now ignore operator spacing within `declare()` statements. +- The `WordPress.WhiteSpace.OperatorSpacing` sniff now extends the upstream `Squiz.WhiteSpace.OperatorSpacing` sniff for improved results and will now also examine the spacing around ternary operators and logical (`&&`, `||`) operators. +- The `WordPress.WP.DeprecatedFunctions` sniff will now detect functions deprecated in WP 4.7 and 4.8. Additionally, a number of other deprecated functions which were previously not being detected have been added to the sniff and for a number of functions the "alternative" for the deprecated function has been added/improved. +- The `WordPress.XSS.EscapeOutput` sniff will now also detect unescaped output when the short open echo tags `has_whitelist_comment( 'CSRF', $stackPtr ) ) { - return; - } - - $this->phpcsFile->addError( ... ); - } -} -``` - -When you introduce a new whitelist comment, please don't forget to update the [whitelisting code wiki page](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Whitelisting-code-which-flags-errors) with the relevant details once your PR has been merged into the `develop` branch. - - -# Unit Testing - -TL;DR - -If you have installed `phpcs` and the WordPress-Coding-Standards as [noted in the README](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards#how-to-use-this), then you can navigate to the directory where the `phpcs` repo is checked out and do: - -```sh -composer install -vendor/bin/phpunit --filter WordPress tests/AllTests.php -``` - -Expected output: - -[![asciicast](https://asciinema.org/a/98078.png)](https://asciinema.org/a/98078) - -You can ignore any skipped tests as these are for `PHP_CodeSniffer` external tools. - -The reason why we need to checkout from `PHP_CodeSniffer` git repo to run the tests is because -PEAR installation is intended for ready-to-use not for development. At some point `WordPress-Coding-Standards` -might be submitted to `PHP_CodeSniffer` repo and using their existing convention for unit tests -will eventually help them to test the code before merging in. - -## Unit Testing conventions - -If you see inside the `WordPress/Tests`, the structure mimics the `WordPress/Sniffs`. For example, -the `WordPress/Sniffs/Arrays/ArrayDeclarationSniff.php` sniff has unit test class defined in -`WordPress/Tests/Arrays/ArrayDeclarationUnitTest.php` that check `WordPress/Tests/Arrays/ArrayDeclarationUnitTest.inc` -file. See the file naming convention? Lets take a look what inside `ArrayDeclarationUnitTest.php`: - -```php -... -class WordPress_Tests_Arrays_ArrayDeclarationUnitTest extends AbstractSniffUnitTest -{ - public function getErrorList() - { - return array( - 3 => 1, - 7 => 1, - 9 => 1, - 16 => 1, - 31 => 2, - ); - - }//end getErrorList() -} -... -``` - -Also note the class name convention. The method `getErrorList` MUST return an array of line numbers -indicating errors (when running `phpcs`) found in `WordPress/Tests/Arrays/ArrayDeclarationUnitTest.inc`. -If you run: - -```sh -$ cd /path-to-cloned/phpcs -$ ./scripts/phpcs --standard=Wordpress -s CodeSniffer/Standards/WordPress/Tests/Arrays/ArrayDeclarationUnitTest.inc -... --------------------------------------------------------------------------------- -FOUND 8 ERROR(S) AND 2 WARNING(S) AFFECTING 6 LINE(S) --------------------------------------------------------------------------------- - 3 | ERROR | Array keyword should be lower case; expected "array" but found - | | "Array" (WordPress.Arrays.ArrayDeclaration) - 7 | ERROR | There must be no space between the Array keyword and the - | | opening parenthesis (WordPress.Arrays.ArrayDeclaration) - 9 | ERROR | Empty array declaration must have no space between the - | | parentheses (WordPress.Arrays.ArrayDeclaration) - 12 | WARNING | No space after opening parenthesis of array is bad style - | | (WordPress.Arrays.ArrayDeclaration) - 12 | WARNING | No space before closing parenthesis of array is bad style - | | (WordPress.Arrays.ArrayDeclaration) - 16 | ERROR | Each line in an array declaration must end in a comma - | | (WordPress.Arrays.ArrayDeclaration) - 31 | ERROR | Expected 1 space between "'type'" and double arrow; 0 found - | | (WordPress.Arrays.ArrayDeclaration) - 31 | ERROR | Expected 1 space between double arrow and "'post'"; 0 found - | | (WordPress.Arrays.ArrayDeclaration) - 31 | ERROR | Expected 1 space before "=>"; 0 found - | | (WordPress.WhiteSpace.OperatorSpacing) - 31 | ERROR | Expected 1 space after "=>"; 0 found - | | (WordPress.WhiteSpace.OperatorSpacing) --------------------------------------------------------------------------------- -.... -``` - -You'll see the line number and number of ERRORs we need to return in `getErrorList` method. -In line #31 there are two ERRORs belong to `WordPress.WhiteSpace.OperatorSpacing` sniff and -it MUST not included in `ArrayDeclarationUnitTest` (that's why we only return 2 errros for line #31). -Also there's `getWarningList` method in unit test class that returns an array of line numbers -indicating WARNINGs. - -## Sniff Code Standards - -The sniffs for WPCS should be written such that they pass the `WordPress-Core` code standards. - diff --git a/README.md b/README.md index 5234a445..0bde609b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ + [Atom](#atom) + [Visual Studio](#visual-studio) * [Running your code through WPCS automatically using CI tools](#running-your-code-through-wpcs-automatically-using-ci-tools) - + [[Travis CI](https://travis-ci.org/)](#-travis-ci--https---travis-ciorg--) + + [Travis CI](#travis-ci) * [Fixing errors or whitelisting them](#fixing-errors-or-whitelisting-them) * [Contributing](#contributing) * [License](#license) @@ -34,20 +34,20 @@ This project is a collection of [PHP_CodeSniffer](https://github.com/squizlabs/P - In April 2009 original project from [Urban Giraffe](http://urbangiraffe.com/articles/wordpress-codesniffer-standard/) was published. - In May 2011 the project was forked on GitHub by [Chris Adams](http://chrisadams.me.uk/). - - In April 2012 [XWP](https://xwp.co/) started to dedicate resources to development and lead creation of the the sniffs and rulesets for `WordPress-Core`, `WordPress-VIP` (WordPress.com VIP), and `WordPress-Extra`. - - In 2015, [J.D. Grimes](https://github.com/JDGrimes) began significant contributions, along with maintanance from [Gary Jones](https://github.com/GaryJones). + - In April 2012 [XWP](https://xwp.co/) started to dedicate resources to develop and lead the creation of the sniffs and rulesets for `WordPress-Core`, `WordPress-VIP` (WordPress.com VIP), and `WordPress-Extra`. + - In 2015, [J.D. Grimes](https://github.com/JDGrimes) began significant contributions, along with maintenance from [Gary Jones](https://github.com/GaryJones). - In 2016, [Juliette Reinders Folmer](https://github.com/jrfnl) began contributing heavily, adding more commits in a year than anyone else in 5 years previous since the project's inception. ## Installation ### Requirements -The WordPress Coding Standards require PHP 5.2 or higher and the [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) version **2.8.1** or higher. -The WordPress Coding Standards are currently [not compatible with the upcoming PHPCS 3 release](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues/718). +The WordPress Coding Standards require PHP 5.3 or higher and [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) version **2.9.0** or higher. +As of version 0.13.0, the WordPress Coding Standards are compatible with PHPCS 3.0.2+. In that case, the minimum PHP requirement is PHP 5.4. ### Composer -Standards can be installed with [Composer](https://getcomposer.org/) dependency manager: +Standards can be installed with the [Composer](https://getcomposer.org/) dependency manager: composer create-project wp-coding-standards/wpcs --no-dev @@ -58,22 +58,36 @@ Running this command will: 3. Register WordPress standards in PHP_CodeSniffer configuration. 4. Make `phpcs` command available from `wpcs/vendor/bin`. -For convenience of using `phpcs` as global command you might want to add path to `wpcs/vendor/bin` directory to a `PATH` environment of your operating system. +For the convenience of using `phpcs` as a global command, you may want to add the path to the `wpcs/vendor/scripts` (PHPCS 2.x) and/or `wpcs/vendor/bin` (PHPCS 3.x) directories to a `PATH` environment variable for your operating system. + +#### Installing WPCS as a dependency + +When installing the WordPress Coding Standards as a dependency in a larger project, the above mentioned step 3 will not be executed automatically. + +There are two actively maintained Composer plugins which can handle the registration of standards with PHP_CodeSniffer for you: +* [composer-phpcodesniffer-standards-plugin](https://github.com/higidi/composer-phpcodesniffer-standards-plugin) +* [phpcodesniffer-composer-installer](https://github.com/DealerDirect/phpcodesniffer-composer-installer):"^0.4.3" + +It is strongly suggested to `require` one of these plugins in your project to handle the registration of external standards with PHPCS for you. ### Standalone -1. Install PHP_CodeSniffer by following its [installation instructions](https://github.com/squizlabs/PHP_CodeSniffer#installation) (via Composer, PEAR, or Git checkout). +1. Install PHP_CodeSniffer by following its [installation instructions](https://github.com/squizlabs/PHP_CodeSniffer#installation) (via Composer, Phar file, PEAR, or Git checkout). - Do ensure, if for example you're using [VVV](https://github.com/Varying-Vagrant-Vagrants/VVV), that PHP_CodeSniffer's version matches our [requirements](#requirements). + Do ensure that PHP_CodeSniffer's version matches our [requirements](#requirements), if, for example, you're using [VVV](https://github.com/Varying-Vagrant-Vagrants/VVV). -2. Clone WordPress standards repository: +2. Clone the WordPress standards repository: git clone -b master https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git wpcs -3. Add its path to PHP_CodeSniffer configuration: +3. Add its path to the PHP_CodeSniffer configuration: phpcs --config-set installed_paths /path/to/wpcs + **Pro-tip:** Alternatively, you can tell PHP_CodeSniffer the path to the WordPress standards by adding the following snippet to your custom ruleset: + ```xml + + ``` To summarize: @@ -82,10 +96,13 @@ cd ~/projects git clone https://github.com/squizlabs/PHP_CodeSniffer.git phpcs git clone -b master https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git wpcs cd phpcs +#PHPCS 2.x ./scripts/phpcs --config-set installed_paths ../wpcs +#PHPCS 3.x +./bin/phpcs --config-set installed_paths ../wpcs ``` -And then add the `~/projects/phpcs/scripts` directory to your `PATH` environment variable via your `.bashrc`. +And then add the `~/projects/phpcs/scripts` (PHPCS 2.x) or `~/projects/phpcs/bin` (PHPCS 3.x) directory to your `PATH` environment variable via your `.bashrc`. You should then see `WordPress-Core` et al listed when you run `phpcs -i`. @@ -93,25 +110,25 @@ You should then see `WordPress-Core` et al listed when you run `phpcs -i`. ### Standards subsets -The project encompasses a super–set of the sniffs that the WordPress community may need. If you use the `WordPress` standard you will get all the checks. Some of them might be unnecessary for your environment, for example those specific to WordPress VIP coding requirements. +The project encompasses a super-set of the sniffs that the WordPress community may need. If you use the `WordPress` standard you will get all the checks. Some of them might be unnecessary for your environment, for example, those specific to WordPress VIP coding requirements. You can use the following as standard names when invoking `phpcs` to select sniffs, fitting your needs: -* `WordPress` — complete set with all of the sniffs in the project - - `WordPress-Core` — main ruleset for [WordPress core coding standards](http://make.wordpress.org/core/handbook/coding-standards/) - - `WordPress-Docs` — additional ruleset for [WordPress inline documentation standards](https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/) - - `WordPress-Extra` — extended ruleset for recommended best practices, not sufficiently covered in the WordPress core coding standards +* `WordPress` - complete set with all of the sniffs in the project + - `WordPress-Core` - main ruleset for [WordPress core coding standards](http://make.wordpress.org/core/handbook/coding-standards/) + - `WordPress-Docs` - additional ruleset for [WordPress inline documentation standards](https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/) + - `WordPress-Extra` - extended ruleset for recommended best practices, not sufficiently covered in the WordPress core coding standards - includes `WordPress-Core` - - `WordPress-VIP` — extended ruleset for [WordPress VIP coding requirements](http://vip.wordpress.com/documentation/code-review-what-we-look-for/) + - `WordPress-VIP` - extended ruleset for [WordPress VIP coding requirements](http://vip.wordpress.com/documentation/code-review-what-we-look-for/) - includes `WordPress-Core` ### Using a custom ruleset -If you need to further customize the selection of sniffs for your project — you can create a custom `phpcs.xml` standard. See provided [project.ruleset.xml.example](project.ruleset.xml.example) file and [fully annotated example](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml) in PHP_CodeSniffer documentation. +If you need to further customize the selection of sniffs for your project - you can create a custom ruleset file. When you name this file either `phpcs.xml` or `phpcs.xml.dist`, PHP_CodeSniffer will automatically locate it as long as it is placed in the directory from which you run the CodeSniffer or in a directory above it. If you follow these naming conventions you don't have to supply a `--standard` arg. For more info, read about [using a default configuration file](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage#using-a-default-configuration-file). See also provided [`phpcs.xml.dist.sample`](phpcs.xml.dist.sample) file and [fully annotated example](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml) in the PHP_CodeSniffer documentation. ### Customizing sniff behaviour -The WordPress Coding Standard contains a number of sniffs which are configurable. This means that you can turn parts of the sniff on or off, or change the behaviour by setting a property for the sniff in your custom `ruleset.xml` file. +The WordPress Coding Standard contains a number of sniffs which are configurable. This means that you can turn parts of the sniff on or off, or change the behaviour by setting a property for the sniff in your custom `phpcs.xml` file. You can find a complete list of all the properties you can change in the [wiki](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties). @@ -121,7 +138,7 @@ The [PHPCompatibility](https://github.com/wimg/PHPCompatibility) ruleset comes h The [PHPCompatibility](https://github.com/wimg/PHPCompatibility) sniffs are designed to analyse your code for cross-PHP version compatibility. Install it as a separate ruleset and either run it separately against your code or add it to your custom ruleset. -Whichever way you run it, do make sure you set the `testVersion` to run the sniffs against. The `testVersion` determines for which PHP versions you will received compatibility information. The recommended setting for this at this moment is `5.2-7.1` to support the same PHP versions as WordPress Core supports. +Whichever way you run it, do make sure you set the `testVersion` to run the sniffs against. The `testVersion` determines for which PHP versions you will receive compatibility information. The recommended setting for this at this moment is `5.2-7.1` to support the same PHP versions as WordPress Core supports. For more information about setting the `testVersion`, see: * [PHPCompatibility: Using the compatibility sniffs](https://github.com/wimg/PHPCompatibility#using-the-compatibility-sniffs) @@ -138,30 +155,37 @@ Run the `phpcs` command line tool on a given file or directory, for example: Will result in following output: -------------------------------------------------------------------------------- - FOUND 8 ERRORS AND 2 WARNINGS AFFECTING 7 LINES + FOUND 10 ERRORS AND 5 WARNINGS AFFECTING 8 LINES -------------------------------------------------------------------------------- - 1 | ERROR | [x] End of line character is invalid; expected "\n" but found "\r\n" - 36 | ERROR | [x] Expected 1 spaces before closing bracket; 0 found - 41 | WARNING | [ ] Silencing errors is discouraged - 41 | WARNING | [ ] Silencing errors is discouraged - 48 | ERROR | [ ] Inline comments must end in full-stops, exclamation marks, or - | | question marks - 48 | ERROR | [x] There must be no blank line following an inline comment - 76 | ERROR | [ ] Inline comments must end in full-stops, exclamation marks, or - | | question marks - 92 | ERROR | [x] String "Create a Configuration File" does not require double - | | quotes; use single quotes instead - 94 | ERROR | [ ] Expected next thing to be an escaping function (see Codex for - | | 'Data Validation'), not '$die' - 94 | ERROR | [ ] Expected next thing to be an escaping function (see Codex for - | | 'Data Validation'), not '__' + 24 | WARNING | [ ] error_reporting() can lead to full path disclosure. + 24 | WARNING | [ ] error_reporting() found. Changing configuration at runtime + | | is rarely necessary. + 34 | ERROR | [x] Expected 1 spaces before closing bracket; 0 found + 39 | WARNING | [ ] Silencing errors is discouraged + 39 | WARNING | [ ] Silencing errors is discouraged + 46 | ERROR | [ ] Inline comments must end in full-stops, exclamation marks, + | | or question marks + 46 | ERROR | [x] There must be no blank line following an inline comment + 63 | WARNING | [ ] Detected access of super global var $_SERVER, probably + | | needs manual inspection. + 63 | ERROR | [ ] Detected usage of a non-validated input variable: $_SERVER + 63 | ERROR | [ ] Missing wp_unslash() before sanitization. + 63 | ERROR | [ ] Detected usage of a non-sanitized input variable: $_SERVER + 74 | ERROR | [ ] Inline comments must end in full-stops, exclamation marks, + | | or question marks + 90 | ERROR | [x] String "Create a Configuration File" does not require + | | double quotes; use single quotes instead + 92 | ERROR | [ ] Expected next thing to be an escaping function (see Codex + | | for 'Data Validation'), not '$die' + 92 | ERROR | [ ] Expected next thing to be an escaping function (see Codex + | | for 'Data Validation'), not '__' -------------------------------------------------------------------------------- - PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY + PHPCBF CAN FIX THE 3 MARKED SNIFF VIOLATIONS AUTOMATICALLY -------------------------------------------------------------------------------- ### PhpStorm -Please see “[PHP Code Sniffer with WordPress Coding Standards Integration](https://confluence.jetbrains.com/display/PhpStorm/WordPress+Development+using+PhpStorm#WordPressDevelopmentusingPhpStorm-PHPCodeSnifferwithWordPressCodingStandardsIntegrationinPhpStorm)” in PhpStorm documentation. +Please see "[PHP Code Sniffer with WordPress Coding Standards Integration](https://confluence.jetbrains.com/display/PhpStorm/WordPress+Development+using+PhpStorm#WordPressDevelopmentusingPhpStorm-PHPCodeSnifferwithWordPressCodingStandardsIntegrationinPhpStorm)" in the PhpStorm documentation. ### Sublime Text @@ -188,15 +212,18 @@ sublime-phpcs is insanely powerful, but if you'd prefer automatic linting, [Subl - Install PHP Sniffer and WordPress Coding Standards per above. - Install [linter-phpcs](https://atom.io/packages/linter-phpcs) via Atom's package manager. - Run `which phpcs` to get your `phpcs` executable path. -- Enter your `phpcs` executable path and one of the coding standards specified above (e.g. `WordPress`, `WordPress-VIP`, etc.). +- Open the linter-phpcs package settings; enter your `phpcs` executable path and one of the coding standards specified above (e.g. `WordPress`, `WordPress-VIP`, etc.). +- Below these settings find the Tab Width setting and change it to `4`. ![Atom Linter WordPress Coding Standards configuration](https://cloud.githubusercontent.com/assets/224636/12740504/ce4e97b8-c941-11e5-8d83-c77a2470d58e.png) ![Atom Linter in action using WordPress Coding Standards](https://cloud.githubusercontent.com/assets/224636/12740542/131c5894-c942-11e5-9e31-5e020c993224.png) +- Note that certain items within PHPCS config file can cause linting to fail, see [linter-phpcs #95](https://github.com/AtomLinter/linter-phpcs/issues/95#issuecomment-316133107) for more details. + ### Visual Studio -Please see “[Setting up PHP CodeSniffer in Visual Studio Code](https://tommcfarlin.com/php-codesniffer-in-visual-studio-code/)”, a tutorial by Tom McFarlin. +Please see "[Setting up PHP CodeSniffer in Visual Studio Code](https://tommcfarlin.com/php-codesniffer-in-visual-studio-code/)", a tutorial by Tom McFarlin. ## Running your code through WPCS automatically using CI tools @@ -220,14 +247,14 @@ matrix: env: SNIFF=1 before_install: - - if [[ "$SNIFF" == "1" ]]; export PHPCS_DIR=/tmp/phpcs; fi - - if [[ "$SNIFF" == "1" ]]; export SNIFFS_DIR=/tmp/sniffs; fi - # Install PHP CodeSniffer. + - if [[ "$SNIFF" == "1" ]]; then export PHPCS_DIR=/tmp/phpcs; fi + - if [[ "$SNIFF" == "1" ]]; then export SNIFFS_DIR=/tmp/sniffs; fi + # Install PHP_CodeSniffer. - if [[ "$SNIFF" == "1" ]]; then git clone -b master --depth 1 https://github.com/squizlabs/PHP_CodeSniffer.git $PHPCS_DIR; fi # Install WordPress Coding Standards. - if [[ "$SNIFF" == "1" ]]; then git clone -b master --depth 1 https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git $SNIFFS_DIR; fi # Set install path for WordPress Coding Standards. - - if [[ "$SNIFF" == "1" ]]; then $PHPCS_DIR/scripts/phpcs --config-set installed_paths $SNIFFS_DIR; fi + - if [[ "$SNIFF" == "1" ]]; then $PHPCS_DIR/bin/phpcs --config-set installed_paths $SNIFFS_DIR; fi # After CodeSniffer install you should refresh your path. - if [[ "$SNIFF" == "1" ]]; then phpenv rehash; fi @@ -237,7 +264,7 @@ script: # for example: `--standard=wpcs.xml`. # You can use any of the normal PHPCS command line arguments in the command: # https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage - - if [[ "$SNIFF" == "1" ]]; then $PHPCS_DIR/scripts/phpcs -p . --standard=WordPress; fi + - if [[ "$SNIFF" == "1" ]]; then $PHPCS_DIR/bin/phpcs -p . --standard=WordPress; fi ``` @@ -248,7 +275,7 @@ You can find information on how to deal with some of the more frequent issues in ## Contributing -See [CONTRIBUTING](CONTRIBUTING.md), including information about [unit testing](CONTRIBUTING.md#unit-testing). +See [CONTRIBUTING](.github/CONTRIBUTING.md), including information about [unit testing](.github/CONTRIBUTING.md#unit-testing) the standard. ## License diff --git a/Test/AllTests.php b/Test/AllTests.php new file mode 100644 index 00000000..de70f8ee --- /dev/null +++ b/Test/AllTests.php @@ -0,0 +1,72 @@ + + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/* Start of WPCS adjustment */ +namespace WordPressCS\Test; + +use WordPressCS\Test\AllSniffs; +use PHP_CodeSniffer_AllTests; +use PHP_CodeSniffer_TestSuite; +/* End of WPCS adjustment */ + +/** + * A test class for running all PHP_CodeSniffer unit tests. + * + * Usage: phpunit AllTests.php + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class AllTests extends PHP_CodeSniffer_AllTests { + + /** + * Add all PHP_CodeSniffer test suites into a single test suite. + * + * @return PHPUnit_Framework_TestSuite + */ + public static function suite() + { + $GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'] = array(); + + // Use a special PHP_CodeSniffer test suite so that we can + // unset our autoload function after the run. + $suite = new PHP_CodeSniffer_TestSuite('PHP CodeSniffer'); + + /* Start of WPCS adjustment */ + // We need to point to the WPCS version of the referenced class + // and we may as well bypass the loading of the PHPCS core unit tests + // while we're at it too. + $suite->addTest(AllSniffs::suite()); + /* End of WPCS adjustment */ + + // Unregister this here because the PEAR tester loads + // all package suites before running then, so our autoloader + // will cause problems for the packages included after us. + spl_autoload_unregister(array('PHP_CodeSniffer', 'autoload')); + + return $suite; + + }//end suite() + + +}//end class diff --git a/Test/Standards/AbstractSniffUnitTest.php b/Test/Standards/AbstractSniffUnitTest.php new file mode 100644 index 00000000..c8e4f7ae --- /dev/null +++ b/Test/Standards/AbstractSniffUnitTest.php @@ -0,0 +1,460 @@ + + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/* Start of WPCS adjustment */ +namespace WordPressCS\Test; + +use PHP_CodeSniffer; +use PHP_CodeSniffer_File; +use PHP_CodeSniffer_Exception; +use PHPUnit_Framework_TestCase; +use DirectoryIterator; +/* End of WPCS adjustment */ + +/** + * An abstract class that all sniff unit tests must extend. + * + * A sniff unit test checks a .inc file for expected violations of a single + * coding standard. Expected errors and warnings that are not found, or + * warnings and errors that are not expected, are considered test failures. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +abstract class AbstractSniffUnitTest extends PHPUnit_Framework_TestCase { + + /** + * Enable or disable the backup and restoration of the $GLOBALS array. + * Overwrite this attribute in a child class of TestCase. + * Setting this attribute in setUp() has no effect! + * + * @var boolean + */ + protected $backupGlobals = false; + + /** + * The PHP_CodeSniffer object used for testing. + * + * @var PHP_CodeSniffer + */ + protected static $phpcs = null; + + /** + * The path to the directory under which the sniff's standard lives. + * + * @var string + */ + public $standardsDir = null; + + + /** + * Sets up this unit test. + * + * @return void + */ + protected function setUp() + { + if (self::$phpcs === null) { + self::$phpcs = new PHP_CodeSniffer(); + } + + $class = get_class($this); + $this->standardsDir = $GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'][$class]; + + }//end setUp() + + + /** + * Get a list of all test files to check. + * + * These will have the same base as the sniff name but different extensions. + * We ignore the .php file as it is the class. + * + * @param string $testFileBase The base path that the unit tests files will have. + * + * @return string[] + */ + protected function getTestFiles($testFileBase) + { + $testFiles = array(); + + $dir = substr($testFileBase, 0, strrpos($testFileBase, DIRECTORY_SEPARATOR)); + $di = new DirectoryIterator($dir); + + foreach ($di as $file) { + $path = $file->getPathname(); + if (substr($path, 0, strlen($testFileBase)) === $testFileBase) { + + /* Start of WPCS adjustment */ + // If we're changing things anyway, we may as well exclude backup files + // from the test runs ;-) + if ($path !== $testFileBase.'php' && substr($path, -5) !== 'fixed' + && substr($path, -3) !== 'bak' && substr($path, -4) !== 'orig' + ) { + $testFiles[] = $path; + } + /* End of WPCS adjustment */ + } + } + + // Put them in order. + sort($testFiles); + + return $testFiles; + + }//end getTestFiles() + + + /** + * Should this test be skipped for some reason. + * + * @return void + */ + protected function shouldSkipTest() + { + return false; + + }//end shouldSkipTest() + + + /** + * Tests the extending classes Sniff class. + * + * @return void + * @throws PHPUnit_Framework_Error + */ + public final function testSniff() + { + // Skip this test if we can't run in this environment. + if ($this->shouldSkipTest() === true) { + $this->markTestSkipped(); + } + + // The basis for determining file locations. + $basename = substr(get_class($this), 0, -8); + + /* Start of WPCS adjustment */ + // Support the use of PHP namespaces. + if (strpos($basename, '\\') !== false) { + $basename = str_replace('\\', '_', $basename); + } + /* End of WPCS adjustment */ + + // The name of the coding standard we are testing. + $standardName = substr($basename, 0, strpos($basename, '_')); + + // The code of the sniff we are testing. + $parts = explode('_', $basename); + $sniffCode = $parts[0].'.'.$parts[2].'.'.$parts[3]; + + $testFileBase = $this->standardsDir.DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $basename).'UnitTest.'; + + // Get a list of all test files to check. + $testFiles = $this->getTestFiles($testFileBase); + + self::$phpcs->initStandard($standardName, array($sniffCode)); + self::$phpcs->setIgnorePatterns(array()); + + $failureMessages = array(); + foreach ($testFiles as $testFile) { + $filename = basename($testFile); + + try { + $cliValues = $this->getCliValues($filename); + self::$phpcs->cli->setCommandLineValues($cliValues); + $phpcsFile = self::$phpcs->processFile($testFile); + } catch (Exception $e) { + $this->fail('An unexpected exception has been caught: '.$e->getMessage()); + } + + $failures = $this->generateFailureMessages($phpcsFile); + $failureMessages = array_merge($failureMessages, $failures); + + if ($phpcsFile->getFixableCount() > 0) { + // Attempt to fix the errors. + $phpcsFile->fixer->fixFile(); + $fixable = $phpcsFile->getFixableCount(); + if ($fixable > 0) { + $failureMessages[] = "Failed to fix $fixable fixable violations in $filename"; + } + + // Check for a .fixed file to check for accuracy of fixes. + $fixedFile = $testFile.'.fixed'; + if (file_exists($fixedFile) === true) { + $diff = $phpcsFile->fixer->generateDiff($fixedFile); + if (trim($diff) !== '') { + $filename = basename($testFile); + $fixedFilename = basename($fixedFile); + $failureMessages[] = "Fixed version of $filename does not match expected version in $fixedFilename; the diff is\n$diff"; + } + } + } + }//end foreach + + if (empty($failureMessages) === false) { + $this->fail(implode(PHP_EOL, $failureMessages)); + } + + }//end runTest() + + + /** + * Generate a list of test failures for a given sniffed file. + * + * @param PHP_CodeSniffer_File $file The file being tested. + * + * @return array + * @throws PHP_CodeSniffer_Exception + */ + public function generateFailureMessages(PHP_CodeSniffer_File $file) + { + $testFile = $file->getFilename(); + + $foundErrors = $file->getErrors(); + $foundWarnings = $file->getWarnings(); + $expectedErrors = $this->getErrorList(basename($testFile)); + $expectedWarnings = $this->getWarningList(basename($testFile)); + + if (is_array($expectedErrors) === false) { + throw new PHP_CodeSniffer_Exception('getErrorList() must return an array'); + } + + if (is_array($expectedWarnings) === false) { + throw new PHP_CodeSniffer_Exception('getWarningList() must return an array'); + } + + /* + We merge errors and warnings together to make it easier + to iterate over them and produce the errors string. In this way, + we can report on errors and warnings in the same line even though + it's not really structured to allow that. + */ + + $allProblems = array(); + $failureMessages = array(); + + foreach ($foundErrors as $line => $lineErrors) { + foreach ($lineErrors as $column => $errors) { + if (isset($allProblems[$line]) === false) { + $allProblems[$line] = array( + 'expected_errors' => 0, + 'expected_warnings' => 0, + 'found_errors' => array(), + 'found_warnings' => array(), + ); + } + + $foundErrorsTemp = array(); + foreach ($allProblems[$line]['found_errors'] as $foundError) { + $foundErrorsTemp[] = $foundError; + } + + $errorsTemp = array(); + foreach ($errors as $foundError) { + $errorsTemp[] = $foundError['message'].' ('.$foundError['source'].')'; + + $source = $foundError['source']; + if (in_array($source, $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES']) === false) { + $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'][] = $source; + } + + if ($foundError['fixable'] === true + && in_array($source, $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES']) === false + ) { + $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'][] = $source; + } + } + + $allProblems[$line]['found_errors'] = array_merge($foundErrorsTemp, $errorsTemp); + }//end foreach + + if (isset($expectedErrors[$line]) === true) { + $allProblems[$line]['expected_errors'] = $expectedErrors[$line]; + } else { + $allProblems[$line]['expected_errors'] = 0; + } + + unset($expectedErrors[$line]); + }//end foreach + + foreach ($expectedErrors as $line => $numErrors) { + if (isset($allProblems[$line]) === false) { + $allProblems[$line] = array( + 'expected_errors' => 0, + 'expected_warnings' => 0, + 'found_errors' => array(), + 'found_warnings' => array(), + ); + } + + $allProblems[$line]['expected_errors'] = $numErrors; + } + + foreach ($foundWarnings as $line => $lineWarnings) { + foreach ($lineWarnings as $column => $warnings) { + if (isset($allProblems[$line]) === false) { + $allProblems[$line] = array( + 'expected_errors' => 0, + 'expected_warnings' => 0, + 'found_errors' => array(), + 'found_warnings' => array(), + ); + } + + $foundWarningsTemp = array(); + foreach ($allProblems[$line]['found_warnings'] as $foundWarning) { + $foundWarningsTemp[] = $foundWarning; + } + + $warningsTemp = array(); + foreach ($warnings as $warning) { + $warningsTemp[] = $warning['message'].' ('.$warning['source'].')'; + } + + $allProblems[$line]['found_warnings'] = array_merge($foundWarningsTemp, $warningsTemp); + }//end foreach + + if (isset($expectedWarnings[$line]) === true) { + $allProblems[$line]['expected_warnings'] = $expectedWarnings[$line]; + } else { + $allProblems[$line]['expected_warnings'] = 0; + } + + unset($expectedWarnings[$line]); + }//end foreach + + foreach ($expectedWarnings as $line => $numWarnings) { + if (isset($allProblems[$line]) === false) { + $allProblems[$line] = array( + 'expected_errors' => 0, + 'expected_warnings' => 0, + 'found_errors' => array(), + 'found_warnings' => array(), + ); + } + + $allProblems[$line]['expected_warnings'] = $numWarnings; + } + + // Order the messages by line number. + ksort($allProblems); + + foreach ($allProblems as $line => $problems) { + $numErrors = count($problems['found_errors']); + $numWarnings = count($problems['found_warnings']); + $expectedErrors = $problems['expected_errors']; + $expectedWarnings = $problems['expected_warnings']; + + $errors = ''; + $foundString = ''; + + if ($expectedErrors !== $numErrors || $expectedWarnings !== $numWarnings) { + $lineMessage = "[LINE $line]"; + $expectedMessage = 'Expected '; + $foundMessage = 'in '.basename($testFile).' but found '; + + if ($expectedErrors !== $numErrors) { + $expectedMessage .= "$expectedErrors error(s)"; + $foundMessage .= "$numErrors error(s)"; + if ($numErrors !== 0) { + $foundString .= 'error(s)'; + $errors .= implode(PHP_EOL.' -> ', $problems['found_errors']); + } + + if ($expectedWarnings !== $numWarnings) { + $expectedMessage .= ' and '; + $foundMessage .= ' and '; + if ($numWarnings !== 0) { + if ($foundString !== '') { + $foundString .= ' and '; + } + } + } + } + + if ($expectedWarnings !== $numWarnings) { + $expectedMessage .= "$expectedWarnings warning(s)"; + $foundMessage .= "$numWarnings warning(s)"; + if ($numWarnings !== 0) { + $foundString .= 'warning(s)'; + if (empty($errors) === false) { + $errors .= PHP_EOL.' -> '; + } + + $errors .= implode(PHP_EOL.' -> ', $problems['found_warnings']); + } + } + + $fullMessage = "$lineMessage $expectedMessage $foundMessage."; + if ($errors !== '') { + $fullMessage .= " The $foundString found were:".PHP_EOL." -> $errors"; + } + + $failureMessages[] = $fullMessage; + }//end if + }//end foreach + + return $failureMessages; + + }//end generateFailureMessages() + + + /** + * Get a list of CLI values to set before the file is tested. + * + * @param string $filename The name of the file being tested. + * + * @return array + */ + public function getCliValues($filename) + { + return array(); + + }//end getCliValues() + + + /** + * Returns the lines where errors should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of errors that should occur on that line. + * + * @return array(int => int) + */ + protected abstract function getErrorList(); + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array(int => int) + */ + protected abstract function getWarningList(); + + +}//end class diff --git a/Test/Standards/AllSniffs.php b/Test/Standards/AllSniffs.php new file mode 100644 index 00000000..ff3d0794 --- /dev/null +++ b/Test/Standards/AllSniffs.php @@ -0,0 +1,157 @@ + + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @link http://pear.php.net/package/PHP_CodeSniffer + */ + +/* Start of WPCS adjustment */ +namespace WordPressCS\Test; + +use PHP_CodeSniffer_Standards_AllSniffs; +use PHP_CodeSniffer; +use PHPUnit_Framework_TestSuite; +use RecursiveIteratorIterator; +use RecursiveDirectoryIterator; +/* End of WPCS adjustment */ + +/** + * A test class for testing all sniffs for installed standards. + * + * Usage: phpunit AllSniffs.php + * + * This test class loads all unit tests for all installed standards into a + * single test suite and runs them. Errors are reported on the command line. + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @author Marc McIntyre + * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @version Release: @package_version@ + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +class AllSniffs extends PHP_CodeSniffer_Standards_AllSniffs +{ + + /** + * Add all sniff unit tests into a test suite. + * + * Sniff unit tests are found by recursing through the 'Tests' directory + * of each installed coding standard. + * + * @return PHPUnit_Framework_TestSuite + */ + public static function suite() + { + $suite = new PHPUnit_Framework_TestSuite('PHP CodeSniffer Standards'); + + /* Start of WPCS adjustment */ + // Set the correct path to PHPCS. + $isInstalled = !is_file(PHPCS_DIR.'/CodeSniffer.php'); + /* End of WPCS adjustment */ + + // Optionally allow for ignoring the tests for one or more standards. + $ignoreTestsForStandards = getenv('PHPCS_IGNORE_TESTS'); + if ($ignoreTestsForStandards === false) { + $ignoreTestsForStandards = array(); + } else { + $ignoreTestsForStandards = explode(',', $ignoreTestsForStandards); + } + + $installedPaths = PHP_CodeSniffer::getInstalledStandardPaths(); + foreach ($installedPaths as $path) { + $path = realpath($path); + $origPath = $path; + $standards = PHP_CodeSniffer::getInstalledStandards(true, $path); + + // If the test is running PEAR installed, the built-in standards + // are split into different directories; one for the sniffs and + // a different file system location for tests. + if ($isInstalled === true + && is_dir($path.DIRECTORY_SEPARATOR.'Generic') === true + ) { + $path = dirname(__FILE__); + } + + foreach ($standards as $standard) { + if (in_array($standard, $ignoreTestsForStandards, true)) { + continue; + } + + $testsDir = $path.DIRECTORY_SEPARATOR.$standard.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR; + + if (is_dir($testsDir) === false) { + // No tests for this standard. + continue; + } + + $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($testsDir)); + + foreach ($di as $file) { + // Skip hidden files. + if (substr($file->getFilename(), 0, 1) === '.') { + continue; + } + + // Tests must have the extension 'php'. + $parts = explode('.', $file); + $ext = array_pop($parts); + if ($ext !== 'php') { + continue; + } + + $filePath = $file->getPathname(); + $className = str_replace($path.DIRECTORY_SEPARATOR, '', $filePath); + $className = substr($className, 0, -4); + $className = str_replace(DIRECTORY_SEPARATOR, '_', $className); + + // Include the sniff here so tests can use it in their setup() methods. + $parts = explode('_', $className); + if (isset($parts[0],$parts[2],$parts[3]) === true) { + $sniffPath = $origPath.DIRECTORY_SEPARATOR.$parts[0].DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[2].DIRECTORY_SEPARATOR.$parts[3]; + $sniffPath = substr($sniffPath, 0, -8).'Sniff.php'; + + if (file_exists($sniffPath) === true) { + include_once $sniffPath; + include_once $filePath; + + /* Start of WPCS adjustment */ + // Support the use of PHP namespaces. If the class name we included + // contains namespace separators instead of underscores, use this as the + // class name from now on. + $classNameNS = str_replace('_', '\\', $className); + if (class_exists($classNameNS, false) === true) { + $className = $classNameNS; + } + /* End of WPCS adjustment */ + + $GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'][$className] = $path; + $suite->addTestSuite($className); + } else { + self::$orphanedTests[] = $filePath; + } + } else { + self::$orphanedTests[] = $filePath; + } + }//end foreach + }//end foreach + }//end foreach + + return $suite; + + }//end suite() + + +}//end class diff --git a/Test/bootstrap.php b/Test/bootstrap.php new file mode 100644 index 00000000..996c887c --- /dev/null +++ b/Test/bootstrap.php @@ -0,0 +1,42 @@ + Non-controversial generally-agreed upon WordPress Coding Standards + ./../WordPress/PHPCSAliases.php + + + + + + + - - - - 0 - + + + + 0 + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - 0 - - - - - - - - - - - - - + + + + + - + - - + + - + - + + + + + + + + + + + - - - + + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - 0 - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + - - + + - - - + + + - - - - + + + + - - + + - - + + - - - - + + + + - + - + - + - + - - + + - - - + + + + + + + + + + + + error + The "goto" language construct should not be used. + + + + + + error + eval() is a security risk so not allowed. + + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + - - + + + + diff --git a/WordPress-Docs/ruleset.xml b/WordPress-Docs/ruleset.xml index a55113ab..05f0ec87 100644 --- a/WordPress-Docs/ruleset.xml +++ b/WordPress-Docs/ruleset.xml @@ -83,14 +83,14 @@ - + - - - + + + diff --git a/WordPress-Extra/ruleset.xml b/WordPress-Extra/ruleset.xml index d82b8631..84514388 100644 --- a/WordPress-Extra/ruleset.xml +++ b/WordPress-Extra/ruleset.xml @@ -2,12 +2,14 @@ Best practices beyond core WordPress Coding Standards + ./../WordPress/PHPCSAliases.php + - + @@ -17,9 +19,14 @@ - + + + + + @@ -30,41 +37,85 @@ - + + + + + + + + + warning + + + warning + + + warning + - - - + + + + + + + warning + Best practice suggestion: Declare only one class in a file. + + + + warning + Best practice suggestion: Declare only one interface in a file. + + + + warning + Best practice suggestion: Declare only one trait in a file. + + + + + + + + + warning + + + + + + 5 + + - + - - - - - - + + + + - - - error - eval() is a security risk so not allowed. - - @@ -75,12 +126,12 @@ - + - + @@ -88,7 +139,7 @@ - + @@ -98,4 +149,21 @@ error + + + + + + + + + + + + diff --git a/WordPress-Theme/ruleset.xml b/WordPress-Theme/ruleset.xml index 1d096d22..3df97bcc 100644 --- a/WordPress-Theme/ruleset.xml +++ b/WordPress-Theme/ruleset.xml @@ -3,6 +3,8 @@ Standards any Theme to be published on wordpress.org should comply with. + ./../WordPress/PHPCSAliases.php + @@ -58,6 +60,7 @@ once the PR has been merged and the miminmum PHPCS version has been increased. https://github.com/squizlabs/PHP_CodeSniffer/pull/1247 --> + warning @@ -77,6 +80,7 @@ once the PR has been merged and the miminmum PHPCS version has been increased. https://github.com/squizlabs/PHP_CodeSniffer/pull/1247 --> + warning @@ -100,4 +104,18 @@ + + + + + error + + + error + + diff --git a/WordPress-VIP/ruleset.xml b/WordPress-VIP/ruleset.xml index 523ede88..9035e489 100644 --- a/WordPress-VIP/ruleset.xml +++ b/WordPress-VIP/ruleset.xml @@ -2,6 +2,8 @@ WordPress VIP Coding Standards + ./../WordPress/PHPCSAliases.php + - + - - - - + - - - - - - - - - error - eval() is a security risk so not allowed. + + + @@ -53,7 +45,7 @@ - + @@ -62,7 +54,7 @@ - + error @@ -72,13 +64,13 @@ - + - + Using cURL functions is highly discouraged within VIP context. Check (Fetching Remote Data) on VIP Documentation. @@ -86,4 +78,8 @@ %s() is highly discouraged, please use vip_safe_wp_remote_get() instead. + + + + diff --git a/WordPress/AbstractArrayAssignmentRestrictionsSniff.php b/WordPress/AbstractArrayAssignmentRestrictionsSniff.php index 8b4da3e9..349995ea 100644 --- a/WordPress/AbstractArrayAssignmentRestrictionsSniff.php +++ b/WordPress/AbstractArrayAssignmentRestrictionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress; + +use WordPress\Sniff; + /** * Restricts array assignment of certain keys. * @@ -18,7 +22,7 @@ * `WordPress_Sniffs_Arrays_ArrayAssignmentRestrictionsSniff` to * `WordPress_AbstractArrayAssignmentRestrictionsSniff`. */ -abstract class WordPress_AbstractArrayAssignmentRestrictionsSniff extends WordPress_Sniff { +abstract class AbstractArrayAssignmentRestrictionsSniff extends Sniff { /** * Exclude groups. @@ -47,12 +51,26 @@ abstract class WordPress_AbstractArrayAssignmentRestrictionsSniff extends WordPr */ protected $excluded_groups = array(); + /** + * Cache for the group information. + * + * @since 0.13.0 + * + * @var array + */ + protected $groups_cache = array(); + /** * Returns an array of tokens this test wants to listen for. * * @return array */ public function register() { + // Retrieve the groups only once and don't set up a listener if there are no groups. + if ( false === $this->setup_groups() ) { + return array(); + } + return array( T_DOUBLE_ARROW, T_CLOSE_SQUARE_BRACKET, @@ -68,18 +86,40 @@ public function register() { * This method should be overridden in extending classes. * * Example: groups => array( - * 'groupname' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'keys' => array( 'key1', 'another_key' ), - * 'callback' => array( 'class', 'method' ), // Optional. - * ) + * 'groupname' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'keys' => array( 'key1', 'another_key' ), + * 'callback' => array( 'class', 'method' ), // Optional. + * ) * ) * * @return array */ abstract public function getGroups(); + /** + * Cache the groups. + * + * @since 0.13.0 + * + * @return bool True if the groups were setup. False if not. + */ + protected function setup_groups() { + $this->groups_cache = $this->getGroups(); + + if ( empty( $this->groups_cache ) && empty( self::$groups ) ) { + return false; + } + + // Allow for adding extra unit tests. + if ( ! empty( self::$groups ) ) { + $this->groups_cache = array_merge( $this->groups_cache, self::$groups ); + } + + return true; + } + /** * Processes this test, when one of its tokens is encountered. * @@ -89,15 +129,8 @@ abstract public function getGroups(); */ public function process_token( $stackPtr ) { - $groups = $this->getGroups(); - - if ( empty( $groups ) ) { - $this->phpcsFile->removeTokenListener( $this, $this->register() ); - return; - } - $this->excluded_groups = $this->merge_custom_array( $this->exclude ); - if ( array_diff_key( $groups, $this->excluded_groups ) === array() ) { + if ( array_diff_key( $this->groups_cache, $this->excluded_groups ) === array() ) { // All groups have been excluded. // Don't remove the listener as the exclude property can be changed inline. return; @@ -116,9 +149,9 @@ public function process_token( $stackPtr ) { $inst = array(); /* - Covers: - $foo = array( 'bar' => 'taz' ); - $foo['bar'] = $taz; + * Covers: + * $foo = array( 'bar' => 'taz' ); + * $foo['bar'] = $taz; */ if ( in_array( $token['code'], array( T_CLOSE_SQUARE_BRACKET, T_DOUBLE_ARROW ), true ) ) { $operator = $stackPtr; // T_DOUBLE_ARROW. @@ -149,7 +182,7 @@ public function process_token( $stackPtr ) { return; } - foreach ( $groups as $groupName => $group ) { + foreach ( $this->groups_cache as $groupName => $group ) { if ( isset( $this->excluded_groups[ $groupName ] ) ) { continue; @@ -184,9 +217,9 @@ public function process_token( $stackPtr ) { ); } } - } // End foreach(). + } - } // End process(). + } // End process_token(). /** * Callback to process each confirmed key, to check value. diff --git a/WordPress/AbstractClassRestrictionsSniff.php b/WordPress/AbstractClassRestrictionsSniff.php index 8ef5d8b7..dd3cef36 100644 --- a/WordPress/AbstractClassRestrictionsSniff.php +++ b/WordPress/AbstractClassRestrictionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Restricts usage of some classes. * @@ -14,10 +18,10 @@ * * @since 0.10.0 */ -abstract class WordPress_AbstractClassRestrictionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +abstract class AbstractClassRestrictionsSniff extends AbstractFunctionRestrictionsSniff { /** - * Regex pattern with placeholder for the function names. + * Regex pattern with placeholder for the class names. * * @var string */ @@ -36,11 +40,11 @@ abstract class WordPress_AbstractClassRestrictionsSniff extends WordPress_Abstra * This method should be overridden in extending classes. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Avoid direct calls to the database.', - * 'classes' => array( 'PDO', '\Namespace\Classname' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Avoid direct calls to the database.', + * 'classes' => array( 'PDO', '\Namespace\Classname' ), + * ) * ) * * You can use * wildcards to target a group of (namespaced) classes. @@ -76,6 +80,10 @@ public function register() { /** * Processes this test, when one of its tokens is encountered. * + * {@internal Unlike in the `WordPress_AbstractFunctionRestrictionsSniff`, + * we can't do a preliminary check on classes as at this point + * we don't know the class name yet.}} + * * @param int $stackPtr The position of the current token in the stack. * * @return int|void Integer stack pointer to skip forward or void to continue @@ -85,7 +93,16 @@ public function process_token( $stackPtr ) { // Reset the temporary storage before processing the token. unset( $this->classname ); - return parent::process_token( $stackPtr ); + $this->excluded_groups = $this->merge_custom_array( $this->exclude ); + if ( array_diff_key( $this->groups, $this->excluded_groups ) === array() ) { + // All groups have been excluded. + // Don't remove the listener as the exclude property can be changed inline. + return; + } + + if ( true === $this->is_targetted_token( $stackPtr ) ) { + return $this->check_for_matches( $stackPtr ); + } } /** @@ -104,9 +121,9 @@ public function is_targetted_token( $stackPtr ) { if ( in_array( $token['code'], array( T_NEW, T_EXTENDS, T_IMPLEMENTS ), true ) ) { if ( T_NEW === $token['code'] ) { - $nameEnd = ( $this->phpcsFile->findNext( array( T_OPEN_PARENTHESIS, T_WHITESPACE, T_SEMICOLON, T_OBJECT_OPERATOR ), ( $stackPtr + 2 ) ) - 1 ); + $nameEnd = ( $this->phpcsFile->findNext( array( T_OPEN_PARENTHESIS, T_WHITESPACE, T_SEMICOLON, T_OBJECT_OPERATOR ), ( $stackPtr + 2 ) ) - 1 ); } else { - $nameEnd = ( $this->phpcsFile->findNext( array( T_CLOSE_CURLY_BRACKET, T_WHITESPACE ), ( $stackPtr + 2 ) ) - 1 ); + $nameEnd = ( $this->phpcsFile->findNext( array( T_CLOSE_CURLY_BRACKET, T_WHITESPACE ), ( $stackPtr + 2 ) ) - 1 ); } $length = ( $nameEnd - ( $stackPtr + 1 ) ); @@ -120,7 +137,7 @@ public function is_targetted_token( $stackPtr ) { if ( T_DOUBLE_COLON === $token['code'] ) { $nameEnd = $this->phpcsFile->findPrevious( T_STRING, ( $stackPtr - 1 ) ); $nameStart = ( $this->phpcsFile->findPrevious( array( T_STRING, T_NS_SEPARATOR, T_NAMESPACE ), ( $nameEnd - 1 ), null, true, null, true ) + 1 ); - $length = ( $nameEnd - ( $nameStart - 1) ); + $length = ( $nameEnd - ( $nameStart - 1 ) ); $classname = $this->phpcsFile->getTokensAsString( $nameStart, $length ); if ( T_NS_SEPARATOR !== $this->tokens[ $nameStart ]['code'] ) { @@ -189,11 +206,7 @@ public function check_for_matches( $stackPtr ) { */ protected function prepare_name_for_regex( $classname ) { $classname = trim( $classname, '\\' ); // Make sure all classnames have a \ prefix, but only one. - $classname = str_replace( array( '.*', '*' ) , '#', $classname ); // Replace wildcards with placeholder. - $classname = preg_quote( $classname, '`' ); - $classname = str_replace( '#', '.*', $classname ); // Replace placeholder with regex wildcard. - - return $classname; + return parent::prepare_name_for_regex( $classname ); } /** @@ -232,47 +245,4 @@ protected function get_namespaced_classname( $classname, $search_from ) { return $classname; } - /** - * Determine the namespace name based on whether this is a scoped namespace or a file namespace. - * - * @param int $search_from The token position to search up from. - * @return string Namespace name or empty string if it couldn't be determined or no namespace applied. - */ - protected function determine_namespace( $search_from ) { - $namespace = ''; - - if ( ! empty( $this->tokens[ $search_from ]['conditions'] ) ) { - // Scoped namespace {}. - foreach ( $this->tokens[ $search_from ]['conditions'] as $pointer => $type ) { - if ( T_NAMESPACE === $type && $this->tokens[ $pointer ]['scope_closer'] > $search_from ) { - $namespace = $this->get_namespace_name( $pointer ); - } - break; // We only need to check the highest level condition. - } - } else { - // Let's see if we can find a file namespace instead. - $first = $this->phpcsFile->findNext( T_NAMESPACE, 0, $search_from ); - - if ( false !== $first && empty( $this->tokens[ $first ]['scope_condition'] ) ) { - $namespace = $this->get_namespace_name( $first ); - } - } - - return $namespace; - } - - /** - * Get the namespace name based on the position of the namespace scope opener. - * - * @param int $namespace_token The token position to search from. - * @return string Namespace name. - */ - protected function get_namespace_name( $namespace_token ) { - $nameEnd = ( $this->phpcsFile->findNext( array( T_OPEN_CURLY_BRACKET, T_WHITESPACE, T_SEMICOLON ), ( $namespace_token + 2 ) ) - 1 ); - $length = ( $nameEnd - ( $namespace_token + 1 ) ); - $namespace = $this->phpcsFile->getTokensAsString( ( $namespace_token + 2 ), $length ); - - return $namespace; - } - } // End class. diff --git a/WordPress/AbstractFunctionParameterSniff.php b/WordPress/AbstractFunctionParameterSniff.php index f9e7fbc6..b201354a 100644 --- a/WordPress/AbstractFunctionParameterSniff.php +++ b/WordPress/AbstractFunctionParameterSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Advises about parameters used in function calls. * @@ -14,7 +18,7 @@ * * @since 0.11.0 */ -abstract class WordPress_AbstractFunctionParameterSniff extends WordPress_AbstractFunctionRestrictionsSniff { +abstract class AbstractFunctionParameterSniff extends AbstractFunctionRestrictionsSniff { /** * The group name for this group of functions. diff --git a/WordPress/AbstractFunctionRestrictionsSniff.php b/WordPress/AbstractFunctionRestrictionsSniff.php index 14e55c51..cba5f6bb 100644 --- a/WordPress/AbstractFunctionRestrictionsSniff.php +++ b/WordPress/AbstractFunctionRestrictionsSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Restricts usage of some functions. * @@ -19,7 +24,7 @@ * `WordPress_AbstractFunctionRestrictionsSniff`. * @since 0.11.0 Extends the WordPress_Sniff class. */ -abstract class WordPress_AbstractFunctionRestrictionsSniff extends WordPress_Sniff { +abstract class AbstractFunctionRestrictionsSniff extends Sniff { /** * Exclude groups. @@ -68,6 +73,15 @@ abstract class WordPress_AbstractFunctionRestrictionsSniff extends WordPress_Sni */ protected $excluded_groups = array(); + /** + * Regex containing the name of all functions handled by a sniff. + * + * Set in `register()` and used to do an initial check. + * + * @var string + */ + private $prelim_check_regex; + /** * Groups of functions to restrict. * @@ -129,12 +143,14 @@ protected function setup_groups( $key ) { $this->groups = array_merge( $this->groups, self::$unittest_groups ); } + $all_items = array(); foreach ( $this->groups as $groupName => $group ) { if ( empty( $group[ $key ] ) ) { unset( $this->groups[ $groupName ] ); } else { - $items = array_map( array( $this, 'prepare_name_for_regex' ), $group[ $key ] ); - $items = implode( '|', $items ); + $items = array_map( array( $this, 'prepare_name_for_regex' ), $group[ $key ] ); + $all_items[] = $items; + $items = implode( '|', $items ); $this->groups[ $groupName ]['regex'] = sprintf( $this->regex_pattern, $items ); } @@ -144,6 +160,11 @@ protected function setup_groups( $key ) { return false; } + // Create one "super-regex" to allow for initial filtering. + $all_items = call_user_func_array( 'array_merge', $all_items ); + $all_items = implode( '|', array_unique( $all_items ) ); + $this->prelim_check_regex = sprintf( $this->regex_pattern, $all_items ); + return true; } @@ -164,11 +185,18 @@ public function process_token( $stackPtr ) { return; } + // Preliminary check. If the content of the T_STRING is not one of the functions we're + // looking for, we can bow out before doing the heavy lifting of checking whether + // this is a function call. + if ( preg_match( $this->prelim_check_regex, $this->tokens[ $stackPtr ]['content'] ) !== 1 ) { + return; + } + if ( true === $this->is_targetted_token( $stackPtr ) ) { return $this->check_for_matches( $stackPtr ); } - } // End process(). + } // End process_token(). /** * Verify is the current token is a function call. @@ -183,7 +211,7 @@ public function is_targetted_token( $stackPtr ) { // Exclude function definitions, class methods, and namespaced calls. if ( T_STRING === $this->tokens[ $stackPtr ]['code'] && isset( $this->tokens[ ( $stackPtr - 1 ) ] ) ) { - $prev = $this->phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + $prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); if ( false !== $prev ) { // Skip sniffing if calling a same-named method, or on function definitions. @@ -199,7 +227,7 @@ public function is_targetted_token( $stackPtr ) { // Skip namespaced functions, ie: \foo\bar() not \bar(). if ( T_NS_SEPARATOR === $this->tokens[ $prev ]['code'] ) { - $pprev = $this->phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$emptyTokens, ( $prev - 1 ), null, true ); + $pprev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $prev - 1 ), null, true ); if ( false !== $pprev && T_STRING === $this->tokens[ $pprev ]['code'] ) { return false; } diff --git a/WordPress/AbstractVariableRestrictionsSniff.php b/WordPress/AbstractVariableRestrictionsSniff.php index 821e3ddf..5ed71212 100644 --- a/WordPress/AbstractVariableRestrictionsSniff.php +++ b/WordPress/AbstractVariableRestrictionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress; + +use WordPress\Sniff; + /** * Restricts usage of some variables. * @@ -19,7 +23,7 @@ * `WordPress_AbstractVariableRestrictionsSniff`. * @since 0.11.0 Extends the WordPress_Sniff class. */ -abstract class WordPress_AbstractVariableRestrictionsSniff extends WordPress_Sniff { +abstract class AbstractVariableRestrictionsSniff extends Sniff { /** * Exclude groups. @@ -48,12 +52,26 @@ abstract class WordPress_AbstractVariableRestrictionsSniff extends WordPress_Sni */ protected $excluded_groups = array(); + /** + * Cache for the group information. + * + * @since 0.13.0 + * + * @var array + */ + protected $groups_cache = array(); + /** * Returns an array of tokens this test wants to listen for. * * @return array */ public function register() { + // Retrieve the groups only once and don't set up a listener if there are no groups. + if ( false === $this->setup_groups() ) { + return array(); + } + return array( T_VARIABLE, T_OBJECT_OPERATOR, @@ -71,19 +89,41 @@ public function register() { * This method should be overridden in extending classes. * * Example: groups => array( - * 'wpdb' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'variables' => array( '$val', '$var' ), - * 'object_vars' => array( '$foo->bar', .. ), - * 'array_members' => array( '$foo['bar']', .. ), - * ) + * 'wpdb' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'variables' => array( '$val', '$var' ), + * 'object_vars' => array( '$foo->bar', .. ), + * 'array_members' => array( '$foo['bar']', .. ), + * ) * ) * * @return array */ abstract public function getGroups(); + /** + * Cache the groups. + * + * @since 0.13.0 + * + * @return bool True if the groups were setup. False if not. + */ + protected function setup_groups() { + $this->groups_cache = $this->getGroups(); + + if ( empty( $this->groups_cache ) && empty( self::$groups ) ) { + return false; + } + + // Allow for adding extra unit tests. + if ( ! empty( self::$groups ) ) { + $this->groups_cache = array_merge( $this->groups_cache, self::$groups ); + } + + return true; + } + /** * Processes this test, when one of its tokens is encountered. * @@ -94,16 +134,10 @@ abstract public function getGroups(); */ public function process_token( $stackPtr ) { - $token = $this->tokens[ $stackPtr ]; - $groups = $this->getGroups(); - - if ( empty( $groups ) ) { - $this->phpcsFile->removeTokenListener( $this, $this->register() ); - return; - } + $token = $this->tokens[ $stackPtr ]; $this->excluded_groups = $this->merge_custom_array( $this->exclude ); - if ( array_diff_key( $groups, $this->excluded_groups ) === array() ) { + if ( array_diff_key( $this->groups_cache, $this->excluded_groups ) === array() ) { // All groups have been excluded. // Don't remove the listener as the exclude property can be changed inline. return; @@ -118,7 +152,7 @@ public function process_token( $stackPtr ) { } } - foreach ( $groups as $groupName => $group ) { + foreach ( $this->groups_cache as $groupName => $group ) { if ( isset( $this->excluded_groups[ $groupName ] ) ) { continue; @@ -180,9 +214,9 @@ public function process_token( $stackPtr ) { return; // Show one error only. - } // End foreach(). + } - } // End process(). + } // End process_token(). /** * Transform a wildcard pattern to a usable regex pattern. diff --git a/WordPress/PHPCSAliases.php b/WordPress/PHPCSAliases.php new file mode 100644 index 00000000..affacaa7 --- /dev/null +++ b/WordPress/PHPCSAliases.php @@ -0,0 +1,81 @@ +` ruleset directive. + * + * {@internal The PHPCS files have been reorganized in PHPCS 3.x, quite + * a few "old" classes have been split and spread out over several "new" + * classes. In other words, this will only work for a limited number + * of classes.}} + * + * {@internal The `class_exists` wrappers are needed to play nice with other + * external PHPCS standards creating cross-version compatibility in the same + * manner.}} + */ +if ( ! defined( 'WPCS_PHPCS_ALIASES_SET' ) ) { + // PHPCS base classes/interface. + if ( ! interface_exists( '\PHP_CodeSniffer_Sniff' ) ) { + class_alias( 'PHP_CodeSniffer\Sniffs\Sniff', '\PHP_CodeSniffer_Sniff' ); + } + if ( ! class_exists( '\PHP_CodeSniffer_File' ) ) { + class_alias( 'PHP_CodeSniffer\Files\File', '\PHP_CodeSniffer_File' ); + } + if ( ! class_exists( '\PHP_CodeSniffer_Tokens' ) ) { + class_alias( 'PHP_CodeSniffer\Util\Tokens', '\PHP_CodeSniffer_Tokens' ); + } + + // PHPCS classes which are being extended by WPCS sniffs. + if ( ! class_exists( '\PHP_CodeSniffer_Standards_AbstractVariableSniff' ) ) { + class_alias( 'PHP_CodeSniffer\Sniffs\AbstractVariableSniff', '\PHP_CodeSniffer_Standards_AbstractVariableSniff' ); + } + if ( ! class_exists( '\PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff' ) ) { + class_alias( 'PHP_CodeSniffer\Standards\PEAR\Sniffs\NamingConventions\ValidFunctionNameSniff', '\PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff' ); + } + if ( ! class_exists( '\Squiz_Sniffs_WhiteSpace_OperatorSpacingSniff' ) ) { + class_alias( 'PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\OperatorSpacingSniff', '\Squiz_Sniffs_WhiteSpace_OperatorSpacingSniff' ); + } + if ( ! class_exists( '\Squiz_Sniffs_WhiteSpace_SemicolonSpacingSniff' ) ) { + class_alias( 'PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\SemicolonSpacingSniff', '\Squiz_Sniffs_WhiteSpace_SemicolonSpacingSniff' ); + } + + define( 'WPCS_PHPCS_ALIASES_SET', true ); + + /* + * Register our own autoloader for the WPCS abstract classes & the helper class. + * + * This can be removed once the minimum required version of WPCS for the + * PHPCS 3.x branch has gone up to 3.1.0 (unreleased as of yet) or + * whichever version contains the fix for upstream #1591. + * + * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1564 + * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1591 + */ + spl_autoload_register( function ( $class ) { + // Only try & load our own classes. + if ( stripos( $class, 'WordPress' ) !== 0 ) { + return; + } + + // PHPCS handles the Test and Sniff classes without problem. + if ( stripos( $class, '\Tests\\' ) !== false || stripos( $class, '\Sniffs\\' ) !== false ) { + return; + } + + $file = dirname( __DIR__ ) . DIRECTORY_SEPARATOR . strtr( $class, '\\', DIRECTORY_SEPARATOR ) . '.php'; + + if ( file_exists( $file ) ) { + include_once $file; + } + } ); +} diff --git a/WordPress/PHPCSHelper.php b/WordPress/PHPCSHelper.php new file mode 100644 index 00000000..96961837 --- /dev/null +++ b/WordPress/PHPCSHelper.php @@ -0,0 +1,141 @@ +config->tabWidth ) && $phpcsFile->config->tabWidth > 0 ) { + $tab_width = $phpcsFile->config->tabWidth; + } + } else { + // PHPCS 2.x. + $cli_values = $phpcsFile->phpcs->cli->getCommandLineValues(); + if ( isset( $cli_values['tabWidth'] ) && $cli_values['tabWidth'] > 0 ) { + $tab_width = $cli_values['tabWidth']; + } + } + + return $tab_width; + } + + /** + * Check whether the `--ignore-annotations` option has been used. + * + * @since 0.13.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile Optional. The current file being processed. + * + * @return bool True if annotations should be ignored, false otherwise. + */ + public static function ignore_annotations( File $phpcsFile = null ) { + if ( class_exists( '\PHP_CodeSniffer\Config' ) ) { + // PHPCS 3.x. + if ( isset( $phpcsFile, $phpcsFile->config->annotations ) ) { + return ! $phpcsFile->config->annotations; + } else { + $annotations = \PHP_CodeSniffer\Config::getConfigData( 'annotations' ); + if ( isset( $annotations ) ) { + return ! $annotations; + } + } + } + + // PHPCS 2.x does not support `--ignore-annotations`. + return false; + } + +} diff --git a/WordPress/Sniff.php b/WordPress/Sniff.php index 1ee522a4..01d2e19f 100644 --- a/WordPress/Sniff.php +++ b/WordPress/Sniff.php @@ -7,6 +7,13 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress; + +use PHP_CodeSniffer_Sniff as PHPCS_Sniff; +use PHP_CodeSniffer_File as File; +use PHP_CodeSniffer_Tokens as Tokens; +use WordPress\PHPCSHelper; + /** * Represents a PHP_CodeSniffer sniff for sniffing WordPress coding standards. * @@ -14,8 +21,86 @@ * * @package WPCS\WordPressCodingStandards * @since 0.4.0 + * + * {@internal This class contains numerous properties where the array format looks + * like `'string' => true`, i.e. the array item is set as the array key. + * This allows for sniffs to verify whether something is in one of these + * lists using `isset()` rather than `in_array()` which is a much more + * efficient (faster) check to execute and therefore improves the + * performance of the sniffs. + * The `true` value in those cases is used as a placeholder and has no + * meaning in and of itself. + * In the rare few cases where the array values *do* have meaning, this + * is documented in the property documentation.}} */ -abstract class WordPress_Sniff implements PHP_CodeSniffer_Sniff { +abstract class Sniff implements PHPCS_Sniff { + + /** + * Regex to get complex variables from T_DOUBLE_QUOTED_STRING or T_HEREDOC. + * + * @since 0.14.0 + * + * @var string + */ + const REGEX_COMPLEX_VARS = '`(?:(\{)?(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:->\$?(?P>varname)|\[[^\]]+\]|::\$?(?P>varname)|\([^\)]*\))*(?(3)\}|)(?(2)\}|)(?(1)\}|)`'; + + /** + * Minimum supported WordPress version. + * + * Currently used by the `WordPress.WP.DeprecatedClasses`, + * `WordPress.WP.DeprecatedFunctions` and the `WordPress.WP.DeprecatedParameter` sniff. + * + * These sniffs will throw an error when usage of a deprecated class/function/parameter + * is detected if the class/function/parameter was deprecated before the minimum + * supported WP version; a warning otherwise. + * By default, it is set to presume that a project will support the current + * WP version and up to three releases before. + * + * This property allows changing the minimum supported WP version used by + * these sniffs by setting a property in a custom phpcs.xml ruleset. + * This property will need to be set for each sniff which uses it. + * + * Example usage: + * + * + * + * + * + * + * Alternatively, the value can be passed in one go for all sniff using it via + * the command line or by setting a `` value in a custom phpcs.xml ruleset. + * Note: the `_wp_` in the command line property name! + * + * CL: `phpcs --runtime-set minimum_supported_wp_version 4.5` + * Ruleset: `` + * + * @since 0.14.0 Previously the individual sniffs each contained this property. + * + * @var string WordPress version. + */ + public $minimum_supported_version = '4.5'; + + /** + * Custom list of classes which test classes can extend. + * + * This property allows end-users to add to the $test_class_whitelist via their ruleset. + * This property will need to be set for each sniff which uses the + * `is_test_class()` method. + * Currently the method is used by the `WordPress.Variables.GlobalVariables`, + * `WordPress.NamingConventions.PrefixAllGlobals` and the `WordPress.Files.Filename` sniffs. + * + * Example usage: + * + * + * + * + * + * + * @since 0.11.0 + * + * @var string|string[] + */ + public $custom_test_class_whitelist = array(); /** * List of the functions which verify nonces. @@ -258,7 +343,7 @@ abstract class WordPress_Sniff implements PHP_CodeSniffer_Sniff { 'sanitize_email' => true, 'sanitize_file_name' => true, 'sanitize_hex_color_no_hash' => true, - 'sanitize_hex_color' => true, + 'sanitize_hex_color' => true, 'sanitize_html_class' => true, 'sanitize_meta' => true, 'sanitize_mime_type' => true, @@ -267,6 +352,7 @@ abstract class WordPress_Sniff implements PHP_CodeSniffer_Sniff { 'sanitize_term_field' => true, 'sanitize_term' => true, 'sanitize_text_field' => true, + 'sanitize_textarea_field' => true, 'sanitize_title_for_query' => true, 'sanitize_title_with_dashes' => true, 'sanitize_title' => true, @@ -423,18 +509,18 @@ abstract class WordPress_Sniff implements PHP_CodeSniffer_Sniff { * @var array */ protected $cacheDeleteFunctions = array( - 'wp_cache_delete' => true, - 'clean_attachment_cache' => true, - 'clean_blog_cache' => true, - 'clean_bookmark_cache' => true, - 'clean_category_cache' => true, - 'clean_comment_cache' => true, - 'clean_network_cache' => true, + 'wp_cache_delete' => true, + 'clean_attachment_cache' => true, + 'clean_blog_cache' => true, + 'clean_bookmark_cache' => true, + 'clean_category_cache' => true, + 'clean_comment_cache' => true, + 'clean_network_cache' => true, 'clean_object_term_cache' => true, - 'clean_page_cache' => true, - 'clean_post_cache' => true, - 'clean_term_cache' => true, - 'clean_user_cache' => true, + 'clean_page_cache' => true, + 'clean_post_cache' => true, + 'clean_term_cache' => true, + 'clean_user_cache' => true, ); /** @@ -478,46 +564,298 @@ abstract class WordPress_Sniff implements PHP_CodeSniffer_Sniff { ); /** - * Whitelist of classes which test classes can extend. + * List of global WP variables. * - * @since 0.11.0 + * @since 0.3.0 + * @since 0.11.0 Changed visibility from public to protected. + * @since 0.12.0 Renamed from `$globals` to `$wp_globals` to be more descriptive. + * @since 0.12.0 Moved from WordPress_Sniffs_Variables_GlobalVariablesSniff to WordPress_Sniff * - * @var string[] + * @var array */ - protected $test_class_whitelist = array( - 'WP_UnitTestCase' => true, - 'PHPUnit_Framework_TestCase' => true, - 'PHPUnit\Framework\TestCase' => true, + protected $wp_globals = array( + '_links_add_base' => true, + '_links_add_target' => true, + '_menu_item_sort_prop' => true, + '_nav_menu_placeholder' => true, + '_new_bundled_files' => true, + '_old_files' => true, + '_parent_pages' => true, + '_registered_pages' => true, + '_updated_user_settings' => true, + '_wp_additional_image_sizes' => true, + '_wp_admin_css_colors' => true, + '_wp_default_headers' => true, + '_wp_deprecated_widgets_callbacks' => true, + '_wp_last_object_menu' => true, + '_wp_last_utility_menu' => true, + '_wp_menu_nopriv' => true, + '_wp_nav_menu_max_depth' => true, + '_wp_post_type_features' => true, + '_wp_real_parent_file' => true, + '_wp_registered_nav_menus' => true, + '_wp_sidebars_widgets' => true, + '_wp_submenu_nopriv' => true, + '_wp_suspend_cache_invalidation' => true, + '_wp_theme_features' => true, + '_wp_using_ext_object_cache' => true, + 'action' => true, + 'active_signup' => true, + 'admin_body_class' => true, + 'admin_page_hooks' => true, + 'all_links' => true, + 'allowedentitynames' => true, + 'allowedposttags' => true, + 'allowedtags' => true, + 'auth_secure_cookie' => true, + 'authordata' => true, + 'avail_post_mime_types' => true, + 'avail_post_stati' => true, + 'blog_id' => true, + 'blog_title' => true, + 'blogname' => true, + 'cat' => true, + 'cat_id' => true, + 'charset_collate' => true, + 'comment' => true, + 'comment_alt' => true, + 'comment_depth' => true, + 'comment_status' => true, + 'comment_thread_alt' => true, + 'comment_type' => true, + 'comments' => true, + 'compress_css' => true, + 'compress_scripts' => true, + 'concatenate_scripts' => true, + 'current_screen' => true, + 'current_site' => true, + 'current_user' => true, + 'currentcat' => true, + 'currentday' => true, + 'currentmonth' => true, + 'custom_background' => true, + 'custom_image_header' => true, + 'default_menu_order' => true, + 'descriptions' => true, + 'domain' => true, + 'editor_styles' => true, + 'error' => true, + 'errors' => true, + 'EZSQL_ERROR' => true, + 'feeds' => true, + 'GETID3_ERRORARRAY' => true, + 'hook_suffix' => true, + 'HTTP_RAW_POST_DATA' => true, + 'id' => true, + 'in_comment_loop' => true, + 'interim_login' => true, + 'is_apache' => true, + 'is_chrome' => true, + 'is_gecko' => true, + 'is_IE' => true, + 'is_IIS' => true, + 'is_iis7' => true, + 'is_macIE' => true, + 'is_NS4' => true, + 'is_opera' => true, + 'is_safari' => true, + 'is_winIE' => true, + 'l10n' => true, + 'link' => true, + 'link_id' => true, + 'locale' => true, + 'locked_post_status' => true, + 'lost' => true, + 'm' => true, + 'map' => true, + 'menu' => true, + 'menu_order' => true, + 'merged_filters' => true, + 'mode' => true, + 'monthnum' => true, + 'more' => true, + 'multipage' => true, + 'names' => true, + 'nav_menu_selected_id' => true, + 'new_whitelist_options' => true, + 'numpages' => true, + 'one_theme_location_no_menus' => true, + 'opml' => true, + 'order' => true, + 'orderby' => true, + 'overridden_cpage' => true, + 'page' => true, + 'paged' => true, + 'pagenow' => true, + 'pages' => true, + 'parent_file' => true, + 'pass_allowed_html' => true, + 'pass_allowed_protocols' => true, + 'path' => true, + 'per_page' => true, + 'PHP_SELF' => true, + 'phpmailer' => true, + 'plugin_page' => true, + 'plugins' => true, + 'post' => true, + 'post_default_category' => true, + 'post_default_title' => true, + 'post_ID' => true, + 'post_id' => true, + 'post_mime_types' => true, + 'post_type' => true, + 'post_type_object' => true, + 'posts' => true, + 'preview' => true, + 'previouscat' => true, + 'previousday' => true, + 'previousweekday' => true, + 'redir_tab' => true, + 'required_mysql_version' => true, + 'required_php_version' => true, + 'rnd_value' => true, + 'role' => true, + 's' => true, + 'search' => true, + 'self' => true, + 'shortcode_tags' => true, + 'show_admin_bar' => true, + 'sidebars_widgets' => true, + 'status' => true, + 'submenu' => true, + 'submenu_file' => true, + 'super_admins' => true, + 'tab' => true, + 'table_prefix' => true, + 'tabs' => true, + 'tag' => true, + 'targets' => true, + 'tax' => true, + 'taxnow' => true, + 'taxonomy' => true, + 'term' => true, + 'text_direction' => true, + 'theme_field_defaults' => true, + 'themes_allowedtags' => true, + 'timeend' => true, + 'timestart' => true, + 'tinymce_version' => true, + 'title' => true, + 'totals' => true, + 'type' => true, + 'typenow' => true, + 'updated_timestamp' => true, + 'upgrading' => true, + 'urls' => true, + 'user_email' => true, + 'user_ID' => true, + 'user_identity' => true, + 'user_level' => true, + 'user_login' => true, + 'user_url' => true, + 'userdata' => true, + 'usersearch' => true, + 'whitelist_options' => true, + 'withcomments' => true, + 'wp' => true, + 'wp_actions' => true, + 'wp_admin_bar' => true, + 'wp_cockneyreplace' => true, + 'wp_current_db_version' => true, + 'wp_current_filter' => true, + 'wp_customize' => true, + 'wp_dashboard_control_callbacks' => true, + 'wp_db_version' => true, + 'wp_did_header' => true, + 'wp_embed' => true, + 'wp_file_descriptions' => true, + 'wp_filesystem' => true, + 'wp_filter' => true, + 'wp_hasher' => true, + 'wp_header_to_desc' => true, + 'wp_importers' => true, + 'wp_json' => true, + 'wp_list_table' => true, + 'wp_local_package' => true, + 'wp_locale' => true, + 'wp_meta_boxes' => true, + 'wp_object_cache' => true, + 'wp_plugin_paths' => true, + 'wp_post_statuses' => true, + 'wp_post_types' => true, + 'wp_queries' => true, + 'wp_query' => true, + 'wp_registered_sidebars' => true, + 'wp_registered_widget_controls' => true, + 'wp_registered_widget_updates' => true, + 'wp_registered_widgets' => true, + 'wp_rewrite' => true, + 'wp_rich_edit' => true, + 'wp_rich_edit_exists' => true, + 'wp_roles' => true, + 'wp_scripts' => true, + 'wp_settings_errors' => true, + 'wp_settings_fields' => true, + 'wp_settings_sections' => true, + 'wp_smiliessearch' => true, + 'wp_styles' => true, + 'wp_taxonomies' => true, + 'wp_the_query' => true, + 'wp_theme_directories' => true, + 'wp_themes' => true, + 'wp_user_roles' => true, + 'wp_version' => true, + 'wp_widget_factory' => true, + 'wp_xmlrpc_server' => true, + 'wpcommentsjavascript' => true, + 'wpcommentspopupfile' => true, + 'wpdb' => true, + 'wpsmiliestrans' => true, + 'year' => true, ); /** - * Custom list of classes which test classes can extend. + * A list of superglobals that incorporate user input. * - * This property allows end-users to add to the $test_class_whitelist via their ruleset. - * This property will need to be set for each sniff which uses the - * `is_test_class()` method. - * Currently the method is used by the `WordPress.Variables.GlobalVariables` - * and the `WordPress.Files.Filename` sniffs. + * @since 0.5.0 + * @since 0.11.0 Changed from static to non-static. * - * Example usage: - * - * - * - * - * + * @var string[] + */ + protected $input_superglobals = array( + '$_COOKIE', + '$_GET', + '$_FILES', + '$_POST', + '$_REQUEST', + '$_SERVER', + ); + + /** + * Whitelist of classes which test classes can extend. * * @since 0.11.0 * - * @var string|string[] + * @var string[] */ - public $custom_test_class_whitelist = array(); + protected $test_class_whitelist = array( + 'WP_UnitTestCase' => true, + 'WP_Ajax_UnitTestCase' => true, + 'WP_Canonical_UnitTestCase' => true, + 'WP_Test_REST_TestCase' => true, + 'WP_Test_REST_Controller_Testcase' => true, + 'WP_Test_REST_Post_Type_Controller_Testcase' => true, + 'WP_XMLRPC_UnitTestCase' => true, + 'PHPUnit_Framework_TestCase' => true, + 'PHPUnit\Framework\TestCase' => true, + ); /** * The current file being sniffed. * * @since 0.4.0 * - * @var PHP_CodeSniffer_File + * @var \PHP_CodeSniffer\Files\File */ protected $phpcsFile; @@ -530,36 +868,19 @@ abstract class WordPress_Sniff implements PHP_CodeSniffer_Sniff { */ protected $tokens; - /** - * A list of superglobals that incorporate user input. - * - * @since 0.5.0 - * @since 0.11.0 Changed from static to non-static. - * - * @var string[] - */ - protected $input_superglobals = array( - '$_COOKIE', - '$_GET', - '$_FILES', - '$_POST', - '$_REQUEST', - '$_SERVER', - ); - /** * Set sniff properties and hand off to child class for processing of the token. * * @since 0.11.0 * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. * * @return int|void Integer stack pointer to skip forward or void to continue * normal file processing. */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { + public function process( File $phpcsFile, $stackPtr ) { $this->init( $phpcsFile ); return $this->process_token( $stackPtr ); } @@ -584,9 +905,9 @@ abstract public function process_token( $stackPtr ); * * @since 0.4.0 * - * @param PHP_CodeSniffer_File $phpcsFile The file currently being processed. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file currently being processed. */ - protected function init( PHP_CodeSniffer_File $phpcsFile ) { + protected function init( File $phpcsFile ) { $this->phpcsFile = $phpcsFile; $this->tokens = $phpcsFile->getTokens(); } @@ -732,9 +1053,12 @@ public static function merge_custom_array( $custom, $base = array(), $flip = tru // Allow for a comma delimited list. if ( is_string( $custom ) ) { - $custom = array_filter( array_map( 'trim', explode( ',', $custom ) ) ); + $custom = explode( ',', $custom ); } + // Always trim whitespace from the values. + $custom = array_filter( array_map( 'trim', $custom ) ); + if ( true === $flip ) { $custom = array_fill_keys( $custom, false ); } @@ -774,21 +1098,42 @@ protected function get_last_ptr_on_line( $stackPtr ) { return $lastPtr; } + /** + * Overrule the minimum supported WordPress version with a command-line/config value. + * + * Handle setting the minimum supported WP version in one go for all sniffs which + * expect it via the command line or via a `` variable in a ruleset. + * The config variable overrules the default `$minimum_supported_version` and/or a + * `$minimum_supported_version` set for individual sniffs through the ruleset. + * + * @since 0.14.0 + */ + protected function get_wp_version_from_cl() { + $cl_supported_version = trim( PHPCSHelper::get_config_data( 'minimum_supported_wp_version' ) ); + if ( ! empty( $cl_supported_version ) + && filter_var( $cl_supported_version, FILTER_VALIDATE_FLOAT ) !== false + ) { + $this->minimum_supported_version = $cl_supported_version; + } + } + /** * Find whitelisting comment. * - * Comment must be at the end of the line, and use // format. + * Comment must be at the end of the line or at the end of the statement + * and must use // format. * It can be prefixed or suffixed with anything e.g. "foobar" will match: * ... // foobar okay * ... // WPCS: foobar whitelist. * * There is an exception, and that is when PHP is being interspersed with HTML. - * In that case, the comment should come at the end of the statement (right + * In that case, the comment should always come at the end of the statement (right * before the closing tag, ?>). For example: * * * * @since 0.4.0 + * @since 0.14.0 Whitelist comments at the end of the statement are now also accepted. * * @param string $comment Comment to find. * @param integer $stackPtr The position of the current token in the stack passed @@ -798,35 +1143,47 @@ protected function get_last_ptr_on_line( $stackPtr ) { */ protected function has_whitelist_comment( $comment, $stackPtr ) { - $lastPtr = $this->get_last_ptr_on_line( $stackPtr ); - $end_of_line = $lastPtr; + // Respect the PHPCS 3.x --ignore-annotations setting. + if ( true === PHPCSHelper::ignore_annotations( $this->phpcsFile ) ) { + return false; + } + + $regex = '#\b' . preg_quote( $comment, '#' ) . '\b#i'; // There is a findEndOfStatement() method, but it considers more tokens than // we need to here. $end_of_statement = $this->phpcsFile->findNext( array( T_CLOSE_TAG, T_SEMICOLON ), $stackPtr ); - // Check at the end of the statement if it comes before - or is - the end of the line. - if ( $end_of_statement <= $end_of_line ) { - - // If the statement was ended by a semicolon, we find the next non- - // whitespace token. If the semicolon was left out and it was terminated - // by an ending tag, we need to look backwards. + if ( false !== $end_of_statement ) { + // If the statement was ended by a semicolon, check if there is a whitelist comment directly after it. if ( T_SEMICOLON === $this->tokens[ $end_of_statement ]['code'] ) { $lastPtr = $this->phpcsFile->findNext( T_WHITESPACE, ( $end_of_statement + 1 ), null, true ); - } else { + } elseif ( T_CLOSE_TAG === $this->tokens[ $end_of_statement ]['code'] ) { + // If the semicolon was left out and it was terminated by an ending tag, we need to look backwards. $lastPtr = $this->phpcsFile->findPrevious( T_WHITESPACE, ( $end_of_statement - 1 ), null, true ); } + + if ( T_COMMENT === $this->tokens[ $lastPtr ]['code'] + && $this->tokens[ $lastPtr ]['line'] === $this->tokens[ $end_of_statement ]['line'] + && preg_match( $regex, $this->tokens[ $lastPtr ]['content'] ) === 1 + ) { + return true; + } } - $last = $this->tokens[ $lastPtr ]; + // No whitelist comment found so far. Check at the end of the stackPtr line. + // Note: a T_COMMENT includes the new line character, so may be the last token on the line! + $end_of_line = $this->get_last_ptr_on_line( $stackPtr ); + $lastPtr = $this->phpcsFile->findPrevious( T_WHITESPACE, $end_of_line, null, true ); - // Ignore if not a comment. - if ( T_COMMENT !== $last['code'] ) { - return false; + if ( T_COMMENT === $this->tokens[ $lastPtr ]['code'] + && $this->tokens[ $lastPtr ]['line'] === $this->tokens[ $stackPtr ]['line'] + && preg_match( $regex, $this->tokens[ $lastPtr ]['content'] ) === 1 + ) { + return true; } - // Now let's see if the comment contains the whitelist remark we're looking for. - return ( preg_match( '#' . preg_quote( $comment, '#' ) . '#i', $last['content'] ) === 1 ); + return false; } /** @@ -864,7 +1221,6 @@ protected function is_token_in_test_method( $stackPtr ) { return $this->is_test_class( $structureToken ); } - /** * Check if a class token is part of a unit test suite. * @@ -919,25 +1275,30 @@ protected function is_test_class( $stackPtr ) { * * @since 0.5.0 * - * @param int $stackPtr The index of the token in the stack. This must points to + * @param int $stackPtr The index of the token in the stack. This must point to * either a T_VARIABLE or T_CLOSE_SQUARE_BRACKET token. * * @return bool Whether the token is a variable being assigned a value. */ protected function is_assignment( $stackPtr ) { - // Must be a variable or closing square bracket (see below). - if ( ! in_array( $this->tokens[ $stackPtr ]['code'], array( T_VARIABLE, T_CLOSE_SQUARE_BRACKET ), true ) ) { + static $valid = array( + T_VARIABLE => true, + T_CLOSE_SQUARE_BRACKET => true, + ); + + // Must be a variable, constant or closing square bracket (see below). + if ( ! isset( $valid[ $this->tokens[ $stackPtr ]['code'] ] ) ) { return false; } $next_non_empty = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens - , ( $stackPtr + 1 ) - , null - , true - , null - , true + Tokens::$emptyTokens, + ( $stackPtr + 1 ), + null, + true, + null, + true ); // No token found. @@ -946,7 +1307,7 @@ protected function is_assignment( $stackPtr ) { } // If the next token is an assignment, that's all we need to know. - if ( isset( PHP_CodeSniffer_Tokens::$assignmentTokens[ $this->tokens[ $next_non_empty ]['code'] ] ) ) { + if ( isset( Tokens::$assignmentTokens[ $this->tokens[ $next_non_empty ]['code'] ] ) ) { return true; } @@ -1126,10 +1487,10 @@ protected function is_safe_casted( $stackPtr ) { // Get the last non-empty token. $prev = $this->phpcsFile->findPrevious( - PHP_CodeSniffer_Tokens::$emptyTokens - , ( $stackPtr - 1 ) - , null - , true + Tokens::$emptyTokens, + ( $stackPtr - 1 ), + null, + true ); // Check if it is a safe cast. @@ -1213,7 +1574,7 @@ protected function is_sanitized( $stackPtr, $require_unslash = false ) { * to resolve the function name, do so. */ $first_non_empty = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, $callback['start'], ( $callback['end'] + 1 ), true @@ -1270,20 +1631,20 @@ protected function get_array_access_key( $stackPtr ) { // Find the next non-empty token. $open_bracket = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); // If it isn't a bracket, this isn't an array-access. - if ( T_OPEN_SQUARE_BRACKET !== $this->tokens[ $open_bracket ]['code'] ) { + if ( false === $open_bracket || T_OPEN_SQUARE_BRACKET !== $this->tokens[ $open_bracket ]['code'] ) { return false; } $key = $this->phpcsFile->getTokensAsString( - ( $open_bracket + 1 ) - , ( $this->tokens[ $open_bracket ]['bracket_closer'] - $open_bracket - 1 ) + ( $open_bracket + 1 ), + ( $this->tokens[ $open_bracket ]['bracket_closer'] - $open_bracket - 1 ) ); return trim( $key ); @@ -1327,8 +1688,8 @@ protected function is_validated( $stackPtr, $array_key = null, $in_condition_onl if ( $in_condition_only ) { /* - This is a stricter check, requiring the variable to be used only - within the validation condition. + * This is a stricter check, requiring the variable to be used only + * within the validation condition. */ // If there are no conditions, there's no validation. @@ -1351,8 +1712,8 @@ protected function is_validated( $stackPtr, $array_key = null, $in_condition_onl } else { /* - We are are more loose, requiring only that the variable be validated - in the same function/file scope as it is used. + * We are are more loose, requiring only that the variable be validated + * in the same function/file scope as it is used. */ $scope_start = 0; @@ -1375,7 +1736,7 @@ protected function is_validated( $stackPtr, $array_key = null, $in_condition_onl $scope_end = $stackPtr; - } // End if(). + } for ( $i = ( $scope_start + 1 ); $i < $scope_end; $i++ ) { @@ -1436,19 +1797,19 @@ protected function is_comparison( $stackPtr ) { // Find the previous non-empty token. We check before the var first because // yoda conditions are usually expected. $previous_token = $this->phpcsFile->findPrevious( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); - if ( isset( PHP_CodeSniffer_Tokens::$comparisonTokens[ $this->tokens[ $previous_token ]['code'] ] ) ) { + if ( isset( Tokens::$comparisonTokens[ $this->tokens[ $previous_token ]['code'] ] ) ) { return true; } // Maybe the comparison operator is after this. $next_token = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true @@ -1458,14 +1819,14 @@ protected function is_comparison( $stackPtr ) { while ( T_OPEN_SQUARE_BRACKET === $this->tokens[ $next_token ]['code'] ) { $next_token = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, ( $this->tokens[ $next_token ]['bracket_closer'] + 1 ), null, true ); } - if ( isset( PHP_CodeSniffer_Tokens::$comparisonTokens[ $this->tokens[ $next_token ]['code'] ] ) ) { + if ( isset( Tokens::$comparisonTokens[ $this->tokens[ $next_token ]['code'] ] ) ) { return true; } @@ -1500,7 +1861,12 @@ protected function get_use_type( $stackPtr ) { } // USE keywords for traits. - if ( $this->phpcsFile->hasCondition( $stackPtr, array( T_CLASS, T_ANON_CLASS, T_TRAIT ) ) ) { + $valid_scopes = array( + 'T_CLASS' => true, + 'T_ANON_CLASS' => true, + 'T_TRAIT' => true, + ); + if ( true === $this->valid_direct_scope( $stackPtr, $valid_scopes ) ) { return 'trait'; } @@ -1523,7 +1889,7 @@ protected function get_interpolated_variables( $string ) { $variables = array(); if ( preg_match_all( '/(?P\\\\*)\$(?P\w+)/', $string, $match_sets, PREG_SET_ORDER ) ) { foreach ( $match_sets as $matches ) { - if ( ( strlen( $matches['backslashes'] ) % 2 ) === 0 ) { + if ( ! isset( $matches['backslashes'] ) || ( strlen( $matches['backslashes'] ) % 2 ) === 0 ) { $variables[] = $matches['symbol']; } } @@ -1531,6 +1897,25 @@ protected function get_interpolated_variables( $string ) { return $variables; } + /** + * Strip variables from an arbitrary double quoted/heredoc string. + * + * Intended for use with the content of a T_DOUBLE_QUOTED_STRING or T_HEREDOC token. + * + * @since 0.14.0 + * + * @param string $string The raw string. + * + * @return string String without variables in it. + */ + public function strip_interpolated_variables( $string ) { + if ( strpos( $string, '$' ) === false ) { + return $string; + } + + return preg_replace( self::REGEX_COMPLEX_VARS, '', $string ); + } + /** * Checks if a function call has parameters. * @@ -1561,7 +1946,7 @@ public function does_function_call_have_parameters( $stackPtr ) { return false; } - $next_non_empty = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); // Deal with short array syntax. if ( 'T_OPEN_SHORT_ARRAY' === $this->tokens[ $stackPtr ]['type'] ) { @@ -1588,7 +1973,7 @@ public function does_function_call_have_parameters( $stackPtr ) { } $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 ); + $next_next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), ( $close_parenthesis + 1 ), true ); if ( $next_next_non_empty === $close_parenthesis ) { // No parameters. @@ -1664,7 +2049,7 @@ public function get_function_call_parameters( $stackPtr ) { $nestedParenthesisCount = 0; } else { - $opener = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + $opener = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); $closer = $this->tokens[ $opener ]['parenthesis_closer']; $nestedParenthesisCount = 1; @@ -1714,7 +2099,7 @@ public function get_function_call_parameters( $stackPtr ) { * 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 ); + $has_next_param = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_comma + 1 ), $closer, true, null, true ); if ( false === $has_next_param ) { break; } @@ -1722,7 +2107,7 @@ public function get_function_call_parameters( $stackPtr ) { // Prepare for the next parameter. $param_start = ( $next_comma + 1 ); $cnt++; - } // End while(). + } return $parameters; } @@ -1755,32 +2140,177 @@ public function get_function_call_parameter( $stackPtr, $param_offset ) { } /** - * Check if a content string contains a specific html open tag. + * Find the array opener & closer based on a T_ARRAY or T_OPEN_SHORT_ARRAY token. * - * {@internal For PHP 5.3+ this is straightforward, just check if $content - * contains the tag. - * PHP 5.2 however, creates a separate token for `text text` as: - * - T_INLINE_HTML 'text' - * - T_INLINE_HTML 'text text' - * - * We don't need to worry about checking the rest of the content of the next - * token as sniffs using this function will be sniffing for all text string - * tokens, so the next token will be passed to the sniff in the next iteration - * and checked then. - * Similarly, no need to check content before the 'tokens[ $stackPtr ]['code'] ) { + if ( isset( $this->tokens[ $stackPtr ]['parenthesis_opener'] ) ) { + $opener = $this->tokens[ $stackPtr ]['parenthesis_opener']; + + if ( isset( $this->tokens[ $opener ]['parenthesis_closer'] ) ) { + $closer = $this->tokens[ $opener ]['parenthesis_closer']; + } + } + } else { + // Short array syntax. + $opener = $stackPtr; + + if ( isset( $this->tokens[ $stackPtr ]['bracket_closer'] ) ) { + $closer = $this->tokens[ $stackPtr ]['bracket_closer']; + } + } + + if ( isset( $opener, $closer ) ) { + return array( + 'opener' => $opener, + 'closer' => $closer, + ); + } + + return false; + } + + /** + * Determine the namespace name an arbitrary token lives in. + * + * @since 0.10.0 + * @since 0.12.0 Moved from the WordPress_AbstractClassRestrictionsSniff to this sniff. + * + * @param int $stackPtr The token position for which to determine the namespace. + * + * @return string Namespace name or empty string if it couldn't be determined or no namespace applies. + */ + public function determine_namespace( $stackPtr ) { + + // Check for the existence of the token. + if ( ! isset( $this->tokens[ $stackPtr ] ) ) { + return ''; + } + + // Check for scoped namespace {}. + if ( ! empty( $this->tokens[ $stackPtr ]['conditions'] ) ) { + $namespacePtr = $this->phpcsFile->getCondition( $stackPtr, T_NAMESPACE ); + if ( false !== $namespacePtr ) { + $namespace = $this->get_declared_namespace_name( $namespacePtr ); + if ( false !== $namespace ) { + return $namespace; + } + + // We are in a scoped namespace, but couldn't determine the name. + // Searching for a global namespace is futile. + return ''; + } + } + + /* + * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead. + * Keeping in mind that: + * - there can be multiple non-scoped namespaces in a file (bad practice, but it happens). + * - the namespace keyword can also be used as part of a function/method call and such. + * - that a non-named namespace resolves to the global namespace. + */ + $previousNSToken = $stackPtr; + $namespace = false; + do { + $previousNSToken = $this->phpcsFile->findPrevious( T_NAMESPACE, ( $previousNSToken - 1 ) ); + + // Stop if we encounter a scoped namespace declaration as we already know we're not in one. + if ( ! empty( $this->tokens[ $previousNSToken ]['scope_condition'] ) + && $this->tokens[ $previousNSToken ]['scope_condition'] === $previousNSToken + ) { + break; + } + + $namespace = $this->get_declared_namespace_name( $previousNSToken ); + + } while ( false === $namespace && false !== $previousNSToken ); + + // If we still haven't got a namespace, return an empty string. + if ( false === $namespace ) { + return ''; + } + + return $namespace; + } + + /** + * Get the complete namespace name for a namespace declaration. + * + * For hierarchical namespaces, the name will be composed of several tokens, + * i.e. MyProject\Sub\Level which will be returned together as one string. + * + * @since 0.12.0 A lesser variant of this method previously existed in the + * WordPress_AbstractClassRestrictionsSniff. + * + * @param int|bool $stackPtr The position of a T_NAMESPACE token. + * + * @return string|false Namespace name or false if not a namespace declaration. + * Namespace name can be an empty string for global namespace declaration. + */ + public function get_declared_namespace_name( $stackPtr ) { + + // Check for the existence of the token. + if ( false === $stackPtr || ! isset( $this->tokens[ $stackPtr ] ) ) { + return false; + } + + if ( T_NAMESPACE !== $this->tokens[ $stackPtr ]['code'] ) { + return false; + } + + if ( T_NS_SEPARATOR === $this->tokens[ ( $stackPtr + 1 ) ]['code'] ) { + // Not a namespace declaration, but use of, i.e. `namespace\someFunction();`. + return false; + } + + $nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( T_OPEN_CURLY_BRACKET === $this->tokens[ $nextToken ]['code'] ) { + // Declaration for global namespace when using multiple namespaces in a file. + // I.e.: `namespace {}`. + return ''; + } + + // Ok, this should be a namespace declaration, so get all the parts together. + $validTokens = array( + T_STRING => true, + T_NS_SEPARATOR => true, + T_WHITESPACE => true, + ); + + $namespaceName = ''; + while ( isset( $validTokens[ $this->tokens[ $nextToken ]['code'] ] ) ) { + $namespaceName .= trim( $this->tokens[ $nextToken ]['content'] ); + $nextToken++; + } + + return $namespaceName; + } + + /** + * Check if a content string contains a specific html open tag. * * @since 0.11.0 + * @since 0.13.0 No longer allows for the PHP 5.2 bug for which the function was + * originally created. + * @since 0.13.0 The $stackPtr parameter is now optional. Either that or the + * $content parameter has to be passed. * * @param string $tag_name The name of the HTML tag without brackets. So if * searching for ' open tag, false otherwise. */ - public function has_html_open_tag( $tag_name, $stackPtr, $content = false ) { - if ( false === $content ) { + public function has_html_open_tag( $tag_name, $stackPtr = null, $content = false ) { + if ( false === $content && isset( $stackPtr ) ) { $content = $this->tokens[ $stackPtr ]['content']; } - // Check for the open tag in normal string tokens and T_INLINE_HTML for PHP 5.3+. - if ( 's' !== $tag_name[0] || PHP_VERSION_ID >= 50300 || T_INLINE_HTML !== $this->tokens[ $stackPtr ]['code'] ) { - if ( false !== strpos( $content, '<' . $tag_name ) ) { - return true; - } - } elseif ( 'tokens[ $next_ptr ] ) - && T_INLINE_HTML === $this->tokens[ $next_ptr ]['code'] - && 0 === strpos( $this->tokens[ $next_ptr ]['content'], $rest_tag_name ) - ) { + if ( ! empty( $content ) && false !== strpos( $content, '<' . $tag_name ) ) { + return true; + } + + return false; + } + + /** + * Check whether a T_CONST token is a class constant declaration. + * + * @since 0.14.0 + * + * @param int $stackPtr The position in the stack of the T_CONST token to verify. + * + * @return bool + */ + public function is_class_constant( $stackPtr ) { + if ( ! isset( $this->tokens[ $stackPtr ] ) || T_CONST !== $this->tokens[ $stackPtr ]['code'] ) { + return false; + } + + // Note: traits can not declare constants. + $valid_scopes = array( + 'T_CLASS' => true, + 'T_ANON_CLASS' => true, + 'T_INTERFACE' => true, + ); + + return $this->valid_direct_scope( $stackPtr, $valid_scopes ); + } + + /** + * Check whether a T_VARIABLE token is a class property declaration. + * + * @since 0.14.0 + * + * @param int $stackPtr The position in the stack of the T_VARIABLE token to verify. + * + * @return bool + */ + public function is_class_property( $stackPtr ) { + if ( ! isset( $this->tokens[ $stackPtr ] ) || T_VARIABLE !== $this->tokens[ $stackPtr ]['code'] ) { + return false; + } + + // Note: interfaces can not declare properties. + $valid_scopes = array( + 'T_CLASS' => true, + 'T_ANON_CLASS' => true, + 'T_TRAIT' => true, + ); + + if ( $this->valid_direct_scope( $stackPtr, $valid_scopes ) ) { + // Make sure it's not a method parameter. + if ( empty( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) { return true; } } @@ -1815,4 +2385,125 @@ public function has_html_open_tag( $tag_name, $stackPtr, $content = false ) { return false; } + /** + * Check whether the direct wrapping scope of a token is within a limited set of + * acceptable tokens. + * + * Used to check, for instance, if a T_CONST is a class constant. + * + * @since 0.14.0 + * + * @param int $stackPtr The position in the stack of the token to verify. + * @param array $valid_scopes Array of token types. + * Keys should be the token types in string format + * to allow for newer token types. + * Value is irrelevant. + * + * @return bool + */ + protected function valid_direct_scope( $stackPtr, array $valid_scopes ) { + if ( empty( $this->tokens[ $stackPtr ]['conditions'] ) ) { + return false; + } + + /* + * Check only the direct wrapping scope of the token. + */ + $conditions = array_keys( $this->tokens[ $stackPtr ]['conditions'] ); + $ptr = array_pop( $conditions ); + + if ( ! isset( $this->tokens[ $ptr ] ) ) { + return false; + } + + return isset( $valid_scopes[ $this->tokens[ $ptr ]['type'] ] ); + } + + /** + * Checks whether this is a call to a $wpdb method that we want to sniff. + * + * If available in the child class, the $methodPtr, $i and $end properties are + * automatically set to correspond to the start and end of the method call. + * The $i property is also set if this is not a method call but rather the + * use of a $wpdb property. + * + * @since 0.8.0 + * @since 0.9.0 The return value is now always boolean. The $end and $i member + * vars are automatically updated. + * @since 0.14.0 Moved this method from the `PreparedSQL` sniff to the base WP sniff. + * + * {@internal This method should probably be refactored.}} + * + * @param int $stackPtr The index of the $wpdb variable. + * @param array $target_methods Array of methods. Key(s) should be method name. + * + * @return bool Whether this is a $wpdb method call. + */ + protected function is_wpdb_method_call( $stackPtr, $target_methods ) { + + // Check for wpdb. + if ( ( T_VARIABLE === $this->tokens[ $stackPtr ]['code'] && '$wpdb' !== $this->tokens[ $stackPtr ]['content'] ) + || ( T_STRING === $this->tokens[ $stackPtr ]['code'] && 'wpdb' !== $this->tokens[ $stackPtr ]['content'] ) + ) { + return false; + } + + // Check that this is a method call. + $is_object_call = $this->phpcsFile->findNext( + array( T_OBJECT_OPERATOR, T_DOUBLE_COLON ), + ( $stackPtr + 1 ), + null, + false, + null, + true + ); + if ( false === $is_object_call ) { + return false; + } + + $methodPtr = $this->phpcsFile->findNext( T_WHITESPACE, ( $is_object_call + 1 ), null, true, null, true ); + if ( false === $methodPtr ) { + return false; + } + + if ( T_STRING === $this->tokens[ $methodPtr ]['code'] && property_exists( $this, 'methodPtr' ) ) { + $this->methodPtr = $methodPtr; + } + + // Find the opening parenthesis. + $opening_paren = $this->phpcsFile->findNext( T_WHITESPACE, ( $methodPtr + 1 ), null, true, null, true ); + + if ( false === $opening_paren ) { + return false; + } + + if ( property_exists( $this, 'i' ) ) { + $this->i = $opening_paren; + } + + if ( T_OPEN_PARENTHESIS !== $this->tokens[ $opening_paren ]['code'] + || ! isset( $this->tokens[ $opening_paren ]['parenthesis_closer'] ) + ) { + return false; + } + + // Check that this is one of the methods that we are interested in. + if ( ! isset( $target_methods[ $this->tokens[ $methodPtr ]['content'] ] ) ) { + return false; + } + + // Find the end of the first parameter. + $end = $this->phpcsFile->findEndOfStatement( $opening_paren + 1 ); + + if ( T_COMMA !== $this->tokens[ $end ]['code'] ) { + ++$end; + } + + if ( property_exists( $this, 'end' ) ) { + $this->end = $end; + } + + return true; + } // End is_wpdb_method_call(). + } diff --git a/WordPress/Sniffs/Arrays/ArrayAssignmentRestrictionsSniff.php b/WordPress/Sniffs/Arrays/ArrayAssignmentRestrictionsSniff.php index e7fd6e54..f48d1e2c 100644 --- a/WordPress/Sniffs/Arrays/ArrayAssignmentRestrictionsSniff.php +++ b/WordPress/Sniffs/Arrays/ArrayAssignmentRestrictionsSniff.php @@ -7,39 +7,44 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Arrays; + +use WordPress\AbstractArrayAssignmentRestrictionsSniff; + /** * Restricts array assignment of certain keys. * * @package WPCS\WordPressCodingStandards * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + * * @deprecated 0.10.0 The functionality which used to be contained in this class has been moved to * the WordPress_AbstractArrayAssignmentRestrictionsSniff class. * This class is left here to prevent backward-compatibility breaks for * custom sniffs extending the old class and references to this * sniff from custom phpcs.xml files. * This file is also still used to unit test the abstract class. - * @see WordPress_AbstractArrayAssignmentRestrictionsSniff + * @see \WordPress\AbstractArrayAssignmentRestrictionsSniff */ -class WordPress_Sniffs_Arrays_ArrayAssignmentRestrictionsSniff extends WordPress_AbstractArrayAssignmentRestrictionsSniff { +class ArrayAssignmentRestrictionsSniff extends AbstractArrayAssignmentRestrictionsSniff { /** * Groups of variables to restrict. * * Example: groups => array( - * 'wpdb' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'variables' => array( '$val', '$var' ), - * 'object_vars' => array( '$foo->bar', .. ), - * 'array_members' => array( '$foo['bar']', .. ), - * ) + * 'groupname' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'keys' => array( 'key1', 'another_key' ), + * 'callback' => array( 'class', 'method' ), // Optional. + * ) * ) * * @return array */ public function getGroups() { - return parent::$groups; + return array(); } /** diff --git a/WordPress/Sniffs/Arrays/ArrayDeclarationSniff.php b/WordPress/Sniffs/Arrays/ArrayDeclarationSniff.php index 83f76385..2f9899f1 100644 --- a/WordPress/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/WordPress/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -7,9 +7,9 @@ * @license https://opensource.org/licenses/MIT MIT */ -if ( ! class_exists( 'Squiz_Sniffs_Arrays_ArrayDeclarationSniff', true ) ) { - throw new PHP_CodeSniffer_Exception( 'Class Squiz_Sniffs_Arrays_ArrayDeclarationSniff not found' ); -} +namespace WordPress\Sniffs\Arrays; + +use PHP_CodeSniffer_File as File; /** * Enforces WordPress array format, based upon Squiz code. @@ -18,594 +18,44 @@ * * @package WPCS\WordPressCodingStandards * - * @since 0.1.0 - * @since 0.5.0 Now extends `Squiz_Sniffs_Arrays_ArrayDeclarationSniff`. - * @since 0.11.0 The additional single-line array checks have been moved to their own - * sniff WordPress.Arrays.ArrayDeclarationSpacing. - * This class now only contains a slimmed down version of the upstream sniff. - * - * DO NOT MAKE CHANGES TO THIS SNIFF - other than syncing with upstream -. - * ANY CHANGES NECESSARY SHOULD BE PULLED TO THE UPSTREAM SNIFF AND SYNCED IN AFTER MERGE! - * The code style of this sniff should be ignored so as to facilitate easy syncing with - * upstream. - * - * {@internal The upstream version is similar, except that we exclude a few errors. - * Unfortunately we have to actually comment out the code rather than just using - * the upstream sniff and `` in our ruleset, due to a bug/non-configurability - * of the sniff. - * {@see https://github.com/squizlabs/PHP_CodeSniffer/issues/582} }} + * @since 0.1.0 + * @since 0.5.0 Now extends `Squiz_Sniffs_Arrays_ArrayDeclarationSniff`. + * @since 0.11.0 The additional single-line array checks have been moved to their own + * sniff WordPress.Arrays.ArrayDeclarationSpacing. + * This class now only contains a slimmed down version of the upstream sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. * - * Last synced with parent class October 5 2016 at commit ea32814346ecf29791de701b3fa464a9ca43f45b. - * @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php + * @deprecated 0.13.0 This sniff has now been deprecated. Most checks which were previously + * contained herein had recently been excluded in favour of dedicated + * sniffs with higher precision. The last remaining checks which were not + * already covered elsewhere have been moved to the `ArrayDeclarationSpacing` + * sniff. + * This class is left here to prevent breaking custom rulesets which refer + * to this sniff. */ -class WordPress_Sniffs_Arrays_ArrayDeclarationSniff extends Squiz_Sniffs_Arrays_ArrayDeclarationSniff { +class ArrayDeclarationSniff { /** - * Process a multi-line array. + * Don't use. * - * @since 0.5.0 + * @deprecated 0.13.0 * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * @param int $arrayStart Position of the array opener in the token stack. - * @param int $arrayEnd Position of the array closer in the token stack. + * @return int[] */ - public function processMultiLineArray( PHP_CodeSniffer_File $phpcsFile, $stackPtr, $arrayStart, $arrayEnd ) { - $tokens = $phpcsFile->getTokens(); - $keywordStart = $tokens[ $stackPtr ]['column']; - - // Check the closing bracket is on a new line. - $lastContent = $phpcsFile->findPrevious( T_WHITESPACE, ( $arrayEnd - 1 ), $arrayStart, true ); - if ( $tokens[ $lastContent ]['line'] === $tokens[ $arrayEnd ]['line'] ) { - $error = 'Closing parenthesis of array declaration must be on a new line'; - $fix = $phpcsFile->addFixableError( $error, $arrayEnd, 'CloseBraceNewLine' ); - if ($fix === true) { - $phpcsFile->fixer->addNewlineBefore( $arrayEnd ); - } - /* - } elseif ( $tokens[ $arrayEnd ]['column'] !== $keywordStart ) { - // Check the closing bracket is lined up under the "a" in array. - $expected = ( $keywordStart - 1 ); - $found = ( $tokens[ $arrayEnd ]['column'] - 1 ); - $error = 'Closing parenthesis not aligned correctly; expected %s space(s) but found %s'; - $data = array( - $expected, - $found, - ); - - $fix = $phpcsFile->addFixableError( $error, $arrayEnd, 'CloseBraceNotAligned', $data ); - if ($fix === true) { - if ($found === 0) { - $phpcsFile->fixer->addContent( ( $arrayEnd - 1 ), str_repeat(' ', $expected ) ); - } else { - $phpcsFile->fixer->replaceToken( ( $arrayEnd - 1 ), str_repeat(' ', $expected ) ); - } - } - */ - } // end if - - $keyUsed = false; - $singleUsed = false; - $indices = array(); - $maxLength = 0; - - if ($tokens[$stackPtr]['code'] === T_ARRAY) { - $lastToken = $tokens[ $stackPtr ]['parenthesis_opener']; - } else { - $lastToken = $stackPtr; - } - - // Find all the double arrows that reside in this scope. - for ( $nextToken = ( $stackPtr + 1 ); $nextToken < $arrayEnd; $nextToken++ ) { - // Skip bracketed statements, like function calls. - if ($tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS - && (isset($tokens[$nextToken]['parenthesis_owner']) === false - || $tokens[ $nextToken ]['parenthesis_owner'] !== $stackPtr ) - ) { - $nextToken = $tokens[ $nextToken ]['parenthesis_closer']; - continue; - } - - if ( in_array( $tokens[ $nextToken ]['code'], array( T_ARRAY, T_OPEN_SHORT_ARRAY, T_CLOSURE ), true ) === true ) { - // Let subsequent calls of this test handle nested arrays. - if ($tokens[$lastToken]['code'] !== T_DOUBLE_ARROW) { - $indices[] = array( 'value' => $nextToken ); - $lastToken = $nextToken; - } - - if ($tokens[$nextToken]['code'] === T_ARRAY) { - $nextToken = $tokens[ $tokens[ $nextToken ]['parenthesis_opener'] ]['parenthesis_closer']; - } else if ($tokens[$nextToken]['code'] === T_OPEN_SHORT_ARRAY) { - $nextToken = $tokens[ $nextToken ]['bracket_closer']; - } else { - // T_CLOSURE. - $nextToken = $tokens[ $nextToken ]['scope_closer']; - } - - $nextToken = $phpcsFile->findNext( T_WHITESPACE, ( $nextToken + 1 ), null, true ); - if ($tokens[$nextToken]['code'] !== T_COMMA) { - $nextToken--; - } else { - $lastToken = $nextToken; - } - - continue; - }//end if - - if ($tokens[$nextToken]['code'] !== T_DOUBLE_ARROW - && $tokens[$nextToken]['code'] !== T_COMMA - ) { - continue; - } - - $currentEntry = array(); - - if ($tokens[$nextToken]['code'] === T_COMMA) { - $stackPtrCount = 0; - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - $stackPtrCount = count( $tokens[ $stackPtr ]['nested_parenthesis'] ); - } - - $commaCount = 0; - if (isset($tokens[$nextToken]['nested_parenthesis']) === true) { - $commaCount = count( $tokens[ $nextToken ]['nested_parenthesis'] ); - if ($tokens[$stackPtr]['code'] === T_ARRAY) { - // Remove parenthesis that are used to define the array. - $commaCount--; - } - } - - if ( $commaCount > $stackPtrCount ) { - // This comma is inside more parenthesis than the ARRAY keyword, - // then there it is actually a comma used to separate arguments - // in a function call. - continue; - } - - /* - if ($keyUsed === true && $tokens[$lastToken]['code'] === T_COMMA) { - $error = 'No key specified for array entry; first entry specifies key'; - $phpcsFile->addError( $error, $nextToken, 'NoKeySpecified' ); - return; - } - */ - - if ($keyUsed === false) { - if ($tokens[($nextToken - 1)]['code'] === T_WHITESPACE) { - $content = $tokens[ ( $nextToken - 2 ) ]['content']; - if ( $tokens[ ( $nextToken - 1 ) ]['content'] === $phpcsFile->eolChar ) { - $spaceLength = 'newline'; - } else { - $spaceLength = $tokens[ ( $nextToken - 1 ) ]['length']; - } - - $error = 'Expected 0 spaces between "%s" and comma; %s found'; - $data = array( - $content, - $spaceLength, - ); - - $fix = $phpcsFile->addFixableError( $error, $nextToken, 'SpaceBeforeComma', $data ); - if ($fix === true) { - $phpcsFile->fixer->replaceToken( ( $nextToken - 1 ), '' ); - } - } - - $valueContent = $phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, - ( $lastToken + 1 ), - $nextToken, - true - ); - - $indices[] = array( 'value' => $valueContent ); - $singleUsed = true; - }//end if - - $lastToken = $nextToken; - continue; - - }//end if - - if ($tokens[$nextToken]['code'] === T_DOUBLE_ARROW) { - /* - if ($singleUsed === true) { - $error = 'Key specified for array entry; first entry has no key'; - $phpcsFile->addError( $error, $nextToken, 'KeySpecified' ); - return; - } - */ - - $currentEntry['arrow'] = $nextToken; - $keyUsed = true; - - // Find the start of index that uses this double arrow. - $indexEnd = $phpcsFile->findPrevious( T_WHITESPACE, ( $nextToken - 1 ), $arrayStart, true ); - $indexStart = $phpcsFile->findStartOfStatement( $indexEnd ); - - if ( $indexStart === $indexEnd ) { - $currentEntry['index'] = $indexEnd; - $currentEntry['index_content'] = $tokens[ $indexEnd ]['content']; - } else { - $currentEntry['index'] = $indexStart; - $currentEntry['index_content'] = $phpcsFile->getTokensAsString( $indexStart, ( $indexEnd - $indexStart + 1 ) ); - } - - $indexLength = strlen( $currentEntry['index_content'] ); - if ( $maxLength < $indexLength ) { - $maxLength = $indexLength; - } - - // Find the value of this index. - $nextContent = $phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, - ( $nextToken + 1 ), - $arrayEnd, - true - ); - - $currentEntry['value'] = $nextContent; - $indices[] = $currentEntry; - $lastToken = $nextToken; - }//end if - }//end for - - // Check for mutli-line arrays that should be single-line. - $singleValue = false; - - if (empty($indices) === true) { - $singleValue = true; - } else if (count($indices) === 1 && $tokens[$lastToken]['code'] === T_COMMA) { - // There may be another array value without a comma. - $exclude = PHP_CodeSniffer_Tokens::$emptyTokens; - $exclude[] = T_COMMA; - $nextContent = $phpcsFile->findNext( $exclude, ( $indices[0]['value'] + 1 ), $arrayEnd, true ); - if ($nextContent === false) { - $singleValue = true; - } - } - - /* - if ($singleValue === true) { - // Array cannot be empty, so this is a multi-line array with - // a single value. It should be defined on single line. - $error = 'Multi-line array contains a single value; use single-line array instead'; - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'MultiLineNotAllowed' ); - - if ($fix === true) { - $phpcsFile->fixer->beginChangeset(); - for ( $i = ( $arrayStart + 1 ); $i < $arrayEnd; $i++ ) { - if ($tokens[$i]['code'] !== T_WHITESPACE) { - break; - } - - $phpcsFile->fixer->replaceToken( $i, '' ); - } - - for ( $i = ( $arrayEnd - 1 ); $i > $arrayStart; $i-- ) { - if ($tokens[$i]['code'] !== T_WHITESPACE) { - break; - } - - $phpcsFile->fixer->replaceToken( $i, '' ); - } - - $phpcsFile->fixer->endChangeset(); - } - - return; - } // end if - */ - - /* - This section checks for arrays that don't specify keys. - - Arrays such as: - array( - 'aaa', - 'bbb', - 'd', - ); - */ - - if ($keyUsed === false && empty($indices) === false) { - $count = count( $indices ); - $lastIndex = $indices[ ( $count - 1 ) ]['value']; - - $trailingContent = $phpcsFile->findPrevious( - PHP_CodeSniffer_Tokens::$emptyTokens, - ( $arrayEnd - 1 ), - $lastIndex, - true - ); - - if ($tokens[$trailingContent]['code'] !== T_COMMA) { - $phpcsFile->recordMetric( $stackPtr, 'Array end comma', 'no' ); - $error = 'Comma required after last value in array declaration'; - $fix = $phpcsFile->addFixableError( $error, $trailingContent, 'NoCommaAfterLast' ); - if ($fix === true) { - $phpcsFile->fixer->addContent( $trailingContent, ',' ); - } - } else { - $phpcsFile->recordMetric( $stackPtr, 'Array end comma', 'yes' ); - } - - $lastValueLine = false; - foreach ( $indices as $value ) { - if (empty($value['value']) === true) { - // Array was malformed and we couldn't figure out - // the array value correctly, so we have to ignore it. - // Other parts of this sniff will correct the error. - continue; - } + public function register() { + return array(); + } - if ($lastValueLine !== false && $tokens[$value['value']]['line'] === $lastValueLine) { - $error = 'Each value in a multi-line array must be on a new line'; - $fix = $phpcsFile->addFixableError( $error, $value['value'], 'ValueNoNewline' ); - if ($fix === true) { - if ($tokens[($value['value'] - 1)]['code'] === T_WHITESPACE) { - $phpcsFile->fixer->replaceToken( ( $value['value'] - 1 ), '' ); - } - - $phpcsFile->fixer->addNewlineBefore( $value['value'] ); - } - /* - } else if ($tokens[($value['value'] - 1)]['code'] === T_WHITESPACE) { - $expected = $keywordStart; - - $first = $phpcsFile->findFirstOnLine( T_WHITESPACE, $value['value'], true ); - $found = ($tokens[ $first ]['column'] - 1); - if ( $found !== $expected ) { - $error = 'Array value not aligned correctly; expected %s spaces but found %s'; - $data = array( - $expected, - $found, - ); - - $fix = $phpcsFile->addFixableError( $error, $value['value'], 'ValueNotAligned', $data ); - if ($fix === true) { - if ($found === 0) { - $phpcsFile->fixer->addContent( ( $value['value'] - 1 ), str_repeat(' ', $expected ) ); - } else { - $phpcsFile->fixer->replaceToken( ( $value['value'] - 1 ), str_repeat(' ', $expected ) ); - } - } - } - */ - } // end if - - $lastValueLine = $tokens[ $value['value'] ]['line']; - }//end foreach - }//end if - - /* - Below the actual indentation of the array is checked. - Errors will be thrown when a key is not aligned, when - a double arrow is not aligned, and when a value is not - aligned correctly. - If an error is found in one of the above areas, then errors - are not reported for the rest of the line to avoid reporting - spaces and columns incorrectly. Often fixing the first - problem will fix the other 2 anyway. - - For example: - - $a = array( - 'index' => '2', - ); - - or - - $a = [ - 'index' => '2', - ]; - - In this array, the double arrow is indented too far, but this - will also cause an error in the value's alignment. If the arrow were - to be moved back one space however, then both errors would be fixed. - */ - - $numValues = count( $indices ); - - $indicesStart = ( $keywordStart + 1 ); - $arrowStart = ( $indicesStart + $maxLength + 1 ); - //$valueStart = ($arrowStart + 3); - $indexLine = $tokens[ $stackPtr ]['line']; - //$lastIndexLine = null; - foreach ( $indices as $index ) { - if (isset($index['index']) === false) { - // Array value only. - if ( $tokens[ $index['value'] ]['line'] === $tokens[ $stackPtr ]['line'] && $numValues > 1 ) { - $error = 'The first value in a multi-value array must be on a new line'; - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'FirstValueNoNewline' ); - if ($fix === true) { - $phpcsFile->fixer->addNewlineBefore( $index['value'] ); - } - } - - continue; - } - - $lastIndexLine = $indexLine; - $indexLine = $tokens[ $index['index'] ]['line']; - - if ( $indexLine === $tokens[ $stackPtr ]['line'] ) { - $error = 'The first index in a multi-value array must be on a new line'; - $fix = $phpcsFile->addFixableError( $error, $index['index'], 'FirstIndexNoNewline' ); - if ($fix === true) { - $phpcsFile->fixer->addNewlineBefore( $index['index'] ); - } - - continue; - } - - if ( $indexLine === $lastIndexLine ) { - $error = 'Each index in a multi-line array must be on a new line'; - $fix = $phpcsFile->addFixableError( $error, $index['index'], 'IndexNoNewline' ); - if ($fix === true) { - if ($tokens[($index['index'] - 1)]['code'] === T_WHITESPACE) { - $phpcsFile->fixer->replaceToken( ( $index['index'] - 1 ), '' ); - } - - $phpcsFile->fixer->addNewlineBefore( $index['index'] ); - } - - continue; - } - - /* - if ( $tokens[ $index['index'] ]['column'] !== $indicesStart ) { - $expected = ( $indicesStart - 1 ); - $found = ( $tokens[ $index['index'] ]['column'] - 1 ); - $error = 'Array key not aligned correctly; expected %s spaces but found %s'; - $data = array( - $expected, - $found, - ); - - $fix = $phpcsFile->addFixableError( $error, $index['index'], 'KeyNotAligned', $data ); - if ($fix === true) { - if ($found === 0) { - $phpcsFile->fixer->addContent( ( $index['index'] - 1 ), str_repeat(' ', $expected ) ); - } else { - $phpcsFile->fixer->replaceToken( ( $index['index'] - 1 ), str_repeat(' ', $expected ) ); - } - } - - continue; - } - - if ( $tokens[ $index['arrow'] ]['column'] !== $arrowStart ) { - $expected = ( $arrowStart - ( strlen( $index['index_content'] ) + $tokens[ $index['index'] ]['column'] ) ); - $found = ( $tokens[ $index['arrow'] ]['column'] - ( strlen( $index['index_content'] ) + $tokens[ $index['index'] ]['column'] ) ); - $error = 'Array double arrow not aligned correctly; expected %s space(s) but found %s'; - $data = array( - $expected, - $found, - ); - - $fix = $phpcsFile->addFixableError( $error, $index['arrow'], 'DoubleArrowNotAligned', $data ); - if ($fix === true) { - if ($found === 0) { - $phpcsFile->fixer->addContent( ( $index['arrow'] - 1 ), str_repeat(' ', $expected ) ); - } else { - $phpcsFile->fixer->replaceToken( ( $index['arrow'] - 1 ), str_repeat(' ', $expected ) ); - } - } - - continue; - } - - if ( $tokens[ $index['value'] ]['column'] !== $valueStart ) { - $expected = ( $valueStart - ( $tokens[ $index['arrow'] ]['length'] + $tokens[ $index['arrow'] ]['column'] ) ); - $found = ( $tokens[ $index['value'] ]['column'] - ( $tokens[ $index['arrow'] ]['length'] + $tokens[ $index['arrow'] ]['column'] ) ); - if ( $found < 0 ) { - $found = 'newline'; - } - - $error = 'Array value not aligned correctly; expected %s space(s) but found %s'; - $data = array( - $expected, - $found, - ); - - $fix = $phpcsFile->addFixableError( $error, $index['arrow'], 'ValueNotAligned', $data ); - if ($fix === true) { - if ($found === 'newline') { - $prev = $phpcsFile->findPrevious( T_WHITESPACE, ( $index['value'] - 1 ), null, true ); - $phpcsFile->fixer->beginChangeset(); - for ( $i = ( $prev + 1 ); $i < $index['value']; $i++ ) { - $phpcsFile->fixer->replaceToken( $i, '' ); - } - - $phpcsFile->fixer->replaceToken( ( $index['value'] - 1 ), str_repeat(' ', $expected ) ); - $phpcsFile->fixer->endChangeset(); - } else if ($found === 0) { - $phpcsFile->fixer->addContent( ( $index['value'] - 1 ), str_repeat(' ', $expected ) ); - } else { - $phpcsFile->fixer->replaceToken( ( $index['value'] - 1 ), str_repeat(' ', $expected ) ); - } - } - } // end if - */ - - // Check each line ends in a comma. - $valueLine = $tokens[ $index['value'] ]['line']; - $nextComma = false; - for ( $i = $index['value']; $i < $arrayEnd; $i++ ) { - // Skip bracketed statements, like function calls. - if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) { - $i = $tokens[ $i ]['parenthesis_closer']; - $valueLine = $tokens[ $i ]['line']; - continue; - } - - if ($tokens[$i]['code'] === T_ARRAY) { - $i = $tokens[ $tokens[ $i ]['parenthesis_opener'] ]['parenthesis_closer']; - $valueLine = $tokens[ $i ]['line']; - continue; - } - - // Skip to the end of multi-line strings. - if (isset(PHP_CodeSniffer_Tokens::$stringTokens[$tokens[$i]['code']]) === true) { - $i = $phpcsFile->findNext($tokens[$i]['code'], ($i + 1), null, true); - $i--; - $valueLine = $tokens[$i]['line']; - continue; - } - - if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { - $i = $tokens[ $i ]['bracket_closer']; - $valueLine = $tokens[ $i ]['line']; - continue; - } - - if ($tokens[$i]['code'] === T_CLOSURE) { - $i = $tokens[ $i ]['scope_closer']; - $valueLine = $tokens[ $i ]['line']; - continue; - } - - if ($tokens[$i]['code'] === T_COMMA) { - $nextComma = $i; - break; - } - }//end for - - if ($nextComma === false || ($tokens[$nextComma]['line'] !== $valueLine)) { - $error = 'Each line in an array declaration must end in a comma'; - $fix = $phpcsFile->addFixableError( $error, $index['value'], 'NoComma' ); - - if ($fix === true) { - // Find the end of the line and put a comma there. - for ($i = ($index['value'] + 1); $i < $arrayEnd; $i++) { - if ( $tokens[ $i ]['line'] > $valueLine ) { - break; - } - } - - $phpcsFile->fixer->addContentBefore( ( $i - 1 ), ',' ); - } - } - - // Check that there is no space before the comma. - if ($nextComma !== false && $tokens[($nextComma - 1)]['code'] === T_WHITESPACE) { - $content = $tokens[ ( $nextComma - 2 ) ]['content']; - $spaceLength = $tokens[ ( $nextComma - 1 ) ]['length']; - $error = 'Expected 0 spaces between "%s" and comma; %s found'; - $data = array( - $content, - $spaceLength, - ); - - $fix = $phpcsFile->addFixableError( $error, $nextComma, 'SpaceBeforeComma', $data ); - if ($fix === true) { - $phpcsFile->fixer->replaceToken( ( $nextComma - 1 ), '' ); - } - } - }//end foreach - - }//end processMultiLineArray() + /** + * Don't use. + * + * @deprecated 0.13.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile A PHP_CodeSniffer file. + * @param int $stackPtr The position of the token. + * + * @return void + */ + public function process( File $phpcsFile, $stackPtr ) {} } // End class. diff --git a/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php b/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php index b982d182..6adfbec9 100644 --- a/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php +++ b/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php @@ -7,12 +7,20 @@ * @license https://opensource.org/licenses/MIT MIT */ -if ( ! class_exists( 'Squiz_Sniffs_Arrays_ArrayDeclarationSniff', true ) ) { - throw new PHP_CodeSniffer_Exception( 'Class Squiz_Sniffs_Arrays_ArrayDeclarationSniff not found' ); -} +namespace WordPress\Sniffs\Arrays; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; /** - * Enforces WordPress array format, based upon Squiz code. + * Enforces WordPress array spacing format. + * + * - Check for no space between array keyword and array opener. + * - Check for no space between the parentheses of an empty array. + * - Checks for one space after the array opener / before the array closer in single-line arrays. + * - Checks that associative arrays are multi-line. + * - Checks that each array item in a multi-line array starts on a new line. + * - Checks that the array closer in a multi-line array is on a new line. * * @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation * @@ -22,227 +30,411 @@ * from the WordPress_Sniffs_Arrays_ArrayDeclaration sniff into * this sniff. * - Added sniffing & fixing for associative arrays. - * - * {@internal This sniff only extends the upstream sniff to get the benefit of the - * process logic which routes the processing to the single-line/multi-line methods. - * Other than that, the actual sniffing from the upstream sniff is disregarded. - * In other words: no real syncing with upstream necessary.}} - * - * Last synced with parent class October 5 2016 at commit ea32814346ecf29791de701b3fa464a9ca43f45b. - * @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php + * @since 0.12.0 Decoupled this sniff from the upstream sniff completely. + * This sniff now extends the `WordPress_Sniff` instead. + * @since 0.13.0 Added the last remaining checks from the `ArrayDeclaration` sniff + * which were not covered elsewhere. The `ArrayDeclaration` sniff has + * now been deprecated. + * @since 0.13.0 Class name changed: this class is now namespaced. + * @since 0.14.0 Single item associative arrays are now by default exempt from the + * "must be multi-line" rule. This behaviour can be changed using the + * `allow_single_item_single_line_associative_arrays` property. */ -class WordPress_Sniffs_Arrays_ArrayDeclarationSpacingSniff extends Squiz_Sniffs_Arrays_ArrayDeclarationSniff { +class ArrayDeclarationSpacingSniff extends Sniff { + + /** + * Whether or not to allow single item associative arrays to be single line. + * + * @since 0.14.0 + * + * @var bool Defaults to true. + */ + public $allow_single_item_single_line_associative_arrays = true; + + /** + * Token this sniff targets. + * + * Also used for distinguishing between the array and an array value + * which is also an array. + * + * @since 0.12.0 + * + * @var array + */ + private $targets = array( + T_ARRAY => T_ARRAY, + T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY, + ); /** - * Process a single line array. + * Returns an array of tokens this test wants to listen for. * - * @since 0.5.0 - * @since 0.11.0 Moved from WordPress_Sniffs_Arrays_ArrayDeclaration to this sniff. + * @since 0.12.0 * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * @param int $arrayStart Position of the array opener in the token stack. - * @param int $arrayEnd Position of the array closer in the token stack. + * @return array */ - public function processSingleLineArray( PHP_CodeSniffer_File $phpcsFile, $stackPtr, $arrayStart, $arrayEnd ) { + public function register() { + return $this->targets; + } - // This array is empty, so the below checks aren't necessary. - if ( ( $arrayStart + 1 ) === $arrayEnd ) { + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.12.0 The actual checks contained in this method used to + * be in the `processSingleLineArray()` method. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + /* + * Determine the array opener & closer. + */ + $array_open_close = $this->find_array_open_close( $stackPtr ); + if ( false === $array_open_close ) { + // Array open/close could not be determined. return; } - $tokens = $phpcsFile->getTokens(); + $opener = $array_open_close['opener']; + $closer = $array_open_close['closer']; + unset( $array_open_close ); - // Check that there is a single space after the array opener. - if ( T_WHITESPACE !== $tokens[ ( $arrayStart + 1 ) ]['code'] ) { + /* + * Long arrays only: Check for space between the array keyword and the open parenthesis. + */ + if ( T_ARRAY === $this->tokens[ $stackPtr ]['code'] ) { - $warning = 'Missing space after array opener.'; - $fix = $phpcsFile->addFixableError( $warning, $arrayStart, 'NoSpaceAfterArrayOpener' ); + if ( ( $stackPtr + 1 ) !== $opener ) { + $error = 'There must be no space between the "array" keyword and the opening parenthesis'; + $error_code = 'SpaceAfterKeyword'; - if ( true === $fix ) { - $phpcsFile->fixer->addContent( $arrayStart, ' ' ); + $nextNonWhitespace = $this->phpcsFile->findNext( T_WHITESPACE, ( $stackPtr + 1 ), ( $opener + 1 ), true ); + if ( $nextNonWhitespace !== $opener ) { + // Don't auto-fix: Something other than whitespace found between keyword and open parenthesis. + $this->phpcsFile->addError( $error, $stackPtr, $error_code ); + } else { + + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, $error_code ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = ( $stackPtr + 1 ); $i < $opener; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + $this->phpcsFile->fixer->endChangeset(); + unset( $i ); + } + } + unset( $error, $error_code, $nextNonWhitespace, $fix ); } - } elseif ( ' ' !== $tokens[ ( $arrayStart + 1 ) ]['content'] ) { + } - $fix = $phpcsFile->addFixableError( - 'Expected 1 space after array opener, found %s.', - $arrayStart, - 'SpaceAfterArrayOpener', - array( strlen( $tokens[ ( $arrayStart + 1 ) ]['content'] ) ) - ); + /* + * Check for empty arrays. + */ + $nextNonWhitespace = $this->phpcsFile->findNext( T_WHITESPACE, ( $opener + 1 ), ( $closer + 1 ), true ); + if ( $nextNonWhitespace === $closer ) { + + if ( ( $opener + 1 ) !== $closer ) { + $fix = $this->phpcsFile->addFixableError( + 'Empty array declaration must have no space between the parentheses', + $stackPtr, + 'SpaceInEmptyArray' + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = ( $opener + 1 ); $i < $closer; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + $this->phpcsFile->fixer->endChangeset(); + unset( $i ); + } + } - if ( true === $fix ) { - $phpcsFile->fixer->replaceToken( ( $arrayStart + 1 ), ' ' ); + // This array is empty, so the below checks aren't necessary. + return; + } + unset( $nextNonWhitespace ); + + // Pass off to either the single line or multi-line array analysis. + if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) { + $this->process_single_line_array( $stackPtr, $opener, $closer ); + } else { + $this->process_multi_line_array( $stackPtr, $opener, $closer ); + } + } + + /** + * Process a single-line array. + * + * @since 0.13.0 The actual checks contained in this method used to + * be in the `process()` method. + * + * @param int $stackPtr The position of the current token in the stack. + * @param int $opener The position of the array opener. + * @param int $closer The position of the array closer. + * + * @return void + */ + protected function process_single_line_array( $stackPtr, $opener, $closer ) { + /* + * Check that associative arrays are always multi-line. + */ + $array_has_keys = $this->phpcsFile->findNext( T_DOUBLE_ARROW, $opener, $closer ); + if ( false !== $array_has_keys ) { + + $array_items = $this->get_function_call_parameters( $stackPtr ); + + if ( ( false === $this->allow_single_item_single_line_associative_arrays + && ! empty( $array_items ) ) + || ( true === $this->allow_single_item_single_line_associative_arrays + && count( $array_items ) > 1 ) + ) { + /* + * Make sure the double arrow is for *this* array, not for a nested one. + */ + $array_has_keys = false; // Reset before doing more detailed check. + foreach ( $array_items as $item ) { + for ( $ptr = $item['start']; $ptr <= $item['end']; $ptr++ ) { + if ( T_DOUBLE_ARROW === $this->tokens[ $ptr ]['code'] ) { + $array_has_keys = true; + break 2; + } + + // Skip passed any nested arrays. + if ( isset( $this->targets[ $this->tokens[ $ptr ]['code'] ] ) ) { + $nested_array_open_close = $this->find_array_open_close( $ptr ); + if ( false === $nested_array_open_close ) { + // Nested array open/close could not be determined. + continue; + } + + $ptr = $nested_array_open_close['closer']; + } + } + } + + if ( true === $array_has_keys ) { + + $phrase = 'an'; + if ( true === $this->allow_single_item_single_line_associative_arrays ) { + $phrase = 'a multi-item'; + } + $fix = $this->phpcsFile->addFixableError( + 'When %s array uses associative keys, each value should start on a new line.', + $closer, + 'AssociativeArrayFound', + array( $phrase ) + ); + + if ( true === $fix ) { + + $this->phpcsFile->fixer->beginChangeset(); + + foreach ( $array_items as $item ) { + /* + * Add a line break before the first non-empty token in the array item. + * Prevents extraneous whitespace at the start of the line which could be + * interpreted as alignment whitespace. + */ + $first_non_empty = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $item['start'], + ( $item['end'] + 1 ), + true + ); + if ( false === $first_non_empty ) { + continue; + } + + if ( $item['start'] <= ( $first_non_empty - 1 ) + && T_WHITESPACE === $this->tokens[ ( $first_non_empty - 1 ) ]['code'] + ) { + // Remove whitespace which would otherwise becoming trailing + // (as it gives problems with the fixed file). + $this->phpcsFile->fixer->replaceToken( ( $first_non_empty - 1 ), '' ); + } + + $this->phpcsFile->fixer->addNewlineBefore( $first_non_empty ); + } + + $this->phpcsFile->fixer->endChangeset(); + } + + // No need to check for spacing around opener/closer as this array should be multi-line. + return; + } } } - if ( T_WHITESPACE !== $tokens[ ( $arrayEnd - 1 ) ]['code'] ) { + /* + * Check that there is a single space after the array opener and before the array closer. + */ + if ( T_WHITESPACE !== $this->tokens[ ( $opener + 1 ) ]['code'] ) { - $warning = 'Missing space before array closer.'; - $fix = $phpcsFile->addFixableError( $warning, $arrayEnd, 'NoSpaceBeforeArrayCloser' ); + $fix = $this->phpcsFile->addFixableError( + 'Missing space after array opener.', + $opener, + 'NoSpaceAfterArrayOpener' + ); if ( true === $fix ) { - $phpcsFile->fixer->addContentBefore( $arrayEnd, ' ' ); + $this->phpcsFile->fixer->addContent( $opener, ' ' ); } - } elseif ( ' ' !== $tokens[ ( $arrayEnd - 1 ) ]['content'] ) { + } elseif ( ' ' !== $this->tokens[ ( $opener + 1 ) ]['content'] ) { - $fix = $phpcsFile->addFixableError( - 'Expected 1 space before array closer, found %s.', - $arrayEnd, - 'SpaceBeforeArrayCloser', - array( strlen( $tokens[ ( $arrayEnd - 1 ) ]['content'] ) ) + $fix = $this->phpcsFile->addFixableError( + 'Expected 1 space after array opener, found %s.', + $opener, + 'SpaceAfterArrayOpener', + array( strlen( $this->tokens[ ( $opener + 1 ) ]['content'] ) ) ); if ( true === $fix ) { - $phpcsFile->fixer->replaceToken( ( $arrayEnd - 1 ), ' ' ); + $this->phpcsFile->fixer->replaceToken( ( $opener + 1 ), ' ' ); } } - $array_has_keys = $phpcsFile->findNext( T_DOUBLE_ARROW, $arrayStart, $arrayEnd ); - if ( false !== $array_has_keys ) { - $fix = $phpcsFile->addFixableError( - 'When an array uses associative keys, each value should start on a new line.', - $arrayEnd, - 'AssociativeKeyFound' + if ( T_WHITESPACE !== $this->tokens[ ( $closer - 1 ) ]['code'] ) { + + $fix = $this->phpcsFile->addFixableError( + 'Missing space before array closer.', + $closer, + 'NoSpaceBeforeArrayCloser' ); if ( true === $fix ) { - // Only deal with one nesting level per loop to have the best chance of getting the indentation right. - static $current_loop = array(); - - if ( ! isset( $current_loop[ $phpcsFile->fixer->loops ] ) ) { - $current_loop[ $phpcsFile->fixer->loops ] = array_fill( 0, $phpcsFile->numTokens, false ); - } + $this->phpcsFile->fixer->addContentBefore( $closer, ' ' ); + } + } elseif ( ' ' !== $this->tokens[ ( $closer - 1 ) ]['content'] ) { - if ( false === $current_loop[ $phpcsFile->fixer->loops ][ $arrayStart ] ) { - for ( $i = $arrayStart; $i <= $arrayEnd; $i++ ) { - $current_loop[ $phpcsFile->fixer->loops ][ $i ] = true; - } + $fix = $this->phpcsFile->addFixableError( + 'Expected 1 space before array closer, found %s.', + $closer, + 'SpaceBeforeArrayCloser', + array( strlen( $this->tokens[ ( $closer - 1 ) ]['content'] ) ) + ); - $this->fix_associative_array( $phpcsFile, $arrayStart, $arrayEnd ); - } + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $closer - 1 ), ' ' ); } } } /** - * (Don't) Process a multi-line array. + * Process a multi-line array. * - * {@internal Multi-line arrays are handled by the upstream sniff via the - * WordPress_Sniffs_Arrays_ArrayDeclaration sniff.}} + * @since 0.13.0 The actual checks contained in this method used to + * be in the `ArrayDeclaration` sniff. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * @param int $arrayStart Position of the array opener in the token stack. - * @param int $arrayEnd Position of the array closer in the token stack. - */ - public function processMultiLineArray( PHP_CodeSniffer_File $phpcsFile, $stackPtr, $arrayStart, $arrayEnd ) { - return; - } // End processMultiLineArray(). - - /** - * Create & apply a changeset for a single line array with associative keys. - * - * @since 0.11.0 + * @param int $stackPtr The position of the current token in the stack. + * @param int $opener The position of the array opener. + * @param int $closer The position of the array closer. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $arrayStart Position of the array opener in the token stack. - * @param int $arrayEnd Position of the array closer in the token stack. * @return void */ - protected function fix_associative_array( PHP_CodeSniffer_File $phpcsFile, $arrayStart, $arrayEnd ) { - - $tokens = $phpcsFile->getTokens(); - - // Determine the needed indentation. - $indentation = ''; - for ( $i = $arrayStart; $i >= 0; $i-- ) { - if ( $tokens[ $i ]['line'] === $tokens[ $arrayStart ]['line'] ) { - continue; - } + protected function process_multi_line_array( $stackPtr, $opener, $closer ) { + /* + * Check that the closing bracket is on a new line. + */ + $last_content = $this->phpcsFile->findPrevious( T_WHITESPACE, ( $closer - 1 ), $opener, true ); + if ( false !== $last_content + && $this->tokens[ $last_content ]['line'] === $this->tokens[ $closer ]['line'] + ) { + $fix = $this->phpcsFile->addFixableError( + 'Closing parenthesis of array declaration must be on a new line', + $closer, + 'CloseBraceNewLine' + ); + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + if ( $last_content < ( $closer - 1 ) + && T_WHITESPACE === $this->tokens[ ( $closer - 1 ) ]['code'] + ) { + // Remove whitespace which would otherwise becoming trailing + // (as it gives problems with the fixed file). + $this->phpcsFile->fixer->replaceToken( ( $closer - 1 ), '' ); + } - if ( T_WHITESPACE === $tokens[ ( $i + 1 ) ]['code'] ) { - // Something weird going on with tabs vs spaces, but this fixes it. - $indentation = str_replace( ' ', "\t", $tokens[ ( $i + 1 ) ]['content'] ); + $this->phpcsFile->fixer->addNewlineBefore( $closer ); + $this->phpcsFile->fixer->endChangeset(); } - break; } - unset( $i ); - $value_indentation = "\t" . $indentation; + /* + * Check that each array item starts on a new line. + */ + $array_items = $this->get_function_call_parameters( $stackPtr ); + $end_of_last_item = $opener; + + foreach ( $array_items as $item ) { + $end_of_this_item = ( $item['end'] + 1 ); + + // Find the line on which the item starts. + $first_content = $this->phpcsFile->findNext( + array( T_WHITESPACE, T_DOC_COMMENT_WHITESPACE ), + $item['start'], + $end_of_this_item, + true + ); - // Which nesting level is the one we are interested in ? - $nesting_count = 1; - if ( T_OPEN_SHORT_ARRAY === $tokens[ $arrayStart ]['code'] ) { - $nesting_count = 0; - } + // Ignore comments after array items if the next real content starts on a new line. + if ( T_COMMENT === $this->tokens[ $first_content ]['code'] ) { + $next = $this->phpcsFile->findNext( + array( T_WHITESPACE, T_DOC_COMMENT_WHITESPACE ), + ( $first_content + 1 ), + $end_of_this_item, + true + ); + + if ( false === $next ) { + // Shouldn't happen, but just in case. + $end_of_last_item = $end_of_this_item; + continue; + } - if ( isset( $tokens[ $arrayStart ]['nested_parenthesis'] ) ) { - $nesting_count += count( $tokens[ $arrayStart ]['nested_parenthesis'] ); - } + if ( $this->tokens[ $next ]['line'] !== $this->tokens[ $first_content ]['line'] ) { + $first_content = $next; + } + } - // Record the required changes. - $phpcsFile->fixer->beginChangeset(); + if ( false === $first_content ) { + // Shouldn't happen, but just in case. + $end_of_last_item = $end_of_this_item; + continue; + } - $phpcsFile->fixer->addNewline( $arrayStart ); - if ( T_WHITESPACE === $tokens[ ( $arrayStart + 1 ) ]['code'] ) { - $phpcsFile->fixer->replaceToken( ( $arrayStart + 1 ), $value_indentation ); - } else { - $phpcsFile->fixer->addContentBefore( ( $arrayStart + 1 ), $value_indentation ); - } + if ( $this->tokens[ $end_of_last_item ]['line'] === $this->tokens[ $first_content ]['line'] ) { - for ( $ptr = ( $arrayStart + 1 ); $ptr < $arrayEnd; $ptr++ ) { - $ptr = $phpcsFile->findNext( array( T_COMMA, T_OPEN_SHORT_ARRAY ), $ptr, $arrayEnd ); + $fix = $this->phpcsFile->addFixableError( + 'Each item in a multi-line array must be on a new line', + $first_content, + 'ArrayItemNoNewLine' + ); - if ( false === $ptr ) { - break; - } + if ( true === $fix ) { - // Ignore anything within short array definition brackets. - // Necessary as the nesting level in that case is still the same. - if ( 'T_OPEN_SHORT_ARRAY' === $tokens[ $ptr ]['type'] - && ( isset( $tokens[ $ptr ]['bracket_opener'] ) - && $tokens[ $ptr ]['bracket_opener'] === $ptr ) - && isset( $tokens[ $ptr ]['bracket_closer'] ) - ) { - $ptr = $tokens[ $ptr ]['bracket_closer']; - continue; - } + $this->phpcsFile->fixer->beginChangeset(); - // Ignore comma's at a lower nesting level. - if ( 'T_COMMA' === $tokens[ $ptr ]['type'] - && isset( $tokens[ $ptr ]['nested_parenthesis'] ) - && count( $tokens[ $ptr ]['nested_parenthesis'] ) !== $nesting_count - ) { - continue; - } + if ( $item['start'] <= ( $first_content - 1 ) + && T_WHITESPACE === $this->tokens[ ( $first_content - 1 ) ]['code'] + ) { + // Remove whitespace which would otherwise becoming trailing + // (as it gives problems with the fixed file). + $this->phpcsFile->fixer->replaceToken( ( $first_content - 1 ), '' ); + } - $phpcsFile->fixer->addNewline( $ptr ); - if ( isset( $tokens[ ( $ptr + 1 ) ] ) ) { - if ( T_WHITESPACE === $tokens[ ( $ptr + 1 ) ]['code'] ) { - $phpcsFile->fixer->replaceToken( ( $ptr + 1 ), $value_indentation ); - } else { - $phpcsFile->fixer->addContentBefore( ( $ptr + 1 ), $value_indentation ); + $this->phpcsFile->fixer->addNewlineBefore( $first_content ); + $this->phpcsFile->fixer->endChangeset(); } } - } - - $token_before_end = $phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$emptyTokens, ( $arrayEnd - 1 ), $arrayStart, true, null, true ); - if ( 'T_COMMA' !== $tokens[ $token_before_end ]['type'] ) { - $phpcsFile->fixer->addContent( $token_before_end, ',' ); - if ( T_WHITESPACE === $tokens[ ( $arrayEnd - 1 ) ]['code'] || "\n" === $phpcsFile->fixer->getTokenContent( ( $arrayEnd - 1 ) ) ) { - $phpcsFile->fixer->replaceToken( ( $arrayEnd - 1 ), "\n" . $indentation ); - } else { - $phpcsFile->fixer->addContentBefore( $arrayEnd, "\n" . $indentation ); - } - } elseif ( $value_indentation === $phpcsFile->fixer->getTokenContent( ( $arrayEnd - 1 ) ) ) { - $phpcsFile->fixer->replaceToken( ( $arrayEnd - 1 ), $indentation ); + $end_of_last_item = $end_of_this_item; } - - $phpcsFile->fixer->endChangeset(); - } // End fix_associative_array(). + } } // End class. diff --git a/WordPress/Sniffs/Arrays/ArrayIndentationSniff.php b/WordPress/Sniffs/Arrays/ArrayIndentationSniff.php new file mode 100644 index 00000000..c62e2ff1 --- /dev/null +++ b/WordPress/Sniffs/Arrays/ArrayIndentationSniff.php @@ -0,0 +1,524 @@ +ignore_tokens = Tokens::$heredocTokens; + unset( $this->ignore_tokens[ T_START_HEREDOC ], $this->ignore_tokens[ T_START_NOWDOC ] ); + $this->ignore_tokens[ T_INLINE_HTML ] = T_INLINE_HTML; + + return array( + T_ARRAY, + T_OPEN_SHORT_ARRAY, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + if ( ! isset( $this->tab_width ) ) { + $this->tab_width = PHPCSHelper::get_tab_width( $this->phpcsFile ); + } + + /* + * Determine the array opener & closer. + */ + $array_open_close = $this->find_array_open_close( $stackPtr ); + if ( false === $array_open_close ) { + // Array open/close could not be determined. + return; + } + + $opener = $array_open_close['opener']; + $closer = $array_open_close['closer']; + + if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) { + // Not interested in single line arrays. + return; + } + + /* + * Check the closing bracket is lined up with the start of the content on the line + * containing the array opener. + */ + $opener_line_spaces = $this->get_indentation_size( $opener ); + $closer_line_spaces = ( $this->tokens[ $closer ]['column'] - 1 ); + + if ( $closer_line_spaces !== $opener_line_spaces ) { + $error = 'Array closer not aligned correctly; expected %s space(s) but found %s'; + $error_code = 'CloseBraceNotAligned'; + + /* + * Report & fix the issue if the close brace is on its own line with + * nothing or only indentation whitespace before it. + */ + if ( 0 === $closer_line_spaces + || ( T_WHITESPACE === $this->tokens[ ( $closer - 1 ) ]['code'] + && 1 === $this->tokens[ ( $closer - 1 ) ]['column'] ) + ) { + $this->add_array_alignment_error( + $closer, + $error, + $error_code, + $opener_line_spaces, + $closer_line_spaces, + $this->get_indentation_string( $opener_line_spaces ) + ); + } else { + /* + * Otherwise, only report the error, don't try and fix it (yet). + * + * It will get corrected in a future loop of the fixer once the closer + * has been moved to its own line by the `ArrayDeclarationSpacing` sniff. + */ + $this->phpcsFile->addError( + $error, + $closer, + $error_code, + array( $opener_line_spaces, $closer_line_spaces ) + ); + } + + unset( $error, $error_code ); + } + + /* + * Verify & correct the array item indentation. + */ + $array_items = $this->get_function_call_parameters( $stackPtr ); + if ( empty( $array_items ) ) { + // Strange, no array items found. + return; + } + + $expected_spaces = ( $opener_line_spaces + $this->tab_width ); + $expected_indent = $this->get_indentation_string( $expected_spaces ); + $end_of_previous_item = $opener; + + foreach ( $array_items as $item ) { + $end_of_this_item = ( $item['end'] + 1 ); + + // Find the line on which the item starts. + $first_content = $this->phpcsFile->findNext( + array( T_WHITESPACE, T_DOC_COMMENT_WHITESPACE ), + $item['start'], + $end_of_this_item, + true + ); + + // Deal with trailing comments. + if ( false !== $first_content + && T_COMMENT === $this->tokens[ $first_content ]['code'] + && $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_previous_item ]['line'] + ) { + $first_content = $this->phpcsFile->findNext( + array( T_WHITESPACE, T_DOC_COMMENT_WHITESPACE ), + ( $first_content + 1 ), + $end_of_this_item, + true + ); + } + + if ( false === $first_content ) { + $end_of_previous_item = $end_of_this_item; + continue; + } + + // Bow out from reporting and fixing mixed multi-line/single-line arrays. + // That is handled by the ArrayDeclarationSpacingSniff. + if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_previous_item ]['line'] + || ( 1 !== $this->tokens[ $first_content ]['column'] + && T_WHITESPACE !== $this->tokens[ ( $first_content - 1 ) ]['code'] ) + ) { + return $closer; + } + + $found_spaces = ( $this->tokens[ $first_content ]['column'] - 1 ); + + if ( $found_spaces !== $expected_spaces ) { + $this->add_array_alignment_error( + $first_content, + 'Array item not aligned correctly; expected %s spaces but found %s', + 'ItemNotAligned', + $expected_spaces, + $found_spaces, + $expected_indent + ); + } + + // No need for further checking if this is a one-line array item. + if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $item['end'] ]['line'] ) { + $end_of_previous_item = $end_of_this_item; + continue; + } + + /* + * Multi-line array items. + * + * Verify & if needed, correct the indentation of subsequent lines. + * Subsequent lines may be indented more or less than the mimimum expected indent, + * but the "first line after" should be indented - at least - as much as the very first line + * of the array item. + * Indentation correction for subsequent lines will be based on that diff. + */ + + // Find first token on second line of the array item. + // If the second line is a heredoc/nowdoc, continue on until we find a line with a different token. + // Same for the second line of a multi-line text string. + for ( $ptr = ( $first_content + 1 ); $ptr <= $item['end']; $ptr++ ) { + if ( $this->tokens[ $first_content ]['line'] !== $this->tokens[ $ptr ]['line'] + && 1 === $this->tokens[ $ptr ]['column'] + && false === $this->ignore_token( $ptr ) + ) { + break; + } + } + + $first_content_on_line2 = $this->phpcsFile->findNext( + array( T_WHITESPACE, T_DOC_COMMENT_WHITESPACE ), + $ptr, + $end_of_this_item, + true + ); + + if ( false === $first_content_on_line2 ) { + /* + * Apparently there were only tokens in the ignore list on subsequent lines. + * + * In that case, the comma after the array item might be on a line by itself, + * so check its placement. + */ + if ( $this->tokens[ $item['end'] ]['line'] !== $this->tokens[ $end_of_this_item ]['line'] + && T_COMMA === $this->tokens[ $end_of_this_item ]['code'] + && ( $this->tokens[ $end_of_this_item ]['column'] - 1 ) !== $expected_spaces + ) { + $this->add_array_alignment_error( + $end_of_this_item, + 'Comma after multi-line array item not aligned correctly; expected %s spaces, but found %s', + 'MultiLineArrayItemCommaNotAligned', + $expected_spaces, + ( $this->tokens[ $end_of_this_item ]['column'] - 1 ), + $expected_indent + ); + } + + $end_of_previous_item = $end_of_this_item; + continue; + } + + $found_spaces_on_line2 = $this->get_indentation_size( $first_content_on_line2 ); + $expected_spaces_on_line2 = $expected_spaces; + + if ( $found_spaces < $found_spaces_on_line2 ) { + $expected_spaces_on_line2 += ( $found_spaces_on_line2 - $found_spaces ); + } + + if ( $found_spaces_on_line2 !== $expected_spaces_on_line2 ) { + + $fix = $this->phpcsFile->addFixableError( + 'Multi-line array item not aligned correctly; expected %s spaces, but found %s', + $first_content_on_line2, + 'MultiLineArrayItemNotAligned', + array( + $expected_spaces_on_line2, + $found_spaces_on_line2, + ) + ); + + if ( true === $fix ) { + $expected_indent_on_line2 = $this->get_indentation_string( $expected_spaces_on_line2 ); + + $this->phpcsFile->fixer->beginChangeset(); + + // Fix second line for the array item. + if ( 1 === $this->tokens[ $first_content_on_line2 ]['column'] + && T_COMMENT === $this->tokens[ $first_content_on_line2 ]['code'] + ) { + $actual_comment = ltrim( $this->tokens[ $first_content_on_line2 ]['content'] ); + $replacement = $expected_indent_on_line2 . $actual_comment; + + $this->phpcsFile->fixer->replaceToken( $first_content_on_line2, $replacement ); + + } else { + $this->fix_alignment_error( $first_content_on_line2, $expected_indent_on_line2 ); + } + + // Fix subsequent lines. + for ( $i = ( $first_content_on_line2 + 1 ); $i <= $item['end']; $i++ ) { + // We're only interested in the first token on each line. + if ( 1 !== $this->tokens[ $i ]['column'] ) { + if ( $this->tokens[ $i ]['line'] === $this->tokens[ $item['end'] ]['line'] ) { + // We might as well quit if we're past the first token on the last line. + break; + } + continue; + } + + $first_content_on_line = $this->phpcsFile->findNext( + array( T_WHITESPACE, T_DOC_COMMENT_WHITESPACE ), + $i, + $end_of_this_item, + true + ); + + if ( false === $first_content_on_line ) { + break; + } + + // Ignore lines with heredoc and nowdoc tokens and subsequent lines in multi-line strings. + if ( true === $this->ignore_token( $first_content_on_line ) ) { + $i = $first_content_on_line; + continue; + } + + $found_spaces_on_line = $this->get_indentation_size( $first_content_on_line ); + $expected_spaces_on_line = ( $expected_spaces_on_line2 + ( $found_spaces_on_line - $found_spaces_on_line2 ) ); + $expected_spaces_on_line = max( $expected_spaces_on_line, 0 ); // Can't be below 0. + $expected_indent_on_line = $this->get_indentation_string( $expected_spaces_on_line ); + + if ( $found_spaces_on_line !== $expected_spaces_on_line ) { + if ( 1 === $this->tokens[ $first_content_on_line ]['column'] + && T_COMMENT === $this->tokens[ $first_content_on_line ]['code'] + ) { + $actual_comment = ltrim( $this->tokens[ $first_content_on_line ]['content'] ); + $replacement = $expected_indent_on_line . $actual_comment; + + $this->phpcsFile->fixer->replaceToken( $first_content_on_line, $replacement ); + } else { + $this->fix_alignment_error( $first_content_on_line, $expected_indent_on_line ); + } + } + + // Move past any potential empty lines between the previous non-empty line and this one. + // No need to do the fixes twice. + $i = $first_content_on_line; + } + + /* + * Check the placement of the comma after the array item as it might be on a line by itself. + */ + if ( $this->tokens[ $item['end'] ]['line'] !== $this->tokens[ $end_of_this_item ]['line'] + && T_COMMA === $this->tokens[ $end_of_this_item ]['code'] + && ( $this->tokens[ $end_of_this_item ]['column'] - 1 ) !== $expected_spaces + ) { + $this->add_array_alignment_error( + $end_of_this_item, + 'Comma after array item not aligned correctly; expected %s spaces, but found %s', + 'MultiLineArrayItemCommaNotAligned', + $expected_spaces, + ( $this->tokens[ $end_of_this_item ]['column'] - 1 ), + $expected_indent + ); + } + + $this->phpcsFile->fixer->endChangeset(); + } + } + + $end_of_previous_item = $end_of_this_item; + } + + } // End process_token(). + + + /** + * Should the token be ignored ? + * + * This method is only intended to be used with the first token on a line + * for subsequent lines in an multi-line array item. + * + * @param int $ptr Stack pointer to the first token on a line. + * + * @return bool + */ + protected function ignore_token( $ptr ) { + $token_code = $this->tokens[ $ptr ]['code']; + + if ( isset( $this->ignore_tokens[ $token_code ] ) ) { + return true; + } + + // If it's a subsequent line of a multi-line sting, it will not start with a quote character. + if ( ( T_CONSTANT_ENCAPSED_STRING === $token_code + || T_DOUBLE_QUOTED_STRING === $token_code ) + && "'" !== $this->tokens[ $ptr ]['content'][0] + && '"' !== $this->tokens[ $ptr ]['content'][0] + ) { + return true; + } + + return false; + } + + /** + * Determine the line indentation whitespace. + * + * @param int $ptr Stack pointer to an arbitrary token on a line. + * + * @return int Nr of spaces found. Where necessary, tabs are translated to spaces. + */ + protected function get_indentation_size( $ptr ) { + + // Find the first token on the line. + for ( ; $ptr >= 0; $ptr-- ) { + if ( 1 === $this->tokens[ $ptr ]['column'] ) { + break; + } + } + + $whitespace = ''; + + if ( T_WHITESPACE === $this->tokens[ $ptr ]['code'] + || T_DOC_COMMENT_WHITESPACE === $this->tokens[ $ptr ]['code'] + ) { + return $this->tokens[ $ptr ]['length']; + } + + /* + * Special case for multi-line, non-docblock comments. + * Only applicable for subsequent lines in an array item. + * + * First/Single line is tokenized as T_WHITESPACE + T_COMMENT + * Subsequent lines are tokenized as T_COMMENT including the indentation whitespace. + */ + if ( T_COMMENT === $this->tokens[ $ptr ]['code'] ) { + $content = $this->tokens[ $ptr ]['content']; + $actual_comment = ltrim( $content ); + $whitespace = str_replace( $actual_comment, '', $content ); + } + + return strlen( $whitespace ); + } + + /** + * Create an indentation string. + * + * @param int $nr Number of spaces the indentation should be. + * + * @return string + */ + protected function get_indentation_string( $nr ) { + if ( 0 >= $nr ) { + return ''; + } + + // Space-based indentation. + if ( false === $this->tabIndent ) { + return str_repeat( ' ', $nr ); + } + + // Tab-based indentation. + $num_tabs = (int) floor( $nr / $this->tab_width ); + $remaining = ( $nr % $this->tab_width ); + $tab_indent = str_repeat( "\t", $num_tabs ); + $tab_indent .= str_repeat( ' ', $remaining ); + + return $tab_indent; + } + + /** + * Throw an error and fix incorrect array alignment. + * + * @param int $ptr Stack pointer to the first content on the line. + * @param string $error Error message. + * @param string $error_code Error code. + * @param int $expected Expected nr of spaces (tabs translated to space value). + * @param int $found Found nr of spaces (tabs translated to space value). + * @param string $new_indent Whitespace indent replacement content. + */ + protected function add_array_alignment_error( $ptr, $error, $error_code, $expected, $found, $new_indent ) { + + $fix = $this->phpcsFile->addFixableError( $error, $ptr, $error_code, array( $expected, $found ) ); + if ( true === $fix ) { + $this->fix_alignment_error( $ptr, $new_indent ); + } + } + + /** + * Fix incorrect array alignment. + * + * @param int $ptr Stack pointer to the first content on the line. + * @param string $new_indent Whitespace indent replacement content. + */ + protected function fix_alignment_error( $ptr, $new_indent ) { + if ( 1 === $this->tokens[ $ptr ]['column'] ) { + $this->phpcsFile->fixer->addContentBefore( $ptr, $new_indent ); + } else { + $this->phpcsFile->fixer->replaceToken( ( $ptr - 1 ), $new_indent ); + } + } + +} // End class. diff --git a/WordPress/Sniffs/Arrays/ArrayKeySpacingRestrictionsSniff.php b/WordPress/Sniffs/Arrays/ArrayKeySpacingRestrictionsSniff.php index ba1147fc..73e38aa6 100644 --- a/WordPress/Sniffs/Arrays/ArrayKeySpacingRestrictionsSniff.php +++ b/WordPress/Sniffs/Arrays/ArrayKeySpacingRestrictionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Arrays; + +use WordPress\Sniff; + /** * Check for proper spacing in array key references. * @@ -15,9 +19,11 @@ * @package WPCS\WordPressCodingStandards * * @since 0.3.0 - * @since 0.7.0 This sniff now has the ability to fix a number of the issues it flags. + * @since 0.7.0 This sniff now has the ability to fix a number of the issues it flags. + * @since 0.12.0 This class now extends WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_Arrays_ArrayKeySpacingRestrictionsSniff implements PHP_CodeSniffer_Sniff { +class ArrayKeySpacingRestrictionsSniff extends Sniff { /** * Returns an array of tokens this test wants to listen for. @@ -34,52 +40,49 @@ public function register() { /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack. * * @return void */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); + public function process_token( $stackPtr ) { - $token = $tokens[ $stackPtr ]; + $token = $this->tokens[ $stackPtr ]; if ( ! isset( $token['bracket_closer'] ) ) { - $phpcsFile->addWarning( 'Missing bracket closer.', $stackPtr, 'MissingBracketCloser' ); + $this->phpcsFile->addWarning( 'Missing bracket closer.', $stackPtr, 'MissingBracketCloser' ); return; } - $need_spaces = $phpcsFile->findNext( + $need_spaces = $this->phpcsFile->findNext( array( T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_WHITESPACE, T_MINUS ), ( $stackPtr + 1 ), $token['bracket_closer'], true ); - $spaced1 = ( T_WHITESPACE === $tokens[ ( $stackPtr + 1 ) ]['code'] ); - $spaced2 = ( T_WHITESPACE === $tokens[ ( $token['bracket_closer'] - 1 ) ]['code'] ); + $spaced1 = ( T_WHITESPACE === $this->tokens[ ( $stackPtr + 1 ) ]['code'] ); + $spaced2 = ( T_WHITESPACE === $this->tokens[ ( $token['bracket_closer'] - 1 ) ]['code'] ); - // It should have spaces only if it only has strings or numbers as the key. + // It should have spaces unless if it only has strings or numbers as the key. if ( false !== $need_spaces && ! ( $spaced1 && $spaced2 ) ) { $error = 'Array keys must be surrounded by spaces unless they contain a string or an integer.'; - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'NoSpacesAroundArrayKeys' ); + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpacesAroundArrayKeys' ); if ( true === $fix ) { if ( ! $spaced1 ) { - $phpcsFile->fixer->addContentBefore( ( $stackPtr + 1 ), ' ' ); + $this->phpcsFile->fixer->addContentBefore( ( $stackPtr + 1 ), ' ' ); } if ( ! $spaced2 ) { - $phpcsFile->fixer->addContentBefore( $token['bracket_closer'], ' ' ); + $this->phpcsFile->fixer->addContentBefore( $token['bracket_closer'], ' ' ); } } } elseif ( false === $need_spaces && ( $spaced1 || $spaced2 ) ) { $error = 'Array keys must NOT be surrounded by spaces if they only contain a string or an integer.'; - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'SpacesAroundArrayKeys' ); + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'SpacesAroundArrayKeys' ); if ( true === $fix ) { if ( $spaced1 ) { - $phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), '' ); + $this->phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), '' ); } if ( $spaced2 ) { - $phpcsFile->fixer->replaceToken( ( $token['bracket_closer'] - 1 ), '' ); + $this->phpcsFile->fixer->replaceToken( ( $token['bracket_closer'] - 1 ), '' ); } } } diff --git a/WordPress/Sniffs/Arrays/CommaAfterArrayItemSniff.php b/WordPress/Sniffs/Arrays/CommaAfterArrayItemSniff.php new file mode 100644 index 00000000..6d90d35c --- /dev/null +++ b/WordPress/Sniffs/Arrays/CommaAfterArrayItemSniff.php @@ -0,0 +1,286 @@ +find_array_open_close( $stackPtr ); + if ( false === $array_open_close ) { + // Array open/close could not be determined. + return; + } + + $opener = $array_open_close['opener']; + $closer = $array_open_close['closer']; + unset( $array_open_close ); + + // This array is empty, so the below checks aren't necessary. + if ( ( $opener + 1 ) === $closer ) { + return; + } + + $single_line = true; + if ( $this->tokens[ $opener ]['line'] !== $this->tokens[ $closer ]['line'] ) { + $single_line = false; + } + + $array_items = $this->get_function_call_parameters( $stackPtr ); + if ( empty( $array_items ) ) { + // Strange, no array items found. + return; + } + + $array_item_count = count( $array_items ); + + // Note: $item_index is 1-based and the array items are split on the commas! + foreach ( $array_items as $item_index => $item ) { + $maybe_comma = ( $item['end'] + 1 ); + $is_comma = false; + if ( isset( $this->tokens[ $maybe_comma ] ) && T_COMMA === $this->tokens[ $maybe_comma ]['code'] ) { + $is_comma = true; + } + + /* + * Check if this is a comma at the end of the last item in a single line array. + */ + if ( true === $single_line && $item_index === $array_item_count ) { + + if ( true === $is_comma ) { + $fix = $this->phpcsFile->addFixableError( + 'Comma not allowed after last value in single-line array declaration', + $maybe_comma, + 'CommaAfterLast' + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( $maybe_comma, '' ); + } + } + + /* + * No need to do the spacing checks for the last item in a single line array. + * This is handled by another sniff checking the spacing before the array closer. + */ + continue; + } + + $last_content = $this->phpcsFile->findPrevious( + Tokens::$emptyTokens, + $item['end'], + $item['start'], + true + ); + + if ( false === $last_content ) { + // Shouldn't be able to happen, but just in case, ignore this array item. + continue; + } + + /** + * Make sure every item in a multi-line array has a comma at the end. + * + * Should in reality only be triggered by the last item in a multi-line array + * as otherwise we'd have a parse error already. + */ + if ( false === $is_comma && false === $single_line ) { + + $fix = $this->phpcsFile->addFixableError( + 'Each array item in a multi-line array declaration must end in a comma', + $last_content, + 'NoComma' + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContent( $last_content, ',' ); + } + } + + if ( false === $is_comma ) { + // Can't check spacing around the comma if there is no comma. + continue; + } + + /* + * Check for whitespace at the end of the array item. + */ + if ( $last_content !== $item['end'] + // Ignore whitespace at the end of a multi-line item if it is the end of a heredoc/nowdoc. + && ( true === $single_line + || ! isset( Tokens::$heredocTokens[ $this->tokens[ $last_content ]['code'] ] ) ) + ) { + $newlines = 0; + $spaces = 0; + for ( $i = $item['end']; $i > $last_content; $i-- ) { + + if ( T_WHITESPACE === $this->tokens[ $i ]['code'] ) { + if ( $this->tokens[ $i ]['content'] === $this->phpcsFile->eolChar ) { + $newlines++; + } else { + $spaces += $this->tokens[ $i ]['length']; + } + } elseif ( T_COMMENT === $this->tokens[ $i ]['code'] ) { + break; + } + } + + $space_phrases = array(); + if ( $spaces > 0 ) { + $space_phrases[] = $spaces . ' spaces'; + } + if ( $newlines > 0 ) { + $space_phrases[] = $newlines . ' newlines'; + } + unset( $newlines, $spaces ); + + $fix = $this->phpcsFile->addFixableError( + 'Expected 0 spaces between "%s" and comma; %s found', + $maybe_comma, + 'SpaceBeforeComma', + array( + $this->tokens[ $last_content ]['content'], + implode( ' and ', $space_phrases ), + ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = $item['end']; $i > $last_content; $i-- ) { + + if ( T_WHITESPACE === $this->tokens[ $i ]['code'] ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + + } elseif ( T_COMMENT === $this->tokens[ $i ]['code'] ) { + // We need to move the comma to before the comment. + $this->phpcsFile->fixer->addContent( $last_content, ',' ); + $this->phpcsFile->fixer->replaceToken( $maybe_comma, '' ); + + /* + * No need to worry about removing too much whitespace in + * combination with a `//` comment as in that case, the newline + * is part of the comment, so we're good. + */ + + break; + } + } + $this->phpcsFile->fixer->endChangeset(); + } + } + + if ( ! isset( $this->tokens[ ( $maybe_comma + 1 ) ] ) ) { + // Shouldn't be able to happen, but just in case. + continue; + } + + /* + * Check whitespace after the comma. + */ + $next_token = $this->tokens[ ( $maybe_comma + 1 ) ]; + + if ( T_WHITESPACE === $next_token['code'] ) { + + if ( false === $single_line && $this->phpcsFile->eolChar === $next_token['content'] ) { + continue; + } + + $next_non_whitespace = $this->phpcsFile->findNext( + T_WHITESPACE, + ( $maybe_comma + 1 ), + $closer, + true + ); + + if ( false === $next_non_whitespace + || ( false === $single_line + && $this->tokens[ $next_non_whitespace ]['line'] === $this->tokens[ $maybe_comma ]['line'] + && T_COMMENT === $this->tokens[ $next_non_whitespace ]['code'] ) + ) { + continue; + } + + $space_length = $next_token['length']; + if ( 1 === $space_length ) { + continue; + } + + $fix = $this->phpcsFile->addFixableError( + 'Expected 1 space between comma and "%s"; %s found', + $maybe_comma, + 'SpaceAfterComma', + array( + $this->tokens[ $next_non_whitespace ]['content'], + $space_length, + ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $maybe_comma + 1 ), ' ' ); + } + } else { + // This is either a comment or a mixed single/multi-line array. + // Just add a space and let other sniffs sort out the array layout. + $fix = $this->phpcsFile->addFixableError( + 'Expected 1 space between comma and "%s"; 0 found', + $maybe_comma, + 'NoSpaceAfterComma', + array( $next_token['content'] ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContent( $maybe_comma, ' ' ); + } + } + } + } + +} // End class. diff --git a/WordPress/Sniffs/Arrays/MultipleStatementAlignmentSniff.php b/WordPress/Sniffs/Arrays/MultipleStatementAlignmentSniff.php new file mode 100644 index 00000000..43d8b9dd --- /dev/null +++ b/WordPress/Sniffs/Arrays/MultipleStatementAlignmentSniff.php @@ -0,0 +1,609 @@ += 60, align at column 60. + * - for the outliers, i.e. the array indexes where the end position + * goes past column 60, it will not align the arrow, the sniff will + * just make sure there is only one space between the end of the + * array index and the double arrow. + * + * The column value is regarded as a hard value, i.e. includes indentation, + * so setting it very low is not a good idea. + * + * @since 0.14.0 + * + * @var int + */ + public $maxColumn = 1000; + + /** + * Whether or not to align the arrow operator for multi-line array items. + * + * Whether or not an item is regarded as multi-line is based on the **value** + * of the item, not the key. + * + * Valid values are: + * - 'always': Default. Align all arrays items regardless of single/multi-line. + * - 'never': Never align array items which span multiple lines. + * This will enforce one space between the array index and the + * double arrow operator for multi-line array items, independently + * of the alignment of the rest of the array items. + * Multi-line items where the arrow is already aligned with the + * "expected" alignment, however, will be left alone. + * - operator : Only align the operator for multi-line arrays items if the + * + number percentage of multi-line items passes the comparison. + * - As it is a percentage, the number has to be between 0 and 100. + * - Supported operators: <, <=, >, >=, ==, =, !=, <> + * - The percentage is calculated against all array items + * (with and without assignment operator). + * - The (new) expected alignment will be calculated based only + * on the items being aligned. + * - Multi-line items where the arrow is already aligned with the + * (new) "expected" alignment, however, will be left alone. + * Examples: + * * Setting this to `!=100` or `<100` means that alignment will + * be enforced, unless *all* array items are multi-line. + * This is probably the most commonly desired situation. + * * Setting this to `=100` means that alignment will only + * be enforced, if *all* array items are multi-line. + * * Setting this to `<50` means that the majority of array items + * need to be single line before alignment is enforced for + * multi-line items in the array. + * * Setting this to `=0` is useless as in that case there are + * no multi-line items in the array anyway. + * + * This setting will respect the `ignoreNewlines` and `maxColumnn` settings. + * + * @since 0.14.0 + * + * @var string|int + */ + public $alignMultilineItems = 'always'; + + /** + * Storage for parsed $alignMultilineItems operator part. + * + * @since 0.14.0 + * + * @var string + */ + private $operator; + + /** + * Storage for parsed $alignMultilineItems numeric part. + * + * Stored as a string as the comparison will be done string based. + * + * @since 0.14.0 + * + * @var string + */ + private $number; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.14.0 + * + * @return array + */ + public function register() { + return array( + T_ARRAY, + T_OPEN_SHORT_ARRAY, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + /* + * Determine the array opener & closer. + */ + $array_open_close = $this->find_array_open_close( $stackPtr ); + if ( false === $array_open_close ) { + // Array open/close could not be determined. + return; + } + + $opener = $array_open_close['opener']; + $closer = $array_open_close['closer']; + + $array_items = $this->get_function_call_parameters( $stackPtr ); + if ( empty( $array_items ) ) { + return; + } + + // Pass off to either the single line or multi-line array analysis. + if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) { + return $this->process_single_line_array( $stackPtr, $array_items, $opener, $closer ); + } else { + return $this->process_multi_line_array( $stackPtr, $array_items, $opener, $closer ); + } + } + + /** + * Process a single-line array. + * + * While the WP standard does not allow single line multi-item associative arrays, + * this sniff should function independently of that. + * + * The `WordPress.WhiteSpace.OperatorSpacing` sniff already covers checking that + * there is a space between the array key and the double arrow, but doesn't + * enforce it to be exactly one space for single line arrays. + * That is what this method covers. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param array $items Info array containing information on each array item. + * @param int $opener The position of the array opener. + * @param int $closer The position of the array closer. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + protected function process_single_line_array( $stackPtr, $items, $opener, $closer ) { + /* + * For single line arrays, we don't care about what level the arrow is from. + * Just find and fix them all. + */ + $next_arrow = $this->phpcsFile->findNext( + T_DOUBLE_ARROW, + ( $opener + 1 ), + $closer + ); + + while ( false !== $next_arrow ) { + if ( T_WHITESPACE === $this->tokens[ ( $next_arrow - 1 ) ]['code'] ) { + $space_length = $this->tokens[ ( $next_arrow - 1 ) ]['length']; + if ( 1 !== $space_length ) { + $error = 'Expected 1 space between "%s" and double arrow; %s found'; + $data = array( + $this->tokens[ ( $next_arrow - 2 ) ]['content'], + $space_length, + ); + + $fix = $this->phpcsFile->addFixableWarning( $error, $next_arrow, 'SpaceBeforeDoubleArrow', $data ); + if ( true === $fix ) { + $this->phpcsFile->fixer->replaceToken( ( $next_arrow - 1 ), ' ' ); + } + } + } + + // Find the position of the next double arrow. + $next_arrow = $this->phpcsFile->findNext( + T_DOUBLE_ARROW, + ( $next_arrow + 1 ), + $closer + ); + } + + // Ignore any child-arrays as the double arrows in these will already have been handled. + return ( $closer + 1 ); + } + + /** + * Process a multi-line array. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * @param array $items Info array containing information on each array item. + * @param int $opener The position of the array opener. + * @param int $closer The position of the array closer. + * + * @return void + */ + protected function process_multi_line_array( $stackPtr, $items, $opener, $closer ) { + + $this->maxColumn = (int) $this->maxColumn; + $this->validate_align_multiline_items(); + + /* + * Determine what the spacing before the arrow should be. + * + * Will unset any array items without double arrow and with new line whitespace + * if newlines are to be ignored, so the second foreach loop only has to deal + * with items which need attention. + * + * This sniff does not take incorrect indentation of array keys into account. + * That's for the `WordPress.Arrays.ArrayIndentation` sniff to fix. + * If that would affect the alignment, a second (or third) loop of the fixer + * will correct it (again) after the indentation has been fixed. + */ + $index_end_cols = array(); // Keep track of the end column position of index keys. + $double_arrow_cols = array(); // Keep track of arrow column position and count. + $multi_line_count = 0; + $total_items = count( $items ); + + foreach ( $items as $key => $item ) { + if ( strpos( $item['raw'], '=>' ) === false ) { + // Ignore items without assignment operators. + unset( $items[ $key ] ); + continue; + } + + // Find the position of the first double arrow. + $double_arrow = $this->phpcsFile->findNext( + T_DOUBLE_ARROW, + $item['start'], + ( $item['end'] + 1 ) + ); + + if ( false === $double_arrow ) { + // Shouldn't happen, just in case. + unset( $items[ $key ] ); + continue; + } + + // Make sure the arrow is for this item and not for a nested array value assignment. + $has_array_opener = $this->phpcsFile->findNext( + $this->register(), + $item['start'], + $double_arrow + ); + + if ( false !== $has_array_opener ) { + // Double arrow is for a nested array. + unset( $items[ $key ] ); + continue; + } + + // Find the end of the array key. + $last_index_token = $this->phpcsFile->findPrevious( + T_WHITESPACE, + ( $double_arrow - 1 ), + $item['start'], + true + ); + + if ( false === $last_index_token ) { + // Shouldn't happen, but just in case. + unset( $items[ $key ] ); + continue; + } + + if ( true === $this->ignoreNewlines + && $this->tokens[ $last_index_token ]['line'] !== $this->tokens[ $double_arrow ]['line'] + ) { + // Ignore this item as it has a new line between the item key and the double arrow. + unset( $items[ $key ] ); + continue; + } + + $index_end_position = ( $this->tokens[ $last_index_token ]['column'] + ( $this->tokens[ $last_index_token ]['length'] - 1 ) ); + $items[ $key ]['operatorPtr'] = $double_arrow; + $items[ $key ]['last_index_token'] = $last_index_token; + $items[ $key ]['last_index_col'] = $index_end_position; + + if ( $this->tokens[ $last_index_token ]['line'] === $this->tokens[ $item['end'] ]['line'] ) { + $items[ $key ]['single_line'] = true; + } else { + $items[ $key ]['single_line'] = false; + $multi_line_count++; + } + + if ( ( $index_end_position + 2 ) <= $this->maxColumn ) { + $index_end_cols[] = $index_end_position; + } + + if ( ! isset( $double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ] ) ) { + $double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ] = 1; + } else { + $double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ]++; + } + } + unset( $key, $item, $double_arrow, $has_array_opener, $last_index_token ); + + if ( empty( $items ) || empty( $index_end_cols ) ) { + // No actionable array items found. + return; + } + + /* + * Determine whether the operators for multi-line items should be aligned. + */ + if ( 'always' === $this->alignMultilineItems ) { + $alignMultilineItems = true; + } elseif ( 'never' === $this->alignMultilineItems ) { + $alignMultilineItems = false; + } else { + $percentage = (string) round( ( $multi_line_count / $total_items ) * 100, 0 ); + + // Bit hacky, but this is the only comparison function in PHP which allows to + // pass the comparison operator. And hey, it works ;-). + $alignMultilineItems = version_compare( $percentage, $this->number, $this->operator ); + } + + /* + * If necessary, rebuild the $index_end_cols and $double_arrow_cols arrays + * excluding multi-line items. + */ + if ( false === $alignMultilineItems ) { + $select_index_end_cols = array(); + $double_arrow_cols = array(); + + foreach ( $items as $item ) { + if ( false === $item['single_line'] ) { + continue; + } + + if ( ( $item['last_index_col'] + 2 ) <= $this->maxColumn ) { + $select_index_end_cols[] = $item['last_index_col']; + } + + if ( ! isset( $double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ] ) ) { + $double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ] = 1; + } else { + $double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ]++; + } + } + } + + /* + * Determine the expected position of the double arrows. + */ + if ( ! empty( $select_index_end_cols ) ) { + $max_index_width = max( $select_index_end_cols ); + } else { + $max_index_width = max( $index_end_cols ); + } + + $expected_col = ( $max_index_width + 2 ); + + if ( false === $this->exact && ! empty( $double_arrow_cols ) ) { + /* + * If the alignment does not have to be exact, see if a majority + * group of the arrows is already at an acceptable position. + */ + arsort( $double_arrow_cols, SORT_NUMERIC ); + reset( $double_arrow_cols ); + $count = current( $double_arrow_cols ); + + if ( $count > 1 || ( 1 === $count && count( $items ) === 1 ) ) { + // Allow for several groups of arrows having the same $count. + $filtered_double_arrow_cols = array_keys( $double_arrow_cols, $count, true ); + + foreach ( $filtered_double_arrow_cols as $col ) { + if ( $col > $expected_col && $col <= $this->maxColumn ) { + $expected_col = $col; + break; + } + } + } + } + unset( $max_index_width, $count, $filtered_double_arrow_cols, $col ); + + /* + * Verify and correct the spacing around the double arrows. + */ + foreach ( $items as $item ) { + if ( $this->tokens[ $item['operatorPtr'] ]['column'] === $expected_col + && $this->tokens[ $item['operatorPtr'] ]['line'] === $this->tokens[ $item['last_index_token'] ]['line'] + ) { + // Already correctly aligned. + continue; + } + + if ( T_WHITESPACE !== $this->tokens[ ( $item['operatorPtr'] - 1 ) ]['code'] ) { + $before = 0; + } else { + if ( $this->tokens[ $item['last_index_token'] ]['line'] !== $this->tokens[ $item['operatorPtr'] ]['line'] ) { + $before = 'newline'; + } else { + $before = $this->tokens[ ( $item['operatorPtr'] - 1 ) ]['length']; + } + } + + /* + * Deal with index sizes larger than maxColumn and with multi-line + * array items which should not be aligned. + */ + if ( ( $item['last_index_col'] + 2 ) > $this->maxColumn + || ( false === $alignMultilineItems && false === $item['single_line'] ) + ) { + + if ( ( $item['last_index_col'] + 2 ) === $this->tokens[ $item['operatorPtr'] ]['column'] + && $this->tokens[ $item['operatorPtr'] ]['line'] === $this->tokens[ $item['last_index_token'] ]['line'] + ) { + // MaxColumn/Multi-line item exception, already correctly aligned. + continue; + } + + $prefix = 'LongIndex'; + if ( false === $alignMultilineItems && false === $item['single_line'] ) { + $prefix = 'MultilineItem'; + } + + $error_code = $prefix . 'SpaceBeforeDoubleArrow'; + if ( 0 === $before ) { + $error_code = $prefix . 'NoSpaceBeforeDoubleArrow'; + } + + $fix = $this->phpcsFile->addFixableWarning( + 'Expected 1 space between "%s" and double arrow; %s found.', + $item['operatorPtr'], + $error_code, + array( + $this->tokens[ $item['last_index_token'] ]['content'], + $before, + ) + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + // Remove whitespace tokens between the end of the index and the arrow, if any. + for ( $i = ( $item['last_index_token'] + 1 ); $i < $item['operatorPtr']; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + // Add the correct whitespace. + $this->phpcsFile->fixer->addContent( $item['last_index_token'], ' ' ); + + $this->phpcsFile->fixer->endChangeset(); + } + continue; + } + + /* + * Deal with the space before double arrows in all other cases. + */ + $expected_whitespace = $expected_col - ( $this->tokens[ $item['last_index_token'] ]['column'] + $this->tokens[ $item['last_index_token'] ]['length'] ); + + $fix = $this->phpcsFile->addFixableWarning( + 'Array double arrow not aligned correctly; expected %s space(s) between "%s" and double arrow, but found %s.', + $item['operatorPtr'], + 'DoubleArrowNotAligned', + array( + $expected_whitespace, + $this->tokens[ $item['last_index_token'] ]['content'], + $before, + ) + ); + + if ( true === $fix ) { + if ( 0 === $before || 'newline' === $before ) { + $this->phpcsFile->fixer->beginChangeset(); + + // Remove whitespace tokens between the end of the index and the arrow, if any. + for ( $i = ( $item['last_index_token'] + 1 ); $i < $item['operatorPtr']; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + // Add the correct whitespace. + $this->phpcsFile->fixer->addContent( + $item['last_index_token'], + str_repeat( ' ', $expected_whitespace ) + ); + + $this->phpcsFile->fixer->endChangeset(); + } elseif ( $expected_whitespace > $before ) { + // Add to the existing whitespace to prevent replacing tabs with spaces. + // That's the concern of another sniff. + $this->phpcsFile->fixer->addContent( + ( $item['operatorPtr'] - 1 ), + str_repeat( ' ', ( $expected_whitespace - $before ) ) + ); + } else { + // Too much whitespace found. + $this->phpcsFile->fixer->replaceToken( + ( $item['operatorPtr'] - 1 ), + str_repeat( ' ', $expected_whitespace ) + ); + } + } + } + } // End process_multi_line_array(). + + /** + * Validate that a valid value has been received for the alignMultilineItems property. + * + * This message may be thrown more than once if the property is being changed inline in a file. + * + * @since 0.14.0 + */ + protected function validate_align_multiline_items() { + $alignMultilineItems = $this->alignMultilineItems; + + if ( 'always' === $alignMultilineItems || 'never' === $alignMultilineItems ) { + return; + } else { + // Correct for a potentially added % sign. + $alignMultilineItems = rtrim( $alignMultilineItems, '%' ); + + if ( preg_match( '`^([=<>!]{1,2})(100|[0-9]{1,2})$`', $alignMultilineItems, $matches ) > 0 ) { + $operator = $matches[1]; + $number = (int) $matches[2]; + + if ( in_array( $operator, array( '<', '<=', '>', '>=', '==', '=', '!=', '<>' ), true ) === true + && ( $number >= 0 && $number <= 100 ) + ) { + $this->alignMultilineItems = $alignMultilineItems; + $this->number = (string) $number; + $this->operator = $operator; + return; + } + } + } + + $this->phpcsFile->addError( + 'Invalid property value passed: "%s". The value for the "alignMultilineItems" property for the "WordPress.Arrays.MultipleStatementAlignment" sniff should be either "always", "never" or an comparison operator + a number between 0 and 100.', + 0, + 'InvalidPropertyPassed', + array( $this->alignMultilineItems ) + ); + + // Reset to the default if an invalid value was received. + $this->alignMultilineItems = 'always'; + } + +} // End class. diff --git a/WordPress/Sniffs/CSRF/NonceVerificationSniff.php b/WordPress/Sniffs/CSRF/NonceVerificationSniff.php index 6335350b..3eff3780 100644 --- a/WordPress/Sniffs/CSRF/NonceVerificationSniff.php +++ b/WordPress/Sniffs/CSRF/NonceVerificationSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\CSRF; + +use WordPress\Sniff; + /** * Checks that nonce verification accompanies form processing. * @@ -15,8 +19,9 @@ * @package WPCS\WordPressCodingStandards * * @since 0.5.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_CSRF_NonceVerificationSniff extends WordPress_Sniff { +class NonceVerificationSniff extends Sniff { /** * Superglobals to notify about when not accompanied by an nonce check. @@ -157,7 +162,7 @@ public function process_token( $stackPtr ) { 'NoNonceVerification' ); - } // End process(). + } // End process_token(). /** * Merge custom functions provided via a custom ruleset with the defaults, if we haven't already. @@ -172,6 +177,7 @@ protected function mergeFunctionLists() { $this->customNonceVerificationFunctions, $this->nonceVerificationFunctions ); + $this->addedCustomFunctions['nonce'] = $this->customNonceVerificationFunctions; } @@ -180,6 +186,7 @@ protected function mergeFunctionLists() { $this->customSanitizingFunctions, $this->sanitizingFunctions ); + $this->addedCustomFunctions['sanitize'] = $this->customSanitizingFunctions; } @@ -188,6 +195,7 @@ protected function mergeFunctionLists() { $this->customUnslashingSanitizingFunctions, $this->unslashingSanitizingFunctions ); + $this->addedCustomFunctions['unslashsanitize'] = $this->customUnslashingSanitizingFunctions; } } diff --git a/WordPress/Sniffs/Classes/ClassInstantiationSniff.php b/WordPress/Sniffs/Classes/ClassInstantiationSniff.php new file mode 100644 index 00000000..12e2043d --- /dev/null +++ b/WordPress/Sniffs/Classes/ClassInstantiationSniff.php @@ -0,0 +1,204 @@ +classname_tokens = Tokens::$emptyTokens; + $this->classname_tokens[ T_NS_SEPARATOR ] = T_NS_SEPARATOR; + $this->classname_tokens[ T_STRING ] = T_STRING; + $this->classname_tokens[ T_SELF ] = T_SELF; + $this->classname_tokens[ T_STATIC ] = T_STATIC; + $this->classname_tokens[ T_PARENT ] = T_PARENT; + $this->classname_tokens[ T_ANON_CLASS ] = T_ANON_CLASS; + + // Classname in a variable. + $this->classname_tokens[ T_VARIABLE ] = T_VARIABLE; + $this->classname_tokens[ T_DOUBLE_COLON ] = T_DOUBLE_COLON; + $this->classname_tokens[ T_OBJECT_OPERATOR ] = T_OBJECT_OPERATOR; + $this->classname_tokens[ T_OPEN_SQUARE_BRACKET ] = T_OPEN_SQUARE_BRACKET; + $this->classname_tokens[ T_CLOSE_SQUARE_BRACKET ] = T_CLOSE_SQUARE_BRACKET; + $this->classname_tokens[ T_CONSTANT_ENCAPSED_STRING ] = T_CONSTANT_ENCAPSED_STRING; + $this->classname_tokens[ T_LNUMBER ] = T_LNUMBER; + + return array( + T_NEW, + T_STRING, // JS. + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + // Make sure we have the right token, JS vs PHP. + if ( ( 'PHP' === $this->phpcsFile->tokenizerType && T_NEW !== $this->tokens[ $stackPtr ]['code'] ) + || ( 'JS' === $this->phpcsFile->tokenizerType + && ( T_STRING !== $this->tokens[ $stackPtr ]['code'] + || 'new' !== strtolower( $this->tokens[ $stackPtr ]['content'] ) ) ) + ) { + return; + } + + /* + * Check for new by reference used in PHP files. + */ + if ( 'PHP' === $this->phpcsFile->tokenizerType ) { + $prev_non_empty = $this->phpcsFile->findPrevious( + Tokens::$emptyTokens, + ( $stackPtr - 1 ), + null, + true + ); + + if ( false !== $prev_non_empty && 'T_BITWISE_AND' === $this->tokens[ $prev_non_empty ]['type'] ) { + $this->phpcsFile->recordMetric( $stackPtr, 'Assigning new by reference', 'yes' ); + + $this->phpcsFile->addError( + 'Assigning the return value of new by reference is no longer supported by PHP.', + $stackPtr, + 'NewByReferenceFound' + ); + } else { + $this->phpcsFile->recordMetric( $stackPtr, 'Assigning new by reference', 'no' ); + } + } + + /* + * Check for parenthesis & correct placement thereof. + */ + $next_non_empty_after_class_name = $this->phpcsFile->findNext( + $this->classname_tokens, + ( $stackPtr + 1 ), + null, + true, + null, + true + ); + + if ( false === $next_non_empty_after_class_name ) { + // Live coding. + return; + } + + // Walk back to the last part of the class name. + $has_comment = false; + for ( $classname_ptr = ( $next_non_empty_after_class_name - 1 ); $classname_ptr >= $stackPtr; $classname_ptr-- ) { + if ( ! isset( Tokens::$emptyTokens[ $this->tokens[ $classname_ptr ]['code'] ] ) ) { + // Prevent a false positive on variable variables, disregard them for now. + if ( $stackPtr === $classname_ptr ) { + return; + } + + break; + } + + if ( T_WHITESPACE !== $this->tokens[ $classname_ptr ]['code'] ) { + $has_comment = true; + } + } + + if ( T_OPEN_PARENTHESIS !== $this->tokens[ $next_non_empty_after_class_name ]['code'] ) { + $this->phpcsFile->recordMetric( $stackPtr, 'Object instantiation with parenthesis', 'no' ); + + $fix = $this->phpcsFile->addFixableError( + 'Parenthesis should always be used when instantiating a new object.', + $classname_ptr, + 'MissingParenthesis' + ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->addContent( $classname_ptr, '()' ); + } + } else { + $this->phpcsFile->recordMetric( $stackPtr, 'Object instantiation with parenthesis', 'yes' ); + + if ( ( $next_non_empty_after_class_name - 1 ) !== $classname_ptr ) { + $this->phpcsFile->recordMetric( + $stackPtr, + 'Space between classname and parenthesis', + ( $next_non_empty_after_class_name - $classname_ptr ) + ); + + $error = 'There must be no spaces between the class name and the open parenthesis when instantiating a new object.'; + $error_code = 'SpaceBeforeParenthesis'; + + if ( false === $has_comment ) { + $fix = $this->phpcsFile->addFixableError( $error, $next_non_empty_after_class_name, $error_code ); + + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = ( $next_non_empty_after_class_name - 1 ); $i > $classname_ptr; $i-- ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + $this->phpcsFile->fixer->endChangeset(); + } + } else { + $fix = $this->phpcsFile->addError( $error, $next_non_empty_after_class_name, $error_code ); + } + } else { + $this->phpcsFile->recordMetric( $stackPtr, 'Space between classname and parenthesis', 0 ); + } + } + } // End process_token(). + +} // End class. diff --git a/WordPress/Sniffs/CodeAnalysis/AssignmentInConditionSniff.php b/WordPress/Sniffs/CodeAnalysis/AssignmentInConditionSniff.php new file mode 100644 index 00000000..e5585b1b --- /dev/null +++ b/WordPress/Sniffs/CodeAnalysis/AssignmentInConditionSniff.php @@ -0,0 +1,220 @@ +assignment_tokens = Tokens::$assignmentTokens; + unset( $this->assignment_tokens[ T_DOUBLE_ARROW ] ); + + $starters = Tokens::$booleanOperators; + $starters[ T_SEMICOLON ] = T_SEMICOLON; + $starters[ T_OPEN_PARENTHESIS ] = T_OPEN_PARENTHESIS; + $starters[ T_INLINE_ELSE ] = T_INLINE_ELSE; + + $this->condition_start_tokens = $starters; + + return array( + T_IF, + T_ELSEIF, + T_FOR, + T_SWITCH, + T_CASE, + T_WHILE, + T_INLINE_THEN, + ); + + }//end register() + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + + $token = $this->tokens[ $stackPtr ]; + + // Find the condition opener/closer. + if ( T_FOR === $token['code'] ) { + if ( isset( $token['parenthesis_opener'], $token['parenthesis_closer'] ) === false ) { + return; + } + + $semicolon = $this->phpcsFile->findNext( T_SEMICOLON, ( $token['parenthesis_opener'] + 1 ), $token['parenthesis_closer'] ); + if ( false === $semicolon ) { + return; + } + + $opener = $semicolon; + $semicolon = $this->phpcsFile->findNext( T_SEMICOLON, ( $opener + 1 ), $token['parenthesis_closer'] ); + if ( false === $semicolon ) { + return; + } + + $closer = $semicolon; + unset( $semicolon ); + + } elseif ( T_CASE === $token['code'] ) { + if ( isset( $token['scope_opener'] ) === false ) { + return; + } + + $opener = $stackPtr; + $closer = $token['scope_opener']; + + } elseif ( T_INLINE_THEN === $token['code'] ) { + // Check if the condition for the ternary is bracketed. + $prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + if ( false === $prev ) { + // Shouldn't happen, but in that case we don't have anything to examine anyway. + return; + } + + if ( T_CLOSE_PARENTHESIS === $this->tokens[ $prev ]['code'] ) { + if ( ! isset( $this->tokens[ $prev ]['parenthesis_opener'] ) ) { + return; + } + + $opener = $this->tokens[ $prev ]['parenthesis_opener']; + $closer = $prev; + } elseif ( isset( $token['nested_parenthesis'] ) ) { + end( $token['nested_parenthesis'] ); + $opener = key( $token['nested_parenthesis'] ); + $closer = $stackPtr; + } else { + // No parenthesis found, can't determine where the conditional part of the ternary starts. + return; + } + } else { + if ( isset( $token['parenthesis_opener'], $token['parenthesis_closer'] ) === false ) { + return; + } + + $opener = $token['parenthesis_opener']; + $closer = $token['parenthesis_closer']; + } + + $startPos = $opener; + + do { + $hasAssignment = $this->phpcsFile->findNext( $this->assignment_tokens, ( $startPos + 1 ), $closer ); + if ( false === $hasAssignment ) { + return; + } + + // Examine whether the left side is a variable. + $hasVariable = false; + $conditionStart = $startPos; + $altConditionStart = $this->phpcsFile->findPrevious( + $this->condition_start_tokens, + ( $hasAssignment - 1 ), + $startPos + ); + if ( false !== $altConditionStart ) { + $conditionStart = $altConditionStart; + } + + for ( $i = $hasAssignment; $i > $conditionStart; $i-- ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + // If this is a variable or array, we've seen all we need to see. + if ( T_VARIABLE === $this->tokens[ $i ]['code'] + || T_CLOSE_SQUARE_BRACKET === $this->tokens[ $i ]['code'] + ) { + $hasVariable = true; + break; + } + + // If this is a function call or something, we are OK. + if ( T_CLOSE_PARENTHESIS === $this->tokens[ $i ]['code'] ) { + break; + } + } + + if ( true === $hasVariable ) { + $errorCode = 'Found'; + if ( T_WHILE === $token['code'] ) { + $errorCode = 'FoundInWhileCondition'; + } + + $this->phpcsFile->addWarning( + 'Variable assignment found within a condition. Did you mean to do a comparison?', + $hasAssignment, + $errorCode + ); + } else { + $this->phpcsFile->addWarning( + 'Assignment found within a condition. Did you mean to do a comparison?', + $hasAssignment, + 'NonVariableAssignmentFound' + ); + } + + $startPos = $hasAssignment; + + } while ( $startPos < $closer ); + + } + +} diff --git a/WordPress/Sniffs/CodeAnalysis/EmptyStatementSniff.php b/WordPress/Sniffs/CodeAnalysis/EmptyStatementSniff.php new file mode 100644 index 00000000..c79f3f08 --- /dev/null +++ b/WordPress/Sniffs/CodeAnalysis/EmptyStatementSniff.php @@ -0,0 +1,144 @@ +tokens[ $stackPtr ]['type'] ) { + /* + * Detect `something();;`. + */ + case 'T_SEMICOLON': + $prevNonEmpty = $this->phpcsFile->findPrevious( + Tokens::$emptyTokens, + ( $stackPtr - 1 ), + null, + true + ); + + if ( false === $prevNonEmpty + || ( T_SEMICOLON !== $this->tokens[ $prevNonEmpty ]['code'] + && T_OPEN_TAG !== $this->tokens[ $prevNonEmpty ]['code'] ) + ) { + return; + } + + $fix = $this->phpcsFile->addFixableWarning( + 'Empty PHP statement detected: superfluous semi-colon.', + $stackPtr, + 'SemicolonWithoutCodeDetected' + ); + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + + if ( T_OPEN_TAG === $this->tokens[ $prevNonEmpty ]['code'] ) { + /* + * Check for superfluous whitespace after the semi-colon which will be + * removed as the `tokens[ ( $stackPtr + 1 ) ]['code'] ) { + $replacement = str_replace( ' ', '', $this->tokens[ ( $stackPtr + 1 ) ]['content'] ); + $this->phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), $replacement ); + } + } + + for ( $i = $stackPtr; $i > $prevNonEmpty; $i-- ) { + if ( T_SEMICOLON !== $this->tokens[ $i ]['code'] + && T_WHITESPACE !== $this->tokens[ $i ]['code'] + ) { + break; + } + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + + $this->phpcsFile->fixer->endChangeset(); + } + break; + + /* + * Detect ``. + */ + case 'T_CLOSE_TAG': + $prevNonEmpty = $this->phpcsFile->findPrevious( + T_WHITESPACE, + ( $stackPtr - 1 ), + null, + true + ); + + if ( false === $prevNonEmpty || T_OPEN_TAG !== $this->tokens[ $prevNonEmpty ]['code'] ) { + return; + } + + $fix = $this->phpcsFile->addFixableWarning( + 'Empty PHP open/close tag combination detected.', + $prevNonEmpty, + 'EmptyPHPOpenCloseTagsDetected' + ); + if ( true === $fix ) { + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = $prevNonEmpty; $i <= $stackPtr; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + $this->phpcsFile->fixer->endChangeset(); + } + break; + + default: + /* Deliberately left empty. */ + break; + } + + } // End process_token(). + +} // End class. diff --git a/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php b/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php new file mode 100644 index 00000000..58af2878 --- /dev/null +++ b/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php @@ -0,0 +1,661 @@ +prepare method. + * + * Check the following issues: + * - The only placeholders supported are: %d, %f (%F) and %s and their variations. + * - Literal % signs need to be properly escaped as `%%`. + * - Simple placeholders (%d, %f, %F, %s) should be left unquoted in the query string. + * - Complex placeholders - numbered and formatted variants - will not be quoted + * automagically by $wpdb->prepare(), so if used for values, should be quoted in + * the query string. + * - Either an array of replacements should be passed matching the number of + * placeholders found or individual parameters for each placeholder should + * be passed. + * - Wildcards for LIKE compare values should be passed in via a replacement parameter. + * + * The sniff allows for a specific pattern with a variable number of placeholders + * created using code along the lines of: + * `sprintf( 'query .... IN (%s) ...', implode( ',', array_fill( 0, count( $something ), '%s' ) ) )`. + * + * A "PreparedSQLPlaceholders replacement count" whitelist comment is supported + * specifically to silence the `ReplacementsWrongNumber` and `UnfinishedPrepare` + * error codes. The other error codes are not affected by it. + * + * @link https://developer.wordpress.org/reference/classes/wpdb/prepare/ + * @link https://core.trac.wordpress.org/changeset/41496 + * @link https://core.trac.wordpress.org/changeset/41471 + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class PreparedSQLPlaceholdersSniff extends Sniff { + + /** + * These regexes copied from http://php.net/manual/en/function.sprintf.php#93552 + * and adjusted for limitations in `$wpdb->prepare()`. + * + * Near duplicate of the one used in the WP.I18n sniff, but with fewer types allowed. + * + * Note: The regex delimiters and modifiers are not included to allow this regex to be + * concatenated together with other regex partials. + * + * @since 0.14.0 + * + * @var string + */ + const PREPARE_PLACEHOLDER_REGEX = '(?: + (? true, + ); + + /** + * Storage for the stack pointer to the method call token. + * + * @since 0.14.0 + * + * @var int + */ + protected $methodPtr; + + /** + * Simple regex snippet to recognize and remember quotes. + * + * @since 0.14.0 + * + * @var string + */ + private $regex_quote = '["\']'; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.14.0 + * + * @return array + */ + public function register() { + return array( + T_VARIABLE, + T_STRING, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_token( $stackPtr ) { + + if ( ! $this->is_wpdb_method_call( $stackPtr, $this->target_methods ) ) { + return; + } + + $parameters = $this->get_function_call_parameters( $this->methodPtr ); + if ( empty( $parameters ) ) { + return; + } + + $query = $parameters[1]; + $text_string_tokens_found = false; + $variable_found = false; + $sql_wildcard_found = false; + $total_placeholders = 0; + $total_parameters = count( $parameters ); + $valid_in_clauses = array( + 'uses_in' => 0, + 'implode_fill' => 0, + 'adjustment_count' => 0, + ); + + for ( $i = $query['start']; $i <= $query['end']; $i++ ) { + // Skip over groups of tokens if they are part of an inline function call. + if ( isset( $skip_from, $skip_to ) && $i >= $skip_from && $i < $skip_to ) { + $i = $skip_to; + continue; + } + + if ( ! isset( Tokens::$textStringTokens[ $this->tokens[ $i ]['code'] ] ) ) { + if ( T_VARIABLE === $this->tokens[ $i ]['code'] ) { + if ( '$wpdb' !== $this->tokens[ $i ]['content'] ) { + $variable_found = true; + } + continue; + } + + // Detect a specific pattern for variable replacements in combination with `IN`. + if ( T_STRING === $this->tokens[ $i ]['code'] ) { + + if ( 'sprintf' === strtolower( $this->tokens[ $i ]['content'] ) ) { + $sprintf_parameters = $this->get_function_call_parameters( $i ); + + if ( ! empty( $sprintf_parameters ) ) { + $skip_from = ( $sprintf_parameters[1]['end'] + 1 ); + $last_param = end( $sprintf_parameters ); + $skip_to = ( $last_param['end'] + 1 ); + + $valid_in_clauses['implode_fill'] += $this->analyse_sprintf( $sprintf_parameters ); + $valid_in_clauses['adjustment_count'] += ( count( $sprintf_parameters ) - 1 ); + } + unset( $sprintf_parameters, $last_param ); + + } elseif ( 'implode' === strtolower( $this->tokens[ $i ]['content'] ) ) { + $prev = $this->phpcsFile->findPrevious( + Tokens::$textStringTokens, + ( $i - 1 ), + $query['start'] + ); + + $prev_content = $this->strip_quotes( $this->tokens[ $prev ]['content'] ); + $regex_quote = $this->get_regex_quote_snippet( $prev_content, $this->tokens[ $prev ]['content'] ); + + // Only examine the implode if preceded by an ` IN (`. + if ( preg_match( '`\s+IN\s*\(\s*(' . $regex_quote . ')?$`i', $prev_content, $match ) > 0 ) { + + if ( isset( $match[1] ) && $regex_quote !== $this->regex_quote ) { + $this->phpcsFile->addError( + 'Dynamic placeholder generation should not have surrounding quotes.', + $i, + 'QuotedDynamicPlaceholderGeneration' + ); + } + + if ( $this->analyse_implode( $i ) === true ) { + ++$valid_in_clauses['uses_in']; + ++$valid_in_clauses['implode_fill']; + + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] + && isset( $this->tokens[ $next ]['parenthesis_closer'] ) + ) { + $skip_from = ( $i + 1 ); + $skip_to = ( $this->tokens[ $next ]['parenthesis_closer'] + 1 ); + } + } + } + unset( $prev, $next, $prev_content, $regex_quote, $match ); + } + } + + continue; + } + + $text_string_tokens_found = true; + $content = $this->tokens[ $i ]['content']; + + $regex_quote = $this->regex_quote; + if ( isset( Tokens::$stringTokens[ $this->tokens[ $i ]['code'] ] ) ) { + $content = $this->strip_quotes( $content ); + $regex_quote = $this->get_regex_quote_snippet( $content, $this->tokens[ $i ]['content'] ); + } + + if ( T_DOUBLE_QUOTED_STRING === $this->tokens[ $i ]['code'] + || T_HEREDOC === $this->tokens[ $i ]['code'] + ) { + // Only interested in actual query text, so strip out variables. + $stripped_content = $this->strip_interpolated_variables( $content ); + if ( $stripped_content !== $content ) { + $interpolated_vars = $this->get_interpolated_variables( $content ); + $vars_without_wpdb = array_diff( $interpolated_vars, array( 'wpdb' ) ); + $content = $stripped_content; + + if ( ! empty( $vars_without_wpdb ) ) { + $variable_found = true; + } + } + unset( $stripped_content, $interpolated_vars, $vars_without_wpdb ); + } + + $placeholders = preg_match_all( '`' . self::PREPARE_PLACEHOLDER_REGEX . '`x', $content, $matches ); + if ( $placeholders > 0 ) { + $total_placeholders += $placeholders; + } + + /* + * Analyse the query for incorrect LIKE queries. + * + * - `LIKE %s` is the only correct one. + * - `LIKE '%s'` or `LIKE "%s"` will not be reported here, but in the quote check. + * - Any other `LIKE` statement should be reported, either for using `LIKE` without + * SQL wildcards or for not passing the SQL wildcards via the replacement. + */ + $regex = '`\s+LIKE\s*(?:(' . $regex_quote . ')(?!%s(?:\1|$))(?P.*?)(?:\1|$)|(?:concat\((?![^\)]*%s[^\)]*\))(?P[^\)]*))\))`i'; + if ( preg_match_all( $regex, $content, $matches ) > 0 ) { + $walk = array(); + if ( ! empty( $matches['content'] ) ) { + $matches['content'] = array_filter( $matches['content'] ); + if ( ! empty( $matches['content'] ) ) { + $walk[] = 'content'; + } + } + if ( ! empty( $matches['concat'] ) ) { + $matches['concat'] = array_filter( $matches['concat'] ); + if ( ! empty( $matches['concat'] ) ) { + $walk[] = 'concat'; + } + } + + if ( ! empty( $walk ) ) { + foreach ( $walk as $match_key ) { + foreach ( $matches[ $match_key ] as $index => $match ) { + $data = array( $matches[0][ $index ] ); + + // Both a `%` as well as a `_` are wildcards in SQL. + if ( strpos( $match, '%' ) === false && strpos( $match, '_' ) === false ) { + $this->phpcsFile->addWarning( + 'Unless you are using SQL wildcards, using LIKE is inefficient. Use a straight compare instead. Found: %s.', + $i, + 'LikeWithoutWildcards', + $data + ); + } else { + $sql_wildcard_found = true; + + if ( strpos( $match, '%s' ) === false ) { + $this->phpcsFile->addError( + 'SQL wildcards for a LIKE query should be passed in through a replacement parameter. Found: %s.', + $i, + 'LikeWildcardsInQuery', + $data + ); + } else { + $this->phpcsFile->addError( + 'SQL wildcards for a LIKE query should be passed in through a replacement parameter and the variable part of the replacement should be escaped using "esc_like()". Found: %s.', + $i, + 'LikeWildcardsInQueryWithPlaceholder', + $data + ); + } + } + + /* + * Don't throw `UnescapedLiteral`, `UnsupportedPlaceholder` or `QuotedPlaceholder` + * for this part of the SQL query. + */ + $content = preg_replace( '`' . preg_quote( $match ) . '`', '', $content, 1 ); + } + } + } + unset( $matches, $index, $match, $data ); + } + + if ( strpos( $content, '%' ) === false ) { + continue; + } + + /* + * Analyse the query for unsupported placeholders. + */ + if ( preg_match_all( self::UNSUPPORTED_PLACEHOLDER_REGEX, $content, $matches ) > 0 ) { + if ( ! empty( $matches[0] ) ) { + foreach ( $matches[0] as $match ) { + if ( '%' === $match ) { + $this->phpcsFile->addError( + 'Found unescaped literal "%%" character.', + $i, + 'UnescapedLiteral', + array( $match ) + ); + } else { + $this->phpcsFile->addError( + 'Unsupported placeholder used in $wpdb->prepare(). Found: "%s".', + $i, + 'UnsupportedPlaceholder', + array( $match ) + ); + } + } + } + unset( $match, $matches ); + } + + /* + * Analyse the query for quoted placeholders. + */ + $regex = '`(' . $regex_quote . ')%[dfFs]\1`'; + if ( preg_match_all( $regex, $content, $matches ) > 0 ) { + if ( ! empty( $matches[0] ) ) { + foreach ( $matches[0] as $match ) { + $this->phpcsFile->addError( + 'Simple placeholders should not be quoted in the query string in $wpdb->prepare(). Found: %s.', + $i, + 'QuotedSimplePlaceholder', + array( $match ) + ); + } + } + unset( $match, $matches ); + } + + /* + * Analyse the query for unquoted complex placeholders. + */ + $regex = '`(? 0 ) { + if ( ! empty( $matches[0] ) ) { + foreach ( $matches[0] as $match ) { + if ( preg_match( '`%[dfFs]`', $match ) !== 1 ) { + $this->phpcsFile->addWarning( + 'Complex placeholders used for values in the query string in $wpdb->prepare() will NOT be quoted automagically. Found: %s.', + $i, + 'UnquotedComplexPlaceholder', + array( $match ) + ); + } + } + } + unset( $match, $matches ); + } + + /* + * Check for an ` IN (%s)` clause. + */ + $found_in = preg_match_all( '`\s+IN\s*\(\s*%s\s*\)`i', $content, $matches ); + if ( $found_in > 0 ) { + $valid_in_clauses['uses_in'] += $found_in; + } + unset( $found_in ); + } + + if ( false === $text_string_tokens_found ) { + // Query string passed in as a variable or function call, nothing to examine. + return; + } + + $count_diff_whitelisted = $this->has_whitelist_comment( + 'PreparedSQLPlaceholders replacement count', + $stackPtr + ); + + if ( 0 === $total_placeholders ) { + if ( 1 === $total_parameters ) { + if ( false === $variable_found && false === $sql_wildcard_found ) { + /* + * Only throw this warning if the PreparedSQL sniff won't throw one about + * variables being found. + * Also don't throw it if we just advised to use a replacement variable to pass a + * string containing an SQL wildcard. + */ + $this->phpcsFile->addWarning( + 'It is not necessary to prepare a query which doesn\'t use variable replacement.', + $i, + 'UnnecessaryPrepare' + ); + } + } elseif ( false === $count_diff_whitelisted && 0 === $valid_in_clauses['uses_in'] ) { + $this->phpcsFile->addWarning( + 'Replacement variables found, but no valid placeholders found in the query.', + $i, + 'UnfinishedPrepare' + ); + } + + return; + } + + if ( 1 === $total_parameters ) { + $this->phpcsFile->addError( + 'Placeholders found in the query passed to $wpdb->prepare(), but no replacements found. Expected %d replacement(s) parameters.', + $stackPtr, + 'MissingReplacements', + array( $total_placeholders ) + ); + return; + } + + if ( true === $count_diff_whitelisted ) { + return; + } + + $replacements = $parameters; + array_shift( $replacements ); // Remove the query. + + // The parameters may have been passed as an array in parameter 2. + if ( isset( $parameters[2] ) && 2 === $total_parameters ) { + $next = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $parameters[2]['start'], + ( $parameters[2]['end'] + 1 ), + true + ); + + if ( false !== $next + && ( T_ARRAY === $this->tokens[ $next ]['code'] + || T_OPEN_SHORT_ARRAY === $this->tokens[ $next ]['code'] ) + ) { + $replacements = $this->get_function_call_parameters( $next ); + } + } + + $total_replacements = count( $replacements ); + $total_placeholders -= $valid_in_clauses['adjustment_count']; + + // Bow out when `IN` clauses have been used which appear to be correct. + if ( $valid_in_clauses['uses_in'] > 0 + && $valid_in_clauses['uses_in'] === $valid_in_clauses['implode_fill'] + && 1 === $total_replacements + ) { + return; + } + + /* + * Verify that the correct amount of replacements have been passed. + */ + if ( $total_replacements !== $total_placeholders ) { + $this->phpcsFile->addWarning( + 'Incorrect number of replacements passed to $wpdb->prepare(). Found %d replacement parameters, expected %d.', + $stackPtr, + 'ReplacementsWrongNumber', + array( $total_replacements, $total_placeholders ) + ); + } + } + + /** + * Retrieve a regex snippet to recognize and remember quotes based on the quote style + * used in the original string (if any). + * + * This allows for recognizing `"` and `\'` in single quoted strings, + * recognizing `'` and `\"` in double quotes strings and `'` and `"`when the quote + * style is unknown or it is a non-quoted string (heredoc/nowdoc and such). + * + * @since 0.14.0 + * + * @param string $stripped_content Text string content without surrounding quotes. + * @param string $original_content Original content for the same text string. + * + * @return string + */ + protected function get_regex_quote_snippet( $stripped_content, $original_content ) { + $regex_quote = $this->regex_quote; + + if ( $original_content !== $stripped_content ) { + $quote_style = $original_content[0]; + + if ( '"' === $quote_style ) { + $regex_quote = '\\\\"|\''; + } elseif ( "'" === $quote_style ) { + $regex_quote = '"|\\\\\''; + } + } + + return $regex_quote; + } + + /** + * Analyse a sprintf() query wrapper to see if it contains a specific code pattern + * to deal correctly with `IN` queries. + * + * The pattern we are searching for is: + * `sprintf( 'query ....', implode( ',', array_fill( 0, count( $something ), '%s' ) ) )` + * + * @since 0.14.0 + * + * @param array $sprintf_params Parameters details for the sprintf call. + * + * @return int The number of times the pattern was found in the replacements. + */ + protected function analyse_sprintf( $sprintf_params ) { + $found = 0; + + unset( $sprintf_params[1] ); + + foreach ( $sprintf_params as $sprintf_param ) { + if ( strpos( strtolower( $sprintf_param['raw'] ), 'implode' ) === false ) { + continue; + } + + $implode = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $sprintf_param['start'], + $sprintf_param['end'], + true + ); + if ( T_STRING === $this->tokens[ $implode ]['code'] + && 'implode' === strtolower( $this->tokens[ $implode ]['content'] ) + ) { + if ( $this->analyse_implode( $implode ) === true ) { + ++$found; + } + } + } + + return $found; + } + + /** + * Analyse an implode() function call to see if it contains a specific code pattern + * to dynamically create placeholders. + * + * The pattern we are searching for is: + * `implode( ',', array_fill( 0, count( $something ), '%s' ) )` + * + * This pattern presumes unquoted placeholders! + * + * @since 0.14.0 + * + * @param int $implode_token The stackPtr to the implode function call. + * + * @return bool True if the pattern is found, false otherwise. + */ + protected function analyse_implode( $implode_token ) { + $implode_params = $this->get_function_call_parameters( $implode_token ); + + if ( empty( $implode_params ) || count( $implode_params ) !== 2 ) { + return false; + } + + if ( preg_match( '`^(["\']), ?\1$`', $implode_params[1]['raw'] ) !== 1 ) { + return false; + } + + if ( strpos( strtolower( $implode_params[2]['raw'] ), 'array_fill' ) === false ) { + return false; + } + + $array_fill = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $implode_params[2]['start'], + $implode_params[2]['end'], + true + ); + + if ( T_STRING !== $this->tokens[ $array_fill ]['code'] + || 'array_fill' !== strtolower( $this->tokens[ $array_fill ]['content'] ) + ) { + return false; + } + + $array_fill_params = $this->get_function_call_parameters( $array_fill ); + + if ( empty( $array_fill_params ) || count( $array_fill_params ) !== 3 ) { + return false; + } + + return (bool) preg_match( '`^(["\'])%[dfFs]\1$`', $array_fill_params[3]['raw'] ); + } + +} diff --git a/WordPress/Sniffs/DB/RestrictedClassesSniff.php b/WordPress/Sniffs/DB/RestrictedClassesSniff.php index e9bdbc76..8da5672e 100644 --- a/WordPress/Sniffs/DB/RestrictedClassesSniff.php +++ b/WordPress/Sniffs/DB/RestrictedClassesSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\DB; + +use WordPress\AbstractClassRestrictionsSniff; + /** * Verifies that no database related PHP classes are used. * @@ -20,18 +24,19 @@ * @package WPCS\WordPressCodingStandards * * @since 0.10.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_DB_RestrictedClassesSniff extends WordPress_AbstractClassRestrictionsSniff { +class RestrictedClassesSniff extends AbstractClassRestrictionsSniff { /** * Groups of classes to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Avoid direct calls to the database.', - * 'classes' => array( 'PDO', '\Namespace\Classname' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Avoid direct calls to the database.', + * 'classes' => array( 'PDO', '\Namespace\Classname' ), + * ) * ) * * @return array @@ -40,8 +45,8 @@ public function getGroups() { return array( 'mysql' => array( - 'type' => 'error', - 'message' => 'Accessing the database directly should be avoided. Please use the $wpdb object and associated functions instead. Found: %s.', + 'type' => 'error', + 'message' => 'Accessing the database directly should be avoided. Please use the $wpdb object and associated functions instead. Found: %s.', 'classes' => array( 'mysqli', 'PDO', diff --git a/WordPress/Sniffs/DB/RestrictedFunctionsSniff.php b/WordPress/Sniffs/DB/RestrictedFunctionsSniff.php index 15d4ffcc..557d5771 100644 --- a/WordPress/Sniffs/DB/RestrictedFunctionsSniff.php +++ b/WordPress/Sniffs/DB/RestrictedFunctionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\DB; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Verifies that no database related PHP functions are used. * @@ -20,18 +24,19 @@ * @package WPCS\WordPressCodingStandards * * @since 0.10.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_DB_RestrictedFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class RestrictedFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'file_get_contents', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/Files/FileNameSniff.php b/WordPress/Sniffs/Files/FileNameSniff.php index a521845e..716b7910 100644 --- a/WordPress/Sniffs/Files/FileNameSniff.php +++ b/WordPress/Sniffs/Files/FileNameSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Files; + +use WordPress\Sniff; + /** * Ensures filenames do not contain underscores. * @@ -21,9 +25,12 @@ * template tags end in `-template`. Based on @subpackage file DocBlock tag. * - This sniff will now allow for underscores in file names for certain theme * specific exceptions if the `$is_theme` property is set to `true`. - * @since 0.12.0 - Now extends the `WordPress_Sniff` class. + * @since 0.12.0 Now extends the `WordPress_Sniff` class. + * @since 0.13.0 Class name changed: this class is now namespaced. + * + * @uses \WordPress\Sniff::$custom_test_class_whitelist */ -class WordPress_Sniffs_Files_FileNameSniff extends WordPress_Sniff { +class FileNameSniff extends Sniff { /** * Regex for the theme specific exceptions. @@ -36,6 +43,8 @@ class WordPress_Sniffs_Files_FileNameSniff extends WordPress_Sniff { * @link https://developer.wordpress.org/themes/basics/template-hierarchy/#custom-post-types * @link https://developer.wordpress.org/themes/basics/template-hierarchy/#embeds * @link https://developer.wordpress.org/themes/basics/template-hierarchy/#attachment + * @link https://developer.wordpress.org/themes/template-files-section/partial-and-miscellaneous-template-files/#content-slug-php + * @link https://wphierarchy.com/ * @link https://en.wikipedia.org/wiki/Media_type#Naming * * @since 0.11.0 @@ -45,7 +54,8 @@ class WordPress_Sniffs_Files_FileNameSniff extends WordPress_Sniff { const THEME_EXCEPTIONS_REGEX = '` ^ # Anchor to the beginning of the string. (?: - (?:archive|embed|single|taxonomy) # Template prefixes which can have exceptions + # Template prefixes which can have exceptions. + (?:archive|category|content|embed|page|single|tag|taxonomy) -[^\.]+ # These need to be followed by a dash and some chars. | (?:application|audio|example|image|message|model|multipart|text|video) #Top-level mime-types @@ -108,7 +118,7 @@ class WordPress_Sniffs_Files_FileNameSniff extends WordPress_Sniff { * @return array */ public function register() { - if ( defined( 'PHP_CODESNIFFER_IN_TESTS' ) ) { + if ( defined( '\PHP_CODESNIFFER_IN_TESTS' ) ) { $this->class_exceptions = array_merge( $this->class_exceptions, $this->unittest_class_exceptions ); } @@ -125,7 +135,12 @@ public function register() { */ public function process_token( $stackPtr ) { - $file = $this->phpcsFile->getFileName(); + // Usage of `strip_quotes` is to ensure `stdin_path` passed by IDEs does not include quotes. + $file = $this->strip_quotes( $this->phpcsFile->getFileName() ); + if ( 'STDIN' === $file ) { + return; + } + $fileName = basename( $file ); $expected = strtolower( str_replace( '_', '-', $fileName ) ); @@ -180,8 +195,8 @@ public function process_token( $stackPtr ) { if ( ( 'Template' === trim( $this->tokens[ $subpackage ]['content'] ) && $this->tokens[ $subpackage_tag ]['line'] === $this->tokens[ $subpackage ]['line'] ) - && ( ( ! defined( 'PHP_CODESNIFFER_IN_TESTS' ) && '-template.php' !== $fileName_end ) - || ( defined( 'PHP_CODESNIFFER_IN_TESTS' ) && '-template.inc' !== $fileName_end ) ) + && ( ( ! defined( '\PHP_CODESNIFFER_IN_TESTS' ) && '-template.php' !== $fileName_end ) + || ( defined( '\PHP_CODESNIFFER_IN_TESTS' ) && '-template.inc' !== $fileName_end ) ) && false === $has_class ) { $this->phpcsFile->addError( @@ -196,11 +211,11 @@ public function process_token( $stackPtr ) { } } } - } // End if(). + } // Only run this sniff once per file, no need to run it again. return ( $this->phpcsFile->numTokens + 1 ); - } // End process(). + } // End process_token(). } // End class. diff --git a/WordPress/Sniffs/Functions/DontExtractSniff.php b/WordPress/Sniffs/Functions/DontExtractSniff.php index fc37f68c..026e6cc5 100644 --- a/WordPress/Sniffs/Functions/DontExtractSniff.php +++ b/WordPress/Sniffs/Functions/DontExtractSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Functions; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Restricts the usage of extract(). * @@ -15,18 +19,19 @@ * @package WPCS\WordPressCodingStandards * * @since 0.10.0 Previously this check was contained within WordPress_Sniffs_VIP_RestrictedFunctionsSniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_Functions_DontExtractSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class DontExtractSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'file_get_contents', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/Functions/FunctionCallSignatureNoParamsSniff.php b/WordPress/Sniffs/Functions/FunctionCallSignatureNoParamsSniff.php new file mode 100644 index 00000000..cb321a0c --- /dev/null +++ b/WordPress/Sniffs/Functions/FunctionCallSignatureNoParamsSniff.php @@ -0,0 +1,92 @@ +phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + + if ( T_OPEN_PARENTHESIS !== $this->tokens[ $openParenthesis ]['code'] ) { + // Not a function call. + return; + } + + if ( ! isset( $this->tokens[ $openParenthesis ]['parenthesis_closer'] ) ) { + // Not a function call. + return; + } + + // Find the previous non-empty token. + $search = Tokens::$emptyTokens; + $search[] = T_BITWISE_AND; + $previous = $this->phpcsFile->findPrevious( $search, ( $stackPtr - 1 ), null, true ); + if ( T_FUNCTION === $this->tokens[ $previous ]['code'] ) { + // It's a function definition, not a function call. + return; + } + + $closer = $this->tokens[ $openParenthesis ]['parenthesis_closer']; + + if ( ( $closer - 1 ) === $openParenthesis ) { + return; + } + + $nextNonWhitespace = $this->phpcsFile->findNext( T_WHITESPACE, ( $openParenthesis + 1 ), null, true ); + + if ( $nextNonWhitespace !== $closer ) { + // Function has params or comment between parenthesis. + return; + } + + $fix = $this->phpcsFile->addFixableError( + 'Function calls without parameters should have no spaces between the parenthesis.', + ( $openParenthesis + 1 ), + 'WhitespaceFound' + ); + if ( true === $fix ) { + // If there is only whitespace between the parenthesis, it will just be the one token. + $this->phpcsFile->fixer->replaceToken( ( $openParenthesis + 1 ), '' ); + } + + } // End process_token(). + +} // End class. diff --git a/WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php b/WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php index f32bfe92..af829d27 100644 --- a/WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php +++ b/WordPress/Sniffs/Functions/FunctionRestrictionsSniff.php @@ -7,30 +7,36 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Functions; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Restricts usage of some functions. * * @package WPCS\WordPressCodingStandards * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + * * @deprecated 0.10.0 The functionality which used to be contained in this class has been moved to * the WordPress_AbstractFunctionRestrictionsSniff class. * This class is left here to prevent backward-compatibility breaks for * custom sniffs extending the old class and references to this * sniff from custom phpcs.xml files. - * @see WordPress_AbstractFunctionRestrictionsSniff + * @see \WordPress\AbstractFunctionRestrictionsSniff */ -class WordPress_Sniffs_Functions_FunctionRestrictionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class FunctionRestrictionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'eval', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php new file mode 100644 index 00000000..82c5a1f1 --- /dev/null +++ b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php @@ -0,0 +1,771 @@ + true, + '_' => true, + ); + + /** + * Target prefixes after validation. + * + * @since 0.12.0 + * + * @var string[] + */ + private $validated_prefixes = array(); + + /** + * Cache of previously set prefixes. + * + * Prevents having to do the same prefix validation over and over again. + * + * @since 0.12.0 + * + * @var string[] + */ + private $previous_prefixes = array(); + + /** + * A list of all PHP superglobals with the exception of $GLOBALS which is handled separately. + * + * @since 0.12.0 + * + * @var array + */ + protected $superglobals = array( + '_COOKIE' => true, + '_ENV' => true, + '_GET' => true, + '_FILES' => true, + '_POST' => true, + '_REQUEST' => true, + '_SERVER' => true, + '_SESSION' => true, + ); + + /** + * A list of core hooks that are allowed to be called by plugins and themes. + * + * @since 0.14.0 + * + * @var array + */ + protected $whitelisted_core_hooks = array( + 'widget_title' => true, + 'add_meta_boxes' => true, + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.12.0 + * + * @return array + */ + public function register() { + $targets = array( + T_FUNCTION => T_FUNCTION, + T_CLASS => T_CLASS, + T_INTERFACE => T_INTERFACE, + T_TRAIT => T_TRAIT, + T_CONST => T_CONST, + T_VARIABLE => T_VARIABLE, + T_DOLLAR => T_DOLLAR, // Variable variables. + ); + + // Add function call target for hook names and constants defined using define(). + $parent = parent::register(); + if ( ! empty( $parent ) ) { + $targets[] = T_STRING; + } + + return $targets; + } + + /** + * Groups of functions to restrict. + * + * @since 0.12.0 + * + * @return array + */ + public function getGroups() { + $this->target_functions = $this->hookInvokeFunctions; + $this->target_functions['define'] = true; + + return parent::getGroups(); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + /* + * Allow for whitelisting. + * + * Generally speaking a theme/plugin should *only* execute their own hooks, but there may be a + * good reason to execute a core hook. + * + * Similarly, newer PHP or WP functions or constants may need to be emulated for continued support + * of older PHP and WP versions. + */ + if ( $this->has_whitelist_comment( 'prefix', $stackPtr ) ) { + return; + } + + // Allow overruling the prefixes set in a ruleset via the command line. + $cl_prefixes = trim( PHPCSHelper::get_config_data( 'prefixes' ) ); + if ( ! empty( $cl_prefixes ) ) { + $this->prefixes = $cl_prefixes; + } + + $this->prefixes = $this->merge_custom_array( $this->prefixes, array(), false ); + if ( empty( $this->prefixes ) ) { + // No prefixes passed, nothing to do. + return; + } + + $this->validate_prefixes(); + if ( empty( $this->validated_prefixes ) ) { + // No _valid_ prefixes passed, nothing to do. + return; + } + + if ( T_STRING === $this->tokens[ $stackPtr ]['code'] ) { + // Disallow excluding function groups for this sniff. + $this->exclude = ''; + + return parent::process_token( $stackPtr ); + + } elseif ( T_DOLLAR === $this->tokens[ $stackPtr ]['code'] ) { + + return $this->process_variable_variable( $stackPtr ); + + } elseif ( T_VARIABLE === $this->tokens[ $stackPtr ]['code'] ) { + + return $this->process_variable_assignment( $stackPtr ); + + } else { + + // Namespaced methods, classes and constants do not need to be prefixed. + $namespace = $this->determine_namespace( $stackPtr ); + if ( '' !== $namespace && '\\' !== $namespace ) { + return; + } + + $item_name = ''; + $error_text = 'Unknown syntax used by'; + $error_code = 'NonPrefixedSyntaxFound'; + + switch ( $this->tokens[ $stackPtr ]['type'] ) { + case 'T_FUNCTION': + // Methods in a class do not need to be prefixed. + if ( $this->phpcsFile->hasCondition( $stackPtr, array( T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT ) ) === true ) { + return; + } + + $item_name = $this->phpcsFile->getDeclarationName( $stackPtr ); + if ( function_exists( '\\' . $item_name ) ) { + // Backfill for PHP native function. + return; + } + + $error_text = 'Functions declared'; + $error_code = 'NonPrefixedFunctionFound'; + break; + + case 'T_CLASS': + case 'T_INTERFACE': + case 'T_TRAIT': + // Ignore test classes. + if ( true === $this->is_test_class( $stackPtr ) ) { + if ( $this->tokens[ $stackPtr ]['scope_condition'] === $stackPtr && isset( $this->tokens[ $stackPtr ]['scope_closer'] ) ) { + // Skip forward to end of test class. + return $this->tokens[ $stackPtr ]['scope_closer']; + } + return; + } + + $item_name = $this->phpcsFile->getDeclarationName( $stackPtr ); + $error_text = 'Classes declared'; + $error_code = 'NonPrefixedClassFound'; + + switch ( $this->tokens[ $stackPtr ]['type'] ) { + case 'T_CLASS': + if ( class_exists( '\\' . $item_name, false ) ) { + // Backfill for PHP native class. + return; + } + break; + + case 'T_INTERFACE': + if ( interface_exists( '\\' . $item_name, false ) ) { + // Backfill for PHP native interface. + return; + } + + $error_text = 'Interfaces declared'; + $error_code = 'NonPrefixedInterfaceFound'; + break; + + case 'T_TRAIT': + if ( function_exists( '\trait_exists' ) && trait_exists( '\\' . $item_name, false ) ) { + // Backfill for PHP native trait. + return; + } + + $error_text = 'Traits declared'; + $error_code = 'NonPrefixedTraitFound'; + break; + + default: + // Left empty on purpose. + break; + } + + break; + + case 'T_CONST': + // Constants in a class do not need to be prefixed. + if ( true === $this->is_class_constant( $stackPtr ) ) { + return; + } + + $constant_name_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( false === $constant_name_ptr ) { + // Live coding. + return; + } + + $item_name = $this->tokens[ $constant_name_ptr ]['content']; + if ( defined( '\\' . $item_name ) ) { + // Backfill for PHP native constant. + return; + } + + $error_text = 'Global constants defined'; + $error_code = 'NonPrefixedConstantFound'; + break; + + default: + // Left empty on purpose. + break; + + } + + if ( empty( $item_name ) || $this->is_prefixed( $item_name ) === true ) { + return; + } + + $this->phpcsFile->addError( + self::ERROR_MSG, + $stackPtr, + $error_code, + array( + $error_text, + $item_name, + ) + ); + } + + } // End process_token(). + + /** + * Handle variable variable defined in the global namespace. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + protected function process_variable_variable( $stackPtr ) { + static $indicators = array( + T_OPEN_CURLY_BRACKET => true, + T_VARIABLE => true, + ); + + // Is this a variable variable ? + // Not concerned with nested ones as those will be recognized on their own token. + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( false === $next_non_empty || ! isset( $indicators[ $this->tokens[ $next_non_empty ]['code'] ] ) ) { + return; + } + + if ( T_OPEN_CURLY_BRACKET === $this->tokens[ $next_non_empty ]['code'] + && isset( $this->tokens[ $next_non_empty ]['bracket_closer'] ) + ) { + // Skip over the variable part. + $next_non_empty = $this->tokens[ $next_non_empty ]['bracket_closer']; + } + + $maybe_assignment = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true, null, true ); + + while ( false !== $maybe_assignment + && T_OPEN_SQUARE_BRACKET === $this->tokens[ $maybe_assignment ]['code'] + && isset( $this->tokens[ $maybe_assignment ]['bracket_closer'] ) + ) { + $maybe_assignment = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + ( $this->tokens[ $maybe_assignment ]['bracket_closer'] + 1 ), + null, + true, + null, + true + ); + } + + if ( false === $maybe_assignment ) { + return; + } + + if ( ! isset( Tokens::$assignmentTokens[ $this->tokens[ $maybe_assignment ]['code'] ] ) ) { + // Not an assignment. + return; + } + + $error = self::ERROR_MSG; + + /* + * Local variable variables in a function do not need to be prefixed. + * But a variable variable could evaluate to the name of an imported global + * variable. + * Not concerned with imported variable variables (global.. ) as that has been + * forbidden since PHP 7.0. Presuming cross-version code and if not, that + * is for the PHPCompatibility standard to detect. + */ + if ( $this->phpcsFile->hasCondition( $stackPtr, array( T_FUNCTION, T_CLOSURE ) ) === true ) { + $condition = $this->phpcsFile->getCondition( $stackPtr, T_FUNCTION ); + if ( false === $condition ) { + $condition = $this->phpcsFile->getCondition( $stackPtr, T_CLOSURE ); + } + + $has_global = $this->phpcsFile->findPrevious( T_GLOBAL, ( $stackPtr - 1 ), $this->tokens[ $condition ]['scope_opener'] ); + if ( false === $has_global ) { + // No variable import happening. + return; + } + + $error = 'Variable variable which could potentially override an imported global variable detected. ' . $error; + } + + $variable_name = $this->phpcsFile->getTokensAsString( $stackPtr, ( ( $next_non_empty - $stackPtr ) + 1 ) ); + + // Still here ? In that case, the variable name should be prefixed. + $this->phpcsFile->addWarning( + $error, + $stackPtr, + 'NonPrefixedVariableFound', + array( + 'Variables defined', + $variable_name, + ) + ); + + // Skip over the variable part of the variable. + return ( $next_non_empty + 1 ); + } + + /** + * Check that defined global variables are prefixed. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + protected function process_variable_assignment( $stackPtr ) { + + // We're only concerned with variables which are being defined. + // `is_assigment()` will not recognize property assignments, which is good in this case. + if ( false === $this->is_assignment( $stackPtr ) ) { + return; + } + + $is_error = true; + $variable_name = substr( $this->tokens[ $stackPtr ]['content'], 1 ); // Strip the dollar sign. + + // Bow out early if we know for certain no prefix is needed. + if ( $this->variable_prefixed_or_whitelisted( $variable_name ) === true ) { + return; + } + + if ( 'GLOBALS' === $variable_name ) { + $array_open = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( false === $array_open || T_OPEN_SQUARE_BRACKET !== $this->tokens[ $array_open ]['code'] ) { + // Live coding or something very silly. + return; + } + + $array_key = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $array_open + 1 ), null, true, null, true ); + if ( false === $array_key ) { + // No key found, nothing to do. + return; + } + + $stackPtr = $array_key; + $variable_name = $this->strip_quotes( $this->tokens[ $array_key ]['content'] ); + + // Check whether a prefix is needed. + if ( isset( Tokens::$stringTokens[ $this->tokens[ $array_key ]['code'] ] ) + && $this->variable_prefixed_or_whitelisted( $variable_name ) === true + ) { + return; + } + + if ( T_DOUBLE_QUOTED_STRING === $this->tokens[ $array_key ]['code'] ) { + // If the array key is a double quoted string, try again with only + // the part before the first variable (if any). + $exploded = explode( '$', $variable_name ); + $first = rtrim( $exploded[0], '{' ); + if ( '' !== $first ) { + if ( $this->variable_prefixed_or_whitelisted( $first ) === true ) { + return; + } + } else { + // If the first part was dynamic, throw a warning. + $is_error = false; + } + } elseif ( ! isset( Tokens::$stringTokens[ $this->tokens[ $array_key ]['code'] ] ) ) { + // Dynamic array key, throw a warning. + $is_error = false; + } + } else { + // Function parameters do not need to be prefixed. + if ( isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) { + foreach ( $this->tokens[ $stackPtr ]['nested_parenthesis'] as $opener => $closer ) { + if ( isset( $this->tokens[ $opener ]['parenthesis_owner'] ) && T_FUNCTION === $this->tokens[ $this->tokens[ $opener ]['parenthesis_owner'] ]['code'] ) { + return; + } + } + unset( $opener, $closer ); + } + + // Properties in a class do not need to be prefixed. + if ( true === $this->is_class_property( $stackPtr ) ) { + return; + } + + // Local variables in a function do not need to be prefixed unless they are being imported. + if ( $this->phpcsFile->hasCondition( $stackPtr, array( T_FUNCTION, T_CLOSURE ) ) === true ) { + $condition = $this->phpcsFile->getCondition( $stackPtr, T_FUNCTION ); + if ( false === $condition ) { + $condition = $this->phpcsFile->getCondition( $stackPtr, T_CLOSURE ); + } + + $has_global = $this->phpcsFile->findPrevious( T_GLOBAL, ( $stackPtr - 1 ), $this->tokens[ $condition ]['scope_opener'] ); + if ( false === $has_global ) { + // No variable import happening. + return; + } + + // Ok, this may be an imported global variable. + $end_of_statement = $this->phpcsFile->findNext( T_SEMICOLON, ( $has_global + 1 ) ); + if ( false === $end_of_statement ) { + // No semi-colon - live coding. + return; + } + + for ( $ptr = ( $has_global + 1 ); $ptr <= $end_of_statement; $ptr++ ) { + // Move the stack pointer to the next variable. + $ptr = $this->phpcsFile->findNext( T_VARIABLE, $ptr, $end_of_statement, false, null, true ); + + if ( false === $ptr ) { + // Reached the end of the global statement without finding the variable, + // so this must be a local variable. + return; + } + + if ( substr( $this->tokens[ $ptr ]['content'], 1 ) === $variable_name ) { + break; + } + } + + unset( $condition, $has_global, $end_of_statement, $ptr, $imported ); + + } + } + + // Still here ? In that case, the variable name should be prefixed. + $this->addMessage( + self::ERROR_MSG, + $stackPtr, + $is_error, + 'NonPrefixedVariableFound', + array( + 'Variables defined', + '$' . $variable_name, + ) + ); + + } // End process_variable_assignment(). + + /** + * Process the parameters of a matched function. + * + * @since 0.12.0 + * + * @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 + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + + // Ignore deprecated hook names. + if ( strpos( $matched_content, '_deprecated' ) > 0 ) { + return; + } + + // No matter whether it is a constant definition or a hook call, both use the first parameter. + if ( ! isset( $parameters[1] ) ) { + return; + } + + $is_error = true; + $raw_content = $this->strip_quotes( $parameters[1]['raw'] ); + + if ( + 'define' !== $matched_content + && isset( $this->whitelisted_core_hooks[ $raw_content ] ) + ) { + return; + } + + if ( $this->is_prefixed( $raw_content ) === true ) { + return; + } else { + // This may be a dynamic hook/constant name. + $first_non_empty = $this->phpcsFile->findNext( + Tokens::$emptyTokens, + $parameters[1]['start'], + ( $parameters[1]['end'] + 1 ), + true + ); + + if ( false === $first_non_empty ) { + return; + } + + $first_non_empty_content = $this->strip_quotes( $this->tokens[ $first_non_empty ]['content'] ); + + // Try again with just the first token if it's a text string. + if ( isset( Tokens::$stringTokens[ $this->tokens[ $first_non_empty ]['code'] ] ) + && $this->is_prefixed( $first_non_empty_content ) === true + ) { + return; + } + + if ( T_DOUBLE_QUOTED_STRING === $this->tokens[ $first_non_empty ]['code'] ) { + // If the first part of the parameter is a double quoted string, try again with only + // the part before the first variable (if any). + $exploded = explode( '$', $first_non_empty_content ); + $first = rtrim( $exploded[0], '{' ); + if ( '' !== $first ) { + if ( $this->is_prefixed( $first ) === true ) { + return; + } + } else { + // Start of hook/constant name is dynamic, throw a warning. + $is_error = false; + } + } elseif ( ! isset( Tokens::$stringTokens[ $this->tokens[ $first_non_empty ]['code'] ] ) ) { + // Dynamic hook/constant name, throw a warning. + $is_error = false; + } + } + + if ( 'define' === $matched_content ) { + if ( defined( '\\' . $raw_content ) ) { + // Backfill for PHP native constant. + return; + } + + if ( strpos( $raw_content, '\\' ) !== false ) { + // Namespaced or unreachable constant. + return; + } + + $data = array( 'Global constants defined' ); + $error_code = 'NonPrefixedConstantFound'; + } else { + $data = array( 'Hook names invoked' ); + $error_code = 'NonPrefixedHooknameFound'; + } + + $data[] = $raw_content; + + $this->addMessage( self::ERROR_MSG, $parameters[1]['start'], $is_error, $error_code, $data ); + + } // End process_parameters(). + + /** + * Check if a function/class/constant/variable name is prefixed with one of the expected prefixes. + * + * @since 0.12.0 + * @since 0.14.0 Allows for other non-word characters as well as underscores to better support hook names. + * + * @param string $name Name to check for a prefix. + * + * @return bool True when the name is the prefix or starts with the prefix + a separator. + * False otherwise. + */ + private function is_prefixed( $name ) { + + foreach ( $this->validated_prefixes as $prefix ) { + if ( strtolower( $name ) === $prefix ) { + // Ok, prefix *is* the hook/constant name. + return true; + + } else { + $prefix_found = stripos( $name, $prefix . '_' ); + + if ( 0 === $prefix_found ) { + // Ok, prefix found at start of hook/constant name. + return true; + } + + if ( preg_match( '`^' . preg_quote( $prefix, '`' ) . '\W`i', $name ) === 1 ) { + // Ok, prefix with other non-word character found at start of hook/constant name. + return true; + } + } + } + + return false; + } + + /** + * Check if a variable name might need a prefix. + * + * Prefix is not needed for: + * - superglobals, + * - WP native globals, + * - variables which are already prefixed. + * + * @param string $name Variable name without the dollar sign. + * @return bool True if the variable name is whitelisted or already prefixed. + * False otherwise. + */ + private function variable_prefixed_or_whitelisted( $name ) { + // Ignore superglobals and WP global variables. + if ( isset( $this->superglobals[ $name ] ) || isset( $this->wp_globals[ $name ] ) ) { + return true; + } + + return $this->is_prefixed( $name ); + } + + /** + * Validate an array of prefixes as passed through a custom property or via the command line. + * + * Checks that the prefix: + * - is not one of the blacklisted ones. + * - complies with the PHP rules for valid function, class, variable, constant names. + * + * @since 0.12.0 + */ + private function validate_prefixes() { + if ( $this->previous_prefixes === $this->prefixes ) { + return; + } + + // Set the cache *before* validation so as to not break the above compare. + $this->previous_prefixes = $this->prefixes; + + // Validate the passed prefix(es). + foreach ( $this->prefixes as $key => $prefix ) { + $prefixLC = strtolower( $prefix ); + + if ( isset( $this->prefix_blacklist[ $prefixLC ] ) ) { + $this->phpcsFile->addError( + 'The "%s" prefix is not allowed.', + 0, + 'ForbiddenPrefixPassed', + array( $prefix ) + ); + unset( $this->prefixes[ $key ] ); + continue; + } + + // Validate the prefix against characters allowed for function, class, constant names etc. + if ( preg_match( '`^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$`', $prefix ) !== 1 ) { + $this->phpcsFile->addError( + 'The "%s" prefix is not a valid function/class/variable/constant prefix in PHP.', + 0, + 'InvalidPrefixPassed', + array( $prefix ) + ); + unset( $this->prefixes[ $key ] ); + } + + // Lowercase the prefix to allow for direct compare. + $this->prefixes[ $key ] = $prefixLC; + } + + // Set the validated prefixes cache. + $this->validated_prefixes = $this->prefixes; + + } // End validate_prefixes(). + +} // End class. diff --git a/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php index d7e0c600..73073e9a 100644 --- a/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php +++ b/WordPress/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -7,9 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ -if ( ! class_exists( 'PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff', true ) ) { - throw new PHP_CodeSniffer_Exception( 'Class PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff not found' ); -} +namespace WordPress\Sniffs\NamingConventions; + +use PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff as PHPCS_PEAR_ValidFunctionNameSniff; +use PHP_CodeSniffer_File as File; /** * Enforces WordPress function name and method name format, based upon Squiz code. @@ -19,6 +20,7 @@ * @package WPCS\WordPressCodingStandards * * @since 0.1.0 + * @since 0.13.0 Class name changed: this class is now namespaced. * * Last synced with parent class July 2016 up to commit 4fea2e651109e41066a81e22e004d851fb1287f6. * @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -26,7 +28,7 @@ * {@internal While this class extends the PEAR parent, it does not actually use the checks * contained in the parent. It only uses the properties and the token registration from the parent.}} */ -class WordPress_Sniffs_NamingConventions_ValidFunctionNameSniff extends PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff { +class ValidFunctionNameSniff extends PHPCS_PEAR_ValidFunctionNameSniff { /** * Additional double underscore prefixed methods specific to certain PHP native extensions. @@ -54,13 +56,13 @@ class WordPress_Sniffs_NamingConventions_ValidFunctionNameSniff extends PEAR_Sni /** * Processes the tokens outside the scope. * - * @param PHP_CodeSniffer_File $phpcsFile The file being processed. - * @param int $stackPtr The position where this token was - * found. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed. + * @param int $stackPtr The position where this token was + * found. * * @return void */ - protected function processTokenOutsideScope( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { + protected function processTokenOutsideScope( File $phpcsFile, $stackPtr ) { $functionName = $phpcsFile->getDeclarationName( $stackPtr ); if ( ! isset( $functionName ) ) { @@ -87,15 +89,10 @@ protected function processTokenOutsideScope( PHP_CodeSniffer_File $phpcsFile, $s } if ( strtolower( $functionName ) !== $functionName ) { - $suggested = preg_replace( '/([A-Z])/', '_$1', $functionName ); - $suggested = strtolower( $suggested ); - $suggested = str_replace( '__', '_', $suggested ); - $suggested = trim( $suggested, '_' ); - $error = 'Function name "%s" is not in snake case format, try "%s"'; $errorData = array( $functionName, - $suggested, + $this->get_name_suggestion( $functionName ), ); $phpcsFile->addError( $error, $stackPtr, 'FunctionNameInvalid', $errorData ); } @@ -105,14 +102,14 @@ protected function processTokenOutsideScope( PHP_CodeSniffer_File $phpcsFile, $s /** * Processes the tokens within the scope. * - * @param PHP_CodeSniffer_File $phpcsFile The file being processed. - * @param int $stackPtr The position where this token was - * found. - * @param int $currScope The position of the current scope. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed. + * @param int $stackPtr The position where this token was + * found. + * @param int $currScope The position of the current scope. * * @return void */ - protected function processTokenWithinScope( PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope ) { + protected function processTokenWithinScope( File $phpcsFile, $stackPtr, $currScope ) { $methodName = $phpcsFile->getDeclarationName( $stackPtr ); if ( ! isset( $methodName ) ) { @@ -120,7 +117,7 @@ protected function processTokenWithinScope( PHP_CodeSniffer_File $phpcsFile, $st return; } - $className = $phpcsFile->getDeclarationName( $currScope ); + $className = $phpcsFile->getDeclarationName( $currScope ); // Ignore special functions. if ( '' === ltrim( $methodName, '_' ) ) { @@ -149,9 +146,9 @@ protected function processTokenWithinScope( PHP_CodeSniffer_File $phpcsFile, $st 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 ); + $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 ); } return; @@ -159,20 +156,29 @@ protected function processTokenWithinScope( PHP_CodeSniffer_File $phpcsFile, $st // Check for all lowercase. if ( strtolower( $methodName ) !== $methodName ) { - $suggested = preg_replace( '/([A-Z])/', '_$1', $methodName ); - $suggested = strtolower( $suggested ); - $suggested = str_replace( '__', '_', $suggested ); - $suggested = trim( $suggested, '_' ); - $error = 'Method name "%s" in class %s is not in snake case format, try "%s"'; $errorData = array( $methodName, $className, - $suggested, + $this->get_name_suggestion( $methodName ), ); $phpcsFile->addError( $error, $stackPtr, 'MethodNameInvalid', $errorData ); } } // End processTokenWithinScope(). + /** + * Transform the existing function/method name to one which complies with the naming conventions. + * + * @param string $name The function/method name. + * @return string + */ + protected function get_name_suggestion( $name ) { + $suggested = preg_replace( '/([A-Z])/', '_$1', $name ); + $suggested = strtolower( $suggested ); + $suggested = str_replace( '__', '_', $suggested ); + $suggested = trim( $suggested, '_' ); + return $suggested; + } + } // End class. diff --git a/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php b/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php index 7acace6d..ef20800f 100644 --- a/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php +++ b/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\NamingConventions; + +use WordPress\AbstractFunctionParameterSniff; + /** * Use lowercase letters in action and filter names. Separate words via underscores. * @@ -21,8 +25,9 @@ * * @since 0.10.0 * @since 0.11.0 Extends the WordPress_AbstractFunctionParameterSniff class. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_NamingConventions_ValidHookNameSniff extends WordPress_AbstractFunctionParameterSniff { +class ValidHookNameSniff extends AbstractFunctionParameterSniff { /** * Additional word separators. @@ -92,7 +97,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p return; } - $regex = $this->prepare_regex(); + $regex = $this->prepare_regex(); $case_errors = 0; $underscores = 0; @@ -107,8 +112,8 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $string = $this->strip_quotes( $this->tokens[ $i ]['content'] ); /* - Here be dragons - a double quoted string can contain extrapolated variables - which don't have to comply with these rules. + * Here be dragons - a double quoted string can contain extrapolated variables + * which don't have to comply with these rules. */ if ( T_DOUBLE_QUOTED_STRING === $this->tokens[ $i ]['code'] ) { $transform = $this->transform_complex_string( $string, $regex ); @@ -137,7 +142,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $underscores++; } } - } // End for(). + } $data = array( implode( '', $expected ), @@ -153,7 +158,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $this->phpcsFile->addWarning( $error, $stackPtr, 'UseUnderscores', $data ); } - } // End process(). + } // End process_parameters(). /** * Prepare the punctuation regular expression. diff --git a/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php b/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php index fcc30f2b..d57924c9 100644 --- a/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php +++ b/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -7,9 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ -if ( ! class_exists( 'PHP_CodeSniffer_Standards_AbstractVariableSniff', true ) ) { - throw new PHP_CodeSniffer_Exception( 'Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found' ); -} +namespace WordPress\Sniffs\NamingConventions; + +use PHP_CodeSniffer_Standards_AbstractVariableSniff as PHPCS_AbstractVariableSniff; +use PHP_CodeSniffer_File as File; +use WordPress\Sniff; /** * Checks the naming of variables and member variables. @@ -19,11 +21,12 @@ * @package WPCS\WordPressCodingStandards * * @since 0.9.0 + * @since 0.13.0 Class name changed: this class is now namespaced. * * Last synced with base class July 2014 at commit ed257ca0e56ad86cd2a4d6fa38ce0b95141c824f. * @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php */ -class WordPress_Sniffs_NamingConventions_ValidVariableNameSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff { +class ValidVariableNameSniff extends PHPCS_AbstractVariableSniff { /** * PHP Reserved Vars. @@ -56,13 +59,16 @@ class WordPress_Sniffs_NamingConventions_ValidVariableNameSniff extends PHP_Code * @var array */ protected $wordpress_mixed_case_vars = array( - 'EZSQL_ERROR' => true, - 'is_IE' => true, - 'is_IIS' => true, - 'is_macIE' => true, - 'is_NS4' => true, - 'is_winIE' => true, - 'PHP_SELF' => true, + 'EZSQL_ERROR' => true, + 'GETID3_ERRORARRAY' => true, + 'is_IE' => true, + 'is_IIS' => true, + 'is_macIE' => true, + 'is_NS4' => true, + 'is_winIE' => true, + 'PHP_SELF' => true, + 'post_ID' => true, + 'user_ID' => true, ); /** @@ -120,13 +126,13 @@ class WordPress_Sniffs_NamingConventions_ValidVariableNameSniff extends PHP_Code /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcs_file The file being scanned. - * @param int $stack_ptr The position of the current token in the - * stack passed in $tokens. + * @param \PHP_CodeSniffer\Files\File $phpcs_file The file being scanned. + * @param int $stack_ptr The position of the current token in the + * stack passed in $tokens. * * @return void */ - protected function processVariable( PHP_CodeSniffer_File $phpcs_file, $stack_ptr ) { + protected function processVariable( File $phpcs_file, $stack_ptr ) { $tokens = $phpcs_file->getTokens(); $var_name = ltrim( $tokens[ $stack_ptr ]['content'], '$' ); @@ -166,9 +172,9 @@ protected function processVariable( PHP_CodeSniffer_File $phpcs_file, $stack_ptr $data = array( $original_var_name ); $phpcs_file->addError( $error, $var, 'NotSnakeCaseMemberVar', $data ); } - } // End if(). - } // End if(). - } // End if(). + } + } + } $in_class = false; $obj_operator = $phpcs_file->findPrevious( array( T_WHITESPACE ), ( $stack_ptr - 1 ), null, true ); @@ -196,7 +202,7 @@ protected function processVariable( PHP_CodeSniffer_File $phpcs_file, $stack_ptr } if ( isset( $error, $error_name ) ) { - $data = array( $original_var_name ); + $data = array( $original_var_name ); $phpcs_file->addError( $error, $stack_ptr, $error_name, $data ); } } @@ -206,13 +212,13 @@ protected function processVariable( PHP_CodeSniffer_File $phpcs_file, $stack_ptr /** * Processes class member variables. * - * @param PHP_CodeSniffer_File $phpcs_file The file being scanned. - * @param int $stack_ptr The position of the current token in the - * stack passed in $tokens. + * @param \PHP_CodeSniffer\Files\File $phpcs_file The file being scanned. + * @param int $stack_ptr The position of the current token in the + * stack passed in $tokens. * * @return void */ - protected function processMemberVar( PHP_CodeSniffer_File $phpcs_file, $stack_ptr ) { + protected function processMemberVar( File $phpcs_file, $stack_ptr ) { $tokens = $phpcs_file->getTokens(); @@ -240,13 +246,13 @@ protected function processMemberVar( PHP_CodeSniffer_File $phpcs_file, $stack_pt /** * Processes the variable found within a double quoted string. * - * @param PHP_CodeSniffer_File $phpcs_file The file being scanned. - * @param int $stack_ptr The position of the double quoted - * string. + * @param \PHP_CodeSniffer\Files\File $phpcs_file The file being scanned. + * @param int $stack_ptr The position of the double quoted + * string. * * @return void */ - protected function processVariableInString( PHP_CodeSniffer_File $phpcs_file, $stack_ptr ) { + protected function processVariableInString( File $phpcs_file, $stack_ptr ) { $tokens = $phpcs_file->getTokens(); @@ -292,19 +298,19 @@ public static function isSnakeCase( $var_name ) { * * @since 0.10.0 * - * @param PHP_CodeSniffer_File $phpcs_file The file being scanned. + * @param \PHP_CodeSniffer\Files\File $phpcs_file The file being scanned. * * @return void */ - protected function mergeWhiteList( $phpcs_file ) { + protected function mergeWhiteList( File $phpcs_file ) { if ( $this->customPropertiesWhitelist !== $this->addedCustomProperties['properties'] || $this->customVariablesWhitelist !== $this->addedCustomProperties['variables'] ) { // Fix property potentially passed as comma-delimited string. - $customProperties = WordPress_Sniff::merge_custom_array( $this->customPropertiesWhitelist, array(), false ); + $customProperties = Sniff::merge_custom_array( $this->customPropertiesWhitelist, array(), false ); if ( ! empty( $this->customVariablesWhitelist ) ) { - $customProperties = WordPress_Sniff::merge_custom_array( + $customProperties = Sniff::merge_custom_array( $this->customVariablesWhitelist, $customProperties, false @@ -317,10 +323,11 @@ protected function mergeWhiteList( $phpcs_file ) { ); } - $this->whitelisted_mixed_case_member_var_names = WordPress_Sniff::merge_custom_array( + $this->whitelisted_mixed_case_member_var_names = Sniff::merge_custom_array( $customProperties, $this->whitelisted_mixed_case_member_var_names ); + $this->addedCustomProperties['properties'] = $this->customPropertiesWhitelist; $this->addedCustomProperties['variables'] = $this->customVariablesWhitelist; } diff --git a/WordPress/Sniffs/PHP/DevelopmentFunctionsSniff.php b/WordPress/Sniffs/PHP/DevelopmentFunctionsSniff.php index 7d60fdc6..a38d190c 100644 --- a/WordPress/Sniffs/PHP/DevelopmentFunctionsSniff.php +++ b/WordPress/Sniffs/PHP/DevelopmentFunctionsSniff.php @@ -7,24 +7,29 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\PHP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Restrict the use of various development functions. * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_PHP_DevelopmentFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class DevelopmentFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'file_get_contents', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/PHP/DiscourageGotoSniff.php b/WordPress/Sniffs/PHP/DiscourageGotoSniff.php new file mode 100644 index 00000000..3e8f7e05 --- /dev/null +++ b/WordPress/Sniffs/PHP/DiscourageGotoSniff.php @@ -0,0 +1,50 @@ +phpcsFile->addWarning( 'Using the "goto" language construct is discouraged', $stackPtr, 'Found' ); + } + +}//end class diff --git a/WordPress/Sniffs/PHP/DiscouragedFunctionsSniff.php b/WordPress/Sniffs/PHP/DiscouragedFunctionsSniff.php index e141eb94..763ab86b 100644 --- a/WordPress/Sniffs/PHP/DiscouragedFunctionsSniff.php +++ b/WordPress/Sniffs/PHP/DiscouragedFunctionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\PHP; + +use PHP_CodeSniffer_File as File; + /** * Discourages the use of various native PHP functions and suggests alternatives. * @@ -15,6 +19,8 @@ * @since 0.1.0 * @since 0.10.0 The checks for the POSIX functions have been replaced by the stand-alone * sniff WordPress_Sniffs_PHP_POSIXFunctionsSniff. + * @since 0.13.0 Class name changed: this class is now namespaced. + * * @deprecated 0.11.0 The checks for the PHP development functions have been replaced by the * stand-alone sniff WordPress_Sniffs_PHP_DevelopmentFunctionsSniff. * The checks for the WP deprecated functions have been replaced by the @@ -29,7 +35,7 @@ * function. To check for `register_globals` ini directive use * PHPCompatibility_Sniffs_PHP_DeprecatedIniDirectivesSniff from wimg/PHPCompatibility. */ -class WordPress_Sniffs_PHP_DiscouragedFunctionsSniff { +class DiscouragedFunctionsSniff { /** * Don't use. @@ -47,11 +53,11 @@ public function register() { * * @deprecated 0.11.0 * - * @param PHP_CodeSniffer_File $phpcsFile A PHP_CodeSniffer file. - * @param int $stackPtr The position of the token. + * @param \PHP_CodeSniffer\Files\File $phpcsFile A PHP_CodeSniffer file. + * @param int $stackPtr The position of the token. * * @return void */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) {} + public function process( File $phpcsFile, $stackPtr ) {} } diff --git a/WordPress/Sniffs/PHP/DiscouragedPHPFunctionsSniff.php b/WordPress/Sniffs/PHP/DiscouragedPHPFunctionsSniff.php index 8af415ba..59f0ef71 100644 --- a/WordPress/Sniffs/PHP/DiscouragedPHPFunctionsSniff.php +++ b/WordPress/Sniffs/PHP/DiscouragedPHPFunctionsSniff.php @@ -7,38 +7,36 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\PHP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Discourages the use of various native PHP functions and suggests alternatives. * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + * @since 0.14.0 `create_function` was moved to the PHP.RestrictedFunctions sniff. */ -class WordPress_Sniffs_PHP_DiscouragedPHPFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class DiscouragedPHPFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to discourage. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'file_get_contents', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array */ public function getGroups() { return array( - 'create_function' => array( - 'type' => 'warning', - 'message' => '%s() is discouraged, please use anonymous functions instead.', - 'functions' => array( - 'create_function', - ), - ), - 'serialize' => array( 'type' => 'warning', 'message' => '%s() found. Serialized data has known vulnerability problems with Object Injection. JSON is generally a better approach for serializing data. See https://www.owasp.org/index.php/PHP_Object_Injection', diff --git a/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php b/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php index 7e8d0366..cab71a4e 100644 --- a/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php +++ b/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\PHP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Perl compatible regular expressions (PCRE, preg_ functions) should be used in preference * to their POSIX counterparts. @@ -18,18 +22,19 @@ * * @since 0.10.0 Previously this check was contained within WordPress_Sniffs_VIP_RestrictedFunctionsSniff * and the WordPress_Sniffs_PHP_DiscouragedPHPFunctionsSniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_PHP_POSIXFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class POSIXFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'file_get_contents', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php b/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php new file mode 100644 index 00000000..7e24a152 --- /dev/null +++ b/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php @@ -0,0 +1,49 @@ + array( + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) + * ) + * + * @return array + */ + public function getGroups() { + return array( + 'create_function' => array( + 'type' => 'error', + 'message' => '%s() is deprecated as of PHP 7.2, please use full fledged functions or anonymous functions instead.', + 'functions' => array( + 'create_function', + ), + ), + + ); + } // end getGroups() + +} // End class. diff --git a/WordPress/Sniffs/PHP/StrictComparisonsSniff.php b/WordPress/Sniffs/PHP/StrictComparisonsSniff.php index c87d9a0b..ee815270 100644 --- a/WordPress/Sniffs/PHP/StrictComparisonsSniff.php +++ b/WordPress/Sniffs/PHP/StrictComparisonsSniff.php @@ -7,19 +7,24 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\PHP; + +use WordPress\Sniff; + /** * Enforces Strict Comparison checks, based upon Squiz code. * * @package WPCS\WordPressCodingStandards * * @since 0.4.0 + * @since 0.13.0 Class name changed: this class is now namespaced. * * Last synced with base class ?[unknown date]? at commit ?[unknown commit]?. * It is currently unclear whether this sniff is actually based on Squiz code on whether the above * reference to it is a copy/paste oversight. * @link Possibly: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php */ -class WordPress_Sniffs_PHP_StrictComparisonsSniff extends WordPress_Sniff { +class StrictComparisonsSniff extends Sniff { /** * Returns an array of tokens this test wants to listen for. @@ -44,7 +49,7 @@ public function register() { public function process_token( $stackPtr ) { if ( ! $this->has_whitelist_comment( 'loose comparison', $stackPtr ) ) { - $error = 'Found: ' . $this->tokens[ $stackPtr ]['content'] . '. Use strict comparisons (=== or !==).'; + $error = 'Found: ' . $this->tokens[ $stackPtr ]['content'] . '. Use strict comparisons (=== or !==).'; $this->phpcsFile->addWarning( $error, $stackPtr, 'LooseComparison' ); } diff --git a/WordPress/Sniffs/PHP/StrictInArraySniff.php b/WordPress/Sniffs/PHP/StrictInArraySniff.php index cc67f244..6feb1b26 100644 --- a/WordPress/Sniffs/PHP/StrictInArraySniff.php +++ b/WordPress/Sniffs/PHP/StrictInArraySniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\PHP; + +use WordPress\AbstractFunctionParameterSniff; + /** * Flag calling in_array(), array_search() and array_keys() without true as the third parameter. * @@ -19,8 +23,9 @@ * The sniff no longer needlessly extends the WordPress_Sniffs_Arrays_ArrayAssignmentRestrictionsSniff * which it didn't use. * @since 0.11.0 Refactored to extend the new WordPress_AbstractFunctionParameterSniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_PHP_StrictInArraySniff extends WordPress_AbstractFunctionParameterSniff { +class StrictInArraySniff extends AbstractFunctionParameterSniff { /** * The group name for this group of functions. @@ -75,11 +80,21 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p } // We're only interested in the third parameter. - if ( false === isset( $parameters[3] ) || 'true' !== $parameters[3]['raw'] ) { + if ( false === isset( $parameters[3] ) || 'true' !== strtolower( $parameters[3]['raw'] ) ) { + $errorcode = 'MissingTrueStrict'; + + /* + * Use a different error code when `false` is found to allow for excluding + * the warning as this will be a conscious choice made by the dev. + */ + if ( isset( $parameters[3] ) && 'false' === strtolower( $parameters[3]['raw'] ) ) { + $errorcode = 'FoundNonStrictFalse'; + } + $this->phpcsFile->addWarning( 'Not using strict comparison for %s; supply true for third argument.', ( isset( $parameters[3]['start'] ) ? $parameters[3]['start'] : $parameters[1]['start'] ), - 'MissingTrueStrict', + $errorcode, array( $matched_content ) ); return; diff --git a/WordPress/Sniffs/PHP/YodaConditionsSniff.php b/WordPress/Sniffs/PHP/YodaConditionsSniff.php index c20972e7..f66b3471 100644 --- a/WordPress/Sniffs/PHP/YodaConditionsSniff.php +++ b/WordPress/Sniffs/PHP/YodaConditionsSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\PHP; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Enforces Yoda conditional statements. * @@ -15,8 +20,19 @@ * @package WPCS\WordPressCodingStandards * * @since 0.3.0 + * @since 0.12.0 This class now extends WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_PHP_YodaConditionsSniff implements PHP_CodeSniffer_Sniff { +class YodaConditionsSniff extends Sniff { + + /** + * The tokens that indicate the start of a condition. + * + * @since 0.12.0 + * + * @var array + */ + protected $condition_start_tokens; /** * Returns an array of tokens this test wants to listen for. @@ -24,6 +40,18 @@ class WordPress_Sniffs_PHP_YodaConditionsSniff implements PHP_CodeSniffer_Sniff * @return array */ public function register() { + + $starters = Tokens::$booleanOperators; + $starters += Tokens::$assignmentTokens; + $starters[ T_CASE ] = T_CASE; + $starters[ T_RETURN ] = T_RETURN; + $starters[ T_INLINE_THEN ] = T_INLINE_THEN; + $starters[ T_INLINE_ELSE ] = T_INLINE_ELSE; + $starters[ T_SEMICOLON ] = T_SEMICOLON; + $starters[ T_OPEN_PARENTHESIS ] = T_OPEN_PARENTHESIS; + + $this->condition_start_tokens = $starters; + return array( T_IS_EQUAL, T_IS_NOT_EQUAL, @@ -36,39 +64,34 @@ public function register() { /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in the - * stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack. * * @return void */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - - $beginners = PHP_CodeSniffer_Tokens::$booleanOperators; - $beginners[] = T_IF; - $beginners[] = T_ELSEIF; + public function process_token( $stackPtr ) { - $beginning = $phpcsFile->findPrevious( $beginners, $stackPtr, null, false, null, true ); + $start = $this->phpcsFile->findPrevious( $this->condition_start_tokens, $stackPtr, null, false, null, true ); $needs_yoda = false; // Note: going backwards! - for ( $i = $stackPtr; $i > $beginning; $i-- ) { + for ( $i = $stackPtr; $i > $start; $i-- ) { // Ignore whitespace. - if ( isset( PHP_CodeSniffer_Tokens::$emptyTokens[ $tokens[ $i ]['code'] ] ) ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { continue; } // If this is a variable or array, we've seen all we need to see. - if ( T_VARIABLE === $tokens[ $i ]['code'] || T_CLOSE_SQUARE_BRACKET === $tokens[ $i ]['code'] ) { + if ( T_VARIABLE === $this->tokens[ $i ]['code'] + || T_CLOSE_SQUARE_BRACKET === $this->tokens[ $i ]['code'] + ) { $needs_yoda = true; break; } // If this is a function call or something, we are OK. - if ( in_array( $tokens[ $i ]['code'], array( T_CONSTANT_ENCAPSED_STRING, T_CLOSE_PARENTHESIS, T_OPEN_PARENTHESIS, T_RETURN ), true ) ) { + if ( T_CLOSE_PARENTHESIS === $this->tokens[ $i ]['code'] ) { return; } } @@ -78,26 +101,26 @@ public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { } // Check if this is a var to var comparison, e.g.: if ( $var1 == $var2 ). - $next_non_empty = $phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); - if ( isset( PHP_CodeSniffer_Tokens::$castTokens[ $tokens[ $next_non_empty ]['code'] ] ) ) { - $next_non_empty = $phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true ); + if ( isset( Tokens::$castTokens[ $this->tokens[ $next_non_empty ]['code'] ] ) ) { + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true ); } - if ( in_array( $tokens[ $next_non_empty ]['code'], array( T_SELF, T_PARENT, T_STATIC ), true ) ) { - $next_non_empty = $phpcsFile->findNext( - array_merge( PHP_CodeSniffer_Tokens::$emptyTokens, array( T_DOUBLE_COLON ) ) - , ( $next_non_empty + 1 ) - , null - , true + if ( in_array( $this->tokens[ $next_non_empty ]['code'], array( T_SELF, T_PARENT, T_STATIC ), true ) ) { + $next_non_empty = $this->phpcsFile->findNext( + array_merge( Tokens::$emptyTokens, array( T_DOUBLE_COLON ) ), + ( $next_non_empty + 1 ), + null, + true ); } - if ( T_VARIABLE === $tokens[ $next_non_empty ]['code'] ) { + if ( T_VARIABLE === $this->tokens[ $next_non_empty ]['code'] ) { return; } - $phpcsFile->addError( 'Use Yoda Condition checks, you must.', $stackPtr, 'NotYoda' ); + $this->phpcsFile->addError( 'Use Yoda Condition checks, you must.', $stackPtr, 'NotYoda' ); } // End process(). diff --git a/WordPress/Sniffs/Theme/DeprecatedWPConstantsSniff.php b/WordPress/Sniffs/Theme/DeprecatedWPConstantsSniff.php deleted file mode 100644 index 757e8675..00000000 --- a/WordPress/Sniffs/Theme/DeprecatedWPConstantsSniff.php +++ /dev/null @@ -1,87 +0,0 @@ - 'get_stylesheet_directory()', - 'TEMPLATEPATH' => 'get_template_directory()', - 'PLUGINDIR' => 'WP_PLUGIN_DIR', - 'MUPLUGINDIR' => 'WPMU_PLUGIN_DIR', - 'HEADER_IMAGE' => 'add_theme_support( \'custom-header\' )', - 'NO_HEADER_TEXT' => 'add_theme_support( \'custom-header\' )', - 'HEADER_TEXTCOLOR' => 'add_theme_support( \'custom-header\' )', - 'HEADER_IMAGE_WIDTH' => 'add_theme_support( \'custom-header\' )', - 'HEADER_IMAGE_HEIGHT' => 'add_theme_support( \'custom-header\' )', - 'BACKGROUND_COLOR' => 'add_theme_support( \'custom-background\' )', - 'BACKGROUND_IMAGE' => 'add_theme_support( \'custom-background\' )', - ); - - /** - * Returns an array of tokens this test wants to listen for. - * - * @return array - */ - public function register() { - return array( - T_STRING, - ); - } - - /** - * Processes this test, when one of its tokens is encountered. - * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void - */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - $token = $tokens[ $stackPtr ]; - - if ( ! isset( $this->deprecated_constants[ $tokens[ $stackPtr ]['content'] ] ) ) { - return; - } - - $prev = $phpcsFile->findPrevious( T_WHITESPACE, ( $stackPtr - 1 ), null, true ); - - if ( T_DOUBLE_COLON === $tokens[ $prev ]['code'] ) { - // Class constant of the same name. - return; - } - - if ( T_NS_SEPARATOR === $tokens[ $prev ]['code'] && T_STRING === $tokens[ ( $prev - 1 ) ]['code'] ) { - // Namespaced constant of the same name. - return; - } - - // Ok, this is really one of the deprecated constants. - $error = 'Found usage of constant "%s". Use %s instead.'; - $data = array( $tokens[ $stackPtr ]['content'], $this->deprecated_constants[ $tokens[ $stackPtr ]['content'] ] ); - $phpcsFile->addError( $error, $stackPtr, 'Found', $data ); - - } - -} // End class. diff --git a/WordPress/Sniffs/Theme/FileIncludeSniff.php b/WordPress/Sniffs/Theme/FileIncludeSniff.php index 9f8a2bae..cb4393f4 100644 --- a/WordPress/Sniffs/Theme/FileIncludeSniff.php +++ b/WordPress/Sniffs/Theme/FileIncludeSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Theme; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Check if a theme uses include(_once) or require(_once) when get_template_part() should be used. * @@ -16,7 +21,7 @@ * * @since 0.xx.0 */ -class WordPress_Sniffs_Theme_FileIncludeSniff implements PHP_CodeSniffer_Sniff { +class FileIncludeSniff extends Sniff { /** * A list of files to skip. @@ -33,27 +38,21 @@ class WordPress_Sniffs_Theme_FileIncludeSniff implements PHP_CodeSniffer_Sniff { * @return array */ public function register() { - return PHP_CodeSniffer_Tokens::$includeTokens; + return Tokens::$includeTokens; } /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void + * @param int $stackPtr The position of the current token in the stack. */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - $token = $tokens[ $stackPtr ]; - - $file_name = basename( $phpcsFile->getFileName() ); + public function process_token( $stackPtr ) { + $token = $this->tokens[ $stackPtr ]; + $file_name = basename( $this->phpcsFile->getFileName() ); if ( ! isset( $this->file_whitelist[ $file_name ] ) ) { - $phpcsFile->addWarning( - 'Check that %s is not being used to load template files. "get_template_part()" should be used to load template files.' , + $this->phpcsFile->addWarning( + 'Check that %s is not being used to load template files. "get_template_part()" should be used to load template files.', $stackPtr, 'FileIncludeFound', array( $token['content'] ) diff --git a/WordPress/Sniffs/Theme/NoAddAdminPagesSniff.php b/WordPress/Sniffs/Theme/NoAddAdminPagesSniff.php index dec45d48..60cca8cf 100644 --- a/WordPress/Sniffs/Theme/NoAddAdminPagesSniff.php +++ b/WordPress/Sniffs/Theme/NoAddAdminPagesSniff.php @@ -7,9 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Theme; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** - * WordPress_Sniffs_Theme_NoAddAdminPagesSniff. - * * Forbids the use of add_..._page() functions within Themes with the exception of `add_theme_page()`. * * @link https://make.wordpress.org/themes/handbook/review/required/theme-check-plugin/#admin-menu @@ -18,24 +20,23 @@ * * @since 0.xx.0 */ -class WordPress_Sniffs_Theme_NoAddAdminPagesSniff extends WordPress_Sniffs_Functions_FunctionRestrictionsSniff { +class NoAddAdminPagesSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'eval', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array */ public function getGroups() { return array( - 'add_menu_pages' => array( 'type' => 'error', 'message' => 'Themes should use add_theme_page() for adding admin pages. Found %s.', @@ -63,4 +64,4 @@ public function getGroups() { ); } -} // End class. +} diff --git a/WordPress/Sniffs/Theme/NoAutoGenerateSniff.php b/WordPress/Sniffs/Theme/NoAutoGenerateSniff.php index 28dca3b9..4359dacd 100644 --- a/WordPress/Sniffs/Theme/NoAutoGenerateSniff.php +++ b/WordPress/Sniffs/Theme/NoAutoGenerateSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Theme; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Check for auto generated themes. * @@ -14,7 +19,7 @@ * * @since 0.xx.0 */ -class WordPress_Sniffs_Theme_NoAutoGenerateSniff implements PHP_CodeSniffer_Sniff { +class NoAutoGenerateSniff extends Sniff { /** * A list of tokenizers this sniff supports. @@ -46,7 +51,7 @@ class WordPress_Sniffs_Theme_NoAutoGenerateSniff implements PHP_CodeSniffer_Snif * @return array */ public function register() { - $tokens = PHP_CodeSniffer_Tokens::$stringTokens; + $tokens = Tokens::$stringTokens; $tokens[] = T_INLINE_HTML; $tokens[] = T_HEREDOC; $tokens[] = T_STRING; // Functions named after or prefixed with the generator name. @@ -59,16 +64,11 @@ public function register() { /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void + * @param int $stackPtr The position of the current token in the stack. */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { + public function process_token( $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - $token = $tokens[ $stackPtr ]; + $token = $this->tokens[ $stackPtr ]; $content = trim( strtolower( $token['content'] ) ); // No need to check an empty string. @@ -85,7 +85,7 @@ public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { continue; } - $phpcsFile->addError( + $this->phpcsFile->addError( 'Auto generated themes are not allowed in the theme directory. Found: %s', $stackPtr, 'AutoGeneratedFound', diff --git a/WordPress/Sniffs/Theme/NoFaviconSniff.php b/WordPress/Sniffs/Theme/NoFaviconSniff.php index bd08cda7..0a689bfe 100644 --- a/WordPress/Sniffs/Theme/NoFaviconSniff.php +++ b/WordPress/Sniffs/Theme/NoFaviconSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Theme; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Check for hardcoded favicons instead of using core implementation. * @@ -16,7 +21,7 @@ * * @since 0.xx.0 */ -class WordPress_Sniffs_Theme_NoFaviconSniff implements PHP_CodeSniffer_Sniff { +class NoFaviconSniff extends Sniff { /** * Regex template. @@ -76,14 +81,14 @@ public function register() { $regex_parts = array(); foreach ( $this->attribute_blacklist as $key => $values ) { - $values = array_map( 'preg_quote', $values, array_fill( 0, count( $values ), '`' ) ); - $values = implode( '|', $values ); + $values = array_map( 'preg_quote', $values, array_fill( 0, count( $values ), '`' ) ); + $values = implode( '|', $values ); $regex_parts[] = sprintf( self::REGEX_ATTR_TEMPLATE, preg_quote( $key, '`' ), $values ); } $this->favicon_regex = sprintf( self::REGEX_TEMPLATE, implode( '|', $regex_parts ) ); - $tokens = PHP_CodeSniffer_Tokens::$stringTokens; + $tokens = Tokens::$stringTokens; $tokens[] = T_INLINE_HTML; $tokens[] = T_HEREDOC; $tokens[] = T_NOWDOC; @@ -94,18 +99,17 @@ public function register() { /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void + * @param int $stackPtr The position of the current token in the stack. */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - $token = $tokens[ $stackPtr ]; + public function process_token( $stackPtr ) { + $token = $this->tokens[ $stackPtr ]; if ( preg_match( $this->favicon_regex, $token['content'] ) > 0 ) { - $phpcsFile->addError( 'Code for favicon found. Favicons are handled by the "Site Icon" setting in the customizer since version 4.3.' , $stackPtr, 'NoFavicon' ); + $this->phpcsFile->addError( + 'Code for favicon found. Favicons are handled by the "Site Icon" setting in the customizer since WP version 4.3.', + $stackPtr, + 'NoFavicon' + ); } } diff --git a/WordPress/Sniffs/Theme/NoTitleTagSniff.php b/WordPress/Sniffs/Theme/NoTitleTagSniff.php index 6f4859cc..de75f84a 100644 --- a/WordPress/Sniffs/Theme/NoTitleTagSniff.php +++ b/WordPress/Sniffs/Theme/NoTitleTagSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Theme; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Restricts the use of the tag, unless it is within a <svg> tag. * @@ -16,7 +21,7 @@ * * @since 0.xx.0 */ -class WordPress_Sniffs_Theme_NoTitleTagSniff extends WordPress_Sniff { +class NoTitleTagSniff extends Sniff { /** * Property to keep track of whether a <svg> open tag has been encountered. @@ -31,7 +36,7 @@ class WordPress_Sniffs_Theme_NoTitleTagSniff extends WordPress_Sniff { * @return array */ public function register() { - $tokens = PHP_CodeSniffer_Tokens::$stringTokens; + $tokens = Tokens::$stringTokens; $tokens[ T_INLINE_HTML ] = T_INLINE_HTML; $tokens[ T_HEREDOC ] = T_HEREDOC; $tokens[ T_NOWDOC ] = T_NOWDOC; @@ -77,7 +82,7 @@ public function process_token( $stackPtr ) { // Skip the next lines until the closing svg tag, but do check any content // on this line before the svg tag. $this->in_svg[ $filename ] = true; - $content = trim( substr( $content, 0, ( strpos( $content, '<svg' ) ) ) ); + $content = trim( substr( $content, 0, ( strpos( $content, '<svg' ) ) ) ); } else { // Ok, we have open and close svg tag on the same line with possibly content before and/or after. $before = trim( substr( $content, 0, ( strpos( $content, '<svg' ) ) ) ); @@ -88,9 +93,12 @@ public function process_token( $stackPtr ) { // Now let's do the check for the <title> tag. if ( false !== strpos( $content, '<title' ) ) { - $this->phpcsFile->addError( "The title tag must not be used. Use add_theme_support( 'title-tag' ) instead.", $stackPtr, 'TagFound' ); + $this->phpcsFile->addError( + "The title tag must not be used. Use add_theme_support( 'title-tag' ) instead.", + $stackPtr, + 'TagFound' + ); } + } - } // End process(). - -} // End Class. +} diff --git a/WordPress/Sniffs/Theme/PluginTerritoryFunctionsSniff.php b/WordPress/Sniffs/Theme/PluginTerritoryFunctionsSniff.php index e48ccaf7..b4a8c8a3 100644 --- a/WordPress/Sniffs/Theme/PluginTerritoryFunctionsSniff.php +++ b/WordPress/Sniffs/Theme/PluginTerritoryFunctionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Theme; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Restricts the use of various functions that are plugin territory. * @@ -16,17 +20,17 @@ * * @since 0.xx.0 */ -class WordPress_Sniffs_Theme_PluginTerritoryFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class PluginTerritoryFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'eval', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array @@ -45,7 +49,6 @@ public function getGroups() { ), ), ); - } -} // End class. +} diff --git a/WordPress/Sniffs/VIP/AdminBarRemovalSniff.php b/WordPress/Sniffs/VIP/AdminBarRemovalSniff.php index 7bcfd4ed..e692ff09 100644 --- a/WordPress/Sniffs/VIP/AdminBarRemovalSniff.php +++ b/WordPress/Sniffs/VIP/AdminBarRemovalSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractFunctionParameterSniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Discourages removal of the admin bar. * @@ -18,8 +23,9 @@ * @since 0.11.0 - Extends the WordPress_AbstractFunctionParameterSniff class. * - Added the $remove_only property. * - Now also sniffs for manipulation of the admin bar visibility through CSS. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_AdminBarRemovalSniff extends WordPress_AbstractFunctionParameterSniff { +class AdminBarRemovalSniff extends AbstractFunctionParameterSniff { /** * A list of tokenizers this sniff supports. @@ -135,18 +141,20 @@ class WordPress_Sniffs_VIP_AdminBarRemovalSniff extends WordPress_AbstractFuncti */ public function register() { // Set up all string targets. - $targets = PHP_CodeSniffer_Tokens::$stringTokens; - $targets[ T_INLINE_HTML ] = T_INLINE_HTML; - $targets[ T_HEREDOC ] = T_HEREDOC; - $targets[ T_NOWDOC ] = T_NOWDOC; + $this->string_tokens = Tokens::$textStringTokens; - $this->string_tokens = $targets; + $targets = $this->string_tokens; // Add CSS style target. $targets[] = T_STYLE; // Set the target selectors regex only once. - $selectors = array_map( 'preg_quote', $this->target_css_selectors, array_fill( 0, count( $this->target_css_selectors ), '`' ) ); + $selectors = array_map( + 'preg_quote', + $this->target_css_selectors, + array_fill( 0, count( $this->target_css_selectors ), '`' ) + ); + // Parse the selectors array into the regex string. $this->target_css_selectors_regex = sprintf( $this->target_css_selectors_regex, implode( '|', $selectors ) ); // Add function call targets. @@ -193,7 +201,7 @@ public function process_token( $stackPtr ) { return parent::process_token( $stackPtr ); } - } // End process(). + } // End process_token(). /** * Process the parameters of a matched function. @@ -359,13 +367,13 @@ protected function process_css_style( $stackPtr ) { $opener = $this->phpcsFile->findPrevious( T_OPEN_CURLY_BRACKET, $stackPtr ); if ( false !== $opener ) { for ( $i = ( $opener - 1 ); $i >= 0; $i-- ) { - if ( isset( PHP_CodeSniffer_Tokens::$commentTokens[ $this->tokens[ $i ]['code'] ] ) + if ( isset( Tokens::$commentTokens[ $this->tokens[ $i ]['code'] ] ) || T_CLOSE_CURLY_BRACKET === $this->tokens[ $i ]['code'] ) { break; } } - $start = ( $i + 1 ); + $start = ( $i + 1 ); $selector = trim( $this->phpcsFile->getTokensAsString( $start, ( $opener - $start ) ) ); unset( $i ); diff --git a/WordPress/Sniffs/VIP/CronIntervalSniff.php b/WordPress/Sniffs/VIP/CronIntervalSniff.php index 0dc7f45f..968243c9 100644 --- a/WordPress/Sniffs/VIP/CronIntervalSniff.php +++ b/WordPress/Sniffs/VIP/CronIntervalSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Flag cron schedules less than 15 minutes. * @@ -17,12 +22,27 @@ * @since 0.3.0 * @since 0.11.0 - Extends the WordPress_Sniff class. * - Now deals correctly with WP time constants. + * @since 0.13.0 Class name changed: this class is now namespaced. + * @since 0.14.0 The minimum cron interval tested against is now configurable. */ -class WordPress_Sniffs_VIP_CronIntervalSniff extends WordPress_Sniff { +class CronIntervalSniff extends Sniff { + + /** + * Minimum allowed cron interval in seconds. + * + * Defaults to 900 (= 15 minutes), which is the requirement for the VIP platform. + * + * @since 0.14.0 + * + * @var int + */ + public $min_interval = 900; /** * Known WP Time constant names and their value. * + * @since 0.11.0 + * * @var array */ protected $wp_time_constants = array( @@ -55,7 +75,7 @@ public function register() { * @return void */ public function process_token( $stackPtr ) { - $token = $this->tokens[ $stackPtr ]; + $token = $this->tokens[ $stackPtr ]; if ( 'cron_schedules' !== $this->strip_quotes( $token['content'] ) ) { return; @@ -63,7 +83,7 @@ public function process_token( $stackPtr ) { // If within add_filter. $functionPtr = $this->phpcsFile->findPrevious( T_STRING, key( $token['nested_parenthesis'] ) ); - if ( 'add_filter' !== $this->tokens[ $functionPtr ]['content'] ) { + if ( false === $functionPtr || 'add_filter' !== $this->tokens[ $functionPtr ]['content'] ) { return; } @@ -73,7 +93,7 @@ public function process_token( $stackPtr ) { } // Detect callback function name. - $callbackArrayPtr = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, $callback['start'], ( $callback['end'] + 1 ), true ); + $callbackArrayPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $callback['start'], ( $callback['end'] + 1 ), true ); // If callback is array, get second element. if ( false !== $callbackArrayPtr @@ -162,12 +182,23 @@ public function process_token( $stackPtr ) { } } - if ( isset( $interval ) && $interval < 900 ) { - $this->phpcsFile->addError( 'Scheduling crons at %s sec ( less than 15 min ) is prohibited.', $stackPtr, 'CronSchedulesInterval', array( $interval ) ); + $this->min_interval = (int) $this->min_interval; + + if ( isset( $interval ) && $interval < $this->min_interval ) { + $minutes = round( ( $this->min_interval / 60 ), 1 ); + $this->phpcsFile->addError( + 'Scheduling crons at %s sec ( less than %s minutes ) is prohibited.', + $stackPtr, + 'CronSchedulesInterval', + array( + $interval, + $minutes, + ) + ); return; } - } // End process(). + } // End process_token(). /** * Add warning about unclear cron schedule change. diff --git a/WordPress/Sniffs/VIP/DirectDatabaseQuerySniff.php b/WordPress/Sniffs/VIP/DirectDatabaseQuerySniff.php index 10d6711f..6458fd45 100644 --- a/WordPress/Sniffs/VIP/DirectDatabaseQuerySniff.php +++ b/WordPress/Sniffs/VIP/DirectDatabaseQuerySniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Flag Database direct queries. * @@ -18,8 +23,9 @@ * @since 0.3.0 * @since 0.6.0 Removed the add_unique_message() function as it is no longer needed. * @since 0.11.0 This class now extends WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_DirectDatabaseQuerySniff extends WordPress_Sniff { +class DirectDatabaseQuerySniff extends Sniff { /** * List of custom cache get functions. @@ -73,14 +79,14 @@ class WordPress_Sniffs_VIP_DirectDatabaseQuerySniff extends WordPress_Sniff { */ protected $methods = array( 'cachable' => array( - 'delete' => true, - 'get_var' => true, - 'get_col' => true, - 'get_row' => true, + 'delete' => true, + 'get_var' => true, + 'get_col' => true, + 'get_row' => true, 'get_results' => true, - 'query' => true, - 'replace' => true, - 'update' => true, + 'query' => true, + 'replace' => true, + 'update' => true, ), 'noncachable' => array( 'insert' => true, @@ -146,8 +152,12 @@ public function process_token( $stackPtr ) { } // Check for Database Schema Changes. - $_pos = $stackPtr; - while ( $_pos = $this->phpcsFile->findNext( array( T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_QUOTED_STRING, T_HEREDOC, T_NOWDOC ), ( $_pos + 1 ), $endOfStatement, false, null, true ) ) { + for ( $_pos = ( $stackPtr + 1 ); $_pos < $endOfStatement; $_pos++ ) { + $_pos = $this->phpcsFile->findNext( Tokens::$textStringTokens, $_pos, $endOfStatement, false, null, true ); + if ( false === $_pos ) { + break; + } + if ( preg_match( '#\b(?:ALTER|CREATE|DROP)\b#i', $this->tokens[ $_pos ]['content'] ) > 0 ) { $this->phpcsFile->addError( 'Attempting a database schema change is highly discouraged.', $_pos, 'SchemaChange' ); } @@ -211,7 +221,7 @@ public function process_token( $stackPtr ) { return $endOfStatement; - } // End process(). + } // End process_token(). /** * Merge custom functions provided via a custom ruleset with the defaults, if we haven't already. @@ -230,6 +240,7 @@ protected function mergeFunctionLists() { $this->customCacheGetFunctions, $this->cacheGetFunctions ); + $this->addedCustomFunctions['cacheget'] = $this->customCacheGetFunctions; } @@ -238,6 +249,7 @@ protected function mergeFunctionLists() { $this->customCacheSetFunctions, $this->cacheSetFunctions ); + $this->addedCustomFunctions['cacheset'] = $this->customCacheSetFunctions; } @@ -246,6 +258,7 @@ protected function mergeFunctionLists() { $this->customCacheDeleteFunctions, $this->cacheDeleteFunctions ); + $this->addedCustomFunctions['cachedelete'] = $this->customCacheDeleteFunctions; } } diff --git a/WordPress/Sniffs/VIP/FileSystemWritesDisallowSniff.php b/WordPress/Sniffs/VIP/FileSystemWritesDisallowSniff.php index 286034a9..104a71a4 100644 --- a/WordPress/Sniffs/VIP/FileSystemWritesDisallowSniff.php +++ b/WordPress/Sniffs/VIP/FileSystemWritesDisallowSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Disallow Filesystem writes. * @@ -17,8 +21,9 @@ * @since 0.3.0 * @since 0.11.0 Extends the WordPress_AbstractFunctionRestrictionsSniff instead of the * Generic_Sniffs_PHP_ForbiddenFunctionsSniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_FileSystemWritesDisallowSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class FileSystemWritesDisallowSniff extends AbstractFunctionRestrictionsSniff { /** * If true, an error will be thrown; otherwise a warning. @@ -31,11 +36,11 @@ class WordPress_Sniffs_VIP_FileSystemWritesDisallowSniff extends WordPress_Abstr * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'eval', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/VIP/OrderByRandSniff.php b/WordPress/Sniffs/VIP/OrderByRandSniff.php index 158a1131..e592a989 100644 --- a/WordPress/Sniffs/VIP/OrderByRandSniff.php +++ b/WordPress/Sniffs/VIP/OrderByRandSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractArrayAssignmentRestrictionsSniff; + /** * Flag using orderby => rand. * @@ -15,8 +19,9 @@ * @package WPCS\WordPressCodingStandards * * @since 0.9.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_OrderByRandSniff extends WordPress_AbstractArrayAssignmentRestrictionsSniff { +class OrderByRandSniff extends AbstractArrayAssignmentRestrictionsSniff { /** * Groups of variables to restrict. diff --git a/WordPress/Sniffs/VIP/PluginMenuSlugSniff.php b/WordPress/Sniffs/VIP/PluginMenuSlugSniff.php index e99b0b20..655d9e79 100644 --- a/WordPress/Sniffs/VIP/PluginMenuSlugSniff.php +++ b/WordPress/Sniffs/VIP/PluginMenuSlugSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractFunctionParameterSniff; + /** * Warn about __FILE__ for page registration. * @@ -16,8 +20,9 @@ * * @since 0.3.0 * @since 0.11.0 Refactored to extend the new WordPress_AbstractFunctionParameterSniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_PluginMenuSlugSniff extends WordPress_AbstractFunctionParameterSniff { +class PluginMenuSlugSniff extends AbstractFunctionParameterSniff { /** * The group name for this group of functions. diff --git a/WordPress/Sniffs/VIP/PostsPerPageSniff.php b/WordPress/Sniffs/VIP/PostsPerPageSniff.php index 6db73490..07692e8d 100644 --- a/WordPress/Sniffs/VIP/PostsPerPageSniff.php +++ b/WordPress/Sniffs/VIP/PostsPerPageSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractArrayAssignmentRestrictionsSniff; + /** * Flag returning high or infinite posts_per_page. * @@ -15,8 +19,21 @@ * @package WPCS\WordPressCodingStandards * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + * @since 0.14.0 Added the posts_per_page property. */ -class WordPress_Sniffs_VIP_PostsPerPageSniff extends WordPress_AbstractArrayAssignmentRestrictionsSniff { +class PostsPerPageSniff extends AbstractArrayAssignmentRestrictionsSniff { + + /** + * Posts per page property + * + * Posts per page limit to check against. + * + * @since 0.14.0 + * + * @var int + */ + public $posts_per_page = 100; /** * Groups of variables to restrict. @@ -48,18 +65,20 @@ public function getGroups() { * with custom error message passed to ->process(). */ public function callback( $key, $val, $line, $group ) { - $key = strtolower( $key ); + $key = strtolower( $key ); + $this->posts_per_page = (int) $this->posts_per_page; + if ( ( 'nopaging' === $key && ( 'true' === $val || 1 === $val ) ) || - ( in_array( $key, array( 'numberposts', 'posts_per_page' ), true ) && '-1' == $val ) + ( in_array( $key, array( 'numberposts', 'posts_per_page' ), true ) && '-1' === $val ) ) { return 'Disabling pagination is prohibited in VIP context, do not set `%s` to `%s` ever.'; } elseif ( in_array( $key, array( 'posts_per_page', 'numberposts' ), true ) ) { - if ( $val > 100 ) { + if ( $val > $this->posts_per_page ) { return 'Detected high pagination limit, `%s` is set to `%s`'; } } diff --git a/WordPress/Sniffs/VIP/RestrictedFunctionsSniff.php b/WordPress/Sniffs/VIP/RestrictedFunctionsSniff.php index 34d2a1a0..53309855 100644 --- a/WordPress/Sniffs/VIP/RestrictedFunctionsSniff.php +++ b/WordPress/Sniffs/VIP/RestrictedFunctionsSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Restricts usage of some functions in VIP context. * @@ -24,18 +28,19 @@ * The check for `parse_url()` and `curl_*` have been moved to the stand-alone sniff * WordPress_Sniffs_WP_AlternativeFunctionsSniff. * The check for `eval()` now defers to the upstream Squiz.PHP.Eval sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_RestrictedFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class RestrictedFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'file_get_contents', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array @@ -58,13 +63,11 @@ public function getGroups() { ), ), - 'get_term_link' => array( + 'wpcom_vip_get_term_link' => array( 'type' => 'error', - 'message' => '%s() is prohibited, please use wpcom_vip_get_term_link() instead.', + 'message' => '%s() is deprecated, please use get_term_link(), get_tag_link(), or get_category_link() instead.', 'functions' => array( - 'get_term_link', - 'get_tag_link', - 'get_category_link', + 'wpcom_vip_get_term_link', ), ), @@ -84,20 +87,19 @@ public function getGroups() { ), ), - 'get_term_by' => array( + 'wpcom_vip_get_term_by' => array( 'type' => 'error', - 'message' => '%s() is prohibited, please use wpcom_vip_get_term_by() instead.', + 'message' => '%s() is deprecated, please use get_term_by() or get_cat_ID() instead.', 'functions' => array( - 'get_term_by', - 'get_cat_ID', + 'wpcom_vip_get_term_by', ), ), - 'get_category_by_slug' => array( + 'wpcom_vip_get_category_by_slug' => array( 'type' => 'error', - 'message' => '%s() is prohibited, please use wpcom_vip_get_category_by_slug() instead.', + 'message' => '%s() is deprecated, please use get_category_by_slug() instead.', 'functions' => array( - 'get_category_by_slug', + 'wpcom_vip_get_category_by_slug', ), ), @@ -168,17 +170,6 @@ public function getGroups() { ), ), - 'wp_get_post_terms' => array( - 'type' => 'error', - 'message' => '%s() is highly discouraged due to not being cached; please use get_the_terms() along with wp_list_pluck() to extract the IDs.', - 'functions' => array( - 'wp_get_post_terms', - 'wp_get_post_categories', - 'wp_get_post_tags', - 'wp_get_object_terms', - ), - ), - 'term_exists' => array( 'type' => 'error', 'message' => '%s() is highly discouraged due to not being cached; please use wpcom_vip_term_exists() instead.', @@ -225,7 +216,7 @@ public function getGroups() { // @link https://vip.wordpress.com/documentation/vip/code-review-what-we-look-for/#use-wp_safe_redirect-instead-of-wp_redirect 'wp_redirect' => array( - 'type' => 'warning', + 'type' => 'warning', 'message' => '%s() found. Using wp_safe_redirect(), along with the allowed_redirect_hosts filter, can help avoid any chances of malicious redirects within code. It is also important to remember to call exit() after a redirect so that no other unwanted code is executed.', 'functions' => array( 'wp_redirect', diff --git a/WordPress/Sniffs/VIP/RestrictedVariablesSniff.php b/WordPress/Sniffs/VIP/RestrictedVariablesSniff.php index 51822b4d..05737ceb 100644 --- a/WordPress/Sniffs/VIP/RestrictedVariablesSniff.php +++ b/WordPress/Sniffs/VIP/RestrictedVariablesSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractVariableRestrictionsSniff; + /** * Restricts usage of some variables in VIP context. * @@ -15,20 +19,21 @@ * @package WPCS\WordPressCodingStandards * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_RestrictedVariablesSniff extends WordPress_AbstractVariableRestrictionsSniff { +class RestrictedVariablesSniff extends AbstractVariableRestrictionsSniff { /** * Groups of variables to restrict. * * Example: groups => array( - * 'wpdb' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'variables' => array( '$val', '$var' ), - * 'object_vars' => array( '$foo->bar', .. ), - * 'array_members' => array( '$foo['bar']', .. ), - * ) + * 'wpdb' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'variables' => array( '$val', '$var' ), + * 'object_vars' => array( '$foo->bar', .. ), + * 'array_members' => array( '$foo['bar']', .. ), + * ) * ) * * @return array @@ -51,7 +56,7 @@ public function getGroups() { 'message' => 'Due to using Batcache, server side based client related logic will not work, use JS instead.', 'variables' => array( '$_COOKIE', - ), + ), 'array_members' => array( '$_SERVER[\'HTTP_USER_AGENT\']', '$_SERVER[\'REMOTE_ADDR\']', diff --git a/WordPress/Sniffs/VIP/SessionFunctionsUsageSniff.php b/WordPress/Sniffs/VIP/SessionFunctionsUsageSniff.php index 9a1b0f21..9ae5c0f1 100644 --- a/WordPress/Sniffs/VIP/SessionFunctionsUsageSniff.php +++ b/WordPress/Sniffs/VIP/SessionFunctionsUsageSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Discourages the use of session functions. * @@ -17,18 +21,19 @@ * @since 0.3.0 * @since 0.11.0 Extends the WordPress_AbstractFunctionRestrictionsSniff instead of the * Generic_Sniffs_PHP_ForbiddenFunctionsSniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_SessionFunctionsUsageSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class SessionFunctionsUsageSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'eval', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/VIP/SessionVariableUsageSniff.php b/WordPress/Sniffs/VIP/SessionVariableUsageSniff.php index 70db76e0..74edbef6 100644 --- a/WordPress/Sniffs/VIP/SessionVariableUsageSniff.php +++ b/WordPress/Sniffs/VIP/SessionVariableUsageSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\Sniff; + /** * Discourages the use of the session variable. * Creating a session writes a file to the server and is unreliable in a multi-server environment. @@ -18,8 +22,10 @@ * @since 0.3.0 * @since 0.10.0 The sniff no longer needlessly extends the Generic_Sniffs_PHP_ForbiddenFunctionsSniff * which it didn't use. + * @since 0.12.0 This class now extends WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_SessionVariableUsageSniff implements PHP_CodeSniffer_Sniff { +class SessionVariableUsageSniff extends Sniff { /** * Returns an array of tokens this test wants to listen for. @@ -36,18 +42,18 @@ public function register() { /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack. + * * @return void */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - - if ( '$_SESSION' === $tokens[ $stackPtr ]['content'] ) { - $phpcsFile->addError( 'Usage of $_SESSION variable is prohibited.', $stackPtr, 'SessionVarsProhibited' ); + public function process_token( $stackPtr ) { + if ( '$_SESSION' === $this->tokens[ $stackPtr ]['content'] ) { + $this->phpcsFile->addError( + 'Usage of $_SESSION variable is prohibited.', + $stackPtr, + 'SessionVarsProhibited' + ); } - } } // End class. diff --git a/WordPress/Sniffs/VIP/SlowDBQuerySniff.php b/WordPress/Sniffs/VIP/SlowDBQuerySniff.php index 5cb08240..d3b022fe 100644 --- a/WordPress/Sniffs/VIP/SlowDBQuerySniff.php +++ b/WordPress/Sniffs/VIP/SlowDBQuerySniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractArrayAssignmentRestrictionsSniff; + /** * Flag potentially slow queries. * @@ -15,8 +19,12 @@ * @package WPCS\WordPressCodingStandards * * @since 0.3.0 + * @since 0.12.0 Introduced new and more intuitively named 'slow query' whitelist + * comment, replacing the 'tax_query' whitelist comment which is now + * deprecated. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_SlowDBQuerySniff extends WordPress_AbstractArrayAssignmentRestrictionsSniff { +class SlowDBQuerySniff extends AbstractArrayAssignmentRestrictionsSniff { /** * Groups of variables to restrict. @@ -50,7 +58,23 @@ public function getGroups() { */ public function process_token( $stackPtr ) { + if ( $this->has_whitelist_comment( 'slow query', $stackPtr ) ) { + return; + } + if ( $this->has_whitelist_comment( 'tax_query', $stackPtr ) ) { + /* + * Only throw the warning about a deprecated comment when the sniff would otherwise + * have been triggered on the array key. + */ + if ( in_array( $this->tokens[ $stackPtr ]['code'], array( T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_QUOTED_STRING ), true ) ) { + $this->phpcsFile->addWarning( + 'The "tax_query" whitelist comment is deprecated in favor of the "slow query" whitelist comment.', + $stackPtr, + 'DeprecatedWhitelistFlagFound' + ); + } + return; } diff --git a/WordPress/Sniffs/VIP/SuperGlobalInputUsageSniff.php b/WordPress/Sniffs/VIP/SuperGlobalInputUsageSniff.php index 5c492065..c3b77372 100644 --- a/WordPress/Sniffs/VIP/SuperGlobalInputUsageSniff.php +++ b/WordPress/Sniffs/VIP/SuperGlobalInputUsageSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\Sniff; + /** * Flag any usage of super global input var ( _GET / _POST / etc. ). * @@ -15,9 +19,10 @@ * @package WPCS\WordPressCodingStandards * * @since 0.3.0 - * @since 0.4.0 This class now extends WordPress_Sniff. + * @since 0.4.0 This class now extends WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_SuperGlobalInputUsageSniff extends WordPress_Sniff { +class SuperGlobalInputUsageSniff extends Sniff { /** * Returns an array of tokens this test wants to listen for. diff --git a/WordPress/Sniffs/VIP/TimezoneChangeSniff.php b/WordPress/Sniffs/VIP/TimezoneChangeSniff.php index b768dd50..5740f9eb 100644 --- a/WordPress/Sniffs/VIP/TimezoneChangeSniff.php +++ b/WordPress/Sniffs/VIP/TimezoneChangeSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Disallow the changing of timezone. * @@ -17,18 +21,19 @@ * @since 0.3.0 * @since 0.11.0 Extends the WordPress_AbstractFunctionRestrictionsSniff instead of the * Generic_Sniffs_PHP_ForbiddenFunctionsSniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_TimezoneChangeSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class TimezoneChangeSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'eval', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/VIP/ValidatedSanitizedInputSniff.php b/WordPress/Sniffs/VIP/ValidatedSanitizedInputSniff.php index 72cc1d37..d51744ed 100644 --- a/WordPress/Sniffs/VIP/ValidatedSanitizedInputSniff.php +++ b/WordPress/Sniffs/VIP/ValidatedSanitizedInputSniff.php @@ -7,6 +7,10 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\VIP; + +use WordPress\Sniff; + /** * Flag any non-validated/sanitized input ( _GET / _POST / etc. ). * @@ -15,10 +19,11 @@ * @package WPCS\WordPressCodingStandards * * @since 0.3.0 - * @since 0.4.0 This class now extends WordPress_Sniff. - * @since 0.5.0 Method getArrayIndexKey() has been moved to WordPress_Sniff. + * @since 0.4.0 This class now extends WordPress_Sniff. + * @since 0.5.0 Method getArrayIndexKey() has been moved to WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_VIP_ValidatedSanitizedInputSniff extends WordPress_Sniff { +class ValidatedSanitizedInputSniff extends Sniff { /** * Check for validation functions for a variable within its own parenthesis only. @@ -90,7 +95,9 @@ public function process_token( $stackPtr ) { || T_HEREDOC === $this->tokens[ $stackPtr ]['code'] ) { $interpolated_variables = array_map( - create_function( '$symbol', 'return "$" . $symbol;' ), // Replace with closure when 5.3 is minimum requirement for PHPCS. + function ( $symbol ) { + return '$' . $symbol; + }, $this->get_interpolated_variables( $this->tokens[ $stackPtr ]['content'] ) ); foreach ( array_intersect( $interpolated_variables, $superglobals ) as $bad_variable ) { @@ -145,7 +152,7 @@ public function process_token( $stackPtr ) { $this->phpcsFile->addError( 'Detected usage of a non-sanitized input variable: %s', $stackPtr, 'InputNotSanitized', $error_data ); } - } // End process(). + } // End process_token(). /** * Merge custom functions provided via a custom ruleset with the defaults, if we haven't already. @@ -160,6 +167,7 @@ protected function mergeFunctionLists() { $this->customSanitizingFunctions, $this->sanitizingFunctions ); + $this->addedCustomFunctions['sanitize'] = $this->customSanitizingFunctions; } @@ -168,6 +176,7 @@ protected function mergeFunctionLists() { $this->customUnslashingSanitizingFunctions, $this->unslashingSanitizingFunctions ); + $this->addedCustomFunctions['unslashsanitize'] = $this->customUnslashingSanitizingFunctions; } } diff --git a/WordPress/Sniffs/Variables/GlobalVariablesSniff.php b/WordPress/Sniffs/Variables/GlobalVariablesSniff.php index 179e00f1..9ec1db80 100644 --- a/WordPress/Sniffs/Variables/GlobalVariablesSniff.php +++ b/WordPress/Sniffs/Variables/GlobalVariablesSniff.php @@ -7,266 +7,24 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Variables; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** - * WordPress_Sniffs_Variables_GlobalVariablesSniff. - * * Warns about overwriting WordPress native global variables. * * @package WPCS\WordPressCodingStandards * * @since 0.3.0 - * @since 0.4.0 This class now extends WordPress_Sniff. + * @since 0.4.0 This class now extends WordPress_Sniff. + * @since 0.12.0 The $wp_globals property has been moved to the WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. + * + * @uses \WordPress\Sniff::$custom_test_class_whitelist */ -class WordPress_Sniffs_Variables_GlobalVariablesSniff extends WordPress_Sniff { - - /** - * List of global WP variables. - * - * @since 0.3.0 - * @since 0.11.0 Changed visibility from public to protected. - * - * @var array - */ - protected $globals = array( - 'comment', - 'comment_alt', - 'comment_depth', - 'comment_thread_alt', - 'wp_rewrite', - 'in_comment_loop', - 'wp_query', - 'withcomments', - 'post', - 'wpdb', - 'id', - 'user_login', - 'user_ID', - 'user_identity', - 'overridden_cpage', - 'wpcommentspopupfile', - 'wpcommentsjavascript', - 'shortcode_tags', - 'wp_version', - 'wp_scripts', - 'comments', - 'is_IE', - '_wp_registered_nav_menus', - '_menu_item_sort_prop', - 'wp_roles', - 'wp_object_cache', - 'currentcat', - 'previouscat', - 'blog_id', - 'is_macIE', - 'is_winIE', - 'plugin_page', - 'wp_themes', - 'wp_rich_edit_exists', - 'allowedposttags', - 'allowedtags', - 'allowedentitynames', - 'pass_allowed_html', - 'pass_allowed_protocols', - 'wp_post_statuses', - 'wp_post_types', - 'wp', - '_wp_post_type_features', - '_wp_suspend_cache_invalidation', - 'wp_theme_directories', - 'wp_locale', - 'locale', - 'l10n', - '_wp_additional_image_sizes', - 'wp_embed', - 'wp_taxonomies', - 'sidebars_widgets', - 'wp_registered_widgets', - 'wp_registered_widget_controls', - 'wp_registered_sidebars', - 'wp_registered_widget_updates', - '_wp_admin_css_colors', - 'concatenate_scripts', - 'compress_scripts', - 'wp_styles', - 'compress_css', - 'wp_the_query', - '_updated_user_settings', - 'wp_filter', - 'wp_actions', - 'merged_filters', - 'wp_current_filter', - 'wp_plugin_paths', - 'GETID3_ERRORARRAY', - 'current_user', - 'phpmailer', - 'is_IIS', - 'wp_hasher', - 'rnd_value', - 'auth_secure_cookie', - 'userdata', - 'user_level', - 'user_email', - 'user_url', - 'wp_customize', - 'wp_widget_factory', - '_wp_deprecated_widgets_callbacks', - '_wp_sidebars_widgets', - 'error', - 'wp_cockneyreplace', - 'wpsmiliestrans', - 'wp_smiliessearch', - '_links_add_base', - '_links_add_target', - 'tinymce_version', - 'PHP_SELF', - 'required_php_version', - 'upgrading', - 'timestart', - 'timeend', - 'table_prefix', - '_wp_using_ext_object_cache', - 'text_direction', - 'custom_image_header', - 'post_default_title', - 'post_default_category', - 'currentday', - 'previousday', - 'wp_header_to_desc', - 'wp_xmlrpc_server', - 'submenu', - 'is_apache', - 'is_iis7', - 'current_site', - 'domain', - 'm', - 'monthnum', - 'year', - 'posts', - 'previousweekday', - 'wp_rich_edit', - 'is_gecko', - 'is_opera', - 'is_safari', - 'is_chrome', - 'wp_local_package', - 'wp_user_roles', - 'super_admins', - '_wp_default_headers', - 'editor_styles', - '_wp_theme_features', - 'custom_background', - 'wp_did_header', - 'wp_admin_bar', - 'tag', - 'show_admin_bar', - 'pagenow', - 'HTTP_RAW_POST_DATA', - 'path', - 'wp_json', - 'page', - 'more', - 'preview', - 'pages', - 'multipage', - 'numpages', - 'paged', - 'authordata', - 'currentmonth', - 'EZSQL_ERROR', - 'required_mysql_version', - 'wp_db_version', - 'opml', - 'map', - 'updated_timestamp', - 'all_links', - 'names', - 'urls', - 'targets', - 'descriptions', - 'feeds', - 'wp_filesystem', - 'menu_order', - 'default_menu_order', - '_wp_nav_menu_max_depth', - '_nav_menu_placeholder', - 'wp_meta_boxes', - 'one_theme_location_no_menus', - 'nav_menu_selected_id', - 'post_ID', - 'link_id', - 'action', - 'link', - 'tabs', - 'tab', - 'type', - 'term', - 'redir_tab', - 'post_mime_types', - 'menu', - 'admin_page_hooks', - '_registered_pages', - '_parent_pages', - '_wp_last_object_menu', - '_wp_last_utility_menu', - '_wp_real_parent_file', - '_wp_submenu_nopriv', - 'parent_file', - 'typenow', - '_wp_menu_nopriv', - 'title', - 'new_whitelist_options', - 'whitelist_options', - 'wp_list_table', - 's', - 'mode', - 'post_type_object', - 'avail_post_stati', - 'per_page', - 'locked_post_status', - 'cat', - 'lost', - 'avail_post_mime_types', - '$var', - 'errors', - 'cat_id', - 'orderby', - 'order', - 'post_type', - 'taxonomy', - 'tax', - 'wp_queries', - 'charset_collate', - 'wp_current_db_version', - 'wp_importers', - 'wp_file_descriptions', - 'theme_field_defaults', - 'themes_allowedtags', - 'post_id', - 'comment_status', - 'search', - 'comment_type', - 'wp_settings_sections', - 'wp_settings_fields', - 'wp_settings_errors', - 'hook_suffix', - 'admin_body_class', - 'current_screen', - 'taxnow', - 'status', - 'totals', - '_old_files', - '_new_bundled_files', - 'usersearch', - 'role', - 'wp_dashboard_control_callbacks', - 'plugins', - 'self', - 'submenu_file', - 'blogname', - 'blog_title', - 'active_signup', - 'interim_login', - ); +class GlobalVariablesSniff extends Sniff { /** * Returns an array of tokens this test wants to listen for. @@ -291,7 +49,7 @@ public function process_token( $stackPtr ) { $token = $this->tokens[ $stackPtr ]; if ( T_VARIABLE === $token['code'] && '$GLOBALS' === $token['content'] ) { - $bracketPtr = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + $bracketPtr = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); if ( false === $bracketPtr || T_OPEN_SQUARE_BRACKET !== $this->tokens[ $bracketPtr ]['code'] || ! isset( $this->tokens[ $bracketPtr ]['bracket_closer'] ) ) { return; @@ -312,7 +70,7 @@ public function process_token( $stackPtr ) { } } - if ( ! in_array( $var_name, $this->globals, true ) ) { + if ( ! isset( $this->wp_globals[ $var_name ] ) ) { return; } @@ -335,7 +93,7 @@ public function process_token( $stackPtr ) { } if ( T_VARIABLE === $var['code'] ) { - if ( in_array( substr( $var['content'], 1 ), $this->globals, true ) ) { + if ( isset( $this->wp_globals[ substr( $var['content'], 1 ) ] ) ) { $search[] = $var['content']; } } @@ -388,7 +146,7 @@ public function process_token( $stackPtr ) { && in_array( $this->tokens[ $ptr ]['content'], $search, true ) ) { // Don't throw false positives for static class properties. - $previous = $this->phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$emptyTokens, ( $ptr - 1 ), null, true, null, true ); + $previous = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $ptr - 1 ), null, true, null, true ); if ( false !== $previous && T_DOUBLE_COLON === $this->tokens[ $previous ]['code'] ) { continue; } @@ -398,9 +156,9 @@ public function process_token( $stackPtr ) { } } } - } // End if(). + } - } // End process(). + } // End process_token(). /** * Add the error if there is no whitelist comment present and the assignment diff --git a/WordPress/Sniffs/Variables/VariableRestrictionsSniff.php b/WordPress/Sniffs/Variables/VariableRestrictionsSniff.php index 7e6b9626..9b85e725 100644 --- a/WordPress/Sniffs/Variables/VariableRestrictionsSniff.php +++ b/WordPress/Sniffs/Variables/VariableRestrictionsSniff.php @@ -7,39 +7,45 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\Variables; + +use WordPress\AbstractVariableRestrictionsSniff; + /** * Restricts usage of some variables. * * @package WPCS\WordPressCodingStandards * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + * * @deprecated 0.10.0 The functionality which used to be contained in this class has been moved to * the WordPress_AbstractVariableRestrictionsSniff class. * This class is left here to prevent backward-compatibility breaks for * custom sniffs extending the old class and references to this * sniff from custom phpcs.xml files. * This file is also still used to unit test the abstract class. - * @see WordPress_AbstractVariableRestrictionsSniff + * @see \WordPress\AbstractVariableRestrictionsSniff */ -class WordPress_Sniffs_Variables_VariableRestrictionsSniff extends WordPress_AbstractVariableRestrictionsSniff { +class VariableRestrictionsSniff extends AbstractVariableRestrictionsSniff { /** * Groups of variables to restrict. * * Example: groups => array( - * 'wpdb' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'variables' => array( '$val', '$var' ), - * 'object_vars' => array( '$foo->bar', .. ), - * 'array_members' => array( '$foo['bar']', .. ), - * ) + * 'wpdb' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'variables' => array( '$val', '$var' ), + * 'object_vars' => array( '$foo->bar', .. ), + * 'array_members' => array( '$foo['bar']', .. ), + * ) * ) * * @return array */ public function getGroups() { - return parent::$groups; + return array(); } } // End class. diff --git a/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php b/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php index 789d2b7d..e96d41a5 100644 --- a/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php +++ b/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php @@ -7,24 +7,29 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Discourages the use of various functions and suggests (WordPress) alternatives. * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_WP_AlternativeFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class AlternativeFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'file_get_contents', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/WP/CapitalPDangitSniff.php b/WordPress/Sniffs/WP/CapitalPDangitSniff.php new file mode 100644 index 00000000..ca42817f --- /dev/null +++ b/WordPress/Sniffs/WP/CapitalPDangitSniff.php @@ -0,0 +1,288 @@ +<?php +/** + * WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Sniffs\WP; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + +/** + * Capital P Dangit! + * + * Verify the correct spelling of `WordPress` in text strings, comments and class names. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class CapitalPDangitSniff extends Sniff { + + /** + * Regex to match a large number or spelling variations of WordPress in text strings. + * + * Prevents matches on: + * - URLs for wordpress.org/com/net/tv. + * - `@...` usernames starting with `wordpress` + * - email addresses with a domain starting with `wordpress` + * - email addresses with a user name ending with `wordpress` + * - (most) variable names. + * - directory paths containing a folder starting or ending with `wordpress`. + * - file names containing `wordpress` for a limited set of extensions. + * - `wordpress` prefixed or suffixed with dashes as those are indicators that the + * term is probably used as part of a CSS class, such as `fa-wordpress` + * or filename/path like `class-wordpress-importer.php`. + * - back-tick quoted `wordpress`. + * + * @var string + */ + const WP_REGEX = '#(?<![\\\\/\$@`-])\b(Word[ _-]*Pres+)\b(?![@/`-]|\.(?:org|com|net|tv)|[^\s<>\'"()]*?\.(?:php|js|css|png|j[e]?pg|gif|pot))#i'; + + /** + * Regex to match a large number or spelling variations of WordPress in class names. + * + * @var string + */ + const WP_CLASSNAME_REGEX = '`(?:^|_)(Word[_]*Pres+)(?:_|$)`i'; + + /** + * String tokens we want to listen for. + * + * @var array + */ + private $text_string_tokens = array( + T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, + T_DOUBLE_QUOTED_STRING => T_DOUBLE_QUOTED_STRING, + T_HEREDOC => T_HEREDOC, + T_NOWDOC => T_NOWDOC, + T_INLINE_HTML => T_INLINE_HTML, + ); + + /** + * Comment tokens we want to listen for as they contain text strings. + * + * @var array + */ + private $comment_text_tokens = array( + T_DOC_COMMENT => T_DOC_COMMENT, + T_DOC_COMMENT_STRING => T_DOC_COMMENT_STRING, + T_COMMENT => T_COMMENT, + ); + + /** + * Class-like structure tokens to listen for. + * + * Using proper spelling in class, interface and trait names does not conflict with the naming conventions. + * + * @var array + */ + private $class_tokens = array( + T_CLASS => T_CLASS, + T_INTERFACE => T_INTERFACE, + T_TRAIT => T_TRAIT, + ); + + /** + * Combined text string and comment tokens array. + * + * This property is set in the register() method and used for lookups. + * + * @var array + */ + private $text_and_comment_tokens = array(); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.12.0 + * + * @return array + */ + public function register() { + // Union the arrays - keeps the array keys. + $this->text_and_comment_tokens = ( $this->text_string_tokens + $this->comment_text_tokens ); + + $targets = ( $this->text_and_comment_tokens + $this->class_tokens ); + + // Also sniff for array tokens to make skipping anything within those more efficient. + $targets[ T_ARRAY ] = T_ARRAY; + $targets[ T_OPEN_SHORT_ARRAY ] = T_OPEN_SHORT_ARRAY; + + return $targets; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.12.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + + if ( $this->has_whitelist_comment( 'spelling', $stackPtr ) ) { + return; + } + + /* + * Ignore tokens within an array definition as this is a false positive in 80% of all cases. + * + * The return values skip to the end of the array. + * This prevents the sniff "hanging" on very long configuration arrays. + */ + if ( T_OPEN_SHORT_ARRAY === $this->tokens[ $stackPtr ]['code'] && isset( $this->tokens[ $stackPtr ]['bracket_closer'] ) ) { + return $this->tokens[ $stackPtr ]['bracket_closer']; + } elseif ( T_ARRAY === $this->tokens[ $stackPtr ]['code'] && isset( $this->tokens[ $stackPtr ]['parenthesis_closer'] ) ) { + return $this->tokens[ $stackPtr ]['parenthesis_closer']; + } + + /* + * Deal with misspellings in class/interface/trait names. + * These are not auto-fixable, but need the attention of a developer. + */ + if ( isset( $this->class_tokens[ $this->tokens[ $stackPtr ]['code'] ] ) ) { + $classname = $this->phpcsFile->getDeclarationName( $stackPtr ); + if ( empty( $classname ) ) { + return; + } + + if ( preg_match_all( self::WP_CLASSNAME_REGEX, $classname, $matches, PREG_PATTERN_ORDER ) > 0 ) { + $mispelled = $this->retrieve_misspellings( $matches[1] ); + + if ( ! empty( $mispelled ) ) { + $this->phpcsFile->addWarning( + 'Please spell "WordPress" correctly. Found: "%s" as part of the class/interface/trait name.', + $stackPtr, + 'MisspelledClassName', + array( implode( ', ', $mispelled ) ) + ); + } + } + + return; + } + + /* + * Deal with misspellings in text strings and documentation. + */ + + // Ignore content of docblock @link tags. + if ( T_DOC_COMMENT_STRING === $this->tokens[ $stackPtr ]['code'] + || T_DOC_COMMENT === $this->tokens[ $stackPtr ]['code'] + ) { + + $comment_start = $this->phpcsFile->findPrevious( T_DOC_COMMENT_OPEN_TAG, ( $stackPtr - 1 ) ); + if ( false !== $comment_start ) { + $comment_tag = $this->phpcsFile->findPrevious( T_DOC_COMMENT_TAG, ( $stackPtr - 1 ), $comment_start ); + if ( false !== $comment_tag && '@link' === $this->tokens[ $comment_tag ]['content'] ) { + // @link tag, so ignore. + return; + } + } + } + + // Ignore any text strings which are array keys `$var['key']` as this is a false positive in 80% of all cases. + if ( T_CONSTANT_ENCAPSED_STRING === $this->tokens[ $stackPtr ]['code'] ) { + $prevToken = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true, null, true ); + if ( false !== $prevToken && T_OPEN_SQUARE_BRACKET === $this->tokens[ $prevToken ]['code'] ) { + $nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true, null, true ); + if ( false !== $nextToken && T_CLOSE_SQUARE_BRACKET === $this->tokens[ $nextToken ]['code'] ) { + return; + } + } + } + + $content = $this->tokens[ $stackPtr ]['content']; + + if ( preg_match_all( self::WP_REGEX, $content, $matches, ( PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE ) ) > 0 ) { + /* + * Prevent some typical false positives. + */ + if ( isset( $this->text_and_comment_tokens[ $this->tokens[ $stackPtr ]['code'] ] ) ) { + $offset = 0; + foreach ( $matches[1] as $key => $match_data ) { + $next_offset = ( $match_data[1] + strlen( $match_data[0] ) ); + + // Prevent matches on part of a URL. + if ( preg_match( '`http[s]?://[^\s<>\'"()]*' . preg_quote( $match_data[0], '`' ) . '`', $content, $discard, 0, $offset ) === 1 ) { + unset( $matches[1][ $key ] ); + } elseif ( preg_match( '`[a-z]+=(["\'])' . preg_quote( $match_data[0], '`' ) . '\1`', $content, $discard, 0, $offset ) === 1 ) { + // Prevent matches on html attributes like: `value="wordpress"`. + unset( $matches[1][ $key ] ); + } elseif ( preg_match( '`\\\\\'' . preg_quote( $match_data[0], '`' ) . '\\\\\'`', $content, $discard, 0, $offset ) === 1 ) { + // Prevent matches on xpath queries and such: `\'wordpress\'`. + unset( $matches[1][ $key ] ); + } elseif ( preg_match( '`(?:\?|&|&)[a-z0-9_]+=' . preg_quote( $match_data[0], '`' ) . '(?:&|$)`', $content, $discard, 0, $offset ) === 1 ) { + // Prevent matches on url query strings: `?something=wordpress`. + unset( $matches[1][ $key ] ); + } + + $offset = $next_offset; + } + + if ( empty( $matches[1] ) ) { + return; + } + } + + $mispelled = $this->retrieve_misspellings( $matches[1] ); + + if ( empty( $mispelled ) ) { + return; + } + + $fix = $this->phpcsFile->addFixableWarning( + 'Please spell "WordPress" correctly. Found %s misspelling(s): %s', + $stackPtr, + 'Misspelled', + array( + count( $mispelled ), + implode( ', ', $mispelled ), + ) + ); + + if ( true === $fix ) { + // Apply fixes based on offset to ensure we don't replace false positives. + $replacement = $content; + foreach ( $matches[1] as $match ) { + $replacement = substr_replace( $replacement, 'WordPress', $match[1], strlen( $match[0] ) ); + } + + $this->phpcsFile->fixer->replaceToken( $stackPtr, $replacement ); + } + } + + } // End process_token(). + + /** + * Retrieve a list of misspellings based on an array of matched variations on the target word. + * + * @param array $match_stack Array of matched variations of the target word. + * @return array Array containing only the misspelled variants. + */ + protected function retrieve_misspellings( $match_stack ) { + $mispelled = array(); + foreach ( $match_stack as $match ) { + // Deal with multi-dimensional arrays when capturing offset. + if ( is_array( $match ) ) { + $match = $match[0]; + } + + if ( 'WordPress' !== $match ) { + $mispelled[] = $match; + } + } + + return $mispelled; + } + +} // End class. diff --git a/WordPress/Sniffs/WP/DeprecatedClassesSniff.php b/WordPress/Sniffs/WP/DeprecatedClassesSniff.php new file mode 100644 index 00000000..1e46dcf3 --- /dev/null +++ b/WordPress/Sniffs/WP/DeprecatedClassesSniff.php @@ -0,0 +1,110 @@ +<?php +/** + * WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Sniffs\WP; + +use WordPress\AbstractClassRestrictionsSniff; + +/** + * Restricts the use of deprecated WordPress classes and suggests alternatives. + * + * This sniff will throw an error when usage of a deprecated class is detected + * if the class was deprecated before the minimum supported WP version; + * a warning otherwise. + * By default, it is set to presume that a project will support the current + * WP version and up to three releases before. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + * @since 0.14.0 Now has the ability to handle minimum supported WP version + * being provided via the command-line or as as <config> value + * in a custom ruleset. + * + * @uses \WordPress\Sniff::$minimum_supported_version + */ +class DeprecatedClassesSniff extends AbstractClassRestrictionsSniff { + + /** + * List of deprecated classes with alternative when available. + * + * To be updated after every major release. + * + * Version numbers should be fully qualified. + * + * @var array + */ + private $deprecated_classes = array( + + // WP 3.1.0. + 'WP_User_Search' => array( + 'alt' => 'WP_User_Query', + 'version' => '3.1.0', + ), + ); + + + /** + * Groups of classes to restrict. + * + * @return array + */ + public function getGroups() { + // Make sure all array keys are lowercase. + $keys = array_keys( $this->deprecated_classes ); + $keys = array_map( 'strtolower', $keys ); + $this->deprecated_classes = array_combine( $keys, $this->deprecated_classes ); + + return array( + 'deprecated_classes' => array( + 'classes' => $keys, + ), + ); + + } // End getGroups(). + + /** + * 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. Will + * always be 'deprecated_functions'. + * @param string $matched_content The token content (class name) which was matched. + * + * @return void + */ + public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + + $this->get_wp_version_from_cl(); + + $class_name = ltrim( strtolower( $matched_content ), '\\' ); + + $message = 'The %s class has been deprecated since WordPress version %s.'; + $data = array( + ltrim( $matched_content, '\\' ), + $this->deprecated_classes[ $class_name ]['version'], + ); + + if ( ! empty( $this->deprecated_classes[ $class_name ]['alt'] ) ) { + $message .= ' Use %s instead.'; + $data[] = $this->deprecated_classes[ $class_name ]['alt']; + } + + $this->addMessage( + $message, + $stackPtr, + ( version_compare( $this->deprecated_classes[ $class_name ]['version'], $this->minimum_supported_version, '<' ) ), + $this->string_to_errorcode( $matched_content . 'Found' ), + $data + ); + + } // End process_matched_token(). + +} // End class. diff --git a/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php b/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php index cacf08e0..23c1d1d0 100644 --- a/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php +++ b/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php @@ -7,1087 +7,1297 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Restricts the use of various deprecated WordPress functions and suggests alternatives. * + * This sniff will throw an error when usage of deprecated functions is detected + * if the function was deprecated before the minimum supported WP version; + * a warning otherwise. + * By default, it is set to presume that a project will support the current + * WP version and up to three releases before. + * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + * @since 0.14.0 Now has the ability to handle minimum supported WP version + * being provided via the command-line or as as <config> value + * in a custom ruleset. + * + * @uses \WordPress\Sniff::$minimum_supported_version */ -class WordPress_Sniffs_WP_DeprecatedFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { - - /** - * Minimum WordPress version. - * - * This sniff will throw an error when usage of deprecated functions is - * detected if the function was deprecated before the minimum supported - * WP version; a warning otherwise. - * By default, it is set to presume that a project will support the current - * WP version and up to three releases before. - * This variable allows changing the minimum supported WP version used by - * this sniff by setting a property in a custom phpcs.xml ruleset. - * - * Example usage: - * <rule ref="WordPress.WP.WP_DeprecatedFunctions"> - * <properties> - * <property name="minimum_supported_version" value="4.3"/> - * </properties> - * </rule> - * - * @var string WordPress versions. - */ - public $minimum_supported_version = '4.4'; +class DeprecatedFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * List of deprecated functions with alternative when available. * * To be updated after every major release. - * Last updated for WordPress 4.7. + * Last updated for WordPress 4.8. + * + * Version numbers should be fully qualified. + * Replacement functions should have parentheses. + * + * To retrieve a function list for comparison, the following tool is available: + * https://github.com/JDGrimes/wp-deprecated-code-scanner * * @var array */ private $deprecated_functions = array( - 'the_category_id' => array( - 'alt' => 'get_the_category()', - 'version' => '0.71', - ), + + // WP 0.71. 'the_category_head' => array( 'alt' => 'get_the_category_by_ID()', 'version' => '0.71', ), + 'the_category_ID' => array( + 'alt' => 'get_the_category()', + 'version' => '0.71', + ), + // WP 1.2.0. 'permalink_link' => array( 'alt' => 'the_permalink()', - 'version' => '1.2', + 'version' => '1.2.0', ), + // WP 1.5.0. 'start_wp' => array( + // Verified correct alternative. 'alt' => 'the Loop', - 'version' => '1.5', + 'version' => '1.5.0', ), + + // WP 1.5.1. 'get_postdata' => array( 'alt' => 'get_post()', 'version' => '1.5.1', ), - 'previous_post' => array( - 'alt' => 'previous_post_link()', - 'version' => '2.0', + // WP 2.0.0. + 'create_user' => array( + 'alt' => 'wp_create_user()', + 'version' => '2.0.0', ), 'next_post' => array( 'alt' => 'next_post_link()', - 'version' => '2.0', + 'version' => '2.0.0', ), - 'user_can_create_post' => array( - 'alt' => 'current_user_can()', - 'version' => '2.0', + 'previous_post' => array( + 'alt' => 'previous_post_link()', + 'version' => '2.0.0', ), 'user_can_create_draft' => array( 'alt' => 'current_user_can()', - 'version' => '2.0', + 'version' => '2.0.0', ), - 'user_can_edit_post' => array( + 'user_can_create_post' => array( 'alt' => 'current_user_can()', - 'version' => '2.0', + 'version' => '2.0.0', ), 'user_can_delete_post' => array( 'alt' => 'current_user_can()', - 'version' => '2.0', + 'version' => '2.0.0', ), - 'user_can_set_post_date' => array( + 'user_can_delete_post_comments' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', + ), + 'user_can_edit_post' => array( 'alt' => 'current_user_can()', - 'version' => '2.0', + 'version' => '2.0.0', ), 'user_can_edit_post_comments' => array( 'alt' => 'current_user_can()', - 'version' => '2.0', + 'version' => '2.0.0', ), - 'user_can_delete_post_comments' => array( + 'user_can_edit_post_date' => array( 'alt' => 'current_user_can()', - 'version' => '2.0', + 'version' => '2.0.0', ), 'user_can_edit_user' => array( 'alt' => 'current_user_can()', - 'version' => '2.0', + 'version' => '2.0.0', ), - 'create_user' => array( - 'alt' => 'wp_create_user()', - 'version' => '2.0', + 'user_can_set_post_date' => array( + 'alt' => 'current_user_can()', + 'version' => '2.0.0', ), - 'get_linksbyname' => array( - 'alt' => 'get_bookmarks()', - 'version' => '2.1', + // WP 2.1.0. + 'dropdown_cats' => array( + 'alt' => 'wp_dropdown_categories()', + 'version' => '2.1.0', ), - 'wp_get_linksbyname' => array( - 'alt' => 'wp_list_bookmarks()', - 'version' => '2.1', + 'get_archives' => array( + 'alt' => 'wp_get_archives()', + 'version' => '2.1.0', ), - 'get_linkobjectsbyname' => array( - 'alt' => 'get_bookmarks()', - 'version' => '2.1', + 'get_author_link' => array( + 'alt' => 'get_author_posts_url()', + 'version' => '2.1.0', + ), + 'get_autotoggle' => array( + 'alt' => '', + 'version' => '2.1.0', + ), + 'get_link' => array( + 'alt' => 'get_bookmark()', + 'version' => '2.1.0', + ), + 'get_linkcatname' => array( + 'alt' => 'get_category()', + 'version' => '2.1.0', ), 'get_linkobjects' => array( 'alt' => 'get_bookmarks()', - 'version' => '2.1', + 'version' => '2.1.0', ), - 'get_linksbyname_withrating' => array( + 'get_linkobjectsbyname' => array( 'alt' => 'get_bookmarks()', - 'version' => '2.1', + 'version' => '2.1.0', ), - 'get_links_withrating' => array( - 'alt' => 'get_bookmarks()', - 'version' => '2.1', + 'get_linkrating' => array( + 'alt' => 'sanitize_bookmark_field()', + 'version' => '2.1.0', ), - 'get_autotoggle' => array( - 'alt' => '', - 'version' => '2.1', + 'get_links' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', ), - 'list_cats' => array( - 'alt' => 'wp_list_categories', - 'version' => '2.1', + 'get_links_list' => array( + 'alt' => 'wp_list_bookmarks()', + 'version' => '2.1.0', ), - 'wp_list_cats' => array( - 'alt' => 'wp_list_categories', - 'version' => '2.1', + 'get_links_withrating' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', ), - 'dropdown_cats' => array( - 'alt' => 'wp_dropdown_categories()', - 'version' => '2.1', + 'get_linksbyname' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', ), - 'list_authors' => array( - 'alt' => 'wp_list_authors()', - 'version' => '2.1', + 'get_linksbyname_withrating' => array( + 'alt' => 'get_bookmarks()', + 'version' => '2.1.0', ), - 'wp_get_post_cats' => array( - 'alt' => 'wp_get_post_categories()', - 'version' => '2.1', + 'get_settings' => array( + 'alt' => 'get_option()', + 'version' => '2.1.0', ), - 'wp_set_post_cats' => array( - 'alt' => 'wp_set_post_categories()', - 'version' => '2.1', + 'link_pages' => array( + 'alt' => 'wp_link_pages()', + 'version' => '2.1.0', ), - 'get_archives' => array( - 'alt' => 'wp_get_archives', - 'version' => '2.1', + 'links_popup_script' => array( + 'alt' => '', + 'version' => '2.1.0', ), - 'get_author_link' => array( - 'alt' => 'get_author_posts_url()', - 'version' => '2.1', + 'list_authors' => array( + 'alt' => 'wp_list_authors()', + 'version' => '2.1.0', ), - 'link_pages' => array( - 'alt' => 'wp_link_pages()', - 'version' => '2.1', + 'list_cats' => array( + 'alt' => 'wp_list_categories()', + 'version' => '2.1.0', ), - 'get_settings' => array( - 'alt' => 'get_option()', - 'version' => '2.1', + 'tinymce_include' => array( + 'alt' => 'wp_editor()', + 'version' => '2.1.0', ), 'wp_get_links' => array( 'alt' => 'wp_list_bookmarks()', - 'version' => '2.1', - ), - 'get_links' => array( - 'alt' => 'get_bookmarks()', - 'version' => '2.1', + 'version' => '2.1.0', ), - 'get_links_list' => array( + 'wp_get_linksbyname' => array( 'alt' => 'wp_list_bookmarks()', - 'version' => '2.1', - ), - 'links_popup_script' => array( - 'alt' => '', - 'version' => '2.1', - ), - 'get_linkrating' => array( - 'alt' => 'sanitize_bookmark_field()', - 'version' => '2.1', + 'version' => '2.1.0', ), - 'get_linkcatname' => array( - 'alt' => 'get_category()', - 'version' => '2.1', + 'wp_get_post_cats' => array( + 'alt' => 'wp_get_post_categories()', + 'version' => '2.1.0', ), - 'get_link' => array( - 'alt' => 'get_bookmark()', - 'version' => '2.1', + 'wp_list_cats' => array( + 'alt' => 'wp_list_categories()', + 'version' => '2.1.0', ), - 'tinymce_include' => array( - 'alt' => 'wp_tiny_mce()', - 'version' => '2.1', + 'wp_set_post_cats' => array( + 'alt' => 'wp_set_post_categories()', + 'version' => '2.1.0', ), + // WP 2.2.0. 'comments_rss' => array( 'alt' => 'get_post_comments_feed_link()', - 'version' => '2.2', + 'version' => '2.2.0', ), + // WP 2.3.0. 'permalink_single_rss' => array( - 'alt' => 'permalink_rss()', - 'version' => '2.3', + 'alt' => 'the_permalink_rss()', + 'version' => '2.3.0', ), + // WP 2.5.0. 'comments_rss_link' => array( 'alt' => 'post_comments_feed_link()', - 'version' => '2.5', - ), - 'get_category_rss_link' => array( - 'alt' => 'get_category_feed_link()', - 'version' => '2.5', + 'version' => '2.5.0', ), - 'get_author_rss_link' => array( - 'alt' => 'get_author_feed_link()', - 'version' => '2.5', + 'documentation_link' => array( + 'alt' => '', + 'version' => '2.5.0', ), - 'get_the_attachment_link' => array( - 'alt' => 'wp_get_attachment_link()', - 'version' => '2.5', + 'get_attachment_icon' => array( + 'alt' => 'wp_get_attachment_image()', + 'version' => '2.5.0', ), 'get_attachment_icon_src' => array( 'alt' => 'wp_get_attachment_image_src()', - 'version' => '2.5', - ), - 'get_attachment_icon' => array( - 'alt' => 'wp_get_attachment_image()', - 'version' => '2.5', + 'version' => '2.5.0', ), 'get_attachment_innerHTML' => array( 'alt' => 'wp_get_attachment_image()', - 'version' => '2.5', + 'version' => '2.5.0', ), - 'documentation_link' => array( - 'alt' => '', - 'version' => '2.5', + 'get_author_rss_link' => array( + 'alt' => 'get_author_feed_link()', + 'version' => '2.5.0', + ), + 'get_category_rss_link' => array( + 'alt' => 'get_category_feed_link()', + 'version' => '2.5.0', + ), + 'get_the_attachment_link' => array( + 'alt' => 'wp_get_attachment_link()', + 'version' => '2.5.0', ), 'gzip_compression' => array( 'alt' => '', - 'version' => '2.5', + 'version' => '2.5.0', ), - 'wp_setcookie' => array( - 'alt' => 'wp_set_auth_cookie()', - 'version' => '2.5', + 'wp_clearcookie' => array( + 'alt' => 'wp_clear_auth_cookie()', + 'version' => '2.5.0', ), 'wp_get_cookie_login' => array( 'alt' => '', - 'version' => '2.5', + 'version' => '2.5.0', ), 'wp_login' => array( 'alt' => 'wp_signon()', - 'version' => '2.5', + 'version' => '2.5.0', + ), + 'wp_setcookie' => array( + 'alt' => 'wp_set_auth_cookie()', + 'version' => '2.5.0', ), + // WP 2.6.0. 'dropdown_categories' => array( 'alt' => 'wp_category_checklist()', - 'version' => '2.6', + 'version' => '2.6.0', ), 'dropdown_link_categories' => array( 'alt' => 'wp_link_category_checklist()', - 'version' => '2.6', + 'version' => '2.6.0', ), + // WP 2.7.0. 'get_commentdata' => array( 'alt' => 'get_comment()', - 'version' => '2.7', + 'version' => '2.7.0', ), // This is a method i.e. WP_Filesystem_Base::find_base_dir() See #731. 'find_base_dir' => array( 'alt' => 'WP_Filesystem::abspath()', - 'version' => '2.7', + 'version' => '2.7.0', ), // This is a method i.e. WP_Filesystem_Base::get_base_dir() See #731. 'get_base_dir' => array( 'alt' => 'WP_Filesystem::abspath()', - 'version' => '2.7', + 'version' => '2.7.0', ), + // WP 2.8.0. + '__ngettext' => array( + 'alt' => '_n()', + 'version' => '2.8.0', + ), + '__ngettext_noop' => array( + 'alt' => '_n_noop()', + 'version' => '2.8.0', + ), + 'attribute_escape' => array( + 'alt' => 'esc_attr()', + 'version' => '2.8.0', + ), + 'get_author_name' => array( + 'alt' => 'get_the_author_meta(\'display_name\')', + 'version' => '2.8.0', + ), + 'get_category_children' => array( + 'alt' => 'get_term_children()', + 'version' => '2.8.0', + ), 'get_catname' => array( 'alt' => 'get_cat_name()', - 'version' => '2.8', + 'version' => '2.8.0', ), - 'get_category_children' => array( - 'alt' => 'get_term_children', - 'version' => '2.8', + 'get_the_author_aim' => array( + 'alt' => 'get_the_author_meta(\'aim\')', + 'version' => '2.8.0', ), 'get_the_author_description' => array( - 'alt' => "get_the_author_meta( 'description' )", - 'version' => '2.8', + 'alt' => 'get_the_author_meta(\'description\')', + 'version' => '2.8.0', ), - 'the_author_description' => array( - 'alt' => 'the_author_meta(\'description\')', - 'version' => '2.8', - ), - 'get_the_author_login' => array( - 'alt' => 'the_author_meta(\'login\')', - 'version' => '2.8', + 'get_the_author_email' => array( + 'alt' => 'get_the_author_meta(\'email\')', + 'version' => '2.8.0', ), 'get_the_author_firstname' => array( 'alt' => 'get_the_author_meta(\'first_name\')', - 'version' => '2.8', + 'version' => '2.8.0', ), - 'the_author_firstname' => array( - 'alt' => 'the_author_meta(\'first_name\')', - 'version' => '2.8', + 'get_the_author_icq' => array( + 'alt' => 'get_the_author_meta(\'icq\')', + 'version' => '2.8.0', + ), + 'get_the_author_ID' => array( + 'alt' => 'get_the_author_meta(\'ID\')', + 'version' => '2.8.0', ), 'get_the_author_lastname' => array( 'alt' => 'get_the_author_meta(\'last_name\')', - 'version' => '2.8', + 'version' => '2.8.0', ), - 'the_author_lastname' => array( - 'alt' => 'the_author_meta(\'last_name\')', - 'version' => '2.8', + 'get_the_author_login' => array( + 'alt' => 'get_the_author_meta(\'login\')', + 'version' => '2.8.0', + ), + 'get_the_author_msn' => array( + 'alt' => 'get_the_author_meta(\'msn\')', + 'version' => '2.8.0', ), 'get_the_author_nickname' => array( 'alt' => 'get_the_author_meta(\'nickname\')', - 'version' => '2.8', - ), - 'the_author_nickname' => array( - 'alt' => 'the_author_meta(\'nickname\')', - 'version' => '2.8', - ), - 'get_the_author_email' => array( - 'alt' => 'get_the_author_meta(\'email\')', - 'version' => '2.8', - ), - 'the_author_email' => array( - 'alt' => 'the_author_meta(\'email\')', - 'version' => '2.8', + 'version' => '2.8.0', ), - 'get_the_author_icq' => array( - 'alt' => 'get_the_author_meta(\'icq\')', - 'version' => '2.8', - ), - 'the_author_icq' => array( - 'alt' => 'the_author_meta(\'icq\')', - 'version' => '2.8', + 'get_the_author_url' => array( + 'alt' => 'get_the_author_meta(\'url\')', + 'version' => '2.8.0', ), 'get_the_author_yim' => array( 'alt' => 'get_the_author_meta(\'yim\')', - 'version' => '2.8', + 'version' => '2.8.0', ), - 'the_author_yim' => array( - 'alt' => 'the_author_meta(\'yim\')', - 'version' => '2.8', + 'js_escape' => array( + 'alt' => 'esc_js()', + 'version' => '2.8.0', ), - 'get_the_author_msn' => array( - 'alt' => 'get_the_author_meta(\'msn\')', - 'version' => '2.8', + 'register_sidebar_widget' => array( + 'alt' => 'wp_register_sidebar_widget()', + 'version' => '2.8.0', ), - 'the_author_msn' => array( - 'alt' => 'the_author_meta(\'msn\')', - 'version' => '2.8', + 'register_widget_control' => array( + 'alt' => 'wp_register_widget_control()', + 'version' => '2.8.0', ), - 'get_the_author_aim' => array( - 'alt' => 'get_the_author_meta(\'aim\')', - 'version' => '2.8', + 'sanitize_url' => array( + 'alt' => 'esc_url_raw()', + 'version' => '2.8.0', ), 'the_author_aim' => array( 'alt' => 'the_author_meta(\'aim\')', - 'version' => '2.8', + 'version' => '2.8.0', ), - 'get_author_name' => array( - 'alt' => 'get_the_author_meta(\'display_name\')', - 'version' => '2.8', + 'the_author_description' => array( + 'alt' => 'the_author_meta(\'description\')', + 'version' => '2.8.0', ), - 'get_the_author_url' => array( - 'alt' => 'get_the_author_meta(\'url\')', - 'version' => '2.8', + 'the_author_email' => array( + 'alt' => 'the_author_meta(\'email\')', + 'version' => '2.8.0', ), - 'the_author_url' => array( - 'alt' => 'the_author_meta(\'url\')', - 'version' => '2.8', + 'the_author_firstname' => array( + 'alt' => 'the_author_meta(\'first_name\')', + 'version' => '2.8.0', ), - 'get_the_author_ID' => array( - 'alt' => 'get_the_author_meta(\'ID\')', - 'version' => '2.8', + 'the_author_icq' => array( + 'alt' => 'the_author_meta(\'icq\')', + 'version' => '2.8.0', ), 'the_author_ID' => array( 'alt' => 'the_author_meta(\'ID\')', - 'version' => '2.8', - ), - '__ngettext' => array( - 'alt' => '_n_noop()', - 'version' => '2.8', + 'version' => '2.8.0', ), - '__ngettext_noop' => array( - 'alt' => '_n_noop()', - 'version' => '2.8', + 'the_author_lastname' => array( + 'alt' => 'the_author_meta(\'last_name\')', + 'version' => '2.8.0', ), - 'sanitize_url' => array( - 'alt' => 'esc_url()', - 'version' => '2.8', + 'the_author_login' => array( + 'alt' => 'the_author_meta(\'login\')', + 'version' => '2.8.0', ), - 'js_escape' => array( - 'alt' => 'esc_js()', - 'version' => '2.8', + 'the_author_msn' => array( + 'alt' => 'the_author_meta(\'msn\')', + 'version' => '2.8.0', ), - 'wp_specialchars' => array( - 'alt' => 'esc_html()', - 'version' => '2.8', + 'the_author_nickname' => array( + 'alt' => 'the_author_meta(\'nickname\')', + 'version' => '2.8.0', ), - 'attribute_escape' => array( - 'alt' => 'esc_attr()', - 'version' => '2.8', + 'the_author_url' => array( + 'alt' => 'the_author_meta(\'url\')', + 'version' => '2.8.0', ), - 'register_sidebar_widget' => array( - 'alt' => 'wp_register_sidebar_widget()', - 'version' => '2.8', + 'the_author_yim' => array( + 'alt' => 'the_author_meta(\'yim\')', + 'version' => '2.8.0', ), 'unregister_sidebar_widget' => array( 'alt' => 'wp_unregister_sidebar_widget()', - 'version' => '2.8', - ), - 'register_widget_control' => array( - 'alt' => 'wp_register_widget_control()', - 'version' => '2.8', + 'version' => '2.8.0', ), 'unregister_widget_control' => array( 'alt' => 'wp_unregister_widget_control()', - 'version' => '2.8', + 'version' => '2.8.0', + ), + 'wp_specialchars' => array( + 'alt' => 'esc_html()', + 'version' => '2.8.0', ), - 'the_content_rss' => array( - 'alt' => 'the_content_feed()', - 'version' => '2.9', + // WP 2.9.0. + '_c' => array( + 'alt' => '_x()', + 'version' => '2.9.0', + ), + '_nc' => array( + 'alt' => '_nx()', + 'version' => '2.9.0', + ), + 'get_real_file_to_edit' => array( + 'alt' => '', + 'version' => '2.9.0', ), 'make_url_footnote' => array( 'alt' => '', - 'version' => '2.9', + 'version' => '2.9.0', ), - '_c' => array( - 'alt' => '_x()', - 'version' => '2.9', + 'the_content_rss' => array( + 'alt' => 'the_content_feed()', + 'version' => '2.9.0', ), - 'translate_with_context' => array( 'alt' => '_x()', - 'version' => '3.0', + 'version' => '2.9.0', ), - 'nc' => array( - 'alt' => 'nx()', - 'version' => '3.0', + + // WP 3.0.0. + 'activate_sitewide_plugin' => array( + 'alt' => 'activate_plugin()', + 'version' => '3.0.0', ), - 'get_alloptions' => array( - 'alt' => 'wp_load_alloptions()', - 'version' => '3.0', + 'add_option_update_handler' => array( + 'alt' => 'register_setting()', + 'version' => '3.0.0', + ), + 'automatic_feed_links' => array( + 'alt' => 'add_theme_support( \'automatic-feed-links\' )', + 'version' => '3.0.0', ), 'clean_url' => array( 'alt' => 'esc_url()', - 'version' => '3.0', + 'version' => '3.0.0', + ), + 'clear_global_post_cache' => array( + 'alt' => 'clean_post_cache()', + 'version' => '3.0.0', + ), + 'codepress_footer_js' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'codepress_get_lang' => array( + 'alt' => '', + 'version' => '3.0.0', + ), + 'deactivate_sitewide_plugin' => array( + 'alt' => 'deactivate_plugin()', + 'version' => '3.0.0', ), 'delete_usermeta' => array( 'alt' => 'delete_user_meta()', - 'version' => '3.0', + 'version' => '3.0.0', ), - 'get_usermeta' => array( - 'alt' => 'get_user_meta()', - 'version' => '3.0', + 'funky_javascript_callback' => array( + 'alt' => '', + 'version' => '3.0.0', ), - 'update_usermeta' => array( - 'alt' => 'update_user_meta()', - 'version' => '3.0', + 'funky_javascript_fix' => array( + 'alt' => '', + 'version' => '3.0.0', ), - 'automatic_feed_links' => array( - 'alt' => 'add_theme_support( \'automatic-feed-links\' )', - 'version' => '3.0', + 'generate_random_password' => array( + 'alt' => 'wp_generate_password()', + 'version' => '3.0.0', + ), + 'get_alloptions' => array( + 'alt' => 'wp_load_alloptions()', + 'version' => '3.0.0', + ), + 'get_blog_list' => array( + 'alt' => 'wp_get_sites()', + 'version' => '3.0.0', + ), + 'get_most_active_blogs' => array( + 'alt' => '', + 'version' => '3.0.0', ), 'get_profile' => array( 'alt' => 'get_the_author_meta()', - 'version' => '3.0', + 'version' => '3.0.0', + ), + 'get_user_details' => array( + 'alt' => 'get_user_by()', + 'version' => '3.0.0', + ), + 'get_usermeta' => array( + 'alt' => 'get_user_meta()', + 'version' => '3.0.0', ), 'get_usernumposts' => array( 'alt' => 'count_user_posts()', - 'version' => '3.0', + 'version' => '3.0.0', ), - 'funky_javascript_callback' => array( - 'alt' => '', - 'version' => '3.0', + 'graceful_fail' => array( + 'alt' => 'wp_die()', + 'version' => '3.0.0', ), - 'funky_javascript_fix' => array( - 'alt' => '', - 'version' => '3.0', + 'is_main_blog' => array( + 'alt' => 'is_main_site()', + 'version' => '3.0.0', + ), + 'is_site_admin' => array( + 'alt' => 'is_super_admin()', + 'version' => '3.0.0', ), 'is_taxonomy' => array( 'alt' => 'taxonomy_exists()', - 'version' => '3.0', + 'version' => '3.0.0', ), 'is_term' => array( 'alt' => 'term_exists()', - 'version' => '3.0', + 'version' => '3.0.0', ), - 'wp_dropdown_cats' => array( - 'alt' => 'wp_dropdown_categories()', - 'version' => '3.0', + 'is_wpmu_sitewide_plugin' => array( + 'alt' => 'is_network_only_plugin()', + 'version' => '3.0.0', ), - 'add_option_update_handler' => array( - 'alt' => 'register_setting()', - 'version' => '3.0', + 'mu_options' => array( + 'alt' => '', + 'version' => '3.0.0', ), 'remove_option_update_handler' => array( 'alt' => 'unregister_setting()', - 'version' => '3.0', + 'version' => '3.0.0', ), - 'codepress_get_lang' => array( - 'alt' => '', - 'version' => '3.0', + 'set_current_user' => array( + 'alt' => 'wp_set_current_user()', + 'version' => '3.0.0', ), - 'codepress_footer_js' => array( - 'alt' => '', - 'version' => '3.0', + 'update_usermeta' => array( + 'alt' => 'update_user_meta()', + 'version' => '3.0.0', ), 'use_codepress' => array( 'alt' => '', - 'version' => '3.0', + 'version' => '3.0.0', + ), + 'validate_email' => array( + 'alt' => 'is_email()', + 'version' => '3.0.0', + ), + 'wp_dropdown_cats' => array( + 'alt' => 'wp_dropdown_categories()', + 'version' => '3.0.0', ), 'wp_shrink_dimensions' => array( 'alt' => 'wp_constrain_dimensions()', - 'version' => '3.0', + 'version' => '3.0.0', ), - - 'is_plugin_page' => array( - 'alt' => '$plugin_page and/or get_plugin_page_hookname() hooks', - 'version' => '3.1', + 'wpmu_checkAvailableSpace' => array( + 'alt' => 'is_upload_space_available()', + 'version' => '3.0.0', ), - 'update_category_cache' => array( + 'wpmu_menu' => array( 'alt' => '', - 'version' => '3.1', + 'version' => '3.0.0', ), - 'get_users_of_blog' => array( + + // WP 3.1.0. + 'get_author_user_ids' => array( 'alt' => 'get_users()', - 'version' => '3.1', + 'version' => '3.1.0', ), - 'get_author_user_ids' => array( + 'get_dashboard_blog' => array( 'alt' => '', - 'version' => '3.1', + 'version' => '3.1.0', ), 'get_editable_authors' => array( - 'alt' => '', - 'version' => '3.1', + 'alt' => 'get_users()', + 'version' => '3.1.0', ), 'get_editable_user_ids' => array( - 'alt' => '', - 'version' => '3.1', + 'alt' => 'get_users()', + 'version' => '3.1.0', ), 'get_nonauthor_user_ids' => array( - 'alt' => '', - 'version' => '3.1', - ), - 'WP_User_Search' => array( - 'alt' => 'WP_User_Query', - 'version' => '3.1', - ), - 'get_others_unpublished_posts' => array( - 'alt' => '', - 'version' => '3.1', + 'alt' => 'get_users()', + 'version' => '3.1.0', ), 'get_others_drafts' => array( 'alt' => '', - 'version' => '3.1', + 'version' => '3.1.0', ), 'get_others_pending' => array( 'alt' => '', - 'version' => '3.1', + 'version' => '3.1.0', ), - - 'wp_timezone_supported' => array( + 'get_others_unpublished_posts' => array( 'alt' => '', - 'version' => '3.2', + 'version' => '3.1.0', ), - 'wp_dashboard_quick_press' => array( - 'alt' => '', - 'version' => '3.2', + 'get_users_of_blog' => array( + 'alt' => 'get_users()', + 'version' => '3.1.0', ), - 'wp_tiny_mce' => array( - 'alt' => 'wp_editor', - 'version' => '3.2', + 'install_themes_feature_list' => array( + 'alt' => 'get_theme_feature_list()', + 'version' => '3.1.0', ), - 'wp_preload_dialogs' => array( - 'alt' => 'wp_editor()', - 'version' => '3.2', - ), - 'wp_print_editor_js' => array( - 'alt' => 'wp_editor()', - 'version' => '3.2', + 'is_plugin_page' => array( + // Verified correct alternative. + 'alt' => 'global $plugin_page and/or get_plugin_page_hookname() hooks', + 'version' => '3.1.0', ), - 'wp_quicktags' => array( - 'alt' => 'wp_editor()', - 'version' => '3.2', + 'update_category_cache' => array( + 'alt' => '', + 'version' => '3.1.0', ), + + // WP 3.2.0. 'favorite_actions' => array( 'alt' => 'WP_Admin_Bar', - 'version' => '3.2', + 'version' => '3.2.0', ), - - 'the_editor' => array( - 'alt' => 'wp_editor', - 'version' => '3.3', + 'wp_dashboard_quick_press_output' => array( + 'alt' => 'wp_dashboard_quick_press()', + 'version' => '3.2.0', ), - 'get_user_metavalues' => array( + 'wp_timezone_supported' => array( 'alt' => '', - 'version' => '3.3', + 'version' => '3.2.0', ), - 'sanitize_user_object' => array( - 'alt' => '', - 'version' => '3.3', + + // WP 3.3.0. + 'add_contextual_help' => array( + 'alt' => 'get_current_screen()->add_help_tab()', + 'version' => '3.3.0', ), 'get_boundary_post_rel_link' => array( 'alt' => '', - 'version' => '3.3', - ), - 'start_post_rel_link' => array( - 'alt' => '', - 'version' => '3.3', + 'version' => '3.3.0', ), 'get_index_rel_link' => array( 'alt' => '', - 'version' => '3.3', - ), - 'index_rel_link' => array( - 'alt' => '', - 'version' => '3.3', + 'version' => '3.3.0', ), 'get_parent_post_rel_link' => array( 'alt' => '', - 'version' => '3.3', + 'version' => '3.3.0', ), - 'parent_post_rel_link' => array( + 'get_user_by_email' => array( + 'alt' => 'get_user_by(\'email\')', + 'version' => '3.3.0', + ), + 'get_user_metavalues' => array( 'alt' => '', - 'version' => '3.3', + 'version' => '3.3.0', ), - 'wp_admin_bar_dashboard_view_site_menu' => array( + 'get_userdatabylogin' => array( + 'alt' => 'get_user_by(\'login\')', + 'version' => '3.3.0', + ), + 'index_rel_link' => array( 'alt' => '', - 'version' => '3.3', + 'version' => '3.3.0', ), 'is_blog_user' => array( - 'alt' => 'is_member_of_blog()', - 'version' => '3.3', + 'alt' => 'is_user_member_of_blog()', + 'version' => '3.3.0', ), - 'debug_fopen' => array( - 'alt' => 'error_log()', - 'version' => '3.3', + 'media_upload_audio' => array( + 'alt' => 'wp_media_upload_handler()', + 'version' => '3.3.0', ), - 'debug_fwrite' => array( - 'alt' => 'error_log()', - 'version' => '3.3', + 'media_upload_file' => array( + 'alt' => 'wp_media_upload_handler()', + 'version' => '3.3.0', ), - 'debug_fclose' => array( - 'alt' => 'error_log()', - 'version' => '3.3', + 'media_upload_image' => array( + 'alt' => 'wp_media_upload_handler()', + 'version' => '3.3.0', + ), + 'media_upload_video' => array( + 'alt' => 'wp_media_upload_handler()', + 'version' => '3.3.0', + ), + 'parent_post_rel_link' => array( + 'alt' => '', + 'version' => '3.3.0', + ), + 'sanitize_user_object' => array( + 'alt' => '', + 'version' => '3.3.0', ), 'screen_layout' => array( 'alt' => '$current_screen->render_screen_layout()', - 'version' => '3.3', - ), - 'screen_options' => array( - 'alt' => '$current_screen->render_per_page_options()', - 'version' => '3.3', + 'version' => '3.3.0', ), 'screen_meta' => array( 'alt' => '$current_screen->render_screen_meta()', - 'version' => '3.3', + 'version' => '3.3.0', ), - 'media_upload_image' => array( - 'alt' => 'wp_media_upload_handler()', - 'version' => '3.3', + 'screen_options' => array( + 'alt' => '$current_screen->render_per_page_options()', + 'version' => '3.3.0', ), - 'media_upload_audio' => array( - 'alt' => 'wp_media_upload_handler()', - 'version' => '3.3', + 'start_post_rel_link' => array( + 'alt' => '', + 'version' => '3.3.0', ), - 'media_upload_video' => array( - 'alt' => 'wp_media_upload_handler()', - 'version' => '3.3', + 'the_editor' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', ), - 'media_upload_file' => array( - 'alt' => 'wp_media_upload_handler()', - 'version' => '3.3', + 'type_url_form_audio' => array( + 'alt' => 'wp_media_insert_url_form(\'audio\')', + 'version' => '3.3.0', ), - 'type_url_form_image' => array( - 'alt' => 'wp_media_insert_url_form( \'image\' )', - 'version' => '3.3', + 'type_url_form_file' => array( + 'alt' => 'wp_media_insert_url_form(\'file\')', + 'version' => '3.3.0', ), - 'type_url_form_audio' => array( - 'alt' => 'wp_media_insert_url_form( \'audio\' )', - 'version' => '3.3', + 'type_url_form_image' => array( + 'alt' => 'wp_media_insert_url_form(\'image\')', + 'version' => '3.3.0', ), 'type_url_form_video' => array( - 'alt' => 'wp_media_insert_url_form( \'video\' )', - 'version' => '3.3', + 'alt' => 'wp_media_insert_url_form(\'video\')', + 'version' => '3.3.0', ), - 'type_url_form_file' => array( - 'alt' => 'wp_media_insert_url_form( \'file\' )', - 'version' => '3.3', + 'wp_admin_bar_dashboard_view_site_menu' => array( + 'alt' => '', + 'version' => '3.3.0', ), - 'add_contextual_help' => array( - 'alt' => 'get_current_screen()->add_help_tab()', - 'version' => '3.3', + 'wp_preload_dialogs' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', ), - - 'get_themes' => array( - 'alt' => 'wp_get_themes()', - 'version' => '3.4', + 'wp_print_editor_js' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', ), - 'get_theme' => array( - 'alt' => 'wp_get_theme()', - 'version' => '3.4', + 'wp_quicktags' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', ), - 'get_current_theme' => array( - 'alt' => 'wp_get_theme()', - 'version' => '3.4', + 'wp_tiny_mce' => array( + 'alt' => 'wp_editor()', + 'version' => '3.3.0', ), - 'clean_pre' => array( + 'wpmu_admin_do_redirect' => array( 'alt' => '', - 'version' => '3.4', - ), - 'add_custom_image_header' => array( - 'alt' => 'add_theme_support( \'custom-header\', $args )', - 'version' => '3.4', + 'version' => '3.3.0', ), - 'remove_custom_image_header' => array( - 'alt' => 'remove_theme_support( \'custom-header\' )', - 'version' => '3.4', + 'wpmu_admin_redirect_add_updated_param' => array( + 'alt' => '', + 'version' => '3.3.0', ), + + // WP 3.4.0. 'add_custom_background' => array( 'alt' => 'add_theme_support( \'custom-background\', $args )', - 'version' => '3.4', + 'version' => '3.4.0', ), - 'remove_custom_background' => array( - 'alt' => 'remove_theme_support( \'custom-background\' )', - 'version' => '3.4', + 'add_custom_image_header' => array( + 'alt' => 'add_theme_support( \'custom-header\', $args )', + 'version' => '3.4.0', ), - 'get_theme_data' => array( + 'clean_page_cache' => array( + 'alt' => 'clean_post_cache()', + 'version' => '3.4.0', + ), + 'clean_pre' => array( + 'alt' => '', + 'version' => '3.4.0', + ), + 'current_theme_info' => array( 'alt' => 'wp_get_theme()', - 'version' => '3.4', + 'version' => '3.4.0', ), - 'update_page_cache' => array( - 'alt' => 'update_post_cache()', - 'version' => '3.4', + 'debug_fclose' => array( + 'alt' => 'error_log()', + 'version' => '3.4.0', ), - 'clean_page_cache' => array( - 'alt' => 'clean_post_cache()', - 'version' => '3.4', + 'debug_fopen' => array( + 'alt' => 'error_log()', + 'version' => '3.4.0', + ), + 'debug_fwrite' => array( + 'alt' => 'error_log()', + 'version' => '3.4.0', + ), + 'display_theme' => array( + 'alt' => '', + 'version' => '3.4.0', ), 'get_allowed_themes' => array( 'alt' => 'wp_get_themes( array( \'allowed\' => true ) )', - 'version' => '3.4', + 'version' => '3.4.0', ), 'get_broken_themes' => array( 'alt' => 'wp_get_themes( array( \'errors\' => true )', - 'version' => '3.4', + 'version' => '3.4.0', ), - 'current_theme_info' => array( + 'get_current_theme' => array( + 'alt' => 'wp_get_theme()', + 'version' => '3.4.0', + ), + 'get_site_allowed_themes' => array( + 'alt' => 'WP_Theme::get_allowed_on_network()', + 'version' => '3.4.0', + ), + 'get_theme' => array( + 'alt' => 'wp_get_theme( $stylesheet )', + 'version' => '3.4.0', + ), + 'get_theme_data' => array( 'alt' => 'wp_get_theme()', - 'version' => '3.4', + 'version' => '3.4.0', + ), + 'get_themes' => array( + 'alt' => 'wp_get_themes()', + 'version' => '3.4.0', + ), + 'logIO' => array( + 'alt' => 'error_log()', + 'version' => '3.4.0', ), + 'remove_custom_background' => array( + 'alt' => 'remove_theme_support( \'custom-background\' )', + 'version' => '3.4.0', + ), + 'remove_custom_image_header' => array( + 'alt' => 'remove_theme_support( \'custom-header\' )', + 'version' => '3.4.0', + ), + 'update_page_cache' => array( + 'alt' => 'update_post_cache()', + 'version' => '3.4.0', + ), + 'wpmu_get_blog_allowedthemes' => array( + 'alt' => 'WP_Theme::get_allowed_on_site()', + 'version' => '3.4.0', + ), + + // WP 3.4.1. 'wp_explain_nonce' => array( - 'alt' => 'wp_nonce_ays', + 'alt' => 'wp_nonce_ays()', 'version' => '3.4.1', ), - 'sticky_class' => array( - 'alt' => 'post_class()', - 'version' => '3.5', + // WP 3.5.0. + '_flip_image_resource' => array( + 'alt' => 'WP_Image_Editor::flip()', + 'version' => '3.5.0', ), '_get_post_ancestors' => array( 'alt' => '', - 'version' => '3.5', - ), - 'wp_load_image' => array( - 'alt' => 'wp_get_image_editor()', - 'version' => '3.5', + 'version' => '3.5.0', ), - 'image_resize' => array( - 'alt' => 'wp_get_image_editor()', - 'version' => '3.5', + '_insert_into_post_button' => array( + 'alt' => '', + 'version' => '3.5.0', ), - 'wp_get_single_post' => array( - 'alt' => 'get_post()', - 'version' => '3.5', + '_media_button' => array( + 'alt' => '', + 'version' => '3.5.0', ), - 'user_pass_ok' => array( - 'alt' => 'wp_authenticate()', - 'version' => '3.5', + '_rotate_image_resource' => array( + 'alt' => 'WP_Image_Editor::rotate()', + 'version' => '3.5.0', ), '_save_post_hook' => array( 'alt' => '', - 'version' => '3.5', + 'version' => '3.5.0', ), 'gd_edit_image_support' => array( - 'alt' => 'wp_image_editor_supports', - 'version' => '3.5', - ), - '_insert_into_post_button' => array( - 'alt' => '', - 'version' => '3.5', + 'alt' => 'wp_image_editor_supports()', + 'version' => '3.5.0', ), - '_media_button' => array( - 'alt' => '', - 'version' => '3.5', + 'get_default_page_to_edit' => array( + 'alt' => 'get_default_post_to_edit( \'page\' )', + 'version' => '3.5.0', ), 'get_post_to_edit' => array( 'alt' => 'get_post()', - 'version' => '3.5', + 'version' => '3.5.0', ), - 'get_default_page_to_edit' => array( - 'alt' => 'get_default_post_to_edit()', - 'version' => '3.5', + 'get_udims' => array( + 'alt' => 'wp_constrain_dimensions()', + 'version' => '3.5.0', + ), + 'image_resize' => array( + 'alt' => 'wp_get_image_editor()', + 'version' => '3.5.0', + ), + 'sticky_class' => array( + 'alt' => 'post_class()', + 'version' => '3.5.0', + ), + 'user_pass_ok' => array( + 'alt' => 'wp_authenticate()', + 'version' => '3.5.0', + ), + 'wp_cache_reset' => array( + 'alt' => '', + 'version' => '3.5.0', ), 'wp_create_thumbnail' => array( 'alt' => 'image_resize()', - 'version' => '3.5', + 'version' => '3.5.0', + ), + 'wp_get_single_post' => array( + 'alt' => 'get_post()', + 'version' => '3.5.0', + ), + 'wp_load_image' => array( + 'alt' => 'wp_get_image_editor()', + 'version' => '3.5.0', ), + // WP 3.6.0. 'get_user_id_from_string' => array( 'alt' => 'get_user_by()', - 'version' => '3.6', + 'version' => '3.6.0', ), 'wp_convert_bytes_to_hr' => array( 'alt' => 'size_format()', - 'version' => '3.6', + 'version' => '3.6.0', ), 'wp_nav_menu_locations_meta_box' => array( 'alt' => '', - 'version' => '3.6', + 'version' => '3.6.0', ), + // WP 3.7.0. + '_search_terms_tidy' => array( + 'alt' => '', + 'version' => '3.7.0', + ), + 'get_blogaddress_by_domain' => array( + 'alt' => '', + 'version' => '3.7.0', + ), 'the_attachment_links' => array( 'alt' => '', - 'version' => '3.7', + 'version' => '3.7.0', ), 'wp_update_core' => array( - 'alt' => 'new Core_Upgrader()', - 'version' => '3.7', + 'alt' => 'new Core_Upgrader();', + 'version' => '3.7.0', ), 'wp_update_plugin' => array( - 'alt' => 'new Plugin_Upgrader()', - 'version' => '3.7', + 'alt' => 'new Plugin_Upgrader();', + 'version' => '3.7.0', ), 'wp_update_theme' => array( - 'alt' => 'new Theme_Upgrader()', - 'version' => '3.7', - ), - '_search_terms_tidy' => array( - 'alt' => '', - 'version' => '3.7', - ), - 'get_blogaddress_by_domain' => array( - 'alt' => '', - 'version' => '3.7', + 'alt' => 'new Theme_Upgrader();', + 'version' => '3.7.0', ), + // WP 3.8.0. 'get_screen_icon' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'screen_icon' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_incoming_links' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_incoming_links_control' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_incoming_links_output' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_plugins' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_primary_control' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_recent_comments_control' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_secondary' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_secondary_control' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), 'wp_dashboard_secondary_output' => array( 'alt' => '', - 'version' => '3.8', + 'version' => '3.8.0', ), - 'rich_edit_exists' => array( + // WP 3.9.0. + '_relocate_children' => array( 'alt' => '', - 'version' => '3.9', + 'version' => '3.9.0', ), 'default_topic_count_text' => array( 'alt' => '', - 'version' => '3.9', + 'version' => '3.9.0', ), 'format_to_post' => array( 'alt' => '', - 'version' => '3.9', + 'version' => '3.9.0', ), 'get_current_site_name' => array( 'alt' => 'get_current_site()', - 'version' => '3.9', + 'version' => '3.9.0', ), - 'wpmu_current_site' => array( + 'rich_edit_exists' => array( 'alt' => '', - 'version' => '3.9', + 'version' => '3.9.0', ), - '_relocate_children' => array( + 'wpmu_current_site' => array( 'alt' => '', - 'version' => '3.9', + 'version' => '3.9.0', ), + // WP 4.0.0. 'get_all_category_ids' => array( 'alt' => 'get_terms()', - 'version' => '4.0', + 'version' => '4.0.0', ), 'like_escape' => array( 'alt' => 'wpdb::esc_like()', - 'version' => '4.0', + 'version' => '4.0.0', ), 'url_is_accessable_via_ssl' => array( 'alt' => '', - 'version' => '4.0', + 'version' => '4.0.0', ), - 'prepare_control' => array( - 'alt' => '', - 'version' => '4.1', - ), + // WP 4.1.0. + // This is a method from the WP_Customize_Image_Control class. See #731. 'add_tab' => array( 'alt' => '', - 'version' => '4.1', + 'version' => '4.1.0', ), - 'remove_tab' => array( + // This is a method from the WP_Customize_Image_Control class. See #731. + 'prepare_control' => array( 'alt' => '', - 'version' => '4.1', + 'version' => '4.1.0', ), + // This is a method from the WP_Customize_Image_Control class. See #731. 'print_tab_image' => array( 'alt' => '', - 'version' => '4.1', + 'version' => '4.1.0', ), - - 'setup_widget_addition_previews' => array( - 'alt' => 'customize_dynamic_setting_args', - 'version' => '4.2', + // This is a method from the WP_Customize_Image_Control class. See #731. + 'remove_tab' => array( + 'alt' => '', + 'version' => '4.1.0', ), + + // WP 4.2.0. + // This is a method from the WP_Customize_Widgets class. See #731. 'prepreview_added_sidebars_widgets' => array( - 'alt' => 'customize_dynamic_setting_args', - 'version' => '4.2', + 'alt' => 'the \'customize_dynamic_setting_args\' filter', + 'version' => '4.2.0', ), + // This is a method from the WP_Customize_Widgets class. See #731. 'prepreview_added_widget_instance' => array( - 'alt' => 'customize_dynamic_setting_args', - 'version' => '4.2', + 'alt' => 'the \'customize_dynamic_setting_args\' filter', + 'version' => '4.2.0', ), + // This is a method from the WP_Customize_Widgets class. See #731. 'remove_prepreview_filters' => array( - 'alt' => 'customize_dynamic_setting_args()', - 'version' => '4.2', + 'alt' => 'the \'customize_dynamic_setting_args\' filter', + 'version' => '4.2.0', + ), + // This is a method from the WP_Customize_Widgets class. See #731. + 'setup_widget_addition_previews' => array( + 'alt' => 'the \'customize_dynamic_setting_args\' filter', + 'version' => '4.2.0', ), - 'preview_theme' => array( + // WP 4.3.0. + '_preview_theme_stylesheet_filter' => array( 'alt' => '', - 'version' => '4.3', + 'version' => '4.3.0', ), '_preview_theme_template_filter' => array( 'alt' => '', - 'version' => '4.3', + 'version' => '4.3.0', ), - '_preview_theme_stylesheet_filter' => array( + 'preview_theme' => array( 'alt' => '', - 'version' => '4.3', + 'version' => '4.3.0', ), 'preview_theme_ob_filter' => array( 'alt' => '', - 'version' => '4.3', + 'version' => '4.3.0', ), 'preview_theme_ob_filter_callback' => array( 'alt' => '', - 'version' => '4.3', + 'version' => '4.3.0', ), - 'wp_richedit_pre' => array( + 'wp_ajax_wp_fullscreen_save_post' => array( 'alt' => '', - 'version' => '4.3', + 'version' => '4.3.0', ), 'wp_htmledit_pre' => array( - 'alt' => '', - 'version' => '4.3', + 'alt' => 'format_for_editor()', + 'version' => '4.3.0', ), - 'wp_ajax_wp_fullscreen_save_post' => array( - 'alt' => '', - 'version' => '4.3', + 'wp_richedit_pre' => array( + 'alt' => 'format_for_editor()', + 'version' => '4.3.0', ), - 'post_permalink' => array( - 'alt' => 'get_permalink', - 'version' => '4.4', - ), - 'force_ssl_login' => array( - 'alt' => 'force_ssl_admin', - 'version' => '4.4', - ), + // WP 4.4.0. 'create_empty_blog' => array( 'alt' => '', - 'version' => '4.4', + 'version' => '4.4.0', + ), + 'force_ssl_login' => array( + 'alt' => 'force_ssl_admin()', + 'version' => '4.4.0', ), 'get_admin_users_for_domain' => array( 'alt' => '', - 'version' => '4.4', + 'version' => '4.4.0', + ), + 'post_permalink' => array( + 'alt' => 'get_permalink()', + 'version' => '4.4.0', ), 'wp_get_http' => array( - 'alt' => 'WP_Http', - 'version' => '4.4', + 'alt' => 'the WP_Http class', + 'version' => '4.4.0', ), // This is a method i.e. WP_Widget_Recent_Comments::flush_widget_cache() See #731. 'flush_widget_cache' => array( 'alt' => '', - 'version' => '4.4', + 'version' => '4.4.0', ), - 'is_comments_popup' => array( - 'alt' => '', - 'version' => '4.5', - ), + // WP 4.5.0. 'add_object_page' => array( 'alt' => 'add_menu_page()', - 'version' => '4.5', + 'version' => '4.5.0', ), 'add_utility_page' => array( 'alt' => 'add_menu_page()', - 'version' => '4.5', - ), - 'get_comments_popup_template' => array( - 'alt' => '', - 'version' => '4.5', + 'version' => '4.5.0', ), 'comments_popup_script' => array( 'alt' => '', - 'version' => '4.5', + 'version' => '4.5.0', ), - 'popuplinks' => array( + 'get_comments_popup_template' => array( 'alt' => '', - 'version' => '4.5', + 'version' => '4.5.0', ), 'get_currentuserinfo' => array( 'alt' => 'wp_get_current_user()', - 'version' => '4.5', + 'version' => '4.5.0', + ), + 'is_comments_popup' => array( + 'alt' => '', + 'version' => '4.5.0', + ), + 'popuplinks' => array( + 'alt' => '', + 'version' => '4.5.0', ), + // WP 4.6.0. + 'post_form_autocomplete_off' => array( + 'alt' => '', + 'version' => '4.6.0', + ), 'wp_embed_handler_googlevideo' => array( 'alt' => '', - 'version' => '4.6', + 'version' => '4.6.0', ), 'wp_get_sites' => array( 'alt' => 'get_sites()', - 'version' => '4.6', + 'version' => '4.6.0', + ), + + // WP 4.7.0. + '_sort_nav_menu_items' => array( + 'alt' => 'wp_list_sort()', + 'version' => '4.7.0', + ), + '_usort_terms_by_ID' => array( + 'alt' => 'wp_list_sort()', + 'version' => '4.7.0', + ), + '_usort_terms_by_name' => array( + 'alt' => 'wp_list_sort()', + 'version' => '4.7.0', + ), + 'get_paged_template' => array( + 'alt' => '', + 'version' => '4.7.0', + ), + 'wp_get_network' => array( + 'alt' => 'get_network()', + 'version' => '4.7.0', + ), + 'wp_kses_js_entities' => array( + 'alt' => '', + 'version' => '4.7.0', ), - // No deprecated functions in WordPress 4.7. + // WP 4.8.0. + 'wp_dashboard_plugins_output' => array( + 'alt' => '', + 'version' => '4.8.0', + ), ); /** @@ -1097,8 +1307,8 @@ class WordPress_Sniffs_WP_DeprecatedFunctionsSniff extends WordPress_AbstractFun */ public function getGroups() { // Make sure all array keys are lowercase. - $keys = array_keys( $this->deprecated_functions ); - $keys = array_map( 'strtolower', $keys ); + $keys = array_keys( $this->deprecated_functions ); + $keys = array_map( 'strtolower', $keys ); $this->deprecated_functions = array_combine( $keys, $this->deprecated_functions ); return array( @@ -1120,6 +1330,9 @@ public function getGroups() { * @return void */ public function process_matched_token( $stackPtr, $group_name, $matched_content ) { + + $this->get_wp_version_from_cl(); + $function_name = strtolower( $matched_content ); $message = '%s() has been deprecated since WordPress version %s.'; diff --git a/WordPress/Sniffs/WP/DeprecatedParametersSniff.php b/WordPress/Sniffs/WP/DeprecatedParametersSniff.php new file mode 100644 index 00000000..30794b7a --- /dev/null +++ b/WordPress/Sniffs/WP/DeprecatedParametersSniff.php @@ -0,0 +1,339 @@ +<?php +/** + * WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Sniffs\WP; + +use WordPress\AbstractFunctionParameterSniff; + +/** + * Check for usage of deprecated parameters in WP functions and suggest alternative based on the parameter passed. + * + * This sniff will throw an error when usage of deprecated parameters is + * detected if the parameter was deprecated before the minimum supported + * WP version; a warning otherwise. + * By default, it is set to presume that a project will support the current + * WP version and up to three releases before. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + * @since 0.14.0 Now has the ability to handle minimum supported WP version + * being provided via the command-line or as as <config> value + * in a custom ruleset. + * + * @uses \WordPress\Sniff::$minimum_supported_version + */ +class DeprecatedParametersSniff extends AbstractFunctionParameterSniff { + + /** + * The group name for this group of functions. + * + * @since 0.12.0 + * + * @var string + */ + protected $group_name = 'wp_deprecated_parameters'; + + /** + * Array of function, argument, and default value for deprecated argument. + * + * The functions are ordered alphabetically. + * Last updated for WordPress 4.8.0. + * + * @since 0.12.0 + * + * @var array Multidimensional array with parameter details. + * $target_functions = array( + * (string) Function name. => array( + * (int) Target parameter position, 1-based. => array( + * 'value' => (mixed) Expected default value for the + * deprecated parameter. Currently the default + * values: true, false, null, empty arrays and + * both empty and non-empty strings can be + * handled correctly by the process_parameters() + * method. When an additional default value is + * added, the relevant code in the + * process_parameters() method will need to be + * adjusted. + * 'version' => (int) The WordPress version when deprecated. + * ) + * ) + * ); + */ + protected $target_functions = array( + + 'add_option' => array( + 3 => array( + 'value' => '', + 'version' => '2.3.0', + ), + ), + 'comments_link' => array( + 1 => array( + 'value' => '', + 'version' => '0.72', + ), + 2 => array( + 'value' => '', + 'version' => '1.3.0', + ), + ), + 'comments_number' => array( + 4 => array( + 'value' => '', + 'version' => '1.3.0', + ), + ), + 'convert_chars' => array( + 2 => array( + 'value' => '', + 'version' => '0.71', + ), + ), + 'discover_pingback_server_uri' => array( + 2 => array( + 'value' => '', + 'version' => '2.7.0', + ), + ), + 'get_category_parents' => array( + 5 => array( + 'value' => array(), + 'version' => '4.8.0', + ), + ), + 'get_delete_post_link' => array( + 2 => array( + 'value' => '', + 'version' => '3.0.0', + ), + ), + 'get_last_updated' => array( + 1 => array( + 'value' => '', + 'version' => '3.0.0', // Was previously part of MU. + ), + ), + 'get_the_author' => array( + 1 => array( + 'value' => '', + 'version' => '2.1.0', + ), + ), + 'get_user_option' => array( + 3 => array( + 'value' => '', + 'version' => '2.3.0', + ), + ), + 'get_wp_title_rss' => array( + 1 => array( + 'value' => '–', + 'version' => '4.4.0', + ), + ), + 'is_email' => array( + 2 => array( + 'value' => false, + 'version' => '3.0.0', + ), + ), + 'load_plugin_textdomain' => array( + 2 => array( + 'value' => false, + 'version' => '2.7.0', + ), + ), + 'safecss_filter_attr' => array( + 2 => array( + 'value' => '', + 'version' => '2.8.1', + ), + ), + 'the_attachment_link' => array( + 3 => array( + 'value' => false, + 'version' => '2.5.0', + ), + ), + 'the_author' => array( + 1 => array( + 'value' => '', + 'version' => '2.1.0', + ), + 2 => array( + 'value' => true, + 'version' => '1.5.0', + ), + ), + 'the_author_posts_link' => array( + 1 => array( + 'value' => '', + 'version' => '2.1.0', + ), + ), + 'trackback_rdf' => array( + 1 => array( + 'value' => '', + 'version' => '2.5.0', + ), + ), + 'trackback_url' => array( + 1 => array( + 'value' => true, + 'version' => '2.5.0', + ), + ), + 'update_blog_option' => array( + 4 => array( + 'value' => null, + 'version' => '3.1.0', + ), + ), + 'update_blog_status' => array( + 4 => array( + 'value' => null, + 'version' => '3.1.0', + ), + ), + 'update_user_status' => array( + 4 => array( + 'value' => null, + 'version' => '3.0.2', + ), + ), + 'unregister_setting' => array( + 4 => array( + 'value' => '', + 'version' => '4.7.0', + ), + ), + 'wp_get_http_headers' => array( + 2 => array( + 'value' => false, + 'version' => '2.7.0', + ), + ), + 'wp_get_sidebars_widgets' => array( + 1 => array( + 'value' => true, + 'version' => '2.8.1', + ), + ), + 'wp_install' => array( + 5 => array( + 'value' => '', + 'version' => '2.6.0', + ), + ), + 'wp_new_user_notification' => array( + 2 => array( + 'value' => null, + 'version' => '4.3.1', + ), + ), + 'wp_notify_postauthor' => array( + 2 => array( + 'value' => null, + 'version' => '3.8.0', + ), + ), + 'wp_title_rss' => array( + 1 => array( + 'value' => '–', + 'version' => '4.4.0', + ), + ), + 'wp_upload_bits' => array( + 2 => array( + 'value' => '', + 'version' => '2.0.0', + ), + ), + 'xfn_check' => array( + 3 => array( + 'value' => '', + 'version' => '2.5.0', + ), + ), + + ); // End $target_functions. + + /** + * Process the parameters of a matched function. + * + * @since 0.12.0 + * + * @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 + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + + $this->get_wp_version_from_cl(); + + $paramCount = count( $parameters ); + foreach ( $this->target_functions[ $matched_content ] as $position => $parameter_args ) { + + // Check that number of parameters defined is not less than the position to check. + if ( $position > $paramCount ) { + break; + } + + // The list will need to updated if the default value is not supported. + switch ( $parameters[ $position ]['raw'] ) { + case 'true': + $matched_parameter = true; + break; + case 'false': + $matched_parameter = false; + break; + case 'null': + $matched_parameter = null; + break; + case 'array()': + case '[]': + $matched_parameter = array(); + break; + default: + $matched_parameter = $this->strip_quotes( $parameters[ $position ]['raw'] ); + break; + } + + if ( $parameter_args['value'] === $matched_parameter ) { + continue; + } + + $message = 'The parameter "%s" at position #%s of %s() has been deprecated since WordPress version %s.'; + $is_error = version_compare( $parameter_args['version'], $this->minimum_supported_version, '<' ); + $code = $this->string_to_errorcode( ucfirst( $matched_content ) . 'Param' . $position . 'Found' ); + + $data = array( + $parameters[ $position ]['raw'], + $position, + $matched_content, + $parameter_args['version'], + ); + + if ( isset( $parameter_args['value'] ) && $position < $paramCount ) { + $message .= ' Use "%s" instead.'; + $data[] = (string) $parameter_args['value']; + } else { + $message .= ' Instead do not pass the parameter.'; + } + + $this->addMessage( $message, $stackPtr, $is_error, $code, $data, 0 ); + } + } + +} diff --git a/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php b/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php new file mode 100644 index 00000000..7e47b042 --- /dev/null +++ b/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php @@ -0,0 +1,221 @@ +<?php +/** + * WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Sniffs\WP; + +use WordPress\AbstractFunctionParameterSniff; +use PHP_CodeSniffer_Tokens as Tokens; + +/** + * Warns against usage of discouraged WP CONSTANTS and recommends alternatives. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class DiscouragedConstantsSniff extends AbstractFunctionParameterSniff { + + /** + * List of discouraged WP constants and their replacements. + * + * @since 0.14.0 + * + * @var array + */ + protected $discouraged_constants = array( + 'STYLESHEETPATH' => 'get_stylesheet_directory()', + 'TEMPLATEPATH' => 'get_template_directory()', + 'PLUGINDIR' => 'WP_PLUGIN_DIR', + 'MUPLUGINDIR' => 'WPMU_PLUGIN_DIR', + 'HEADER_IMAGE' => 'add_theme_support( \'custom-header\' )', + 'NO_HEADER_TEXT' => 'add_theme_support( \'custom-header\' )', + 'HEADER_TEXTCOLOR' => 'add_theme_support( \'custom-header\' )', + 'HEADER_IMAGE_WIDTH' => 'add_theme_support( \'custom-header\' )', + 'HEADER_IMAGE_HEIGHT' => 'add_theme_support( \'custom-header\' )', + 'BACKGROUND_COLOR' => 'add_theme_support( \'custom-background\' )', + 'BACKGROUND_IMAGE' => 'add_theme_support( \'custom-background\' )', + ); + + /** + * Array of functions to check. + * + * @since 0.14.0 + * + * @var array <string function name> => <int parameter position> + */ + protected $target_functions = array( + 'define' => 1, + ); + + /** + * Array of tokens which if found preceding the $stackPtr indicate that a T_STRING is not a constant. + * + * @var array + */ + private $preceding_tokens_to_ignore = array( + T_NAMESPACE => true, + T_USE => true, + T_CLASS => true, + T_TRAIT => true, + T_INTERFACE => true, + T_EXTENDS => true, + T_IMPLEMENTS => true, + T_NEW => true, + T_FUNCTION => true, + T_DOUBLE_COLON => true, + T_OBJECT_OPERATOR => true, + T_INSTANCEOF => true, + T_GOTO => true, + + ); + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + if ( isset( $this->target_functions[ strtolower( $this->tokens[ $stackPtr ]['content'] ) ] ) ) { + // Disallow excluding function groups for this sniff. + $this->exclude = ''; + + return parent::process_token( $stackPtr ); + + } else { + return $this->process_arbitrary_tstring( $stackPtr ); + } + } + + /** + * Process an arbitrary T_STRING token to determine whether it is one of the target constants. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return void + */ + public function process_arbitrary_tstring( $stackPtr ) { + $content = $this->tokens[ $stackPtr ]['content']; + + if ( ! isset( $this->discouraged_constants[ $content ] ) ) { + return; + } + + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( false !== $next && T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] ) { + // Function call or declaration. + return; + } + + $prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + if ( false !== $prev && isset( $this->preceding_tokens_to_ignore[ $this->tokens[ $prev ]['code'] ] ) ) { + // Not the use of a constant. + return; + } + + if ( false !== $prev + && T_NS_SEPARATOR === $this->tokens[ $prev ]['code'] + && T_STRING === $this->tokens[ ( $prev - 1 ) ]['code'] + ) { + // Namespaced constant of the same name. + return; + } + + if ( false !== $prev + && T_CONST === $this->tokens[ $prev ]['code'] + && true === $this->is_class_constant( $prev ) + ) { + // Class constant of the same name. + return; + } + + /* + * Deal with a number of variations of use statements. + */ + for ( $i = $stackPtr; $i > 0; $i-- ) { + if ( $this->tokens[ $i ]['line'] !== $this->tokens[ $stackPtr ]['line'] ) { + break; + } + } + + $first_on_line = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( false !== $first_on_line && T_USE === $this->tokens[ $first_on_line ]['code'] ) { + $next_on_line = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $first_on_line + 1 ), null, true ); + if ( false !== $next_on_line ) { + if ( ( T_STRING === $this->tokens[ $next_on_line ]['code'] + && 'const' === $this->tokens[ $next_on_line ]['content'] ) + || T_CONST === $this->tokens[ $next_on_line ]['code'] // Happens in some PHPCS versions. + ) { + $has_ns_sep = $this->phpcsFile->findNext( T_NS_SEPARATOR, ( $next_on_line + 1 ), $stackPtr ); + if ( false !== $has_ns_sep ) { + // Namespaced const (group) use statement. + return; + } + } else { + // Not a const use statement. + return; + } + } + } + + // Ok, this is really one of the discouraged constants. + $this->phpcsFile->addWarning( + 'Found usage of constant "%s". Use %s instead.', + $stackPtr, + 'UsageFound', + array( + $content, + $this->discouraged_constants[ $content ], + ) + ); + } + + /** + * Process the parameters of a matched `define` function call. + * + * @since 0.14.0 + * + * @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 + */ + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $function_name = strtolower( $matched_content ); + $target_param = $this->target_functions[ $function_name ]; + + // Was the target parameter passed ? + if ( ! isset( $parameters[ $target_param ] ) ) { + return; + } + + $raw_content = $this->strip_quotes( $parameters[ $target_param ]['raw'] ); + + if ( isset( $this->discouraged_constants[ $raw_content ] ) ) { + $this->phpcsFile->addWarning( + 'Found declaration of constant "%s". Use %s instead.', + $stackPtr, + 'DeclarationFound', + array( + $raw_content, + $this->discouraged_constants[ $raw_content ], + ) + ); + } + } + +} // End class. diff --git a/WordPress/Sniffs/WP/DiscouragedFunctionsSniff.php b/WordPress/Sniffs/WP/DiscouragedFunctionsSniff.php index 2f1d8859..8e1deb76 100644 --- a/WordPress/Sniffs/WP/DiscouragedFunctionsSniff.php +++ b/WordPress/Sniffs/WP/DiscouragedFunctionsSniff.php @@ -7,24 +7,29 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WP; + +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Discourages the use of various WordPress functions and suggests alternatives. * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_WP_DiscouragedFunctionsSniff extends WordPress_AbstractFunctionRestrictionsSniff { +class DiscouragedFunctionsSniff extends AbstractFunctionRestrictionsSniff { /** * Groups of functions to restrict. * * Example: groups => array( - * 'lambda' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Use anonymous functions instead please!', - * 'functions' => array( 'file_get_contents', 'create_function' ), - * ) + * 'lambda' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Use anonymous functions instead please!', + * 'functions' => array( 'file_get_contents', 'create_function' ), + * ) * ) * * @return array diff --git a/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php b/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php index ff6bf6b5..965a28ab 100644 --- a/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php +++ b/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WP; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Makes sure scripts and styles are enqueued and not explicitly echo'd. * @@ -15,8 +20,10 @@ * @package WPCS\WordPressCodingStandards * * @since 0.3.0 + * @since 0.12.0 This class now extends WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_WP_EnqueuedResourcesSniff implements PHP_CodeSniffer_Sniff { +class EnqueuedResourcesSniff extends Sniff { /** * Returns an array of tokens this test wants to listen for. @@ -24,35 +31,33 @@ class WordPress_Sniffs_WP_EnqueuedResourcesSniff implements PHP_CodeSniffer_Snif * @return array */ public function register() { - return array( - T_CONSTANT_ENCAPSED_STRING, - T_DOUBLE_QUOTED_STRING, - T_INLINE_HTML, - T_HEREDOC, - T_NOWDOC, - ); - + return Tokens::$textStringTokens; } /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack. * * @return void */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - $token = $tokens[ $stackPtr ]; + public function process_token( $stackPtr ) { + $token = $this->tokens[ $stackPtr ]; if ( preg_match( '#rel=\\\\?[\'"]?stylesheet\\\\?[\'"]?#', $token['content'] ) > 0 ) { - $phpcsFile->addError( 'Stylesheets must be registered/enqueued via wp_enqueue_style', $stackPtr, 'NonEnqueuedStylesheet' ); + $this->phpcsFile->addError( + 'Stylesheets must be registered/enqueued via wp_enqueue_style', + $stackPtr, + 'NonEnqueuedStylesheet' + ); } if ( preg_match( '#<script[^>]*(?<=src=)#', $token['content'] ) > 0 ) { - $phpcsFile->addError( 'Scripts must be registered/enqueued via wp_enqueue_script', $stackPtr, 'NonEnqueuedScript' ); + $this->phpcsFile->addError( + 'Scripts must be registered/enqueued via wp_enqueue_script', + $stackPtr, + 'NonEnqueuedScript' + ); } } // End process(). diff --git a/WordPress/Sniffs/WP/I18nSniff.php b/WordPress/Sniffs/WP/I18nSniff.php index 284ee429..1967701d 100644 --- a/WordPress/Sniffs/WP/I18nSniff.php +++ b/WordPress/Sniffs/WP/I18nSniff.php @@ -7,6 +7,12 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WP; + +use WordPress\Sniff; +use WordPress\PHPCSHelper; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Makes sure WP internationalization functions are used properly. * @@ -20,8 +26,9 @@ * - Now has the ability to handle text-domain set via the command-line * as a comma-delimited list. * `phpcs --runtime-set text_domain my-slug,default` + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_WP_I18nSniff extends WordPress_Sniff { +class I18nSniff extends Sniff { /** * These Regexes copied from http://php.net/manual/en/function.sprintf.php#93552 @@ -118,6 +125,24 @@ class WordPress_Sniffs_WP_I18nSniff extends WordPress_Sniff { */ public $check_translator_comments = true; + /** + * Whether or not the `default` text domain is one of the allowed text domains. + * + * @since 0.14.0 + * + * @var bool + */ + private $text_domain_contains_default = false; + + /** + * Whether or not the `default` text domain is the only allowed text domain. + * + * @since 0.14.0 + * + * @var bool + */ + private $text_domain_is_default = false; + /** * Returns an array of tokens this test wants to listen for. * @@ -139,14 +164,27 @@ public function register() { public function process_token( $stack_ptr ) { $token = $this->tokens[ $stack_ptr ]; + // Reset defaults. + $this->text_domain_contains_default = false; + $this->text_domain_is_default = false; + // Allow overruling the text_domain set in a ruleset via the command line. - $cl_text_domain = trim( PHP_CodeSniffer::getConfigData( 'text_domain' ) ); + $cl_text_domain = trim( PHPCSHelper::get_config_data( 'text_domain' ) ); if ( ! empty( $cl_text_domain ) ) { $this->text_domain = $cl_text_domain; } $this->text_domain = $this->merge_custom_array( $this->text_domain, array(), false ); + if ( ! empty( $this->text_domain ) ) { + if ( in_array( 'default', $this->text_domain, true ) ) { + $this->text_domain_contains_default = true; + if ( count( $this->text_domain ) === 1 ) { + $this->text_domain_is_default = true; + } + } + } + if ( '_' === $token['content'] ) { $this->phpcsFile->addError( 'Found single-underscore "_()" function when double-underscore expected.', $stack_ptr, 'SingleUnderscoreGetTextFunction' ); } @@ -162,7 +200,7 @@ public function process_token( $stack_ptr ) { $func_open_paren_token = $this->phpcsFile->findNext( T_WHITESPACE, ( $stack_ptr + 1 ), null, true ); if ( false === $func_open_paren_token || T_OPEN_PARENTHESIS !== $this->tokens[ $func_open_paren_token ]['code'] ) { - return; + return; } $arguments_tokens = array(); @@ -173,7 +211,7 @@ public function process_token( $stack_ptr ) { for ( $i = ( $func_open_paren_token + 1 ); $i < $this->tokens[ $func_open_paren_token ]['parenthesis_closer']; $i++ ) { $this_token = $this->tokens[ $i ]; $this_token['token_index'] = $i; - if ( isset( PHP_CodeSniffer_Tokens::$emptyTokens[ $this_token['code'] ] ) ) { + if ( isset( Tokens::$emptyTokens[ $this_token['code'] ] ) ) { continue; } if ( T_COMMA === $this_token['code'] ) { @@ -183,9 +221,7 @@ public function process_token( $stack_ptr ) { } // Merge consecutive single or double quoted strings (when they span multiple lines). - if ( T_CONSTANT_ENCAPSED_STRING === $this_token['code'] || 'T_DOUBLE_QUOTED_STRING' === $this_token['type'] - || T_HEREDOC === $this_token['code'] || 'T_NOWDOC' === $this_token['type'] - ) { + if ( isset( Tokens::$textStringTokens[ $this_token['code'] ] ) ) { for ( $j = ( $i + 1 ); $j < $this->tokens[ $func_open_paren_token ]['parenthesis_closer']; $j++ ) { if ( $this_token['code'] === $this->tokens[ $j ]['code'] ) { $this_token['content'] .= $this->tokens[ $j ]['content']; @@ -205,7 +241,7 @@ public function process_token( $stack_ptr ) { } $i = $this_token['parenthesis_closer']; } - } // End for(). + } if ( ! empty( $argument_tokens ) ) { $arguments_tokens[] = $argument_tokens; @@ -274,7 +310,7 @@ public function process_token( $stack_ptr ) { ); $argument_assertions[] = array( 'arg_name' => 'plural', - 'tokens' => array_shift( $arguments_tokens ), + 'tokens' => array_shift( $arguments_tokens ), ); $argument_assertions[] = array( 'arg_name' => 'domain', @@ -297,7 +333,7 @@ public function process_token( $stack_ptr ) { 'arg_name' => 'domain', 'tokens' => array_shift( $arguments_tokens ), ); - } // End if(). + } if ( ! empty( $arguments_tokens ) ) { $this->phpcsFile->addError( 'Too many arguments for function "%s".', $func_open_paren_token, 'TooManyFunctionArgs', array( $translation_function ) ); @@ -336,15 +372,34 @@ protected function check_argument_tokens( $context ) { $tokens = $context['tokens']; $arg_name = $context['arg_name']; $is_error = empty( $context['warning'] ); - $content = $tokens[0]['content']; + $content = isset( $tokens[0] ) ? $tokens[0]['content'] : ''; if ( empty( $tokens ) || 0 === count( $tokens ) ) { $code = $this->string_to_errorcode( 'MissingArg' . ucfirst( $arg_name ) ); - if ( 'domain' !== $arg_name || ! empty( $this->text_domain ) ) { + if ( 'domain' !== $arg_name ) { $this->addMessage( 'Missing $%s arg.', $stack_ptr, $is_error, $code, array( $arg_name ) ); + return false; + } + + // Ok, we're examining a text domain, now deal correctly with the 'default' text domain. + if ( true === $this->text_domain_is_default ) { + return true; } + + if ( true === $this->text_domain_contains_default ) { + $this->phpcsFile->addWarning( + 'Missing $%s arg. If this text string is supposed to use a WP Core translation, use the "default" text domain.', + $stack_ptr, + $code . 'Default', + array( $arg_name ) + ); + } elseif ( ! empty( $this->text_domain ) ) { + $this->addMessage( 'Missing $%s arg.', $stack_ptr, $is_error, $code, array( $arg_name ) ); + } + return false; } + if ( count( $tokens ) > 1 ) { $contents = ''; foreach ( $tokens as $token ) { @@ -359,13 +414,6 @@ protected function check_argument_tokens( $context ) { $this->check_text( $context ); } - if ( T_CONSTANT_ENCAPSED_STRING === $tokens[0]['code'] ) { - if ( 'domain' === $arg_name && ! empty( $this->text_domain ) && ! in_array( $this->strip_quotes( $content ), $this->text_domain, true ) ) { - $this->addMessage( 'Mismatch text domain. Expected \'%s\' but got %s.', $stack_ptr, $is_error, 'TextDomainMismatch', array( implode( "' or '", $this->text_domain ), $content ) ); - return false; - } - return true; - } if ( T_DOUBLE_QUOTED_STRING === $tokens[0]['code'] || T_HEREDOC === $tokens[0]['code'] ) { $interpolated_variables = $this->get_interpolated_variables( $content ); foreach ( $interpolated_variables as $interpolated_variable ) { @@ -375,10 +423,54 @@ protected function check_argument_tokens( $context ) { if ( ! empty( $interpolated_variables ) ) { return false; } - if ( 'domain' === $arg_name && ! empty( $this->text_domain ) && ! in_array( $this->strip_quotes( $content ), $this->text_domain, true ) ) { - $this->addMessage( 'Mismatch text domain. Expected \'%s\' but got %s.', $stack_ptr, $is_error, 'TextDomainMismatch', array( implode( "' or '", $this->text_domain ), $content ) ); - return false; + } + + if ( isset( Tokens::$textStringTokens[ $tokens[0]['code'] ] ) ) { + if ( 'domain' === $arg_name && ! empty( $this->text_domain ) ) { + $stripped_content = $this->strip_quotes( $content ); + + if ( ! in_array( $stripped_content, $this->text_domain, true ) ) { + $this->addMessage( + 'Mismatched text domain. Expected \'%s\' but got %s.', + $stack_ptr, + $is_error, + 'TextDomainMismatch', + array( implode( "' or '", $this->text_domain ), $content ) + ); + return false; + } + + if ( true === $this->text_domain_is_default && 'default' === $stripped_content ) { + $fixable = false; + $error = 'No need to supply the text domain when the only accepted text-domain is "default".'; + $error_code = 'SuperfluousDefaultTextDomain'; + + if ( $tokens[0]['token_index'] === $stack_ptr ) { + $prev = $this->phpcsFile->findPrevious( T_WHITESPACE, ( $stack_ptr - 1 ), null, true ); + if ( false !== $prev && T_COMMA === $this->tokens[ $prev ]['code'] ) { + $fixable = true; + } + } + + if ( false === $fixable ) { + $this->phpcsFile->addWarning( $error, $stack_ptr, $error_code ); + return false; + } + + $fix = $this->phpcsFile->addFixableWarning( $error, $stack_ptr, $error_code ); + if ( true === $fix ) { + // Remove preceeding comma, whitespace and the text domain token. + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = $prev; $i <= $stack_ptr; $i++ ) { + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + $this->phpcsFile->fixer->endChangeset(); + } + + return false; + } } + return true; } @@ -463,9 +555,9 @@ protected function check_text( $context ) { // Prepare the strings for use a regex. $replace_regexes[ $i ] = '`\Q' . $unordered_matches[ $i ] . '\E`'; // Note: the initial \\ is a literal \, the four \ in the replacement translate to also to a literal \. - $replacements[ $i ] = str_replace( '\\', '\\\\', $suggestions[ $i ] ); + $replacements[ $i ] = str_replace( '\\', '\\\\', $suggestions[ $i ] ); // Note: the $ needs escaping to prevent numeric sequences after the $ being interpreted as match replacements. - $replacements[ $i ] = str_replace( '$', '\\$', $replacements[ $i ] ); + $replacements[ $i ] = str_replace( '$', '\\$', $replacements[ $i ] ); } $fix = $this->addFixableMessage( @@ -479,11 +571,9 @@ protected function check_text( $context ) { if ( true === $fix ) { $fixed_str = preg_replace( $replace_regexes, $replacements, $content, 1 ); - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->replaceToken( $stack_ptr, $fixed_str ); - $this->phpcsFile->fixer->endChangeset(); } - } // End if(). + } /* * NoEmptyStrings. @@ -525,7 +615,7 @@ protected function check_for_translator_comment( $stack_ptr, $args ) { continue; } - $previous_comment = $this->phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$commentTokens, ( $stack_ptr - 1 ) ); + $previous_comment = $this->phpcsFile->findPrevious( Tokens::$commentTokens, ( $stack_ptr - 1 ) ); if ( false !== $previous_comment ) { /* @@ -553,8 +643,8 @@ protected function check_for_translator_comment( $stack_ptr, $args ) { if ( T_COMMENT === $this->tokens[ $previous_comment ]['code'] ) { $comment_text = trim( $this->tokens[ $previous_comment ]['content'] ); - // If it's multi-line /* */ comment, collect all the parts. - if ( '*/' === substr( $comment_text, -2 ) && '/*' !== substr( $comment_text, 0, 2 ) ) { + // If it's multi-line /* */ comment, collect all the parts. + if ( '*/' === substr( $comment_text, -2 ) && '/*' !== substr( $comment_text, 0, 2 ) ) { for ( $i = ( $previous_comment - 1 ); 0 <= $i; $i-- ) { if ( T_COMMENT !== $this->tokens[ $i ]['code'] ) { break; @@ -564,14 +654,14 @@ protected function check_for_translator_comment( $stack_ptr, $args ) { } } - if ( true === $this->is_translators_comment( $comment_text ) ) { + if ( true === $this->is_translators_comment( $comment_text ) ) { // Comment is ok. return; } } elseif ( T_DOC_COMMENT_CLOSE_TAG === $this->tokens[ $previous_comment ]['code'] ) { // If it's docblock comment (wrong style) make sure that it's a translators comment. $db_start = $this->phpcsFile->findPrevious( T_DOC_COMMENT_OPEN_TAG, ( $previous_comment - 1 ) ); - $db_first_text = $this->phpcsFile->findNext( T_DOC_COMMENT_STRING, ( $db_start + 1 ), $previous_comment ); + $db_first_text = $this->phpcsFile->findNext( T_DOC_COMMENT_STRING, ( $db_start + 1 ), $previous_comment ); if ( true === $this->is_translators_comment( $this->tokens[ $db_first_text ]['content'] ) ) { $this->phpcsFile->addWarning( @@ -582,8 +672,8 @@ protected function check_for_translator_comment( $stack_ptr, $args ) { return; } } - } // End if(). - } // End if(). + } + } // Found placeholders but no translators comment. $this->phpcsFile->addWarning( @@ -592,8 +682,8 @@ protected function check_for_translator_comment( $stack_ptr, $args ) { 'MissingTranslatorsComment' ); return; - } // End foreach(). - } // End foreach(). + } + } } // End check_for_translator_comment(). diff --git a/WordPress/Sniffs/WP/PreparedSQLSniff.php b/WordPress/Sniffs/WP/PreparedSQLSniff.php index abcbbf7c..ff21f7a8 100644 --- a/WordPress/Sniffs/WP/PreparedSQLSniff.php +++ b/WordPress/Sniffs/WP/PreparedSQLSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WP; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Sniff for prepared SQL. * @@ -17,8 +22,9 @@ * @package WPCS\WordPressCodingStandards * * @since 0.8.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_WP_PreparedSQLSniff extends WordPress_Sniff { +class PreparedSQLSniff extends Sniff { /** * The lists of $wpdb methods. @@ -48,7 +54,6 @@ class WordPress_Sniffs_WP_PreparedSQLSniff extends WordPress_Sniff { T_OBJECT_OPERATOR => true, T_OPEN_PARENTHESIS => true, T_CLOSE_PARENTHESIS => true, - T_WHITESPACE => true, T_STRING_CONCAT => true, T_CONSTANT_ENCAPSED_STRING => true, T_OPEN_SQUARE_BRACKET => true, @@ -60,6 +65,9 @@ class WordPress_Sniffs_WP_PreparedSQLSniff extends WordPress_Sniff { T_START_NOWDOC => true, T_NOWDOC => true, T_END_NOWDOC => true, + T_INT_CAST => true, + T_DOUBLE_CAST => true, + T_BOOL_CAST => true, ); /** @@ -92,8 +100,12 @@ class WordPress_Sniffs_WP_PreparedSQLSniff extends WordPress_Sniff { * @return array */ public function register() { + + $this->ignored_tokens = $this->ignored_tokens + Tokens::$emptyTokens; + return array( T_VARIABLE, + T_STRING, ); } @@ -109,12 +121,7 @@ public function register() { */ public function process_token( $stackPtr ) { - // Check for $wpdb variable. - if ( '$wpdb' !== $this->tokens[ $stackPtr ]['content'] ) { - return; - } - - if ( ! $this->is_wpdb_method_call( $stackPtr ) ) { + if ( ! $this->is_wpdb_method_call( $stackPtr, $this->methods ) ) { return; } @@ -134,7 +141,9 @@ public function process_token( $stackPtr ) { $bad_variables = array_filter( $this->get_interpolated_variables( $this->tokens[ $this->i ]['content'] ), - create_function( '$symbol', 'return ! in_array( $symbol, array( "wpdb" ), true );' ) // Replace this with closure once 5.3 is minimum requirement. + function ( $symbol ) { + return ( 'wpdb' !== $symbol ); + } ); foreach ( $bad_variables as $bad_variable ) { @@ -153,7 +162,11 @@ public function process_token( $stackPtr ) { if ( T_VARIABLE === $this->tokens[ $this->i ]['code'] ) { if ( '$wpdb' === $this->tokens[ $this->i ]['content'] ) { - $this->is_wpdb_method_call( $this->i ); + $this->is_wpdb_method_call( $this->i, $this->methods ); + continue; + } + + if ( $this->is_safe_casted( $this->i ) ) { continue; } } @@ -188,64 +201,10 @@ public function process_token( $stackPtr ) { 'NotPrepared', array( $this->tokens[ $this->i ]['content'] ) ); - } // End for(). - - return $this->end; - - } // End process(). - - /** - * Checks whether this is a call to a $wpdb method that we want to sniff. - * - * The $i and $end properties are automatically set to correspond to the start - * and end of the method call. The $i property is also set if this is not a - * method call but rather the use of a $wpdb property. - * - * @since 0.8.0 - * @since 0.9.0 The return value is now always boolean. The $end and $i member - * vars are automatically updated. - * - * @param int $stackPtr The index of the $wpdb variable. - * - * @return bool Whether this is a $wpdb method call. - */ - protected function is_wpdb_method_call( $stackPtr ) { - - // Check that this is a method call. - $is_object_call = $this->phpcsFile->findNext( T_OBJECT_OPERATOR, ( $stackPtr + 1 ), null, false, null, true ); - if ( false === $is_object_call ) { - return false; - } - - $methodPtr = $this->phpcsFile->findNext( array( T_WHITESPACE ), ( $is_object_call + 1 ), null, true, null, true ); - $method = $this->tokens[ $methodPtr ]['content']; - - // Find the opening parenthesis. - $opening_paren = $this->phpcsFile->findNext( T_WHITESPACE, ( $methodPtr + 1 ), null, true, null, true ); - - if ( false === $opening_paren ) { - return false; } - $this->i = $opening_paren; - - if ( T_OPEN_PARENTHESIS !== $this->tokens[ $opening_paren ]['code'] ) { - return false; - } - - // Check that this is one of the methods that we are interested in. - if ( ! isset( $this->methods[ $method ] ) ) { - return false; - } - - // Find the end of the first parameter. - $this->end = $this->phpcsFile->findEndOfStatement( $opening_paren + 1 ); - - if ( T_COMMA !== $this->tokens[ $this->end ]['code'] ) { - $this->end += 1; - } + return $this->end; - return true; - } // End is_wpdb_method_call(). + } // End process_token(). } // End class. diff --git a/WordPress/Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php b/WordPress/Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php new file mode 100644 index 00000000..e01df14e --- /dev/null +++ b/WordPress/Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php @@ -0,0 +1,256 @@ +<?php +/** + * WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Sniffs\WhiteSpace; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + +/** + * Check & fix whitespace on the inside of arbitrary parentheses. + * + * Arbitrary parentheses are those which are not owned by a function (call), array or control structure. + * Spacing on the outside is not checked on purpose as this would too easily conflict with other spacing rules. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + * + * {@internal This sniff is a duplicate of the same sniff as pulled upstream. + * Once the upstream sniff has been merged and the minimum WPCS PHPCS requirement has gone up to + * the version in which the sniff was merged, this version can be safely removed. + * {@link https://github.com/squizlabs/PHP_CodeSniffer/pull/1701} }} + */ +class ArbitraryParenthesesSpacingSniff extends Sniff { + + /** + * The number of spaces desired on the inside of the parentheses. + * + * @since 0.14.0 + * + * @var integer + */ + public $spacingInside = 0; + + /** + * Allow newlines instead of spaces. + * + * @since 0.14.0 + * + * @var boolean + */ + public $ignoreNewlines = false; + + /** + * Tokens which when they precede an open parenthesis indicate + * that this is a type of structure this sniff should ignore. + * + * @since 0.14.0 + * + * @var array + */ + private $ignoreTokens = array(); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @since 0.14.0 + * + * @return array + */ + public function register() { + + $this->ignoreTokens = Tokens::$functionNameTokens; + $this->ignoreTokens[ T_VARIABLE ] = T_VARIABLE; + $this->ignoreTokens[ T_CLOSE_PARENTHESIS ] = T_CLOSE_PARENTHESIS; + $this->ignoreTokens[ T_CLOSE_CURLY_BRACKET ] = T_CLOSE_CURLY_BRACKET; + $this->ignoreTokens[ T_CLOSE_SQUARE_BRACKET ] = T_CLOSE_SQUARE_BRACKET; + $this->ignoreTokens[ T_CLOSE_SHORT_ARRAY ] = T_CLOSE_SHORT_ARRAY; + $this->ignoreTokens[ T_ANON_CLASS ] = T_ANON_CLASS; + $this->ignoreTokens[ T_USE ] = T_USE; + $this->ignoreTokens[ T_LIST ] = T_LIST; + $this->ignoreTokens[ T_DECLARE ] = T_DECLARE; + + // The below two tokens have been added to the Tokens::$functionNameTokens array in PHPCS 3.1.0, + // so they can be removed once the minimum PHPCS requirement of WPCS has gone up. + $this->ignoreTokens[ T_SELF ] = T_SELF; + $this->ignoreTokens[ T_STATIC ] = T_STATIC; + + // Language constructs where the use of parentheses should be discouraged instead. + $this->ignoreTokens[ T_THROW ] = T_THROW; + $this->ignoreTokens[ T_YIELD ] = T_YIELD; + $this->ignoreTokens[ T_YIELD_FROM ] = T_YIELD_FROM; + $this->ignoreTokens[ T_CLONE ] = T_CLONE; + + return array( + T_OPEN_PARENTHESIS, + T_CLOSE_PARENTHESIS, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 0.14.0 + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int|void Integer stack pointer to skip forward or void to continue + * normal file processing. + */ + public function process_token( $stackPtr ) { + + if ( isset( $this->tokens[ $stackPtr ]['parenthesis_owner'] ) ) { + // This parenthesis is owned by a function/control structure etc. + return; + } + + // More checking for the type of parenthesis we *don't* want to handle. + $opener = $stackPtr; + if ( T_CLOSE_PARENTHESIS === $this->tokens[ $stackPtr ]['code'] ) { + if ( ! isset( $this->tokens[ $stackPtr ]['parenthesis_opener'] ) ) { + // Parse error. + return; + } + + $opener = $this->tokens[ $stackPtr ]['parenthesis_opener']; + } + + $preOpener = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $opener - 1 ), null, true ); + if ( false !== $preOpener && isset( $this->ignoreTokens[ $this->tokens[ $preOpener ]['code'] ] ) ) { + // Function or language construct call. + return; + } + + /* + * Check for empty parentheses. + */ + if ( T_OPEN_PARENTHESIS === $this->tokens[ $stackPtr ]['code'] + && isset( $this->tokens[ $stackPtr ]['parenthesis_closer'] ) + ) { + $nextNonEmpty = $this->phpcsFile->findNext( T_WHITESPACE, ( $stackPtr + 1 ), null, true ); + if ( $nextNonEmpty === $this->tokens[ $stackPtr ]['parenthesis_closer'] ) { + $this->phpcsFile->addWarning( 'Empty set of arbitrary parentheses found.', $stackPtr, 'FoundEmpty' ); + + return ( $this->tokens[ $stackPtr ]['parenthesis_closer'] + 1 ); + } + } + + /* + * Check the spacing on the inside of the parentheses. + */ + $this->spacingInside = (int) $this->spacingInside; + + if ( T_OPEN_PARENTHESIS === $this->tokens[ $stackPtr ]['code'] + && isset( $this->tokens[ ( $stackPtr + 1 ) ], $this->tokens[ ( $stackPtr + 2 ) ] ) + ) { + $nextToken = $this->tokens[ ( $stackPtr + 1 ) ]; + + if ( T_WHITESPACE !== $nextToken['code'] ) { + $inside = 0; + } else { + if ( $this->tokens[ ( $stackPtr + 2 ) ]['line'] !== $this->tokens[ $stackPtr ]['line'] ) { + $inside = 'newline'; + } else { + $inside = $nextToken['length']; + } + } + + if ( $this->spacingInside !== $inside + && ( 'newline' !== $inside || false === $this->ignoreNewlines ) + ) { + $error = 'Expected %s space after open parenthesis; %s found'; + $data = array( + $this->spacingInside, + $inside, + ); + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'SpaceAfterOpen', $data ); + + if ( true === $fix ) { + $expected = ''; + if ( $this->spacingInside > 0 ) { + $expected = str_repeat( ' ', $this->spacingInside ); + } + + if ( 0 === $inside ) { + if ( '' !== $expected ) { + $this->phpcsFile->fixer->addContent( $stackPtr, $expected ); + } + } elseif ( 'newline' === $inside ) { + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = ( $stackPtr + 2 ); $i < $this->phpcsFile->numTokens; $i++ ) { + if ( T_WHITESPACE !== $this->tokens[ $i ]['code'] ) { + break; + } + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + $this->phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), $expected ); + $this->phpcsFile->fixer->endChangeset(); + } else { + $this->phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), $expected ); + } + } + } + } + + if ( T_CLOSE_PARENTHESIS === $this->tokens[ $stackPtr ]['code'] + && isset( $this->tokens[ ( $stackPtr - 1 ) ], $this->tokens[ ( $stackPtr - 2 ) ] ) + ) { + $prevToken = $this->tokens[ ( $stackPtr - 1 ) ]; + + if ( T_WHITESPACE !== $prevToken['code'] ) { + $inside = 0; + } else { + if ( $this->tokens[ ( $stackPtr - 2 ) ]['line'] !== $this->tokens[ $stackPtr ]['line'] ) { + $inside = 'newline'; + } else { + $inside = $prevToken['length']; + } + } + + if ( $this->spacingInside !== $inside + && ( 'newline' !== $inside || false === $this->ignoreNewlines ) + ) { + $error = 'Expected %s space before close parenthesis; %s found'; + $data = array( + $this->spacingInside, + $inside, + ); + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'SpaceBeforeClose', $data ); + + if ( true === $fix ) { + $expected = ''; + if ( $this->spacingInside > 0 ) { + $expected = str_repeat( ' ', $this->spacingInside ); + } + + if ( 0 === $inside ) { + if ( '' !== $expected ) { + $this->phpcsFile->fixer->addContentBefore( $stackPtr, $expected ); + } + } elseif ( 'newline' === $inside ) { + $this->phpcsFile->fixer->beginChangeset(); + for ( $i = ( $stackPtr - 2 ); $i > 0; $i-- ) { + if ( T_WHITESPACE !== $this->tokens[ $i ]['code'] ) { + break; + } + $this->phpcsFile->fixer->replaceToken( $i, '' ); + } + $this->phpcsFile->fixer->replaceToken( ( $stackPtr - 1 ), $expected ); + $this->phpcsFile->fixer->endChangeset(); + } else { + $this->phpcsFile->fixer->replaceToken( ( $stackPtr - 1 ), $expected ); + } + } + } + } + + } // End process_token(). + +} // End class. diff --git a/WordPress/Sniffs/WhiteSpace/CastStructureSpacingSniff.php b/WordPress/Sniffs/WhiteSpace/CastStructureSpacingSniff.php index 5c4a646c..75b42014 100755 --- a/WordPress/Sniffs/WhiteSpace/CastStructureSpacingSniff.php +++ b/WordPress/Sniffs/WhiteSpace/CastStructureSpacingSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WhiteSpace; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Ensure cast statements don't contain whitespace, but *are* surrounded by whitespace, based upon Squiz code. * @@ -17,8 +22,10 @@ * @since 0.3.0 * @since 0.11.0 This sniff now has the ability to fix the issues it flags. * @since 0.11.0 The error level for all errors thrown by this sniff has been raised from warning to error. + * @since 0.12.0 This class now extends WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_WhiteSpace_CastStructureSpacingSniff implements PHP_CodeSniffer_Sniff { +class CastStructureSpacingSniff extends Sniff { /** * Returns an array of tokens this test wants to listen for. @@ -26,34 +33,31 @@ class WordPress_Sniffs_WhiteSpace_CastStructureSpacingSniff implements PHP_CodeS * @return array */ public function register() { - return PHP_CodeSniffer_Tokens::$castTokens; + return Tokens::$castTokens; } /** * Processes this test, when one of its tokens is encountered. * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in the - * stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack. * * @return void */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); + public function process_token( $stackPtr ) { - if ( T_WHITESPACE !== $tokens[ ( $stackPtr - 1 ) ]['code'] ) { + if ( T_WHITESPACE !== $this->tokens[ ( $stackPtr - 1 ) ]['code'] ) { $error = 'No space before opening casting parenthesis is prohibited'; - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceBeforeOpenParenthesis' ); + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceBeforeOpenParenthesis' ); if ( true === $fix ) { - $phpcsFile->fixer->addContentBefore( $stackPtr, ' ' ); + $this->phpcsFile->fixer->addContentBefore( $stackPtr, ' ' ); } } - if ( T_WHITESPACE !== $tokens[ ( $stackPtr + 1 ) ]['code'] ) { + if ( T_WHITESPACE !== $this->tokens[ ( $stackPtr + 1 ) ]['code'] ) { $error = 'No space after closing casting parenthesis is prohibited'; - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceAfterCloseParenthesis' ); + $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceAfterCloseParenthesis' ); if ( true === $fix ) { - $phpcsFile->fixer->addContent( $stackPtr, ' ' ); + $this->phpcsFile->fixer->addContent( $stackPtr, ' ' ); } } } diff --git a/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php b/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php index 36d89977..2c70b77e 100644 --- a/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php +++ b/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WhiteSpace; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Enforces spacing around logical operators and assignments, based upon Squiz code. * @@ -16,13 +21,14 @@ * @since 2013-06-11 This sniff no longer supports JS. * @since 0.3.0 This sniff now has the ability to fix most errors it flags. * @since 0.7.0 This class now extends WordPress_Sniff. + * @since 0.13.0 Class name changed: this class is now namespaced. * * Last synced with base class 2017-01-15 at commit b024ad84656c37ef5733c6998ebc1e60957b2277. - * Note: This class has diverged quite far from the original. All the same, checking occassionally + * Note: This class has diverged quite far from the original. All the same, checking occasionally * to see if there are upstream fixes made from which this sniff can benefit, is warranted. * @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php */ -class WordPress_Sniffs_WhiteSpace_ControlStructureSpacingSniff extends WordPress_Sniff { +class ControlStructureSpacingSniff extends Sniff { /** * Check for blank lines on start/end of control structures. @@ -41,7 +47,7 @@ class WordPress_Sniffs_WhiteSpace_ControlStructureSpacingSniff extends WordPress /** * Require for space before T_COLON when using the alternative syntax for control structures. * - * @var string one of 'required', 'forbidden', optional' + * @var string one of 'required', 'forbidden', 'optional' */ public $space_before_colon = 'required'; @@ -117,9 +123,7 @@ public function process_token( $stackPtr ) { $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceAfterStructureOpen' ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->addContent( $stackPtr, ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } @@ -146,9 +150,7 @@ public function process_token( $stackPtr ) { $fix = $this->phpcsFile->addFixableError( $error, $scopeOpener, 'NoSpaceBetweenStructureColon' ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->addContentBefore( $scopeOpener, ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } } elseif ( 'forbidden' === $this->space_before_colon ) { @@ -158,15 +160,13 @@ public function process_token( $stackPtr ) { $fix = $this->phpcsFile->addFixableError( $error, ( $scopeOpener - 1 ), 'SpaceBetweenStructureColon' ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->replaceToken( ( $scopeOpener - 1 ), '' ); - $this->phpcsFile->fixer->endChangeset(); } } } - } // End if(). + } - $parenthesisOpener = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + $parenthesisOpener = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); // If this is a function declaration. if ( T_FUNCTION === $this->tokens[ $stackPtr ]['code'] ) { @@ -179,7 +179,7 @@ public function process_token( $stackPtr ) { // This function returns by reference (function &function_name() {}). $parenthesisOpener = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, ( $parenthesisOpener + 1 ), null, true @@ -189,7 +189,7 @@ public function process_token( $stackPtr ) { if ( isset( $function_name_ptr ) ) { $parenthesisOpener = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, ( $parenthesisOpener + 1 ), null, true @@ -207,9 +207,7 @@ public function process_token( $stackPtr ) { ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->replaceToken( ( $function_name_ptr + 1 ), '' ); - $this->phpcsFile->fixer->endChangeset(); } } } @@ -219,7 +217,7 @@ public function process_token( $stackPtr ) { if ( isset( $this->tokens[ $parenthesisOpener ]['parenthesis_closer'] ) ) { $usePtr = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, ( $this->tokens[ $parenthesisOpener ]['parenthesis_closer'] + 1 ), null, true, @@ -232,7 +230,7 @@ public function process_token( $stackPtr ) { $scopeOpener = $usePtr; } } - } // End if(). + } if ( T_COLON !== $this->tokens[ $parenthesisOpener ]['code'] @@ -250,9 +248,7 @@ public function process_token( $stackPtr ) { $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'SpaceBeforeClosureOpenParenthesis' ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), '' ); - $this->phpcsFile->fixer->endChangeset(); } } } elseif ( @@ -268,12 +264,10 @@ public function process_token( $stackPtr ) { $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceBeforeOpenParenthesis' ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->addContent( $stackPtr, ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } - } // End if(). + } if ( T_WHITESPACE === $this->tokens[ ( $stackPtr + 1 ) ]['code'] @@ -289,9 +283,7 @@ public function process_token( $stackPtr ) { ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } @@ -302,9 +294,7 @@ public function process_token( $stackPtr ) { $fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceAfterOpenParenthesis' ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->addContent( $parenthesisOpener, ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } elseif ( ( ' ' !== $this->tokens[ ( $parenthesisOpener + 1 ) ]['content'] && "\n" !== $this->tokens[ ( $parenthesisOpener + 1 ) ]['content'] @@ -321,9 +311,7 @@ public function process_token( $stackPtr ) { ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->replaceToken( ( $parenthesisOpener + 1 ), ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } } @@ -340,12 +328,10 @@ public function process_token( $stackPtr ) { $fix = $this->phpcsFile->addFixableError( $error, $parenthesisCloser, 'NoSpaceBeforeCloseParenthesis' ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->addContentBefore( $parenthesisCloser, ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } elseif ( ' ' !== $this->tokens[ ( $parenthesisCloser - 1 ) ]['content'] ) { - $prevNonEmpty = $this->phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$emptyTokens, ( $parenthesisCloser - 1 ), null, true ); + $prevNonEmpty = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $parenthesisCloser - 1 ), null, true ); if ( $this->tokens[ ( $parenthesisCloser ) ]['line'] === $this->tokens[ ( $prevNonEmpty + 1 ) ]['line'] ) { $error = 'Expected exactly one space before closing parenthesis; "%s" found.'; $fix = $this->phpcsFile->addFixableError( @@ -356,9 +342,7 @@ public function process_token( $stackPtr ) { ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->replaceToken( ( $parenthesisCloser - 1 ), ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } } @@ -371,12 +355,10 @@ public function process_token( $stackPtr ) { $fix = $this->phpcsFile->addFixableError( $error, $scopeOpener, 'NoSpaceAfterCloseParenthesis' ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->addContentBefore( $scopeOpener, ' ' ); - $this->phpcsFile->fixer->endChangeset(); } } - } // End if(). + } if ( isset( $this->tokens[ $parenthesisOpener ]['parenthesis_owner'] ) && ( isset( $scopeOpener ) @@ -412,12 +394,10 @@ public function process_token( $stackPtr ) { ); if ( true === $fix ) { - $this->phpcsFile->fixer->beginChangeset(); $this->phpcsFile->fixer->replaceToken( ( $parenthesisCloser + 1 ), ' ' ); - $this->phpcsFile->fixer->endChangeset(); } - } // End if(). - } // End if(). + } + } if ( false !== $this->blank_line_check && isset( $scopeOpener ) ) { $firstContent = $this->phpcsFile->findNext( T_WHITESPACE, ( $scopeOpener + 1 ), null, true ); @@ -456,7 +436,7 @@ public function process_token( $stackPtr ) { if ( $firstContent !== $scopeCloser ) { $lastContent = $this->phpcsFile->findPrevious( T_WHITESPACE, ( $scopeCloser - 1 ), null, true ); - $lastNonEmptyContent = $this->phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$emptyTokens, ( $scopeCloser - 1 ), null, true ); + $lastNonEmptyContent = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $scopeCloser - 1 ), null, true ); $checkToken = $lastContent; if ( isset( $this->tokens[ $lastNonEmptyContent ]['scope_condition'] ) ) { @@ -485,36 +465,50 @@ public function process_token( $stackPtr ) { $this->phpcsFile->fixer->endChangeset(); } break; - } // End if(). - } // End for(). - } // End if(). - } // End if(). + } + } + } + } unset( $ignore ); - } // End if(). + } if ( ! isset( $scopeCloser ) || true !== $this->blank_line_after_check ) { return; } - $trailingContent = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $scopeCloser + 1 ), null, true ); + // {@internal This is just for the blank line check. Only whitespace should be considered, + // not "other" empty tokens.}} + $trailingContent = $this->phpcsFile->findNext( T_WHITESPACE, ( $scopeCloser + 1 ), null, true ); if ( false === $trailingContent ) { return; } + if ( T_COMMENT === $this->tokens[ $trailingContent ]['code'] ) { + // Special exception for code where the comment about + // an ELSE or ELSEIF is written between the control structures. + $nextCode = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $scopeCloser + 1 ), null, true ); + + if ( T_ELSE === $this->tokens[ $nextCode ]['code'] || T_ELSEIF === $this->tokens[ $nextCode ]['code'] ) { + $trailingContent = $nextCode; + } + + // Move past end comments. + if ( $this->tokens[ $trailingContent ]['line'] === $this->tokens[ $scopeCloser ]['line'] ) { + if ( preg_match( '`^//[ ]?end`i', $this->tokens[ $trailingContent ]['content'], $matches ) > 0 ) { + $scopeCloser = $trailingContent; + $trailingContent = $this->phpcsFile->findNext( T_WHITESPACE, ( $trailingContent + 1 ), null, true ); + } + } + } + if ( T_ELSE === $this->tokens[ $trailingContent ]['code'] && T_IF === $this->tokens[ $stackPtr ]['code'] ) { // IF with ELSE. return; } - if ( T_BREAK === $this->tokens[ $trailingContent ]['code'] ) { - // If this BREAK is closing a CASE, we don't need the - // blank line after this control structure. - if ( isset( $this->tokens[ $trailingContent ]['scope_condition'] ) ) { - $condition = $this->tokens[ $trailingContent ]['scope_condition']; - if ( T_CASE === $this->tokens[ $condition ]['code'] || T_DEFAULT === $this->tokens[ $condition ]['code'] ) { - return; - } - } + if ( T_WHILE === $this->tokens[ $trailingContent ]['code'] && T_DO === $this->tokens[ $stackPtr ]['code'] ) { + // DO with WHILE. + return; } if ( T_CLOSE_TAG === $this->tokens[ $trailingContent ]['code'] ) { @@ -522,15 +516,15 @@ public function process_token( $stackPtr ) { return; } - if ( T_CLOSE_CURLY_BRACKET === $this->tokens[ $trailingContent ]['code'] ) { + if ( isset( $this->tokens[ $trailingContent ]['scope_condition'] ) + && T_CLOSE_CURLY_BRACKET === $this->tokens[ $trailingContent ]['code'] + ) { // Another control structure's closing brace. - if ( isset( $this->tokens[ $trailingContent ]['scope_condition'] ) ) { - $owner = $this->tokens[ $trailingContent ]['scope_condition']; - if ( in_array( $this->tokens[ $owner ]['code'], array( T_FUNCTION, T_CLOSURE, T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT ), true ) ) { - // The next content is the closing brace of a function, class, interface or trait - // so normal function/class rules apply and we can ignore it. - return; - } + $owner = $this->tokens[ $trailingContent ]['scope_condition']; + if ( in_array( $this->tokens[ $owner ]['code'], array( T_FUNCTION, T_CLOSURE, T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT ), true ) ) { + // The next content is the closing brace of a function, class, interface or trait + // so normal function/class rules apply and we can ignore it. + return; } if ( ( $this->tokens[ $scopeCloser ]['line'] + 1 ) !== $this->tokens[ $trailingContent ]['line'] ) { @@ -541,17 +535,21 @@ public function process_token( $stackPtr ) { if ( true === $fix ) { $this->phpcsFile->fixer->beginChangeset(); - for ( $i = ( $scopeCloser + 1 ); $i < $trailingContent; $i++ ) { + $i = ( $scopeCloser + 1 ); + while ( $this->tokens[ $i ]['line'] !== $this->tokens[ $trailingContent ]['line'] ) { $this->phpcsFile->fixer->replaceToken( $i, '' ); + $i++; } // TODO: Instead a separate error should be triggered when content comes right after closing brace. - $this->phpcsFile->fixer->addNewlineBefore( $trailingContent ); + if ( T_COMMENT !== $this->tokens[ $scopeCloser ]['code'] ) { + $this->phpcsFile->fixer->addNewlineBefore( $trailingContent ); + } $this->phpcsFile->fixer->endChangeset(); } } - } // End if(). + } - } // End process(). + } // End process_token(). } // End class. diff --git a/WordPress/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php b/WordPress/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php new file mode 100644 index 00000000..cf19c318 --- /dev/null +++ b/WordPress/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php @@ -0,0 +1,105 @@ +<?php +/** + * WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Sniffs\WhiteSpace; + +use WordPress\Sniff; +use WordPress\PHPCSHelper; + +/** + * Enforces using spaces for mid-line alignment. + * + * @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class DisallowInlineTabsSniff extends Sniff { + + /** + * The --tab-width CLI value that is being used. + * + * @var int + */ + private $tab_width; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return array( + T_OPEN_TAG, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int Integer stack pointer to skip the rest of the file. + */ + public function process_token( $stackPtr ) { + if ( ! isset( $this->tab_width ) ) { + $this->tab_width = PHPCSHelper::get_tab_width( $this->phpcsFile ); + } + + $check_tokens = array( + T_WHITESPACE => true, + T_DOC_COMMENT_WHITESPACE => true, + T_DOC_COMMENT_STRING => true, + ); + + for ( $i = ( $stackPtr + 1 ); $i < $this->phpcsFile->numTokens; $i++ ) { + // Skip all non-whitespace tokens and skip whitespace at the start of a new line. + if ( ! isset( $check_tokens[ $this->tokens[ $i ]['code'] ] ) || 1 === $this->tokens[ $i ]['column'] ) { + continue; + } + + // If tabs are being converted to spaces by the tokenizer, the + // original content should be checked instead of the converted content. + if ( isset( $this->tokens[ $i ]['orig_content'] ) ) { + $content = $this->tokens[ $i ]['orig_content']; + } else { + $content = $this->tokens[ $i ]['content']; + } + + if ( '' === $content || strpos( $content, "\t" ) === false ) { + continue; + } + + $fix = $this->phpcsFile->addFixableError( + 'Spaces must be used for mid-line alignment; tabs are not allowed', + $i, + 'NonIndentTabsUsed' + ); + if ( true === $fix ) { + if ( isset( $this->tokens[ $i ]['orig_content'] ) ) { + // Use the replacement that PHPCS has already done. + $this->phpcsFile->fixer->replaceToken( $i, $this->tokens[ $i ]['content'] ); + } else { + // Replace tabs with spaces, using an indent of $tab_width. + // Other sniffs can then correct the indent if they need to. + $spaces = str_repeat( ' ', $this->tab_width ); + $newContent = str_replace( "\t", $spaces, $this->tokens[ $i ]['content'] ); + $this->phpcsFile->fixer->replaceToken( $i, $newContent ); + } + } + } + + // Ignore the rest of the file. + return ( $this->phpcsFile->numTokens + 1 ); + + } // End process(). + +} // End class. diff --git a/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php index f6892219..25cc4c39 100644 --- a/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php +++ b/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -7,8 +7,13 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\WhiteSpace; + +use Squiz_Sniffs_WhiteSpace_OperatorSpacingSniff as PHPCS_Squiz_OperatorSpacingSniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** - * Verify operator spacing, based upon Squiz code. + * Verify operator spacing, uses the Squiz sniff, but additionally also sniffs for the `!` (boolean not) operator. * * "Always put spaces after commas, and on both sides of logical, comparison, string and assignment operators." * @@ -17,22 +22,29 @@ * @package WPCS\WordPressCodingStandards * * @since 0.1.0 - * @since 0.3.0 This sniff now has the ability to fix the issues it flags. + * @since 0.3.0 This sniff now has the ability to fix the issues it flags. + * @since 0.12.0 This sniff used to be a copy of a very old and outdated version of the + * upstream sniff. + * Now, the sniff defers completely to the upstream sniff, adding just the + * T_BOOLEAN_NOT and the logical operators (`&&` and the like) - via the + * registration method and changing the value of the customizable + * $ignoreNewlines property. + * @since 0.13.0 Class name changed: this class is now namespaced. * - * Last synced with base class December 2008 at commit f01746fd1c89e98174b16c76efd325825eb58bf1. + * Last synced with base class June 2017 at commit 41127aa4764536f38f504fb3f7b8831f05919c89. * @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php */ -class WordPress_Sniffs_WhiteSpace_OperatorSpacingSniff implements PHP_CodeSniffer_Sniff { +class OperatorSpacingSniff extends PHPCS_Squiz_OperatorSpacingSniff { /** - * A list of tokenizers this sniff supports. + * Allow newlines instead of spaces. * - * @var array + * N.B.: The upstream sniff defaults to `false`. + * + * @var boolean */ - public $supportedTokenizers = array( - 'PHP', - 'JS', - ); + public $ignoreNewlines = true; + /** * Returns an array of tokens this test wants to listen for. @@ -40,165 +52,12 @@ class WordPress_Sniffs_WhiteSpace_OperatorSpacingSniff implements PHP_CodeSniffe * @return array */ public function register() { - $comparison = PHP_CodeSniffer_Tokens::$comparisonTokens; - $operators = PHP_CodeSniffer_Tokens::$operators; - $assignment = PHP_CodeSniffer_Tokens::$assignmentTokens; - - // Union the arrays - keeps the array keys and - in this case - automatically de-dups. - $tokens = $comparison + $operators + $assignment; - $tokens[] = T_BOOLEAN_NOT; - - return $tokens; + $tokens = parent::register(); + $tokens[ T_BOOLEAN_NOT ] = T_BOOLEAN_NOT; + $logical_operators = Tokens::$booleanOperators; + // Using array union to auto-dedup. + return $tokens + $logical_operators; } - /** - * Processes this sniff, when one of its tokens is encountered. - * - * @param PHP_CodeSniffer_File $phpcsFile The current file being checked. - * @param int $stackPtr The position of the current token in the - * stack passed in $tokens. - * - * @return void - */ - public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - - if ( T_EQUAL === $tokens[ $stackPtr ]['code'] ) { - // Skip for '=&' case. - if ( isset( $tokens[ ( $stackPtr + 1 ) ] ) && T_BITWISE_AND === $tokens[ ( $stackPtr + 1 ) ]['code'] ) { - return; - } - - // Skip default values in function declarations. - if ( isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) { - $bracket = end( $tokens[ $stackPtr ]['nested_parenthesis'] ); - if ( isset( $tokens[ $bracket ]['parenthesis_owner'] ) ) { - $function = $tokens[ $bracket ]['parenthesis_owner']; - if ( T_FUNCTION === $tokens[ $function ]['code'] - || T_CLOSURE === $tokens[ $function ]['code'] - ) { - return; - } - } - } - } - - if ( T_BITWISE_AND === $tokens[ $stackPtr ]['code'] ) { - /* - // If it's not a reference, then we expect one space either side of the - // bitwise operator. - if ( false === $phpcsFile->isReference( $stackPtr ) ) { - // @todo Implement or remove ? - } - */ - return; - - } else { - if ( T_MINUS === $tokens[ $stackPtr ]['code'] ) { - // Check minus spacing, but make sure we aren't just assigning - // a minus value or returning one. - $prev = $phpcsFile->findPrevious( T_WHITESPACE, ( $stackPtr - 1 ), null, true ); - if ( T_RETURN === $tokens[ $prev ]['code'] ) { - // Just returning a negative value; eg. return -1. - return; - } - - if ( isset( PHP_CodeSniffer_Tokens::$operators[ $tokens[ $prev ]['code'] ] ) ) { - // Just trying to operate on a negative value; eg. ($var * -1). - return; - } - - if ( isset( PHP_CodeSniffer_Tokens::$comparisonTokens[ $tokens[ $prev ]['code'] ] ) ) { - // Just trying to compare a negative value; eg. ($var === -1). - return; - } - - // A list of tokens that indicate that the token is not - // part of an arithmetic operation. - $invalidTokens = array( - T_COMMA, - T_OPEN_PARENTHESIS, - T_OPEN_SQUARE_BRACKET, - ); - - if ( in_array( $tokens[ $prev ]['code'], $invalidTokens, true ) ) { - // Just trying to use a negative value; eg. myFunction($var, -2). - return; - } - - $number = $phpcsFile->findNext( T_WHITESPACE, ( $stackPtr + 1 ), null, true ); - if ( T_LNUMBER === $tokens[ $number ]['code'] ) { - $semi = $phpcsFile->findNext( T_WHITESPACE, ( $number + 1 ), null, true ); - if ( T_SEMICOLON === $tokens[ $semi ]['code'] ) { - if ( false !== $prev && isset( PHP_CodeSniffer_Tokens::$assignmentTokens[ $tokens[ $prev ]['code'] ] ) ) { - // This is a negative assignment. - return; - } - } - } - } // End if(). - - $operator = $tokens[ $stackPtr ]['content']; - - if ( T_WHITESPACE !== $tokens[ ( $stackPtr - 1 ) ]['code'] ) { - $error = 'Expected 1 space before "%s"; 0 found'; - $data = array( $operator ); - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceBefore', $data ); - if ( true === $fix ) { - $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->addContentBefore( $stackPtr, ' ' ); - $phpcsFile->fixer->endChangeset(); - } - } elseif ( 1 !== strlen( $tokens[ ( $stackPtr - 1 ) ]['content'] ) && 1 !== $tokens[ ( $stackPtr - 1 ) ]['column'] ) { - // Don't throw an error for assignments, because other standards allow - // multiple spaces there to align multiple assignments. - if ( false === isset( PHP_CodeSniffer_Tokens::$assignmentTokens[ $tokens[ $stackPtr ]['code'] ] ) ) { - $found = strlen( $tokens[ ( $stackPtr - 1 ) ]['content'] ); - $error = 'Expected 1 space before "%s"; %s found'; - $data = array( - $operator, - $found, - ); - - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'SpacingBefore', $data ); - if ( true === $fix ) { - $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->replaceToken( ( $stackPtr - 1 ), ' ' ); - $phpcsFile->fixer->endChangeset(); - } - } - } // End if(). - - if ( '-' !== $operator ) { - if ( T_WHITESPACE !== $tokens[ ( $stackPtr + 1 ) ]['code'] ) { - $error = 'Expected 1 space after "%s"; 0 found'; - $data = array( $operator ); - - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'NoSpaceAfter', $data ); - if ( true === $fix ) { - $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->addContent( $stackPtr, ' ' ); - $phpcsFile->fixer->endChangeset(); - } - } elseif ( 1 !== strlen( $tokens[ ( $stackPtr + 1 ) ]['content'] ) ) { - $found = strlen( $tokens[ ( $stackPtr + 1 ) ]['content'] ); - $error = 'Expected 1 space after "%s"; %s found'; - $data = array( - $operator, - $found, - ); - - $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'SpacingAfter', $data ); - if ( true === $fix ) { - $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), ' ' ); - $phpcsFile->fixer->endChangeset(); - } - } // End if(). - } // End if(). - } // End if(). - - } // End process(). - } // End class. diff --git a/WordPress/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php b/WordPress/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php new file mode 100644 index 00000000..7f0aba3a --- /dev/null +++ b/WordPress/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php @@ -0,0 +1,181 @@ +<?php +/** + * WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Sniffs\WhiteSpace; + +use WordPress\Sniff; +use WordPress\PHPCSHelper; + +/** + * Warn on line indentation ending with spaces for precision alignment. + * + * WP demands tabs for indentation. In rare cases, spaces for precision alignment can be + * intentional and acceptable, but more often than not, this is a typo. + * + * The `Generic.WhiteSpace.DisallowSpaceIndent` sniff already checks for space indentation + * and auto-fixes to tabs. + * + * This sniff only checks for precision alignments which can not be corrected by the + * `Generic.WhiteSpace.DisallowSpaceIndent` sniff. + * + * As this may be intentional, this sniff explicitly does *NOT* contain a fixer. + * + * @link https://make.wordpress.org/core/handbook/coding-standards/php/#indentation + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class PrecisionAlignmentSniff extends Sniff { + + /** + * A list of tokenizers this sniff supports. + * + * @var array + */ + public $supportedTokenizers = array( + 'PHP', + 'JS', + 'CSS', + ); + + /** + * Allow for providing a list of tokens for which (preceding) precision alignment should be ignored. + * + * <rule ref="WordPress.WhiteSpace.PrecisionAlignment"> + * <properties> + * <property name="ignoreAlignmentTokens" type="array" + * value="T_COMMENT,T_INLINE_HTML"/> + * </properties> + * </rule> + * + * @var array + */ + public $ignoreAlignmentTokens = array(); + + /** + * The --tab-width CLI value that is being used. + * + * @var int + */ + private $tab_width; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return array( + T_OPEN_TAG, + ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param int $stackPtr The position of the current token in the stack. + * + * @return int Integer stack pointer to skip the rest of the file. + */ + public function process_token( $stackPtr ) { + if ( ! isset( $this->tab_width ) ) { + $this->tab_width = PHPCSHelper::get_tab_width( $this->phpcsFile ); + } + + // Handle any custom ignore tokens received from a ruleset. + $this->ignoreAlignmentTokens = $this->merge_custom_array( $this->ignoreAlignmentTokens ); + + $check_tokens = array( + T_WHITESPACE => true, + T_INLINE_HTML => true, + T_DOC_COMMENT_WHITESPACE => true, + T_COMMENT => true, + ); + + for ( $i = ( $stackPtr + 1 ); $i < $this->phpcsFile->numTokens; $i++ ) { + if ( ! isset( $this->tokens[ ( $i + 1 ) ] ) ) { + break; + } + + if ( 1 !== $this->tokens[ $i ]['column'] ) { + continue; + } elseif ( isset( $check_tokens[ $this->tokens[ $i ]['code'] ] ) === false + || T_WHITESPACE === $this->tokens[ ( $i + 1 ) ]['code'] + || $this->tokens[ $i ]['content'] === $this->phpcsFile->eolChar + || isset( $this->ignoreAlignmentTokens[ $this->tokens[ $i ]['type'] ] ) + || isset( $this->ignoreAlignmentTokens[ $this->tokens[ ( $i + 1 ) ]['type'] ] ) + ) { + continue; + } + + $spaces = 0; + switch ( $this->tokens[ $i ]['type'] ) { + case 'T_WHITESPACE': + $spaces = ( $this->tokens[ $i ]['length'] % $this->tab_width ); + break; + + case 'T_DOC_COMMENT_WHITESPACE': + $length = $this->tokens[ $i ]['length']; + $spaces = ( $length % $this->tab_width ); + + if ( ( T_DOC_COMMENT_STAR === $this->tokens[ ( $i + 1 ) ]['code'] + || T_DOC_COMMENT_CLOSE_TAG === $this->tokens[ ( $i + 1 ) ]['code'] ) + && 0 !== $spaces + ) { + // One alignment space expected before the *. + --$spaces; + } + break; + + case 'T_COMMENT': + /* + * Indentation whitespace for subsequent lines of multi-line comments + * are tokenized as part of the comment. + */ + $comment = ltrim( $this->tokens[ $i ]['content'] ); + $whitespace = str_replace( $comment, '', $this->tokens[ $i ]['content'] ); + $length = strlen( $whitespace ); + $spaces = ( $length % $this->tab_width ); + + if ( isset( $comment[0] ) && '*' === $comment[0] && 0 !== $spaces ) { + --$spaces; + } + break; + + case 'T_INLINE_HTML': + if ( $this->tokens[ $i ]['content'] === $this->phpcsFile->eolChar ) { + $spaces = 0; + } else { + /* + * Indentation whitespace for inline HTML is part of the T_INLINE_HTML token. + */ + $content = ltrim( $this->tokens[ $i ]['content'] ); + $whitespace = str_replace( $content, '', $this->tokens[ $i ]['content'] ); + $spaces = ( strlen( $whitespace ) % $this->tab_width ); + } + break; + } + + if ( $spaces > 0 && ! $this->has_whitelist_comment( 'precision alignment', $i ) ) { + $this->phpcsFile->addWarning( + 'Found precision alignment of %s spaces.', + $i, + 'Found', + array( $spaces ) + ); + } + } + + // Ignore the rest of the file. + return ( $this->phpcsFile->numTokens + 1 ); + + } // End process(). + +} // End class. diff --git a/WordPress/Sniffs/WhiteSpace/SemicolonSpacingSniff.php b/WordPress/Sniffs/WhiteSpace/SemicolonSpacingSniff.php new file mode 100644 index 00000000..14733186 --- /dev/null +++ b/WordPress/Sniffs/WhiteSpace/SemicolonSpacingSniff.php @@ -0,0 +1,65 @@ +<?php +/** + * WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Sniffs\WhiteSpace; + +use Squiz_Sniffs_WhiteSpace_SemicolonSpacingSniff as PHPCS_Squiz_SemicolonSpacingSniff; +use PHP_CodeSniffer_File as File; +use PHP_CodeSniffer_Tokens as Tokens; + +/** + * Ensure there is no whitespace before a semicolon, while allowing for empty conditions in a `for`. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class SemicolonSpacingSniff extends PHPCS_Squiz_SemicolonSpacingSniff { + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void|int + */ + public function process( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + // Don't examine semi-colons for empty conditions in `for()` control structures. + if ( isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) { + $close_parenthesis = end( $tokens[ $stackPtr ]['nested_parenthesis'] ); + + if ( isset( $tokens[ $close_parenthesis ]['parenthesis_owner'] ) ) { + $owner = $tokens[ $close_parenthesis ]['parenthesis_owner']; + + if ( T_FOR === $tokens[ $owner ]['code'] ) { + $previous = $phpcsFile->findPrevious( + Tokens::$emptyTokens, + ( $stackPtr - 1 ), + $tokens[ $owner ]['parenthesis_opener'], + true + ); + + if ( false !== $previous + && ( $previous === $tokens[ $owner ]['parenthesis_opener'] + || T_SEMICOLON === $tokens[ $previous ]['code'] ) + ) { + return; + } + } + } + } + + return parent::process( $phpcsFile, $stackPtr ); + } + +} // End class. diff --git a/WordPress/Sniffs/XSS/EscapeOutputSniff.php b/WordPress/Sniffs/XSS/EscapeOutputSniff.php index 7dec382e..8c0c76b4 100644 --- a/WordPress/Sniffs/XSS/EscapeOutputSniff.php +++ b/WordPress/Sniffs/XSS/EscapeOutputSniff.php @@ -7,6 +7,11 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Sniffs\XSS; + +use WordPress\Sniff; +use PHP_CodeSniffer_Tokens as Tokens; + /** * Verifies that all outputted strings are escaped. * @@ -15,11 +20,14 @@ * @package WPCS\WordPressCodingStandards * * @since 2013-06-11 - * @since 0.4.0 This class now extends WordPress_Sniff. - * @since 0.5.0 The various function list properties which used to be contained in this class - * have been moved to the WordPress_Sniff parent class. + * @since 0.4.0 This class now extends WordPress_Sniff. + * @since 0.5.0 The various function list properties which used to be contained in this class + * have been moved to the WordPress_Sniff parent class. + * @since 0.12.0 This sniff will now also check for output escaping when using shorthand + * echo tags `<?=`. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Sniffs_XSS_EscapeOutputSniff extends WordPress_Sniff { +class EscapeOutputSniff extends Sniff { /** * Custom list of functions which escape values for output. @@ -44,7 +52,7 @@ class WordPress_Sniffs_XSS_EscapeOutputSniff extends WordPress_Sniff { * * @since 0.3.0 * @deprecated 0.5.0 Use $customEscapingFunctions instead. - * @see WordPress_Sniffs_XSS_EscapeOutputSniff::$customEscapingFunctions + * @see \WordPress\Sniffs\XSS\EscapeOutputSniff::$customEscapingFunctions * * @var string|string[] */ @@ -69,7 +77,7 @@ class WordPress_Sniffs_XSS_EscapeOutputSniff extends WordPress_Sniff { */ protected $unsafePrintingFunctions = array( '_e' => 'esc_html_e() or esc_attr_e()', - '_ex' => 'esc_html_ex() or esc_attr_ex()', + '_ex' => 'echo esc_html_x() or echo esc_attr_x()', ); /** @@ -93,6 +101,8 @@ class WordPress_Sniffs_XSS_EscapeOutputSniff extends WordPress_Sniff { /** * List of names of the tokens representing PHP magic constants. * + * @since 0.10.0 + * * @var array */ private $magic_constant_tokens = array( @@ -150,13 +160,28 @@ class WordPress_Sniffs_XSS_EscapeOutputSniff extends WordPress_Sniff { * @return array */ public function register() { - return array( + + $tokens = array( T_ECHO, T_PRINT, T_EXIT, T_STRING, + T_OPEN_TAG_WITH_ECHO, ); + /* + * Check whether short open echo tags are disabled and if so, register the + * T_INLINE_HTML token which is how short open tags are being handled in that case. + * + * In PHP < 5.4, support for short open echo tags depended on whether the + * `short_open_tag` ini directive was set to `true`. + * For PHP >= 5.4, the `short_open_tag` no longer affects the short open + * echo tags and these are now always enabled. + */ + if ( PHP_VERSION_ID < 50400 && false === (bool) ini_get( 'short_open_tag' ) ) { + $tokens[] = T_INLINE_HTML; + } + return $tokens; } /** @@ -174,7 +199,7 @@ public function process_token( $stackPtr ) { $function = $this->tokens[ $stackPtr ]['content']; // Find the opening parenthesis (if present; T_ECHO might not have it). - $open_paren = $this->phpcsFile->findNext( PHP_CodeSniffer_Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + $open_paren = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); // If function, not T_ECHO nor T_PRINT. if ( T_STRING === $this->tokens[ $stackPtr ]['code'] ) { @@ -191,6 +216,24 @@ public function process_token( $stackPtr ) { if ( in_array( $function, array( 'trigger_error', 'user_error' ), true ) ) { $end_of_statement = $this->phpcsFile->findEndOfStatement( $open_paren + 1 ); } + } elseif ( T_INLINE_HTML === $this->tokens[ $stackPtr ]['code'] ) { + // Skip if no PHP short_open_tag is found in the string. + if ( false === strpos( $this->tokens[ $stackPtr ]['content'], '<?=' ) ) { + return; + } + + // Report on what is very likely a PHP short open echo tag outputting a variable. + if ( preg_match( '`\<\?\=[\s]*(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:(?:->\S+|\[[^\]]+\]))*)[\s]*;?[\s]*\?\>`', $this->tokens[ $stackPtr ]['content'], $matches ) > 0 ) { + $this->phpcsFile->addError( + "All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '%s'.", + $stackPtr, + 'OutputNotEscapedShortEcho', + array( $matches[1] ) + ); + return; + } + + return; } // Checking for the ignore comment, ex: //xss ok. @@ -199,7 +242,12 @@ public function process_token( $stackPtr ) { } if ( isset( $end_of_statement, $this->unsafePrintingFunctions[ $function ] ) ) { - $error = $this->phpcsFile->addError( "Expected next thing to be an escaping function (like %s), not '%s'", $stackPtr, 'UnsafePrintingFunction', array( $this->unsafePrintingFunctions[ $function ], $function ) ); + $error = $this->phpcsFile->addError( + "All output should be run through an escaping function (like %s), found '%s'.", + $stackPtr, + 'UnsafePrintingFunction', + array( $this->unsafePrintingFunctions[ $function ], $function ) + ); // If the error was reported, don't bother checking the function's arguments. if ( $error ) { @@ -213,7 +261,7 @@ public function process_token( $stackPtr ) { if ( ! isset( $end_of_statement ) ) { $end_of_statement = $this->phpcsFile->findNext( array( T_SEMICOLON, T_CLOSE_TAG ), $stackPtr ); - $last_token = $this->phpcsFile->findPrevious( PHP_CodeSniffer_Tokens::$emptyTokens, ( $end_of_statement - 1 ), null, true ); + $last_token = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $end_of_statement - 1 ), null, true ); // Check for the ternary operator. We only need to do this here if this // echo is lacking parenthesis. Otherwise it will be handled below. @@ -239,7 +287,12 @@ public function process_token( $stackPtr ) { for ( $i = $stackPtr; $i < $end_of_statement; $i++ ) { // Ignore whitespaces and comments. - if ( isset( PHP_CodeSniffer_Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) ) { + continue; + } + + // Ignore namespace separators. + if ( T_NS_SEPARATOR === $this->tokens[ $i ]['code'] ) { continue; } @@ -341,7 +394,7 @@ public function process_token( $stackPtr ) { // Get the first parameter (name of function being used on the array). $mapped_function = $this->phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, + Tokens::$emptyTokens, ( $function_opener + 1 ), $this->tokens[ $function_opener ]['parenthesis_closer'], true @@ -350,7 +403,7 @@ public function process_token( $stackPtr ) { // If we're able to resolve the function name, do so. if ( $mapped_function && T_CONSTANT_ENCAPSED_STRING === $this->tokens[ $mapped_function ]['code'] ) { $functionName = $this->strip_quotes( $this->tokens[ $mapped_function ]['content'] ); - $ptr = $mapped_function; + $ptr = $mapped_function; } } @@ -384,19 +437,19 @@ public function process_token( $stackPtr ) { } else { $content = $this->tokens[ $i ]['content']; $ptr = $i; - } // End if(). + } $this->phpcsFile->addError( - "Expected next thing to be an escaping function (see Codex for 'Data Validation'), not '%s'", + "All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '%s'.", $ptr, 'OutputNotEscaped', $content ); - } // End for(). + } return $end_of_statement; - } // End process(). + } // End process_token(). /** * Merge custom functions provided via a custom ruleset with the defaults, if we haven't already. @@ -429,6 +482,7 @@ protected function mergeFunctionLists() { $customEscapeFunctions, $this->escapingFunctions ); + $this->addedCustomFunctions['escape'] = $this->customEscapingFunctions; $this->addedCustomFunctions['sanitize'] = $this->customSanitizingFunctions; } @@ -438,6 +492,7 @@ protected function mergeFunctionLists() { $this->customAutoEscapedFunctions, $this->autoEscapedFunctions ); + $this->addedCustomFunctions['autoescape'] = $this->customAutoEscapedFunctions; } @@ -447,6 +502,7 @@ protected function mergeFunctionLists() { $this->customPrintingFunctions, $this->printingFunctions ); + $this->addedCustomFunctions['print'] = $this->customPrintingFunctions; } } diff --git a/WordPress/Tests/Arrays/ArrayAssignmentRestrictionsUnitTest.php b/WordPress/Tests/Arrays/ArrayAssignmentRestrictionsUnitTest.php index 11cd629d..0cd720fd 100644 --- a/WordPress/Tests/Arrays/ArrayAssignmentRestrictionsUnitTest.php +++ b/WordPress/Tests/Arrays/ArrayAssignmentRestrictionsUnitTest.php @@ -7,13 +7,20 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Arrays; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use WordPress\AbstractArrayAssignmentRestrictionsSniff; + /** * Unit test class for the ArrayAssignmentRestrictions sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_Arrays_ArrayAssignmentRestrictionsUnitTest extends AbstractSniffUnitTest { +class ArrayAssignmentRestrictionsUnitTest extends AbstractSniffUnitTest { /** * Fill in the $groups property to test the abstract class. @@ -21,7 +28,7 @@ class WordPress_Tests_Arrays_ArrayAssignmentRestrictionsUnitTest extends Abstrac protected function setUp() { parent::setUp(); - WordPress_AbstractArrayAssignmentRestrictionsSniff::$groups = array( + AbstractArrayAssignmentRestrictionsSniff::$groups = array( 'foobar' => array( 'type' => 'error', 'message' => 'Found assignment value of %s to be %s', @@ -33,6 +40,14 @@ protected function setUp() { ); } + /** + * Reset the $groups property. + */ + protected function tearDown() { + AbstractArrayAssignmentRestrictionsSniff::$groups = array(); + parent::tearDown(); + } + /** * Returns the lines where errors should occur. * @@ -40,11 +55,11 @@ protected function setUp() { */ public function getErrorList() { return array( - 3 => 1, - 5 => 1, - 7 => 2, + 3 => 1, + 5 => 1, + 7 => 2, 20 => 1, - ); + ); } diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc new file mode 100644 index 00000000..2fcbe2df --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc @@ -0,0 +1,111 @@ +<?php +/* + * Test sniff with long arrays. + */ + +$good = array( 'value1', 'value2' ); // Ok. + +$query_vars = array('food'); // Bad, no spaces after opening and before closing parenthesis. + +// Test for fixing of extra whitespace. +$test = array( 1, 2 ); + +$bad = array( 'key' => 'value' ); // OK, one item single-line associative arrays are ok. + +// Test for fixing nested associative arrays. +$bad = array( array( 'key1' => 'value1', 'key2' => array('sub1' => 1, 'sub2' => 2) ), $key3 => 'value3', array( 'value4', 10 => 'value5', ) ); // Bad. + +// Test for fixing mixed single & multi-line nested associative arrays. +$bad = array( + array( 'key1' => 'value1', array('sub1' => 1,'sub2' => 2,)), + $key3 => 'value3', + array( 'value4', 10 => 'value5' ) +); // Bad. + +// Test for fixing associative arrays with multiple values & line indented with whitespace. + $bad = array( 'key1' => 'value1', 'key2' => 'value2', $key3 => 'value3', 'value4', 10 => 'value5' ); // Bad. + +// Test for fixing associative arrays with comments between values. +$bad = array( 'key1' => 'value1', /* comment */ 'key2' => 'value2' ); // Bad. + +// Test for fixing non-associative array with a nested associative array which *will* be fixed. +$bad = array( 'value1', 'value2', array( 'sub1' => 1, 'sub2' => 2 ), 'value4' ); // Bad. + +/* + * Test spacing between array keyword and open parenthesis. + */ +$a = array(); // OK. +$b = array (); // Bad. +$train = array +( + true, + 'aaa' +); // Bad - space between keyword and opener. + +$a = array +// Bad. +( 'a', 'b' ); + +$a = array /* Bad. */ ( 'a', 'b' ); + +/* + * Tests for empty array with space between parentheses. + */ +// OK. +$a = array(); +$value = array( /* comment */ ); +$x = array( + // comment + ); + +// Bad. +$value = array ( ); +$value = array( ); +$x = array( + ); + +/* + * Tests for multi-line arrays - closing brace on new line + array items each on new line. + */ +// OK. +$value = array( + 1, + 2, /* Comment. */ + 3, +); + +$value = array( + 1 => $one, + 2 => $two, // Comment. + 3 => $three, // Comment. +); + +// Bad. +$value = array(1, + 2 , 3 , +); + +$value = array(1 => $one, + 2 => $two , /* Comment. */ 3 => $three , ); + +$value = array( + '1'=> TRUE, FALSE, '3' => 'aaa',); + +$x = array('name' => 'test', + ); + +$foo = array(1 +, 2); + +$fields = array( + 'value' => 'type'); + +$bad = array('key' => 'value'); // Bad, spacing around parenthesis. +$bad = array( 'key' => 'value' ); // Bad, spacing around parenthesis. + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays false + +$bad = array( 'key' => 'value' ); // Bad. +$bad = array( 'key1' => 'value1', 'key2' => 'value2' ); // Bad. + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays true diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc.fixed b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc.fixed new file mode 100644 index 00000000..3ffd300e --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc.fixed @@ -0,0 +1,166 @@ +<?php +/* + * Test sniff with long arrays. + */ + +$good = array( 'value1', 'value2' ); // Ok. + +$query_vars = array( 'food' ); // Bad, no spaces after opening and before closing parenthesis. + +// Test for fixing of extra whitespace. +$test = array( 1, 2 ); + +$bad = array( 'key' => 'value' ); // OK, one item single-line associative arrays are ok. + +// Test for fixing nested associative arrays. +$bad = array( +array( +'key1' => 'value1', +'key2' => array( +'sub1' => 1, +'sub2' => 2 +) +), +$key3 => 'value3', +array( +'value4', +10 => 'value5', +) +); // Bad. + +// Test for fixing mixed single & multi-line nested associative arrays. +$bad = array( + array( +'key1' => 'value1', +array( +'sub1' => 1, +'sub2' => 2, +) +), + $key3 => 'value3', + array( +'value4', +10 => 'value5' +) +); // Bad. + +// Test for fixing associative arrays with multiple values & line indented with whitespace. + $bad = array( +'key1' => 'value1', +'key2' => 'value2', +$key3 => 'value3', +'value4', +10 => 'value5' +); // Bad. + +// Test for fixing associative arrays with comments between values. +$bad = array( +'key1' => 'value1', /* comment */ +'key2' => 'value2' +); // Bad. + +// Test for fixing non-associative array with a nested associative array which *will* be fixed. +$bad = array( +'value1', +'value2', +array( +'sub1' => 1, +'sub2' => 2 +), +'value4' +); // Bad. + +/* + * Test spacing between array keyword and open parenthesis. + */ +$a = array(); // OK. +$b = array(); // Bad. +$train = array( + true, + 'aaa' +); // Bad - space between keyword and opener. + +$a = array +// Bad. +( 'a', 'b' ); + +$a = array /* Bad. */ ( 'a', 'b' ); + +/* + * Tests for empty array with space between parentheses. + */ +// OK. +$a = array(); +$value = array( /* comment */ ); +$x = array( + // comment + ); + +// Bad. +$value = array(); +$value = array(); +$x = array(); + +/* + * Tests for multi-line arrays - closing brace on new line + array items each on new line. + */ +// OK. +$value = array( + 1, + 2, /* Comment. */ + 3, +); + +$value = array( + 1 => $one, + 2 => $two, // Comment. + 3 => $three, // Comment. +); + +// Bad. +$value = array( +1, + 2 , +3 , +); + +$value = array( +1 => $one, + 2 => $two , +/* Comment. */ 3 => $three , +); + +$value = array( + '1'=> TRUE, +FALSE, +'3' => 'aaa', +); + +$x = array( +'name' => 'test', + ); + +$foo = array( +1 +, +2 +); + +$fields = array( + 'value' => 'type' +); + +$bad = array( 'key' => 'value' ); // Bad, spacing around parenthesis. +$bad = array( 'key' => 'value' ); // Bad, spacing around parenthesis. + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays false + +$bad = array( +'key' => 'value' +); // Bad. +$bad = array( +'key1' => 'value1', +'key2' => 'value2' +); // Bad. + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays true diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc new file mode 100644 index 00000000..495847de --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc @@ -0,0 +1,94 @@ +<?php +/* + * Test sniff with short arrays. + */ + +$good = [ 'value1', 'value2' ]; // Ok. + +$query_vars = ['food']; // Bad, no spaces after opening and before closing parenthesis. + +// Test for fixing of extra whitespace. +$test = [ 1, 2 ]; + +$bad = [ 'key' => 'value' ]; // OK, one item single-line associative arrays are ok. + +// Test for fixing nested associative arrays. +$bad = [ [ 'key1' => 'value1', 'key2' => ['sub1' => 1, 'sub2' => 2] ], $key3 => 'value3', [ 'value4', 10 => 'value5', ] ]; // Bad. + +// Test for fixing mixed single & multi-line nested associative arrays. +$bad = [ + [ 'key1' => 'value1', ['sub1' => 1,'sub2' => 2,]], + $key3 => 'value3', + [ 'value4', 10 => 'value5' ] +]; // Bad. + +// Test for fixing associative arrays with multiple values & line indented with whitespace. + $bad = [ 'key1' => 'value1', 'key2' => 'value2', $key3 => 'value3', 'value4', 10 => 'value5' ]; // Bad. + +// Test for fixing associative arrays with comments between values. +$bad = [ 'key1' => 'value1', /* comment */ 'key2' => 'value2' ]; // Bad. + +// Test for fixing non-associative array with a nested associative array which *will* be fixed. +$bad = [ 'value1', 'value2', [ 'sub1' => 1, 'sub2' => 2 ], 'value4' ]; // Bad. + +/* + * Tests for empty array with space between parentheses. + */ +// OK. +$a = []; +$value = [ /* comment */ ]; +$x = [ + // comment + ]; + +// Bad. +$value = [ ]; +$value = [ ]; +$x = [ + ]; + +/* + * Tests for multi-line arrays - closing brace on new line + array items each on new line. + */ +// OK. +$value = [ + 1, + 2, /* Comment. */ + 3, +]; + +$value = [ + 1 => $one, + 2 => $two, // Comment. + 3 => $three, // Comment. +]; + +// Bad. +$value = [1, + 2 , 3 , +]; + +$value = [1 => $one, + 2 => $two , /* Comment. */ 3 => $three , ]; + +$value = [ + '1'=> TRUE, FALSE, '3' => 'aaa',]; + +$x = ['name' => 'test', + ]; + +$foo = [1 +, 2]; + +$fields = [ + 'value' => 'type']; + +$bad = ['key' => 'value']; // Bad, spacing around parenthesis. +$bad = [ 'key' => 'value' ]; // Bad, spacing around parenthesis. + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays false + +$bad = [ 'key' => 'value' ]; // Bad. +$bad = [ 'key1' => 'value1', 'key2' => 'value2' ]; // Bad. + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays true diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc.fixed b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc.fixed new file mode 100644 index 00000000..015fc2de --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc.fixed @@ -0,0 +1,150 @@ +<?php +/* + * Test sniff with short arrays. + */ + +$good = [ 'value1', 'value2' ]; // Ok. + +$query_vars = [ 'food' ]; // Bad, no spaces after opening and before closing parenthesis. + +// Test for fixing of extra whitespace. +$test = [ 1, 2 ]; + +$bad = [ 'key' => 'value' ]; // OK, one item single-line associative arrays are ok. + +// Test for fixing nested associative arrays. +$bad = [ +[ +'key1' => 'value1', +'key2' => [ +'sub1' => 1, +'sub2' => 2 +] +], +$key3 => 'value3', +[ +'value4', +10 => 'value5', +] +]; // Bad. + +// Test for fixing mixed single & multi-line nested associative arrays. +$bad = [ + [ +'key1' => 'value1', +[ +'sub1' => 1, +'sub2' => 2, +] +], + $key3 => 'value3', + [ +'value4', +10 => 'value5' +] +]; // Bad. + +// Test for fixing associative arrays with multiple values & line indented with whitespace. + $bad = [ +'key1' => 'value1', +'key2' => 'value2', +$key3 => 'value3', +'value4', +10 => 'value5' +]; // Bad. + +// Test for fixing associative arrays with comments between values. +$bad = [ +'key1' => 'value1', /* comment */ +'key2' => 'value2' +]; // Bad. + +// Test for fixing non-associative array with a nested associative array which *will* be fixed. +$bad = [ +'value1', +'value2', +[ +'sub1' => 1, +'sub2' => 2 +], +'value4' +]; // Bad. + +/* + * Tests for empty array with space between parentheses. + */ +// OK. +$a = []; +$value = [ /* comment */ ]; +$x = [ + // comment + ]; + +// Bad. +$value = []; +$value = []; +$x = []; + +/* + * Tests for multi-line arrays - closing brace on new line + array items each on new line. + */ +// OK. +$value = [ + 1, + 2, /* Comment. */ + 3, +]; + +$value = [ + 1 => $one, + 2 => $two, // Comment. + 3 => $three, // Comment. +]; + +// Bad. +$value = [ +1, + 2 , +3 , +]; + +$value = [ +1 => $one, + 2 => $two , +/* Comment. */ 3 => $three , +]; + +$value = [ + '1'=> TRUE, +FALSE, +'3' => 'aaa', +]; + +$x = [ +'name' => 'test', + ]; + +$foo = [ +1 +, +2 +]; + +$fields = [ + 'value' => 'type' +]; + +$bad = [ 'key' => 'value' ]; // Bad, spacing around parenthesis. +$bad = [ 'key' => 'value' ]; // Bad, spacing around parenthesis. + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays false + +$bad = [ +'key' => 'value' +]; // Bad. +$bad = [ +'key1' => 'value1', +'key2' => 'value2' +]; // Bad. + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays true diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.inc b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.inc deleted file mode 100644 index 31689bfb..00000000 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.inc +++ /dev/null @@ -1,24 +0,0 @@ -<?php - -$good = array( 'value1', 'value2' ); // Ok. - -$query_vars = array('food'); // Bad, no spaces after opening and before closing parenthesis. - -// Test for fixing of extra whitespace. -$test = array( 1, 2 ); - -$bad = array( 'key' => 'value' ); // Bad, each value of an associative array should start on a new line. - -// Test for fixing nested associative arrays. -$bad = array( array( 'key1' => 'value1', 'key2' => ['sub1' => 1, 'sub2' => 2] ), $key3 => 'value3', [ 'value4', 10 => 'value5', ] ); // Bad. - -// Test for fixing mixed single & multi-line nested associative arrays. -$bad = array( - array( 'key1' => 'value1', array('sub1' => 1,'sub2' => 2,)), - $key3 => 'value3', - [ 'value4', 10 => 'value5' ] -); // Bad. - -// Test for fixing associative arrays with multiple values & whitespace in front. -// This needs to be the last test as the scope indent sniff will otherwise screw things up. - $bad = array( 'key1' => 'value1', 'key2' => 'value2', $key3 => 'value3', 'value4', 10 => 'value5' ); // Bad. diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.inc.fixed b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.inc.fixed deleted file mode 100644 index 72d2c815..00000000 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.inc.fixed +++ /dev/null @@ -1,54 +0,0 @@ -<?php - -$good = array( 'value1', 'value2' ); // Ok. - -$query_vars = array( 'food' ); // Bad, no spaces after opening and before closing parenthesis. - -// Test for fixing of extra whitespace. -$test = array( 1, 2 ); - -$bad = array( - 'key' => 'value', -); // Bad, each value of an associative array should start on a new line. - -// Test for fixing nested associative arrays. -$bad = array( - array( - 'key1' => 'value1', - 'key2' => [ - 'sub1' => 1, - 'sub2' => 2, - ], - ), - $key3 => 'value3', - [ - 'value4', - 10 => 'value5', - ], -); // Bad. - -// Test for fixing mixed single & multi-line nested associative arrays. -$bad = array( - array( - 'key1' => 'value1', - array( - 'sub1' => 1, - 'sub2' => 2, - ), - ), - $key3 => 'value3', - [ - 'value4', - 10 => 'value5', - ] -); // Bad. - -// Test for fixing associative arrays with multiple values & whitespace in front. -// This needs to be the last test as the scope indent sniff will otherwise screw things up. - $bad = array( - 'key1' => 'value1', - 'key2' => 'value2', - $key3 => 'value3', - 'value4', - 10 => 'value5', - ); // Bad. diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php index 2cb700eb..4ee95461 100644 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php @@ -7,29 +7,95 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Arrays; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the ArrayDeclarationSpacing sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_Arrays_ArrayDeclarationSpacingUnitTest extends AbstractSniffUnitTest { +class ArrayDeclarationSpacingUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. * + * @param string $testFile The name of the file being tested. + * * @return array <int line number> => <int number of errors> */ - public function getErrorList() { - return array( - 5 => 2, - 8 => 2, - 10 => 1, - 13 => 6, - 17 => 5, - 19 => 1, - 24 => 1, - ); + public function getErrorList( $testFile = '' ) { + + switch ( $testFile ) { + // Long arrays. + case 'ArrayDeclarationSpacingUnitTest.1.inc': + return array( + 8 => 2, + 11 => 2, + 16 => 4, + 20 => 2, + 22 => 1, + 26 => 1, + 29 => 1, + 32 => 1, + 38 => 1, + 39 => 1, + 45 => 1, + 49 => 1, + 62 => 2, + 63 => 1, + 64 => 1, + 84 => 1, + 85 => 1, + 88 => 1, + 89 => 2, + 92 => 3, + 94 => 1, + 97 => 1, + 98 => 2, + 101 => 1, + 103 => 2, + 104 => 2, + 108 => 1, + 109 => 1, + ); + + // Short arrays. + case 'ArrayDeclarationSpacingUnitTest.2.inc': + return array( + 8 => 2, + 11 => 2, + 16 => 4, + 20 => 2, + 22 => 1, + 26 => 1, + 29 => 1, + 32 => 1, + 45 => 1, + 46 => 1, + 47 => 1, + 67 => 1, + 68 => 1, + 71 => 1, + 72 => 2, + 75 => 3, + 77 => 1, + 80 => 1, + 81 => 2, + 84 => 1, + 86 => 2, + 87 => 2, + 91 => 1, + 92 => 1, + ); + + default: + return array(); + } } /** diff --git a/WordPress/Tests/Arrays/ArrayDeclarationUnitTest.txt b/WordPress/Tests/Arrays/ArrayDeclarationUnitTest.txt deleted file mode 100644 index 4b8ea342..00000000 --- a/WordPress/Tests/Arrays/ArrayDeclarationUnitTest.txt +++ /dev/null @@ -1,13 +0,0 @@ -/** - * THIS SNIFF IS INTENTIONALLY NOT TESTED HERE. - * - * The WordPress.Arrays.ArrayDeclaration sniff is a duplicate of the upstream - * Squiz.Arrays.ArrayDeclaration sniff with some checks commented out. - * - * As such all checks contained in the sniff are already tested upstream and - * the tests should not be duplicated here. - * - * @package WPCS\WordPressCodingStandards - * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards - * @license https://opensource.org/licenses/MIT MIT - */ diff --git a/WordPress/Tests/Arrays/ArrayIndentationUnitTest.1.inc b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.1.inc new file mode 100644 index 00000000..eb870c31 --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.1.inc @@ -0,0 +1,401 @@ +<?php + +$ok = array( + 'value', + 123, +); + +$ok_with_keys = [ + 'key1' => 'value', + 'key2' => 'value', // Comment after item. +]; + +$ok_nested = array( + 'key1' => array( + 'key1' => 'value', + 'key2' => 'value', + ), + 'key2' => 'value', +); + + +$bad_phpcs_style = array( + 'value', + 123, // Comment after item. + ); + +$bad_phpcs_style_with_keys = array( + 'key1' => 'value', + 'key2' => 'value', + ); + +$bad_phpcs_style_nested = array( + 'key1' => [ + 'key1' => 'value', + + 'key2' => 'value', + + ], + 'key2' => 'value', + ); + +// Arrays with initial indent. + $bad_mixed_indent = [ +'value', + 123, + ]; + + $bad_mixed_indent_with_keys = array( + + 'key1' => 'value', +'key2' => 'value', + ); + + $bad_mixed_indent_nested = [ + 'key1' => array( + 'key1' => 'value', + 'key2' => 'value', + ), + + 'key2' => 'value', + ]; + +$empty = [ + + + ]; + +// Same-line items in mixed arrays should be ignored. +$mixed_1 = array('something', 'else', array( + 'key1' => 'value', + 'key2' => 'value', // Comment after item. + ), +); + +$mixed_2 = array( + array( + 'key1' => 'value', + 'key2' => 'value', // Comment after item. + ), + 'something', 'else', +); + +// Issue 998: don't fix the indentation for the closer if it's not on its own line. +$a = array( + 'foo' => bar(1, 29)); + +$a = array( + 'foo' => bar(1, 29) ); + +/* + * Issue 973 - multi-line value indentation. + */ +// OK. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + +// Too little overall indentation. +get_current_screen()->add_help_tab( array( +'id' => 'overview', +'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + +// Too much overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + + +// Too little overall with more than the minimum indentation for some subsequent lines. + get_current_screen()->add_help_tab( array( + 'id' => 'screen-content', + 'content' => '<p>' . __('You can customize the display of this screen’s contents in a number of ways:') . '</p>' . + '<ul>' . + '<li>' . __('You can hide/display columns based on your needs and decide how many posts to list per screen using the Screen Options tab.') . '</li>' . + '<li>' . __( 'You can filter the list of posts by post status using the text links above the posts list to only show posts with that status. The default view is to show all posts.' ) . '</li>' . + '<li>' . __('You can view posts in a simple title list or with an excerpt using the Screen Options tab.') . '</li>' . + '<li>' . __('You can refine the list to show only posts in a specific category or from a specific month by using the dropdown menus above the posts list. Click the Filter button after making your selection. You also can refine the list by clicking on the post author, category or tag in the posts list.') . '</li>' . + '</ul>' + ) ); + +// Too much overall with more than the minimum indentation for some subsequent lines. + get_current_screen()->add_help_tab( array( + 'id' => 'bulk-actions', + 'content' => + '<p>' . __('You can also edit or move multiple posts to the trash at once. Select the posts you want to act on using the checkboxes, then select the action you want to take from the Bulk Actions menu and click Apply.') . '</p>' . + '<p>' . __('When using Bulk Edit, you can change the metadata (categories, author, etc.) for all selected posts at once. To remove a post from the grouping, just click the x next to its name in the Bulk Edit area that appears.') . '</p>' + ) ); + +// Too much overall with less than the minimum indentation for some subsequent lines. +get_current_screen()->add_help_tab( array( + 'id' => 'screen-content', + 'content' => +'<div>' . + '<ul>' . + '<li>' . __('You can hide/display columns based on your needs and decide how many posts to list per screen using the Screen Options tab.') . '</li>' . + '</ul>' . +'</div>' +) ); + + // Mixed indentation. + get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. +'title' => + __('Overview') . + 'something', + /* Here we have a comment. */ + 'content' => 'value' . + 'value' . + 'value' . + 'value', + ) ); + +// OK: Ignore empty lines when found as subsequent line in an item. +get_current_screen()->add_help_tab( array( + 'id' => 'something' . + + 'something else', +) ); + +/* + * Issue: Subsequent lines with Heredoc/Nowdoc syntax should be ignored. + */ +// Should only report the first line and fix the first line + the comma on the last line. +get_current_screen()->add_help_tab( array( + 'id' => <<<EOD +Here comes some text. +And some more text. +EOD +, +) ); + +// Should report the second line and fix the second line + the comma on the last line. +get_current_screen()->add_help_tab( array( + 'id' => +<<<EOD +Here comes some text. +And some more text. +EOD +, +) ); + +/* + * Heredoc/Nowdoc subsequent line fixer: lines before/after the heredoc/nowdoc *should* be fixed. + */ +// Should report on the 'id' line and the first line after the heredoc and fix 'id' line +// and both lines after the heredoc. +get_current_screen()->add_help_tab( array( +'id' => <<<EOD +Here comes some text. +And some more text. +EOD +. '</p>' +. '</hr>', +) ); + +// Should report on the heredoc opener and fix the opener and the line after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => +<<<EOD +Here comes some text. +And some more text. +EOD +. '</p>', +) ); + +// Should report on the 'id' line and the line after. Should fix both + line after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => + '<p>' . <<<EOD +Here comes some text. +And some more text. +EOD + . '</p>', +) ); + +// OK. Not pretty, but not handled by this sniff as both first line + line after have acceptable alignment. +get_current_screen()->add_help_tab( array( + 'id' => + '<p>' . +<<<EOD +Here comes some text. +And some more text. +EOD + . '</p>', +) ); + +/* + * Issue 985 - arrays with comments between items. + */ +// OK. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Too little overall indentation. +get_current_screen()->add_help_tab( array( +'id' => 'overview', +// Here we have a comment. +'title' => __('Overview'), +/* Here we have a comment. */ +'content' => 'value', +) ); + +// Too much overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Mixed indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. +'title' => + __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Various multi-line comments. +$my_array = [ +/** + * Docblock about a filtered value... + */ +'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /** + * Docblock about a filtered value... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ +/* + * Multi-line inline comment about something... + */ +'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /* + * Multi-line inline comment about something... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = array( +'something' => + /** + * Docblock about a filtered value... + */ + $a = apply_filters( '...', true ), +); + +$my_array = [ + 'something' => + /** + * Docblock about a filtered value... + */ + $a = apply_filters( '...', true ), +]; + +$my_array = [ +'something' => +/* + * Multi-line inline comment about something... + */ +apply_filters( '...', true ), +]; + +$my_array = [ + 'something' => + /* + * Multi-line inline comment about something... + */ + apply_filters( '...', true ), +]; + +/* + * Test multi-line strings as the value for an array item. + */ +$test = array( + 'notes1' => '<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: + </p>', + + 'notes1' => +'<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: +</p>' . // The sniff should check & fix the next line as it is a new token. +'<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that <code>isset()</code> is faster than <code>array_key_exists()</code>, but as said above, will return false if no value has been assigned. +</p>', + + 'notes1' => // Same, but now using double quotes. +"<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: +</p>" . // The sniff should check & fix the next line as it is a new token. +"<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that <code>isset()</code> is faster than <code>array_key_exists()</code>, but as said above, will return false if no value has been assigned. +</p>", + + // Now using double quotes with a variable to force T_DOUBLE_QUOTED_STRING token. + 'notes1' => array( + "<p> + Whether to use $abc or $def depends on what you want to know: + </p>" . // The sniff should check & fix the next line as it is a new token. + "<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that $abc is faster than $def, but as said above, will return false if no value has been assigned. + </p>", + ), + + 'notes2' => array( + '<p><strong>Important</strong>: Integers between -128 and 255 are interpreted as the ASCII value pointing to a character (negative values have 256 added in order to allow characters in the Extended ASCII range).<br /> + In any other case, integers are interpreted as a string containing the decimal digits of the integer.</p>', + ), + + 'tooltip' => ' +if( ! is_array( $x ) ) { + filter_var( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); +} +else { + filter_var_array( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); // = Simplified... see note +} + ', + 'notes3' => array( + '<p>Please note: On some PHP versions <code>filter_var( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE )</code> where <code>$x = false</code> will incorrectly return <code>null</code>.<br /> + Also: with the same parameters filter_var() will return <code>false</code> instead of <code>null</code> for most objects.</p>', + '<p>The code snippet is simplified for brevity. Please refer to the source of this file on <a href="http://github.com/jrfnl/PHP-cheat-sheet-extended" target="_blank">GitHub</a> for full details on how to use filter_var_array().</p>', + ), +); + +// Issue #1179. +b( + array( + 1 => false, // wat + 2 => false, + ) +); diff --git a/WordPress/Tests/Arrays/ArrayIndentationUnitTest.1.inc.fixed b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.1.inc.fixed new file mode 100644 index 00000000..6a78a1a1 --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.1.inc.fixed @@ -0,0 +1,401 @@ +<?php + +$ok = array( + 'value', + 123, +); + +$ok_with_keys = [ + 'key1' => 'value', + 'key2' => 'value', // Comment after item. +]; + +$ok_nested = array( + 'key1' => array( + 'key1' => 'value', + 'key2' => 'value', + ), + 'key2' => 'value', +); + + +$bad_phpcs_style = array( + 'value', + 123, // Comment after item. +); + +$bad_phpcs_style_with_keys = array( + 'key1' => 'value', + 'key2' => 'value', +); + +$bad_phpcs_style_nested = array( + 'key1' => [ + 'key1' => 'value', + + 'key2' => 'value', + + ], + 'key2' => 'value', +); + +// Arrays with initial indent. + $bad_mixed_indent = [ + 'value', + 123, + ]; + + $bad_mixed_indent_with_keys = array( + + 'key1' => 'value', + 'key2' => 'value', + ); + + $bad_mixed_indent_nested = [ + 'key1' => array( + 'key1' => 'value', + 'key2' => 'value', + ), + + 'key2' => 'value', + ]; + +$empty = [ + + +]; + +// Same-line items in mixed arrays should be ignored. +$mixed_1 = array('something', 'else', array( + 'key1' => 'value', + 'key2' => 'value', // Comment after item. + ), +); + +$mixed_2 = array( + array( + 'key1' => 'value', + 'key2' => 'value', // Comment after item. + ), + 'something', 'else', +); + +// Issue 998: don't fix the indentation for the closer if it's not on its own line. +$a = array( + 'foo' => bar(1, 29)); + +$a = array( + 'foo' => bar(1, 29) ); + +/* + * Issue 973 - multi-line value indentation. + */ +// OK. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + +// Too little overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + +// Too much overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + + +// Too little overall with more than the minimum indentation for some subsequent lines. + get_current_screen()->add_help_tab( array( + 'id' => 'screen-content', + 'content' => '<p>' . __('You can customize the display of this screen’s contents in a number of ways:') . '</p>' . + '<ul>' . + '<li>' . __('You can hide/display columns based on your needs and decide how many posts to list per screen using the Screen Options tab.') . '</li>' . + '<li>' . __( 'You can filter the list of posts by post status using the text links above the posts list to only show posts with that status. The default view is to show all posts.' ) . '</li>' . + '<li>' . __('You can view posts in a simple title list or with an excerpt using the Screen Options tab.') . '</li>' . + '<li>' . __('You can refine the list to show only posts in a specific category or from a specific month by using the dropdown menus above the posts list. Click the Filter button after making your selection. You also can refine the list by clicking on the post author, category or tag in the posts list.') . '</li>' . + '</ul>' + ) ); + +// Too much overall with more than the minimum indentation for some subsequent lines. + get_current_screen()->add_help_tab( array( + 'id' => 'bulk-actions', + 'content' => + '<p>' . __('You can also edit or move multiple posts to the trash at once. Select the posts you want to act on using the checkboxes, then select the action you want to take from the Bulk Actions menu and click Apply.') . '</p>' . + '<p>' . __('When using Bulk Edit, you can change the metadata (categories, author, etc.) for all selected posts at once. To remove a post from the grouping, just click the x next to its name in the Bulk Edit area that appears.') . '</p>' + ) ); + +// Too much overall with less than the minimum indentation for some subsequent lines. +get_current_screen()->add_help_tab( array( + 'id' => 'screen-content', + 'content' => + '<div>' . + '<ul>' . + '<li>' . __('You can hide/display columns based on your needs and decide how many posts to list per screen using the Screen Options tab.') . '</li>' . + '</ul>' . + '</div>' +) ); + + // Mixed indentation. + get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => + __('Overview') . + 'something', + /* Here we have a comment. */ + 'content' => 'value' . + 'value' . + 'value' . + 'value', + ) ); + +// OK: Ignore empty lines when found as subsequent line in an item. +get_current_screen()->add_help_tab( array( + 'id' => 'something' . + + 'something else', +) ); + +/* + * Issue: Subsequent lines with Heredoc/Nowdoc syntax should be ignored. + */ +// Should only report the first line and fix the first line + the comma on the last line. +get_current_screen()->add_help_tab( array( + 'id' => <<<EOD +Here comes some text. +And some more text. +EOD + , +) ); + +// Should report the second line and fix the second line + the comma on the last line. +get_current_screen()->add_help_tab( array( + 'id' => + <<<EOD +Here comes some text. +And some more text. +EOD + , +) ); + +/* + * Heredoc/Nowdoc subsequent line fixer: lines before/after the heredoc/nowdoc *should* be fixed. + */ +// Should report on the 'id' line and the first line after the heredoc and fix 'id' line +// and both lines after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => <<<EOD +Here comes some text. +And some more text. +EOD + . '</p>' + . '</hr>', +) ); + +// Should report on the heredoc opener and fix the opener and the line after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => + <<<EOD +Here comes some text. +And some more text. +EOD + . '</p>', +) ); + +// Should report on the 'id' line and the line after. Should fix both + line after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => + '<p>' . <<<EOD +Here comes some text. +And some more text. +EOD +. '</p>', +) ); + +// OK. Not pretty, but not handled by this sniff as both first line + line after have acceptable alignment. +get_current_screen()->add_help_tab( array( + 'id' => + '<p>' . +<<<EOD +Here comes some text. +And some more text. +EOD + . '</p>', +) ); + +/* + * Issue 985 - arrays with comments between items. + */ +// OK. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Too little overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Too much overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Mixed indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => + __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Various multi-line comments. +$my_array = [ + /** + * Docblock about a filtered value... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /** + * Docblock about a filtered value... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /* + * Multi-line inline comment about something... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /* + * Multi-line inline comment about something... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = array( + 'something' => + /** + * Docblock about a filtered value... + */ + $a = apply_filters( '...', true ), +); + +$my_array = [ + 'something' => + /** + * Docblock about a filtered value... + */ + $a = apply_filters( '...', true ), +]; + +$my_array = [ + 'something' => + /* + * Multi-line inline comment about something... + */ + apply_filters( '...', true ), +]; + +$my_array = [ + 'something' => + /* + * Multi-line inline comment about something... + */ + apply_filters( '...', true ), +]; + +/* + * Test multi-line strings as the value for an array item. + */ +$test = array( + 'notes1' => '<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: + </p>', + + 'notes1' => + '<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: +</p>' . // The sniff should check & fix the next line as it is a new token. + '<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that <code>isset()</code> is faster than <code>array_key_exists()</code>, but as said above, will return false if no value has been assigned. +</p>', + + 'notes1' => // Same, but now using double quotes. + "<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: +</p>" . // The sniff should check & fix the next line as it is a new token. + "<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that <code>isset()</code> is faster than <code>array_key_exists()</code>, but as said above, will return false if no value has been assigned. +</p>", + + // Now using double quotes with a variable to force T_DOUBLE_QUOTED_STRING token. + 'notes1' => array( + "<p> + Whether to use $abc or $def depends on what you want to know: + </p>" . // The sniff should check & fix the next line as it is a new token. + "<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that $abc is faster than $def, but as said above, will return false if no value has been assigned. + </p>", + ), + + 'notes2' => array( + '<p><strong>Important</strong>: Integers between -128 and 255 are interpreted as the ASCII value pointing to a character (negative values have 256 added in order to allow characters in the Extended ASCII range).<br /> + In any other case, integers are interpreted as a string containing the decimal digits of the integer.</p>', + ), + + 'tooltip' => ' +if( ! is_array( $x ) ) { + filter_var( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); +} +else { + filter_var_array( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); // = Simplified... see note +} + ', + 'notes3' => array( + '<p>Please note: On some PHP versions <code>filter_var( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE )</code> where <code>$x = false</code> will incorrectly return <code>null</code>.<br /> + Also: with the same parameters filter_var() will return <code>false</code> instead of <code>null</code> for most objects.</p>', + '<p>The code snippet is simplified for brevity. Please refer to the source of this file on <a href="http://github.com/jrfnl/PHP-cheat-sheet-extended" target="_blank">GitHub</a> for full details on how to use filter_var_array().</p>', + ), +); + +// Issue #1179. +b( + array( + 1 => false, // wat + 2 => false, + ) +); diff --git a/WordPress/Tests/Arrays/ArrayIndentationUnitTest.2.inc b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.2.inc new file mode 100644 index 00000000..faf10b74 --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.2.inc @@ -0,0 +1,403 @@ +<?php +// @codingStandardsChangeSetting WordPress.Arrays.ArrayIndentation tabIndent false +$ok = array( + 'value', + 123, +); + +$ok_with_keys = [ + 'key1' => 'value', + 'key2' => 'value', // Comment after item. +]; + +$ok_nested = array( + 'key1' => array( + 'key1' => 'value', + 'key2' => 'value', + ), + 'key2' => 'value', +); + + +$bad_phpcs_style = array( + 'value', + 123, // Comment after item. + ); + +$bad_phpcs_style_with_keys = array( + 'key1' => 'value', + 'key2' => 'value', + ); + +$bad_phpcs_style_nested = array( + 'key1' => [ + 'key1' => 'value', + + 'key2' => 'value', + + ], + 'key2' => 'value', + ); + +// Arrays with initial indent. + $bad_mixed_indent = [ +'value', + 123, + ]; + + $bad_mixed_indent_with_keys = array( + + 'key1' => 'value', +'key2' => 'value', + ); + + $bad_mixed_indent_nested = [ + 'key1' => array( + 'key1' => 'value', + 'key2' => 'value', + ), + + 'key2' => 'value', + ]; + +$empty = [ + + + ]; + +// Same-line items in mixed arrays should be ignored. +$mixed_1 = array('something', 'else', array( + 'key1' => 'value', + 'key2' => 'value', // Comment after item. + ), +); + +$mixed_2 = array( + array( + 'key1' => 'value', + 'key2' => 'value', // Comment after item. + ), + 'something', 'else', +); + +// Issue 998: don't fix the indentation for the closer if it's not on its own line. +$a = array( + 'foo' => bar(1, 29)); + +$a = array( + 'foo' => bar(1, 29) ); + +/* + * Issue 973 - multi-line value indentation. + */ +// OK. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + +// Too little overall indentation. +get_current_screen()->add_help_tab( array( +'id' => 'overview', +'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + +// Too much overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + + +// Too little overall with more than the minimum indentation for some subsequent lines. + get_current_screen()->add_help_tab( array( + 'id' => 'screen-content', + 'content' => '<p>' . __('You can customize the display of this screen’s contents in a number of ways:') . '</p>' . + '<ul>' . + '<li>' . __('You can hide/display columns based on your needs and decide how many posts to list per screen using the Screen Options tab.') . '</li>' . + '<li>' . __( 'You can filter the list of posts by post status using the text links above the posts list to only show posts with that status. The default view is to show all posts.' ) . '</li>' . + '<li>' . __('You can view posts in a simple title list or with an excerpt using the Screen Options tab.') . '</li>' . + '<li>' . __('You can refine the list to show only posts in a specific category or from a specific month by using the dropdown menus above the posts list. Click the Filter button after making your selection. You also can refine the list by clicking on the post author, category or tag in the posts list.') . '</li>' . + '</ul>' + ) ); + +// Too much overall with more than the minimum indentation for some subsequent lines. + get_current_screen()->add_help_tab( array( + 'id' => 'bulk-actions', + 'content' => + '<p>' . __('You can also edit or move multiple posts to the trash at once. Select the posts you want to act on using the checkboxes, then select the action you want to take from the Bulk Actions menu and click Apply.') . '</p>' . + '<p>' . __('When using Bulk Edit, you can change the metadata (categories, author, etc.) for all selected posts at once. To remove a post from the grouping, just click the x next to its name in the Bulk Edit area that appears.') . '</p>' + ) ); + +// Too much overall with less than the minimum indentation for some subsequent lines. +get_current_screen()->add_help_tab( array( + 'id' => 'screen-content', + 'content' => +'<div>' . + '<ul>' . + '<li>' . __('You can hide/display columns based on your needs and decide how many posts to list per screen using the Screen Options tab.') . '</li>' . + '</ul>' . +'</div>' +) ); + + // Mixed indentation. + get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. +'title' => + __('Overview') . + 'something', + /* Here we have a comment. */ + 'content' => 'value' . + 'value' . + 'value' . + 'value', + ) ); + +// OK: Ignore empty lines when found as subsequent line in an item. +get_current_screen()->add_help_tab( array( + 'id' => 'something' . + + 'something else', +) ); + +/* + * Issue: Subsequent lines with Heredoc/Nowdoc syntax should be ignored. + */ +// Should only report the first line and fix the first line + the comma on the last line. +get_current_screen()->add_help_tab( array( + 'id' => <<<EOD +Here comes some text. +And some more text. +EOD +, +) ); + +// Should report the second line and fix the second line + the comma on the last line. +get_current_screen()->add_help_tab( array( + 'id' => +<<<EOD +Here comes some text. +And some more text. +EOD +, +) ); + +/* + * Heredoc/Nowdoc subsequent line fixer: lines before/after the heredoc/nowdoc *should* be fixed. + */ +// Should report on the 'id' line and the first line after the heredoc and fix 'id' line +// and both lines after the heredoc. +get_current_screen()->add_help_tab( array( +'id' => <<<EOD +Here comes some text. +And some more text. +EOD +. '</p>' +. '</hr>', +) ); + +// Should report on the heredoc opener and fix the opener and the line after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => +<<<EOD +Here comes some text. +And some more text. +EOD +. '</p>', +) ); + +// Should report on the 'id' line and the line after. Should fix both + line after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => + '<p>' . <<<EOD +Here comes some text. +And some more text. +EOD + . '</p>', +) ); + +// OK. Not pretty, but not handled by this sniff as both first line + line after have acceptable alignment. +get_current_screen()->add_help_tab( array( + 'id' => + '<p>' . +<<<EOD +Here comes some text. +And some more text. +EOD + . '</p>', +) ); + +/* + * Issue 985 - arrays with comments between items. + */ +// OK. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Too little overall indentation. +get_current_screen()->add_help_tab( array( +'id' => 'overview', +// Here we have a comment. +'title' => __('Overview'), +/* Here we have a comment. */ +'content' => 'value', +) ); + +// Too much overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Mixed indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. +'title' => + __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Various multi-line comments. +$my_array = [ +/** + * Docblock about a filtered value... + */ +'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /** + * Docblock about a filtered value... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ +/* + * Multi-line inline comment about something... + */ +'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /* + * Multi-line inline comment about something... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = array( +'something' => + /** + * Docblock about a filtered value... + */ + $a = apply_filters( '...', true ), +); + +$my_array = [ + 'something' => + /** + * Docblock about a filtered value... + */ + $a = apply_filters( '...', true ), +]; + +$my_array = [ +'something' => +/* + * Multi-line inline comment about something... + */ +apply_filters( '...', true ), +]; + +$my_array = [ + 'something' => + /* + * Multi-line inline comment about something... + */ + apply_filters( '...', true ), +]; + +/* + * Test multi-line strings as the value for an array item. + */ +$test = array( + 'notes1' => '<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: + </p>', + + 'notes1' => +'<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: +</p>' . // The sniff should check & fix the next line as it is a new token. +'<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that <code>isset()</code> is faster than <code>array_key_exists()</code>, but as said above, will return false if no value has been assigned. +</p>', + + 'notes1' => // Same, but now using double quotes. +"<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: +</p>" . // The sniff should check & fix the next line as it is a new token. +"<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that <code>isset()</code> is faster than <code>array_key_exists()</code>, but as said above, will return false if no value has been assigned. +</p>", + + // Now using double quotes with a variable to force T_DOUBLE_QUOTED_STRING token. + 'notes1' => array( + "<p> + Whether to use $abc or $def depends on what you want to know: + </p>" . // The sniff should check & fix the next line as it is a new token. + "<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that $abc is faster than $def, but as said above, will return false if no value has been assigned. + </p>", + ), + + 'notes2' => array( + '<p><strong>Important</strong>: Integers between -128 and 255 are interpreted as the ASCII value pointing to a character (negative values have 256 added in order to allow characters in the Extended ASCII range).<br /> + In any other case, integers are interpreted as a string containing the decimal digits of the integer.</p>', + ), + + 'tooltip' => ' +if( ! is_array( $x ) ) { + filter_var( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); +} +else { + filter_var_array( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); // = Simplified... see note +} + ', + 'notes3' => array( + '<p>Please note: On some PHP versions <code>filter_var( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE )</code> where <code>$x = false</code> will incorrectly return <code>null</code>.<br /> + Also: with the same parameters filter_var() will return <code>false</code> instead of <code>null</code> for most objects.</p>', + '<p>The code snippet is simplified for brevity. Please refer to the source of this file on <a href="http://github.com/jrfnl/PHP-cheat-sheet-extended" target="_blank">GitHub</a> for full details on how to use filter_var_array().</p>', + ), +); + +// Issue #1179. +b( + [ + 1 => false, // wat + 2 => false, + ] +); + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayIndentation tabIndent true diff --git a/WordPress/Tests/Arrays/ArrayIndentationUnitTest.2.inc.fixed b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.2.inc.fixed new file mode 100644 index 00000000..4335fc39 --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.2.inc.fixed @@ -0,0 +1,403 @@ +<?php +// @codingStandardsChangeSetting WordPress.Arrays.ArrayIndentation tabIndent false +$ok = array( + 'value', + 123, +); + +$ok_with_keys = [ + 'key1' => 'value', + 'key2' => 'value', // Comment after item. +]; + +$ok_nested = array( + 'key1' => array( + 'key1' => 'value', + 'key2' => 'value', + ), + 'key2' => 'value', +); + + +$bad_phpcs_style = array( + 'value', + 123, // Comment after item. +); + +$bad_phpcs_style_with_keys = array( + 'key1' => 'value', + 'key2' => 'value', +); + +$bad_phpcs_style_nested = array( + 'key1' => [ + 'key1' => 'value', + + 'key2' => 'value', + + ], + 'key2' => 'value', +); + +// Arrays with initial indent. + $bad_mixed_indent = [ + 'value', + 123, + ]; + + $bad_mixed_indent_with_keys = array( + + 'key1' => 'value', + 'key2' => 'value', + ); + + $bad_mixed_indent_nested = [ + 'key1' => array( + 'key1' => 'value', + 'key2' => 'value', + ), + + 'key2' => 'value', + ]; + +$empty = [ + + +]; + +// Same-line items in mixed arrays should be ignored. +$mixed_1 = array('something', 'else', array( + 'key1' => 'value', + 'key2' => 'value', // Comment after item. + ), +); + +$mixed_2 = array( + array( + 'key1' => 'value', + 'key2' => 'value', // Comment after item. + ), + 'something', 'else', +); + +// Issue 998: don't fix the indentation for the closer if it's not on its own line. +$a = array( + 'foo' => bar(1, 29)); + +$a = array( + 'foo' => bar(1, 29) ); + +/* + * Issue 973 - multi-line value indentation. + */ +// OK. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + +// Too little overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + +// Too much overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + 'content' => + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' . + '<p>' . __( 'You can manage comments made on your site similar to the way you manage posts and other content. This screen is customizable in the same ways as other management screens, and you can act on comments using the on-hover action links or the Bulk Actions.' ) . '</p>' +) ); + + +// Too little overall with more than the minimum indentation for some subsequent lines. + get_current_screen()->add_help_tab( array( + 'id' => 'screen-content', + 'content' => '<p>' . __('You can customize the display of this screen’s contents in a number of ways:') . '</p>' . + '<ul>' . + '<li>' . __('You can hide/display columns based on your needs and decide how many posts to list per screen using the Screen Options tab.') . '</li>' . + '<li>' . __( 'You can filter the list of posts by post status using the text links above the posts list to only show posts with that status. The default view is to show all posts.' ) . '</li>' . + '<li>' . __('You can view posts in a simple title list or with an excerpt using the Screen Options tab.') . '</li>' . + '<li>' . __('You can refine the list to show only posts in a specific category or from a specific month by using the dropdown menus above the posts list. Click the Filter button after making your selection. You also can refine the list by clicking on the post author, category or tag in the posts list.') . '</li>' . + '</ul>' + ) ); + +// Too much overall with more than the minimum indentation for some subsequent lines. + get_current_screen()->add_help_tab( array( + 'id' => 'bulk-actions', + 'content' => + '<p>' . __('You can also edit or move multiple posts to the trash at once. Select the posts you want to act on using the checkboxes, then select the action you want to take from the Bulk Actions menu and click Apply.') . '</p>' . + '<p>' . __('When using Bulk Edit, you can change the metadata (categories, author, etc.) for all selected posts at once. To remove a post from the grouping, just click the x next to its name in the Bulk Edit area that appears.') . '</p>' + ) ); + +// Too much overall with less than the minimum indentation for some subsequent lines. +get_current_screen()->add_help_tab( array( + 'id' => 'screen-content', + 'content' => + '<div>' . + '<ul>' . + '<li>' . __('You can hide/display columns based on your needs and decide how many posts to list per screen using the Screen Options tab.') . '</li>' . + '</ul>' . + '</div>' +) ); + + // Mixed indentation. + get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => + __('Overview') . + 'something', + /* Here we have a comment. */ + 'content' => 'value' . + 'value' . + 'value' . + 'value', + ) ); + +// OK: Ignore empty lines when found as subsequent line in an item. +get_current_screen()->add_help_tab( array( + 'id' => 'something' . + + 'something else', +) ); + +/* + * Issue: Subsequent lines with Heredoc/Nowdoc syntax should be ignored. + */ +// Should only report the first line and fix the first line + the comma on the last line. +get_current_screen()->add_help_tab( array( + 'id' => <<<EOD +Here comes some text. +And some more text. +EOD + , +) ); + +// Should report the second line and fix the second line + the comma on the last line. +get_current_screen()->add_help_tab( array( + 'id' => + <<<EOD +Here comes some text. +And some more text. +EOD + , +) ); + +/* + * Heredoc/Nowdoc subsequent line fixer: lines before/after the heredoc/nowdoc *should* be fixed. + */ +// Should report on the 'id' line and the first line after the heredoc and fix 'id' line +// and both lines after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => <<<EOD +Here comes some text. +And some more text. +EOD + . '</p>' + . '</hr>', +) ); + +// Should report on the heredoc opener and fix the opener and the line after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => + <<<EOD +Here comes some text. +And some more text. +EOD + . '</p>', +) ); + +// Should report on the 'id' line and the line after. Should fix both + line after the heredoc. +get_current_screen()->add_help_tab( array( + 'id' => + '<p>' . <<<EOD +Here comes some text. +And some more text. +EOD +. '</p>', +) ); + +// OK. Not pretty, but not handled by this sniff as both first line + line after have acceptable alignment. +get_current_screen()->add_help_tab( array( + 'id' => + '<p>' . +<<<EOD +Here comes some text. +And some more text. +EOD + . '</p>', +) ); + +/* + * Issue 985 - arrays with comments between items. + */ +// OK. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Too little overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Too much overall indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Mixed indentation. +get_current_screen()->add_help_tab( array( + 'id' => 'overview', + // Here we have a comment. + 'title' => + __('Overview'), + /* Here we have a comment. */ + 'content' => 'value', +) ); + +// Various multi-line comments. +$my_array = [ + /** + * Docblock about a filtered value... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /** + * Docblock about a filtered value... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /* + * Multi-line inline comment about something... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = [ + /* + * Multi-line inline comment about something... + */ + 'truthy' => apply_filters( '...', true ), +]; + +$my_array = array( + 'something' => + /** + * Docblock about a filtered value... + */ + $a = apply_filters( '...', true ), +); + +$my_array = [ + 'something' => + /** + * Docblock about a filtered value... + */ + $a = apply_filters( '...', true ), +]; + +$my_array = [ + 'something' => + /* + * Multi-line inline comment about something... + */ + apply_filters( '...', true ), +]; + +$my_array = [ + 'something' => + /* + * Multi-line inline comment about something... + */ + apply_filters( '...', true ), +]; + +/* + * Test multi-line strings as the value for an array item. + */ +$test = array( + 'notes1' => '<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: + </p>', + + 'notes1' => + '<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: +</p>' . // The sniff should check & fix the next line as it is a new token. + '<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that <code>isset()</code> is faster than <code>array_key_exists()</code>, but as said above, will return false if no value has been assigned. +</p>', + + 'notes1' => // Same, but now using double quotes. + "<p> + Whether to use <code>isset()</code> or <code>array_key_exists()</code> depends on what you want to know: +</p>" . // The sniff should check & fix the next line as it is a new token. + "<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that <code>isset()</code> is faster than <code>array_key_exists()</code>, but as said above, will return false if no value has been assigned. +</p>", + + // Now using double quotes with a variable to force T_DOUBLE_QUOTED_STRING token. + 'notes1' => array( + "<p> + Whether to use $abc or $def depends on what you want to know: + </p>" . // The sniff should check & fix the next line as it is a new token. + "<p> + This said, we can concluded that to avoid warnings and undesired results you will always have to use an <code>is_array()</code> first.<br /> + Also note that $abc is faster than $def, but as said above, will return false if no value has been assigned. + </p>", + ), + + 'notes2' => array( + '<p><strong>Important</strong>: Integers between -128 and 255 are interpreted as the ASCII value pointing to a character (negative values have 256 added in order to allow characters in the Extended ASCII range).<br /> + In any other case, integers are interpreted as a string containing the decimal digits of the integer.</p>', + ), + + 'tooltip' => ' +if( ! is_array( $x ) ) { + filter_var( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); +} +else { + filter_var_array( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); // = Simplified... see note +} + ', + 'notes3' => array( + '<p>Please note: On some PHP versions <code>filter_var( $x, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE )</code> where <code>$x = false</code> will incorrectly return <code>null</code>.<br /> + Also: with the same parameters filter_var() will return <code>false</code> instead of <code>null</code> for most objects.</p>', + '<p>The code snippet is simplified for brevity. Please refer to the source of this file on <a href="http://github.com/jrfnl/PHP-cheat-sheet-extended" target="_blank">GitHub</a> for full details on how to use filter_var_array().</p>', + ), +); + +// Issue #1179. +b( + [ + 1 => false, // wat + 2 => false, + ] +); + +// @codingStandardsChangeSetting WordPress.Arrays.ArrayIndentation tabIndent true diff --git a/WordPress/Tests/Arrays/ArrayIndentationUnitTest.php b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.php new file mode 100644 index 00000000..b13a53fc --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.php @@ -0,0 +1,180 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\Arrays; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the ArrayIndentation sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class ArrayIndentationUnitTest extends AbstractSniffUnitTest { + + /** + * The tab width to use during testing. + * + * @var int + */ + private $tab_width = 4; + + /** + * Get a list of CLI values to set before the file is tested. + * + * Used by PHPCS 2.x. + * + * @param string $testFile The name of the file being tested. + * + * @return array + */ + public function getCliValues( $testFile ) { + // Tab width setting is only needed for the tabbed file. + if ( 'ArrayIndentationUnitTest.1.inc' === $testFile ) { + return array( '--tab-width=' . $this->tab_width ); + } + + return array(); + } + + /** + * Set CLI values before the file is tested. + * + * Used by PHPCS 3.x. + * + * @param string $testFile The name of the file being tested. + * @param \PHP_CodeSniffer\Config $config The config data for the test run. + * + * @return void + */ + public function setCliValues( $testFile, $config ) { + // Tab width setting is only needed for the tabbed file. + if ( 'ArrayIndentationUnitTest.1.inc' === $testFile ) { + $config->tabWidth = $this->tab_width; + } else { + $config->tabWidth = 0; + } + } + + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 23 => 1, + 24 => 1, + 25 => 1, + 28 => 1, + 29 => 1, + 30 => 1, + 33 => 1, + 34 => 2, + 36 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 44 => 1, + 45 => 1, + 46 => 1, + 50 => 1, + 51 => 1, + 52 => 1, + 55 => 1, + 56 => 1, + 57 => 1, + 58 => 1, + 60 => 1, + 61 => 1, + 66 => 1, + 80 => 1, + 85 => 1, + 88 => 1, + 103 => 1, + 104 => 1, + 105 => 1, + 111 => 1, + 112 => 1, + 113 => 1, + 120 => 1, + 121 => 1, + 122 => 1, + 132 => 1, + 133 => 1, + 134 => 1, + 140 => 1, + 141 => 1, + 142 => 1, + 151 => 1, + 152 => 1, + 153 => 1, + 156 => 1, + 157 => 1, + 175 => 1, + 179 => 1, + 185 => 1, + 198 => 1, + 202 => 1, + 209 => 1, + 218 => 1, + 219 => 1, + 251 => 1, + 252 => 1, + 253 => 1, + 254 => 1, + 255 => 1, + 260 => 1, + 261 => 1, + 262 => 1, + 263 => 1, + 264 => 1, + 269 => 1, + 271 => 1, + 273 => 1, + 274 => 1, + 279 => 1, + 280 => 1, + 286 => 1, + 287 => 1, + 293 => 1, + 294 => 1, + 300 => 1, + 301 => 1, + 307 => 1, + 308 => 1, + 315 => 1, + 316 => 1, + 323 => 1, + 324 => 1, + 331 => 1, + 332 => 1, + 347 => 1, + 356 => 1, + 369 => 1, + 398 => 1, + 399 => 1, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array(); + + } + +} // End class. diff --git a/WordPress/Tests/Arrays/ArrayKeySpacingRestrictionsUnitTest.php b/WordPress/Tests/Arrays/ArrayKeySpacingRestrictionsUnitTest.php index 0eb46435..bc2e3ba9 100644 --- a/WordPress/Tests/Arrays/ArrayKeySpacingRestrictionsUnitTest.php +++ b/WordPress/Tests/Arrays/ArrayKeySpacingRestrictionsUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Arrays; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the ArrayKeySpacingRestrictions sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_Arrays_ArrayKeySpacingRestrictionsUnitTest extends AbstractSniffUnitTest { +class ArrayKeySpacingRestrictionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,9 +28,9 @@ class WordPress_Tests_Arrays_ArrayKeySpacingRestrictionsUnitTest extends Abstrac */ public function getErrorList() { return array( - 4 => 1, - 5 => 1, - 6 => 1, + 4 => 1, + 5 => 1, + 6 => 1, 11 => 1, 12 => 1, 13 => 1, diff --git a/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.inc b/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.inc new file mode 100644 index 00000000..6b6e2ed7 --- /dev/null +++ b/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.inc @@ -0,0 +1,155 @@ +<?php + +/* + * Good. + */ +$good = array(); + +$good = array( 1, 2, 3 ); +$good = [ 'a', 'b', 'c' ]; + +$good = array( + 1, + 2, + 3, +); +$good = [ + 'a', + 'b', + 'c', +]; + +$good = array( + 1 => 'value', + 2 => array( 1 ), + 3 => apply_filters( 'filter', $input, $var ), /* Comment. */ +); +$good = [ + 'a' => 'value', // Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( 1 ), + 'c' => apply_filters( 'filter', $input, $var ), +]; + +// OK: that this should be a multi-line array is not the concern of this sniff. +$good = array( 1 => 'a', 2 => 'b', 3 => 'c' ); +$good = [ 'a' => 1, 'b' => 2, 'c' => 3 ]; + +$good = array( 1, 2, // OK: that each item should be on its own line or single line array is not the concern of this sniff. + 3, ); // OK: that the brace should be on another line is not the concern of this sniff. + +/* + * Bad. + */ +// Spacing before comma. +$bad = array( 1 , 2 , 3 ); // Bad x2. +$bad = [ 'a' , 'b' , 'c' ]; // Bad x2. + +// Spacing after comma. +$bad = array( 1,2, 3 ); // Bad x2. +$bad = [ 'a','b', 'c' ]; // Bad x2. + +// Comma after last. +$bad = array( 1, 2, 3, ); +$bad = [ 'a', 'b', 'c', ]; + +// Spacing before comma. +$bad = array( + 1 => 'value' , + 2 => array( 1 ) , + 3 => apply_filters( 'filter', $input, $var ) , /* Comment. */ +); +$bad = [ + 'a' => 'value' , // Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( 1 ) + + + , + 'c' => apply_filters( 'filter', $input, $var ) , +]; + +// NO spacing after comma. +$bad = array( + 3 => apply_filters( 'filter', $input, $var ),/* Comment. */ +); +$bad = [ + 'a' => 'value',// Comment. +]; + +// NO comma after last. +$bad = array( + 1 => 'value', + 2 => array( 1 ), + 3 => apply_filters( 'filter', $input, $var )/* Comment. */ +); +$bad = [ + 'a' => 'value', // Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( 1 ), + 'c' => apply_filters( 'filter', $input, $var ) +]; + +$bad = array( 1 => 'a' , 2 => 'b',3 => 'c', ); +$bad = [ 'a' => 1 , 'b' => 2,'c' => 3, ]; + +$bad = array( 1 , 2, + 3 ); + +// Combining a lot of errors in a nested array. +$bad = array( + 1 => 'value' , + 2 => [ + 'a' => 'value' ,// Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( + 1 + ), + 'c' => apply_filters( 'filter', $input, $var ) + ], + 3 => apply_filters( 'filter', $input, $var )/* Comment. */ +); + +// Alternative array style. +$bad = array( + 1 => 'value' + , 2 => array( 1 ) + , 3 => apply_filters( 'filter', $input, $var )/* Comment. */ +); +$bad = [ + 'a' => 'value' // Comment - the extra spacing is fine, might be for alignment with other comments. + , 'b' => array( 1 ) + , 'c' => apply_filters( 'filter', $input, $var ) +]; +$bad = [ + 'a' => 'value' // Comment - the extra spacing is fine, might be for alignment with other comments. + ,'b' => array( 1 ) + ,'c' => apply_filters( 'filter', $input, $var ), +]; + +$bad = array( + 'first', + 'second' + //'third', + ); + +$bad = array( + 'key3' => function($bar) { + return $bar; + } +); + +// Issue #998. +$a = array( + 'foo' => bar( 1, 29 )); + +// Issue #986. +get_current_screen()->add_help_tab( array( + 'id' => <<<EOD +Here comes some text. +EOD +, +) ); + +get_current_screen()->add_help_tab( array( +'id' => <<<EOD +Here comes some text. +EOD +. '</hr>', +) ); diff --git a/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.inc.fixed b/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.inc.fixed new file mode 100644 index 00000000..3fbeae06 --- /dev/null +++ b/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.inc.fixed @@ -0,0 +1,148 @@ +<?php + +/* + * Good. + */ +$good = array(); + +$good = array( 1, 2, 3 ); +$good = [ 'a', 'b', 'c' ]; + +$good = array( + 1, + 2, + 3, +); +$good = [ + 'a', + 'b', + 'c', +]; + +$good = array( + 1 => 'value', + 2 => array( 1 ), + 3 => apply_filters( 'filter', $input, $var ), /* Comment. */ +); +$good = [ + 'a' => 'value', // Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( 1 ), + 'c' => apply_filters( 'filter', $input, $var ), +]; + +// OK: that this should be a multi-line array is not the concern of this sniff. +$good = array( 1 => 'a', 2 => 'b', 3 => 'c' ); +$good = [ 'a' => 1, 'b' => 2, 'c' => 3 ]; + +$good = array( 1, 2, // OK: that each item should be on its own line or single line array is not the concern of this sniff. + 3, ); // OK: that the brace should be on another line is not the concern of this sniff. + +/* + * Bad. + */ +// Spacing before comma. +$bad = array( 1, 2, 3 ); // Bad x2. +$bad = [ 'a', 'b', 'c' ]; // Bad x2. + +// Spacing after comma. +$bad = array( 1, 2, 3 ); // Bad x2. +$bad = [ 'a', 'b', 'c' ]; // Bad x2. + +// Comma after last. +$bad = array( 1, 2, 3 ); +$bad = [ 'a', 'b', 'c' ]; + +// Spacing before comma. +$bad = array( + 1 => 'value', + 2 => array( 1 ), + 3 => apply_filters( 'filter', $input, $var ), /* Comment. */ +); +$bad = [ + 'a' => 'value', // Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( 1 ), + 'c' => apply_filters( 'filter', $input, $var ), +]; + +// NO spacing after comma. +$bad = array( + 3 => apply_filters( 'filter', $input, $var ), /* Comment. */ +); +$bad = [ + 'a' => 'value', // Comment. +]; + +// NO comma after last. +$bad = array( + 1 => 'value', + 2 => array( 1 ), + 3 => apply_filters( 'filter', $input, $var ), /* Comment. */ +); +$bad = [ + 'a' => 'value', // Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( 1 ), + 'c' => apply_filters( 'filter', $input, $var ), +]; + +$bad = array( 1 => 'a', 2 => 'b', 3 => 'c' ); +$bad = [ 'a' => 1, 'b' => 2, 'c' => 3 ]; + +$bad = array( 1, 2, + 3, ); + +// Combining a lot of errors in a nested array. +$bad = array( + 1 => 'value', + 2 => [ + 'a' => 'value', // Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( + 1, + ), + 'c' => apply_filters( 'filter', $input, $var ), + ], + 3 => apply_filters( 'filter', $input, $var ), /* Comment. */ +); + +// Alternative array style. +$bad = array( + 1 => 'value', 2 => array( 1 ), 3 => apply_filters( 'filter', $input, $var ), /* Comment. */ +); +$bad = [ + 'a' => 'value', // Comment - the extra spacing is fine, might be for alignment with other comments. + 'b' => array( 1 ), 'c' => apply_filters( 'filter', $input, $var ), +]; +$bad = [ + 'a' => 'value', // Comment - the extra spacing is fine, might be for alignment with other comments. +'b' => array( 1 ), 'c' => apply_filters( 'filter', $input, $var ), +]; + +$bad = array( + 'first', + 'second', + //'third', + ); + +$bad = array( + 'key3' => function($bar) { + return $bar; + }, +); + +// Issue #998. +$a = array( + 'foo' => bar( 1, 29 ), ); + +// Issue #986. +get_current_screen()->add_help_tab( array( + 'id' => <<<EOD +Here comes some text. +EOD +, +) ); + +get_current_screen()->add_help_tab( array( +'id' => <<<EOD +Here comes some text. +EOD +. '</hr>', +) ); diff --git a/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.php b/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.php new file mode 100644 index 00000000..ed576335 --- /dev/null +++ b/WordPress/Tests/Arrays/CommaAfterArrayItemUnitTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\Arrays; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the Arrays.CommaAfterArrayItem sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class CommaAfterArrayItemUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 44 => 2, + 45 => 2, + 48 => 2, + 49 => 2, + 52 => 1, + 53 => 1, + 57 => 1, + 58 => 1, + 59 => 1, + 62 => 1, + 66 => 1, + 67 => 1, + 72 => 1, + 75 => 1, + 82 => 1, + 87 => 1, + 90 => 4, + 91 => 4, + 93 => 2, + 94 => 1, + 98 => 1, + 100 => 2, + 102 => 1, + 104 => 1, + 106 => 1, + 112 => 1, + 113 => 2, + 117 => 1, + 118 => 2, + 122 => 2, + 123 => 2, + 128 => 1, + 135 => 1, + 140 => 1, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array(); + + } + +} // End class. diff --git a/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.1.inc b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.1.inc new file mode 100644 index 00000000..af61483f --- /dev/null +++ b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.1.inc @@ -0,0 +1,574 @@ +<?php + +/* + * Test reporting and fixing "too much space before" errors in single line arrays. + */ +$array = array( 'b', 'd' ); // OK. +$array = array( 'a' => 'b', 'c' => 'd' ); // OK. +$array = array( 'a' => 'b', 'c' => 'd' ); // Bad x 2. + +$array = array( 'a' /* Comment. */ => /* Comment. */ 'b' ); // OK. +$array = array( 'a' /* Comment. */ => /* Comment. */ 'b' ); // Bad. + +$array = array( array( 'a' => 'b', 'c' => 'd' ) ); // OK. +$array = array( array( 'a' => array( 'c' => 'd' ) ) ); // Bad x 2. + +/* + * Test reporting errors and fixing double arrow alignment in a multi-line arrays. + */ +$array = array( + 'b', + 'd', + // Something. + 'f', +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +); +$array = array( + 'a' => 'b', + 123 => 'd', + 56958 => 'f', + 'g' + => 'h', +); + +// Test no space before. +$array = array( + 'a'=> 'b', // Bad. +); +$array = array( + 'a'=> 'b', // Bad. + 'ccc'=> 'd', // Bad. + 'ee'=> 'f', // Bad. + 'g' + => 'h', +); + +// Test too much space before. +$array = array( + 'a' => 'b', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', +); + +// Test exact number of spaces before vs minimum. +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'g' + => 'h', +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +$array = array( + 'a' => 'b', +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', +); +$array = array( + 'a' => 'b', // Bad - also note: uses mid-line tab. + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true + + +/* + * Test dealing with new lines. + */ +$array = array( + 'a' + => 'b', + 'ccc' + => 'd', +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +$array = array( + 'a' + => 'b', // Bad. + 'ccc' + => 'd', // Bad. +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +// Test combined ignore new lines false + exact false. +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', + 'g' + => 'h', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', // Bad. +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + + +/* + * Test with maxColumn value set. + */ +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', + 'kkkkkkkkkkkk' + => 'l', +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg'=> 'h', // Bad - no space before. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', // Bad - too much space before. +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +// Test combined maxColumn value set + exact false. +$array = array( + 'a' => 'b', +); +$array = array( + 'a' => 'b', +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', + 'kkkkkkkkkkkk' + => 'l', +); + +$array = array( + 'a' => 'b', // Bad. +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +// Test combined maxColumn value set + exact false + ignore new lines false. +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', // Bad. + 'kkkkkkkkkkkk' + => 'l', // Bad. +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', + 'i' + => 'j', // Bad. + 'kkkkkkkkkkkk' + => 'l', // Bad. +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg'=> 'h', // Bad. + 'i' + => 'j', // Bad. + 'kkkkkkkkkkkk' + => 'l', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', + 'i' + => 'j', // Bad. + 'kkkkkkkkkkkk' + => 'l', // Bad. +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + + +/* + * Test mixed complex array. + */ +$array = array( + 'a' => 'b', // Bad. + 12345 => 'd', // Bad. + 'f', + 'g' + => 'h', + array( 'something' ), + array( 'a' => 'something' ), + array( + 'a' => 'something', + ), + 'i' => array( 'something' ), // Bad. + 'jjjjjj' => array( 'a' => 'something' ), + 'kk' => array( // Bad. + 'a' => 'something', // Bad. + ), + $ab . 'l' => array( 'a' => 'something' ), // Bad. + // Compound multi-line key. + $ab . + 'l' => array( 'a' => 'something' ), // Bad. + $ab . 'l' => array( 'a' => 'something' ), // Bad. +); + + +/* + * Test multi-line array item handling. Default = always. + */ +$deprecated_functions = array( + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +$deprecated_functions = array( + 'the_category_head' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad - no space. + 'the_category_ID' + => function_call( // Ok, ignore newline is being respected. + $a, + $b + ), +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems never + +// OK - alignments to the expected column. +$deprecated_functions = array( + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +// OK - alignments to index + 1. +$deprecated_functions = array( + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +// OK - mixed alignments to index + 1 and expected column. +$deprecated_functions = array( + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); + +$deprecated_functions = array( + 'the_category_head' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID'=> function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems sometimes +// Test invalid property value error. +$array = array( + 'a' => 'b', +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems =103 +// Test invalid property value error. +$array = array( + 'a' => 'b', +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems !50 +// Test invalid property value error. +$array = array( + 'a' => 'b', +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems <25 + +// OK - alignments to the expected column. +$deprecated_functions = array( + 'single1' => 'something', + 'single12' => 'something_else', + 'single123' => 'something_else_again', + 'single1234' => 'another_something', + 'multi1' => array( + 'alt' => 'current_user_can()', + ), +); + +// Bad - 20% multi-line, alignment should be enforced. +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', // Bad. + 'single1234' => 'another_something', + 'multi1' => array( // Bad. + 'alt' => 'current_user_can()', + ), +); + +// Bad - more than 25% multi-line, alignment should be enforced for single line, key + 1/expected for multi-line. +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // OK - expected. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => array( // OK - expected. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // OK - key + 1 . + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +); +$deprecated_functions = array( + 'multi1' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad - no space. +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems <=50% +// Test with value 50 and test validation for incorrectly passed % sign. + +$deprecated_functions = array( + 'single1' => 'something', + 'single12' => 'something_else', + 'single123' => 'something_else_again', + 'multi1' => array( + 'alt' => 'get_the_category()', + ), + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); + +// Bad - 50% multi-line, alignment should be enforced. +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', // Bad. + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad - no space. +); + +// Bad - more than 50% multi-line, alignment should be enforced for single line, key + 1/expected for multi-line. +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); + +$deprecated_functions = array( + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad - no space. +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems !=100 + +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single1234' => 'another_something', // Bad. + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), +); +$deprecated_functions = array( + 'multi1' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); + +$deprecated_functions = array( + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad. +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems always diff --git a/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.1.inc.fixed b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.1.inc.fixed new file mode 100644 index 00000000..7169de6e --- /dev/null +++ b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.1.inc.fixed @@ -0,0 +1,560 @@ +<?php + +/* + * Test reporting and fixing "too much space before" errors in single line arrays. + */ +$array = array( 'b', 'd' ); // OK. +$array = array( 'a' => 'b', 'c' => 'd' ); // OK. +$array = array( 'a' => 'b', 'c' => 'd' ); // Bad x 2. + +$array = array( 'a' /* Comment. */ => /* Comment. */ 'b' ); // OK. +$array = array( 'a' /* Comment. */ => /* Comment. */ 'b' ); // Bad. + +$array = array( array( 'a' => 'b', 'c' => 'd' ) ); // OK. +$array = array( array( 'a' => array( 'c' => 'd' ) ) ); // Bad x 2. + +/* + * Test reporting errors and fixing double arrow alignment in a multi-line arrays. + */ +$array = array( + 'b', + 'd', + // Something. + 'f', +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +); +$array = array( + 'a' => 'b', + 123 => 'd', + 56958 => 'f', + 'g' + => 'h', +); + +// Test no space before. +$array = array( + 'a' => 'b', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'g' + => 'h', +); + +// Test too much space before. +$array = array( + 'a' => 'b', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', +); + +// Test exact number of spaces before vs minimum. +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'g' + => 'h', +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +$array = array( + 'a' => 'b', +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', +); +$array = array( + 'a' => 'b', // Bad - also note: uses mid-line tab. + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true + + +/* + * Test dealing with new lines. + */ +$array = array( + 'a' + => 'b', + 'ccc' + => 'd', +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +// Test combined ignore new lines false + exact false. +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' => 'h', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' => 'h', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', + 'g' => 'h', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', + 'g' => 'h', // Bad. +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + + +/* + * Test with maxColumn value set. + */ +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', + 'kkkkkkkkkkkk' + => 'l', +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', // Bad - no space before. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', // Bad - too much space before. +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +// Test combined maxColumn value set + exact false. +$array = array( + 'a' => 'b', +); +$array = array( + 'a' => 'b', +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', + 'kkkkkkkkkkkk' + => 'l', +); + +$array = array( + 'a' => 'b', // Bad. +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +// Test combined maxColumn value set + exact false + ignore new lines false. +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' => 'j', // Bad. + 'kkkkkkkkkkkk' => 'l', // Bad. +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', + 'i' => 'j', // Bad. + 'kkkkkkkkkkkk' => 'l', // Bad. +); +$array = array( + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', // Bad. + 'i' => 'j', // Bad. + 'kkkkkkkkkkkk' => 'l', // Bad. +); +$array = array( + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', + 'i' => 'j', // Bad. + 'kkkkkkkkkkkk' => 'l', // Bad. +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + + +/* + * Test mixed complex array. + */ +$array = array( + 'a' => 'b', // Bad. + 12345 => 'd', // Bad. + 'f', + 'g' + => 'h', + array( 'something' ), + array( 'a' => 'something' ), + array( + 'a' => 'something', + ), + 'i' => array( 'something' ), // Bad. + 'jjjjjj' => array( 'a' => 'something' ), + 'kk' => array( // Bad. + 'a' => 'something', // Bad. + ), + $ab . 'l' => array( 'a' => 'something' ), // Bad. + // Compound multi-line key. + $ab . + 'l' => array( 'a' => 'something' ), // Bad. + $ab . 'l' => array( 'a' => 'something' ), // Bad. +); + + +/* + * Test multi-line array item handling. Default = always. + */ +$deprecated_functions = array( + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +$deprecated_functions = array( + 'the_category_head' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad - no space. + 'the_category_ID' + => function_call( // Ok, ignore newline is being respected. + $a, + $b + ), +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems never + +// OK - alignments to the expected column. +$deprecated_functions = array( + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +// OK - alignments to index + 1. +$deprecated_functions = array( + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +// OK - mixed alignments to index + 1 and expected column. +$deprecated_functions = array( + 'the_category_head' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); + +$deprecated_functions = array( + 'the_category_head' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'the_category_ID' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems sometimes +// Test invalid property value error. +$array = array( + 'a' => 'b', +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems =103 +// Test invalid property value error. +$array = array( + 'a' => 'b', +); +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems !50 +// Test invalid property value error. +$array = array( + 'a' => 'b', +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems <25 + +// OK - alignments to the expected column. +$deprecated_functions = array( + 'single1' => 'something', + 'single12' => 'something_else', + 'single123' => 'something_else_again', + 'single1234' => 'another_something', + 'multi1' => array( + 'alt' => 'current_user_can()', + ), +); + +// Bad - 20% multi-line, alignment should be enforced. +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', // Bad. + 'single1234' => 'another_something', + 'multi1' => array( // Bad. + 'alt' => 'current_user_can()', + ), +); + +// Bad - more than 25% multi-line, alignment should be enforced for single line, key + 1/expected for multi-line. +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // OK - expected. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => array( // OK - expected. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // OK - key + 1 . + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +); +$deprecated_functions = array( + 'multi1' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad - no space. +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems <=50% +// Test with value 50 and test validation for incorrectly passed % sign. + +$deprecated_functions = array( + 'single1' => 'something', + 'single12' => 'something_else', + 'single123' => 'something_else_again', + 'multi1' => array( + 'alt' => 'get_the_category()', + ), + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); + +// Bad - 50% multi-line, alignment should be enforced. +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', // Bad. + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad - no space. +); + +// Bad - more than 50% multi-line, alignment should be enforced for single line, key + 1/expected for multi-line. +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); + +$deprecated_functions = array( + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad - no space. +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems !=100 + +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single1234' => 'another_something', // Bad. + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); +$deprecated_functions = array( + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), +); +$deprecated_functions = array( + 'multi1' => array( + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +); + +$deprecated_functions = array( + 'multi1' => array( // Bad. + 'alt' => 'get_the_category_by_ID()', + ), + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +); + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems always diff --git a/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.2.inc b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.2.inc new file mode 100644 index 00000000..d26927cd --- /dev/null +++ b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.2.inc @@ -0,0 +1,574 @@ +<?php + +/* + * Test reporting and fixing "too much space before" errors in single line arrays. + */ +$array = [ 'b', 'd' ]; // OK. +$array = [ 'a' => 'b', 'c' => 'd' ]; // OK. +$array = [ 'a' => 'b', 'c' => 'd' ]; // Bad x 2. + +$array = [ 'a' /* Comment. */ => /* Comment. */ 'b' ]; // OK. +$array = [ 'a' /* Comment. */ => /* Comment. */ 'b' ]; // Bad. + +$array = [[ 'a' => 'b', 'c' => 'd' ]]; // OK. +$array = [[ 'a' => [ 'c' => 'd' ]]]; // Bad x 2. + +/* + * Test reporting errors and fixing double arrow alignment in a multi-line arrays. + */ +$array = [ + 'b', + 'd', + // Something. + 'f', +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +]; +$array = [ + 'a' => 'b', + 123 => 'd', + 56958 => 'f', + 'g' + => 'h', +]; + +// Test no space before. +$array = [ + 'a'=> 'b', // Bad. +]; +$array = [ + 'a'=> 'b', // Bad. + 'ccc'=> 'd', // Bad. + 'ee'=> 'f', // Bad. + 'g' + => 'h', +]; + +// Test too much space before. +$array = [ + 'a' => 'b', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', +]; + +// Test exact number of spaces before vs minimum. +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'g' + => 'h', +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +$array = [ + 'a' => 'b', +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true + + +/* + * Test dealing with new lines. + */ +$array = [ + 'a' + => 'b', + 'ccc' + => 'd', +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +$array = [ + 'a' + => 'b', // Bad. + 'ccc' + => 'd', // Bad. +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +// Test combined ignore new lines false + exact false. +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', + 'g' + => 'h', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', // Bad. +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + + +/* + * Test with maxColumn value set. + */ +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', + 'kkkkkkkkkkkk' + => 'l', +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg'=> 'h', // Bad - no space before. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', // Bad - too much space before. +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +// Test combined maxColumn value set + exact false. +$array = [ + 'a' => 'b', +]; +$array = [ + 'a' => 'b', +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', + 'kkkkkkkkkkkk' + => 'l', +]; + +$array = [ + 'a' => 'b', // Bad. +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +// Test combined maxColumn value set + exact false + ignore new lines false. +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', // Bad. + 'kkkkkkkkkkkk' + => 'l', // Bad. +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', + 'i' + => 'j', // Bad. + 'kkkkkkkkkkkk' + => 'l', // Bad. +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg'=> 'h', // Bad. + 'i' + => 'j', // Bad. + 'kkkkkkkkkkkk' + => 'l', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', + 'i' + => 'j', // Bad. + 'kkkkkkkkkkkk' + => 'l', // Bad. +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + + +/* + * Test mixed complex array. + */ +$array = [ + 'a' => 'b', // Bad. + 12345 => 'd', // Bad. + 'f', + 'g' + => 'h', + [ 'something' ], + [ 'a' => 'something' ], + [ + 'a' => 'something', + ], + 'i' => [ 'something' ], // Bad. + 'jjjjjj' => [ 'a' => 'something' ], + 'kk' => [ // Bad. + 'a' => 'something', // Bad. + ], + $ab . 'l' => [ 'a' => 'something' ], // Bad. + // Compound multi-line key. + $ab . + 'l' => [ 'a' => 'something' ], // Bad. + $ab . 'l' => [ 'a' => 'something' ], // Bad. +]; + + +/* + * Test multi-line array item handling. Default = always. + */ +$deprecated_functions = [ + 'the_category_head' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +$deprecated_functions = [ + 'the_category_head' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad - no space. + 'the_category_ID' + => function_call( // Ok, ignore newline is being respected. + $a, + $b + ), +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems never + +// OK - alignments to the expected column. +$deprecated_functions = [ + 'the_category_head' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +// OK - alignments to index + 1. +$deprecated_functions = [ + 'the_category_head' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +// OK - mixed alignments to index + 1 and expected column. +$deprecated_functions = [ + 'the_category_head' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; + +$deprecated_functions = [ + 'the_category_head' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID'=> function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems sometimes +// Test invalid property value error. +$array = [ + 'a' => 'b', +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems =103 +// Test invalid property value error. +$array = [ + 'a' => 'b', +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems !50 +// Test invalid property value error. +$array = [ + 'a' => 'b', +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems <25 + +// OK - alignments to the expected column. +$deprecated_functions = [ + 'single1' => 'something', + 'single12' => 'something_else', + 'single123' => 'something_else_again', + 'single1234' => 'another_something', + 'multi1' => [ + 'alt' => 'current_user_can()', + ], +]; + +// Bad - 20% multi-line, alignment should be enforced. +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', // Bad. + 'single1234' => 'another_something', + 'multi1' => [ // Bad. + 'alt' => 'current_user_can()', + ], +]; + +// Bad - more than 25% multi-line, alignment should be enforced for single line, key + 1/expected for multi-line. +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // OK - expected. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => [ // OK - expected. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // OK - key + 1 . + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +]; +$deprecated_functions = [ + 'multi1' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad - no space. +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems <=50% +// Test with value 50 and test validation for incorrectly passed % sign. + +$deprecated_functions = [ + 'single1' => 'something', + 'single12' => 'something_else', + 'single123' => 'something_else_again', + 'multi1' => [ + 'alt' => 'get_the_category()', + ], + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; + +// Bad - 50% multi-line, alignment should be enforced. +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', // Bad. + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad - no space. +]; + +// Bad - more than 50% multi-line, alignment should be enforced for single line, key + 1/expected for multi-line. +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; + +$deprecated_functions = [ + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad - no space. +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems !=100 + +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single1234' => 'another_something', // Bad. + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), +]; +$deprecated_functions = [ + 'multi1' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; + +$deprecated_functions = [ + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments'=> 'multi-line + string', // Bad. +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems always diff --git a/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.2.inc.fixed b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.2.inc.fixed new file mode 100644 index 00000000..ccd188dc --- /dev/null +++ b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.2.inc.fixed @@ -0,0 +1,560 @@ +<?php + +/* + * Test reporting and fixing "too much space before" errors in single line arrays. + */ +$array = [ 'b', 'd' ]; // OK. +$array = [ 'a' => 'b', 'c' => 'd' ]; // OK. +$array = [ 'a' => 'b', 'c' => 'd' ]; // Bad x 2. + +$array = [ 'a' /* Comment. */ => /* Comment. */ 'b' ]; // OK. +$array = [ 'a' /* Comment. */ => /* Comment. */ 'b' ]; // Bad. + +$array = [[ 'a' => 'b', 'c' => 'd' ]]; // OK. +$array = [[ 'a' => [ 'c' => 'd' ]]]; // Bad x 2. + +/* + * Test reporting errors and fixing double arrow alignment in a multi-line arrays. + */ +$array = [ + 'b', + 'd', + // Something. + 'f', +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +]; +$array = [ + 'a' => 'b', + 123 => 'd', + 56958 => 'f', + 'g' + => 'h', +]; + +// Test no space before. +$array = [ + 'a' => 'b', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'g' + => 'h', +]; + +// Test too much space before. +$array = [ + 'a' => 'b', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', +]; + +// Test exact number of spaces before vs minimum. +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'g' + => 'h', +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +$array = [ + 'a' => 'b', +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' + => 'h', +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', + 'g' + => 'h', +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true + + +/* + * Test dealing with new lines. + */ +$array = [ + 'a' + => 'b', + 'ccc' + => 'd', +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +// Test combined ignore new lines false + exact false. +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'g' => 'h', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'g' => 'h', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', + 'g' => 'h', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', + 'g' => 'h', // Bad. +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + + +/* + * Test with maxColumn value set. + */ +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', + 'kkkkkkkkkkkk' + => 'l', +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', // Bad - no space before. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', // Bad - too much space before. +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +// Test combined maxColumn value set + exact false. +$array = [ + 'a' => 'b', +]; +$array = [ + 'a' => 'b', +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' + => 'j', + 'kkkkkkkkkkkk' + => 'l', +]; + +$array = [ + 'a' => 'b', // Bad. +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact false +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 12 +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines false +// Test combined maxColumn value set + exact false + ignore new lines false. +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', + 'gggggggggg' => 'h', + 'i' => 'j', // Bad. + 'kkkkkkkkkkkk' => 'l', // Bad. +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', + 'i' => 'j', // Bad. + 'kkkkkkkkkkkk' => 'l', // Bad. +]; +$array = [ + 'a' => 'b', + 'ccc' => 'd', + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', // Bad. + 'i' => 'j', // Bad. + 'kkkkkkkkkkkk' => 'l', // Bad. +]; +$array = [ + 'a' => 'b', // Bad. + 'ccc' => 'd', // Bad. + 'ee' => 'f', // Bad. + 'gggggggggg' => 'h', + 'i' => 'j', // Bad. + 'kkkkkkkkkkkk' => 'l', // Bad. +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment exact true +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment maxColumn 1000 +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment ignoreNewlines true + + +/* + * Test mixed complex array. + */ +$array = [ + 'a' => 'b', // Bad. + 12345 => 'd', // Bad. + 'f', + 'g' + => 'h', + [ 'something' ], + [ 'a' => 'something' ], + [ + 'a' => 'something', + ], + 'i' => [ 'something' ], // Bad. + 'jjjjjj' => [ 'a' => 'something' ], + 'kk' => [ // Bad. + 'a' => 'something', // Bad. + ], + $ab . 'l' => [ 'a' => 'something' ], // Bad. + // Compound multi-line key. + $ab . + 'l' => [ 'a' => 'something' ], // Bad. + $ab . 'l' => [ 'a' => 'something' ], // Bad. +]; + + +/* + * Test multi-line array item handling. Default = always. + */ +$deprecated_functions = [ + 'the_category_head' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +$deprecated_functions = [ + 'the_category_head' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad - no space. + 'the_category_ID' + => function_call( // Ok, ignore newline is being respected. + $a, + $b + ), +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems never + +// OK - alignments to the expected column. +$deprecated_functions = [ + 'the_category_head' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +// OK - alignments to index + 1. +$deprecated_functions = [ + 'the_category_head' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +// OK - mixed alignments to index + 1 and expected column. +$deprecated_functions = [ + 'the_category_head' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; + +$deprecated_functions = [ + 'the_category_head' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'the_category_ID' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems sometimes +// Test invalid property value error. +$array = [ + 'a' => 'b', +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems =103 +// Test invalid property value error. +$array = [ + 'a' => 'b', +]; +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems !50 +// Test invalid property value error. +$array = [ + 'a' => 'b', +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems <25 + +// OK - alignments to the expected column. +$deprecated_functions = [ + 'single1' => 'something', + 'single12' => 'something_else', + 'single123' => 'something_else_again', + 'single1234' => 'another_something', + 'multi1' => [ + 'alt' => 'current_user_can()', + ], +]; + +// Bad - 20% multi-line, alignment should be enforced. +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', // Bad. + 'single1234' => 'another_something', + 'multi1' => [ // Bad. + 'alt' => 'current_user_can()', + ], +]; + +// Bad - more than 25% multi-line, alignment should be enforced for single line, key + 1/expected for multi-line. +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // OK - expected. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => [ // OK - expected. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // OK - key + 1 . + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +]; +$deprecated_functions = [ + 'multi1' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad - no space. +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems <=50% +// Test with value 50 and test validation for incorrectly passed % sign. + +$deprecated_functions = [ + 'single1' => 'something', + 'single12' => 'something_else', + 'single123' => 'something_else_again', + 'multi1' => [ + 'alt' => 'get_the_category()', + ], + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; + +// Bad - 50% multi-line, alignment should be enforced. +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single123' => 'something_else_again', // Bad. + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad - no space. +]; + +// Bad - more than 50% multi-line, alignment should be enforced for single line, key + 1/expected for multi-line. +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; + +$deprecated_functions = [ + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad - no space. +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems !=100 + +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', // Bad. + 'single1234' => 'another_something', // Bad. + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; +$deprecated_functions = [ + 'single1' => 'something', // Bad. + 'single12' => 'something_else', + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), +]; +$deprecated_functions = [ + 'multi1' => [ + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', +]; + +$deprecated_functions = [ + 'multi1' => [ // Bad. + 'alt' => 'get_the_category_by_ID()', + ], + 'multi12' => function_call( // Bad. + $a, + $b + ), + 'user_can_edit_post_comments' => 'multi-line + string', // Bad. +]; + +// @codingStandardsChangeSetting WordPress.Arrays.MultipleStatementAlignment alignMultilineItems always diff --git a/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.php b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.php new file mode 100644 index 00000000..371aea87 --- /dev/null +++ b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.php @@ -0,0 +1,192 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\Arrays; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the Arrays.MultipleStatementAlignment sniff. + * + * The unit test class uses two test files to cover all possibilities: + * 1. Tab-based indentation, long arrays. + * 2. Space-based indentation, short arrays. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class MultipleStatementAlignmentUnitTest extends AbstractSniffUnitTest { + + /** + * The tab width to use during testing. + * + * @var int + */ + private $tab_width = 4; + + /** + * Get a list of CLI values to set before the file is tested. + * + * Used by PHPCS 2.x. + * + * @param string $testFile The name of the file being tested. + * + * @return array + */ + public function getCliValues( $testFile ) { + // Tab width setting is only needed for the tabbed file. + if ( 'MultipleStatementAlignmentUnitTest.1.inc' === $testFile ) { + return array( '--tab-width=' . $this->tab_width ); + } + + return array(); + } + + /** + * Set CLI values before the file is tested. + * + * Used by PHPCS 3.x. + * + * @param string $testFile The name of the file being tested. + * @param \PHP_CodeSniffer\Config $config The config data for the test run. + * + * @return void + */ + public function setCliValues( $testFile, $config ) { + // Tab width setting is only needed for the tabbed file. + if ( 'MultipleStatementAlignmentUnitTest.1.inc' === $testFile ) { + $config->tabWidth = $this->tab_width; + } else { + $config->tabWidth = 0; + } + } + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 1 => 3, // Invalid property value error. + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array( + 8 => 2, + 11 => 1, + 14 => 2, + 42 => 1, + 45 => 1, + 46 => 1, + 47 => 1, + 54 => 1, + 57 => 1, + 59 => 1, + 66 => 1, + 67 => 1, + 68 => 1, + 85 => 1, + 87 => 1, + 92 => 1, + 113 => 1, + 115 => 1, + 127 => 1, + 130 => 1, + 132 => 1, + 134 => 1, + 137 => 1, + 138 => 1, + 141 => 1, + 144 => 1, + 148 => 1, + 169 => 1, + 171 => 1, + 172 => 1, + 175 => 1, + 176 => 1, + 177 => 1, + 178 => 1, + 203 => 1, + 208 => 1, + 214 => 1, + 218 => 1, + 219 => 1, + 220 => 1, + 236 => 1, + 238 => 1, + 243 => 1, + 246 => 1, + 248 => 1, + 253 => 1, + 254 => 1, + 256 => 1, + 258 => 1, + 261 => 1, + 262 => 1, + 263 => 1, + 266 => 1, + 268 => 1, + 279 => 1, + 280 => 1, + 289 => 1, + 291 => 1, + 292 => 1, + 294 => 1, + 297 => 1, + 298 => 1, + 317 => 1, + 320 => 1, + 324 => 1, + 373 => 1, + 376 => 1, + 380 => 1, + 416 => 1, + 417 => 1, + 418 => 1, + 420 => 1, + 427 => 1, + 428 => 1, + 430 => 1, + 441 => 1, + 450 => 1, + 457 => 1, + 461 => 1, + 485 => 1, + 486 => 1, + 487 => 1, + 488 => 1, + 491 => 1, + 495 => 1, + 501 => 1, + 503 => 1, + 506 => 1, + 515 => 1, + 522 => 1, + 529 => 1, + 530 => 1, + 531 => 1, + 532 => 1, + 540 => 1, + 542 => 1, + 545 => 1, + 563 => 1, + 566 => 1, + 570 => 1, + ); + } + +} // End class. diff --git a/WordPress/Tests/CSRF/NonceVerificationUnitTest.inc b/WordPress/Tests/CSRF/NonceVerificationUnitTest.inc index 3acd57bd..750cc0ec 100644 --- a/WordPress/Tests/CSRF/NonceVerificationUnitTest.inc +++ b/WordPress/Tests/CSRF/NonceVerificationUnitTest.inc @@ -133,7 +133,7 @@ $b = function () { // @codingStandardsChangeSetting WordPress.CSRF.NonceVerification customSanitizingFunctions sanitize_pc,sanitize_twitter // @codingStandardsChangeSetting WordPress.CSRF.NonceVerification customUnslashingSanitizingFunctions do_something -function foo_5() { +function foo_6() { sanitize_twitter( $_POST['foo'] ); // OK. sanitize_pc( $_POST['bar'] ); // OK. @@ -143,7 +143,7 @@ function foo_5() { // @codingStandardsChangeSetting WordPress.CSRF.NonceVerification customSanitizingFunctions sanitize_pc // @codingStandardsChangeSetting WordPress.CSRF.NonceVerification customUnslashingSanitizingFunctions false -function foo_5() { +function foo_7() { do_something( $_POST['foo'] ); // Bad. sanitize_pc( $_POST['bar'] ); // OK. @@ -154,7 +154,7 @@ function foo_5() { // @codingStandardsChangeSetting WordPress.CSRF.NonceVerification customNonceVerificationFunctions false // @codingStandardsChangeSetting WordPress.CSRF.NonceVerification customSanitizingFunctions false -function foo_5() { +function foo_8() { do_something( $_POST['foo'] ); // Bad. sanitize_pc( $_POST['bar'] ); // Bad. diff --git a/WordPress/Tests/CSRF/NonceVerificationUnitTest.php b/WordPress/Tests/CSRF/NonceVerificationUnitTest.php index 83b507cb..ab50fe04 100644 --- a/WordPress/Tests/CSRF/NonceVerificationUnitTest.php +++ b/WordPress/Tests/CSRF/NonceVerificationUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\CSRF; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the NonceVerification sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.5.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_CSRF_NonceVerificationUnitTest extends AbstractSniffUnitTest { +class NonceVerificationUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Classes/ClassInstantiationUnitTest.inc b/WordPress/Tests/Classes/ClassInstantiationUnitTest.inc new file mode 100644 index 00000000..d0a9ae7a --- /dev/null +++ b/WordPress/Tests/Classes/ClassInstantiationUnitTest.inc @@ -0,0 +1,106 @@ +<?php +/* + * All OK. + */ +$a = new MyClass(); +$a = new MyClass( $something ); +$a = new $varHoldingClassName(); +$a = new self::$transport[$cap_string](); +$renderer = new $this->inline_diff_renderer(); +$b = ( new MyClass() )->my_function(); +$b = ( new MyClass() )::$property; + +class ClassA { + public static function get_instance() { + return new self(); + } + + public static function get_other_instance() { + return new static(); + } +} + +class ClassB extends ClassA { + public function get_parent_instance() { + return new parent(); + } +} + +// PHP 7: Anonymous classes. +$util->setLogger( new class() {} ); +$b = new class( 10 ) extends SomeClass implements SomeInterface {}; + + +/* + * Bad. + */ +$a = new MyClass; +$a = new $varHoldingClassName; +$a = new self::$transport[$cap_string]; +$renderer = new $this->inline_diff_renderer; +$b = ( new MyClass )->my_function(); +$b = ( new MyClass )::$property; + +class ClassAA { + public static function get_instance() { + return new self; + } + + public static function get_other_instance() { + return new static; + } +} + +class ClassBB extends ClassA { + public function get_parent_instance() { + return new parent; + } +} + +// PHP 7: Anonymous classes. +$util->setLogger( new class {} ); +$b = new class extends SomeClass implements SomeInterface {}; + + +// Test some non-typical spacing. +$renderer = new $this-> + inline_diff_renderer (); +$renderer = new $this-> // There can be a comment here. + inline_diff_renderer (); +$renderer = new $this-> + inline_diff_renderer /* or here */ (); +$a = new self :: $transport [ $cap_string ] (); + +$renderer = new $this-> + inline_diff_renderer; +$renderer = new $this-> // There can be a comment here. + inline_diff_renderer; +$renderer = new $this-> + inline_diff_renderer /* or here */ ; +$a = new self :: $transport [ $cap_string ]; + + +// Assigning new by reference. +$b = &new Foobar(); +$b = & new Foobar(); + + +// Currently not accounted for by the sniff, i.e. false negatives. +$a = new $$varHoldingClassName; +$a = new ${$varHoldingClassName}; + +// Namespaced classes: OK. +$a = new \MyClass(); +$a = new \MyNamespace\MyClass(); + +// Namespaced classes: Bad. +$a = new \MyClass; +$a = new \MyNamespace\MyClass; + +// Non-variable keys: OK. +$a = new $array['key'](); +$a = new $array[0](); + +// Non-variable keys: Bad. +$a = new $array['key']; +$a = new $array[0]; diff --git a/WordPress/Tests/Classes/ClassInstantiationUnitTest.inc.fixed b/WordPress/Tests/Classes/ClassInstantiationUnitTest.inc.fixed new file mode 100644 index 00000000..d906b2d4 --- /dev/null +++ b/WordPress/Tests/Classes/ClassInstantiationUnitTest.inc.fixed @@ -0,0 +1,106 @@ +<?php +/* + * All OK. + */ +$a = new MyClass(); +$a = new MyClass( $something ); +$a = new $varHoldingClassName(); +$a = new self::$transport[$cap_string](); +$renderer = new $this->inline_diff_renderer(); +$b = ( new MyClass() )->my_function(); +$b = ( new MyClass() )::$property; + +class ClassA { + public static function get_instance() { + return new self(); + } + + public static function get_other_instance() { + return new static(); + } +} + +class ClassB extends ClassA { + public function get_parent_instance() { + return new parent(); + } +} + +// PHP 7: Anonymous classes. +$util->setLogger( new class() {} ); +$b = new class( 10 ) extends SomeClass implements SomeInterface {}; + + +/* + * Bad. + */ +$a = new MyClass(); +$a = new $varHoldingClassName(); +$a = new self::$transport[$cap_string](); +$renderer = new $this->inline_diff_renderer(); +$b = ( new MyClass() )->my_function(); +$b = ( new MyClass() )::$property; + +class ClassAA { + public static function get_instance() { + return new self(); + } + + public static function get_other_instance() { + return new static(); + } +} + +class ClassBB extends ClassA { + public function get_parent_instance() { + return new parent(); + } +} + +// PHP 7: Anonymous classes. +$util->setLogger( new class() {} ); +$b = new class() extends SomeClass implements SomeInterface {}; + + +// Test some non-typical spacing. +$renderer = new $this-> + inline_diff_renderer(); +$renderer = new $this-> // There can be a comment here. + inline_diff_renderer(); +$renderer = new $this-> + inline_diff_renderer /* or here */ (); +$a = new self :: $transport [ $cap_string ](); + +$renderer = new $this-> + inline_diff_renderer(); +$renderer = new $this-> // There can be a comment here. + inline_diff_renderer(); +$renderer = new $this-> + inline_diff_renderer() /* or here */ ; +$a = new self :: $transport [ $cap_string ](); + + +// Assigning new by reference. +$b = &new Foobar(); +$b = & new Foobar(); + + +// Currently not accounted for by the sniff, i.e. false negatives. +$a = new $$varHoldingClassName; +$a = new ${$varHoldingClassName}; + +// Namespaced classes: OK. +$a = new \MyClass(); +$a = new \MyNamespace\MyClass(); + +// Namespaced classes: Bad. +$a = new \MyClass(); +$a = new \MyNamespace\MyClass(); + +// Non-variable keys: OK. +$a = new $array['key'](); +$a = new $array[0](); + +// Non-variable keys: Bad. +$a = new $array['key'](); +$a = new $array[0](); diff --git a/WordPress/Tests/Classes/ClassInstantiationUnitTest.js b/WordPress/Tests/Classes/ClassInstantiationUnitTest.js new file mode 100644 index 00000000..4800633f --- /dev/null +++ b/WordPress/Tests/Classes/ClassInstantiationUnitTest.js @@ -0,0 +1,4 @@ +var firstBook = new Book(); // OK. +var secondBook = new Book; // Bad. +var thirdBook = new Book (); // Bad. +var fourthBook = new Book ( 'title' ); // Bad. diff --git a/WordPress/Tests/Classes/ClassInstantiationUnitTest.js.fixed b/WordPress/Tests/Classes/ClassInstantiationUnitTest.js.fixed new file mode 100644 index 00000000..73116a47 --- /dev/null +++ b/WordPress/Tests/Classes/ClassInstantiationUnitTest.js.fixed @@ -0,0 +1,4 @@ +var firstBook = new Book(); // OK. +var secondBook = new Book(); // Bad. +var thirdBook = new Book(); // Bad. +var fourthBook = new Book( 'title' ); // Bad. diff --git a/WordPress/Tests/Classes/ClassInstantiationUnitTest.php b/WordPress/Tests/Classes/ClassInstantiationUnitTest.php new file mode 100644 index 00000000..c4000602 --- /dev/null +++ b/WordPress/Tests/Classes/ClassInstantiationUnitTest.php @@ -0,0 +1,85 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\Classes; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the ClassInstantiation sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class ClassInstantiationUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @param string $testFile The name of the file being tested. + * @return array <int line number> => <int number of errors> + */ + public function getErrorList( $testFile = '' ) { + + switch ( $testFile ) { + case 'ClassInstantiationUnitTest.inc': + return array( + 37 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 41 => 1, + 42 => 1, + 46 => 1, + 50 => 1, + 56 => 1, + 61 => 1, + 62 => 1, + 67 => 1, + 69 => 1, + 71 => 1, + 72 => 1, + 75 => 1, + 77 => 1, + 79 => 1, + 80 => 1, + 84 => 1, + 85 => 1, + 97 => 1, + 98 => 1, + 105 => 1, + 106 => 1, + ); + + case 'ClassInstantiationUnitTest.js': + return array( + 2 => 1, + 3 => 1, + 4 => 1, + ); + + default: + return array(); + + } + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array(); + + } + +} // End class. diff --git a/WordPress/Tests/CodeAnalysis/AssignmentInConditionUnitTest.inc b/WordPress/Tests/CodeAnalysis/AssignmentInConditionUnitTest.inc new file mode 100644 index 00000000..ffae7751 --- /dev/null +++ b/WordPress/Tests/CodeAnalysis/AssignmentInConditionUnitTest.inc @@ -0,0 +1,117 @@ +<?php + +// OK. +if ($a === 123) { +} elseif ($a == 123) { +} elseif ($a !== 123) { +} elseif ($a != 123) {} + +function abc( $a = 'default' ) {} +if (in_array( $a, array( 1 => 'a', 2 => 'b' ) ) ) {} + +switch ( $a === $b ) {} +switch ( true ) { + case $sample == 'something': + break; +} + +for ( $i = 0; $i == 100; $i++ ) {} +for ( $i = 0; $i >= 100; $i++ ) {} +for ( $i = 0; ; $i++ ) {} +for (;;) {} + +do { +} while ( $sample == false ); + +while ( $sample === false ) {} + +// Assignments in condition. +if ($a = 123) { +} elseif ($a = 'abc') { +} else if( $a += 10 ) { +} else if($a -= 10) { +} else if($a *= 10) { +} else if($a **= 10) { +} else if($a /= 10) { +} else if($a .= strtolower($b)) { +} else if($a %= SOME_CONSTANT) { +} else if($a &= 2) { +} else if($a |= 2) { +} else if($a ^= 2) { +} else if($a <<= 2) { +} else if($a >>= 2) { +} else if($a ??= $b) { +} elseif( $a = 'abc' && $b = 'def' ) { +} elseif( + $a = 'abc' + && $a .= 'def' +) {} + +if ($a[] = 123) { +} elseif ($a['something'] = 123) { +} elseif (self::$a = 123) { +} elseif (parent::$a *= 123) { +} elseif (static::$a = 123) { +} elseif (MyClass::$a .= 'abc') { +} else if( $this->something += 10 ) {} + +switch ( $a = $b ) {} +switch ( true ) { + case $sample = 'something': + break; + + case $sample = 'something' && $a = $b: + break; +} + +for ( $i = 0; $i = 100; $i++ ) {} +for ( $i = 0; $i = 100 && $b = false; $i++ ) {} + +do { +} while ( $sample = false ); + +while ( $sample = false ) {} + +if ($a = 123) : +endif; + +// Non-variable assignment found. +if (123 = $a) {} +if (strtolower($b) = $b) {} +if (array( 1 => 'a', 2 => 'b' ) = $b) {} + +if (SOME_CONSTANT = 123) { +} else if(self::SOME_CONSTANT -= 10) {} + +if ( $a() = 123 ) { +} else if ( $b->something() = 123 ) { +} elseif ( $c::something() = 123 ) {} + +switch ( true ) { + case 'something' = $sample: + break; +} + +while (list($field_name, $file_names) = each($formfiles)) {} + +/* + * Ternaries should also be checked, but can only be reliably done when in parenthesis. + */ + +// OK. +$mode = ( $a == 'something' ) ? 'on' : 'off'; +$mode = ( $a == 'on' ? 'true' : $a == 'off' ? 't' : 'f' ); + +// Bad. +$mode = ( $a = 'on' ) ? 'on' : 'off'; +$mode = ( $a = 'on' ) ?: 'off'; +$mode = ( $a = 'on' ) ? 'true' : ( $a = 'off' ) ? 't' : 'f'; // Bad x 2. +$mode = ( $a = 'on' ? 'on' : 'off' ); +$mode = ( $a = 'on' ?: 'off' ); +$mode = ( $a = 'on' ? 'true' : ( $a = 'off' ? 't' : 'f' ) ); // Bad x 2. +$mode = ( $a = 'on' ? 'true' : $a = 'off' ? 't' : 'f' ); // Bad x 3. The first ? triggers 1, the second (correctly) 2. + +// Currently not checked. +$mode = $a = 'on' ? 'on' : 'off'; +$mode = $a = 'on' ?: 'off'; +$mode = $a = 'on' ? 'true' : $a = 'off' ? 't' : 'f'; diff --git a/WordPress/Tests/CodeAnalysis/AssignmentInConditionUnitTest.php b/WordPress/Tests/CodeAnalysis/AssignmentInConditionUnitTest.php new file mode 100644 index 00000000..89ba814a --- /dev/null +++ b/WordPress/Tests/CodeAnalysis/AssignmentInConditionUnitTest.php @@ -0,0 +1,94 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\CodeAnalysis; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the AssignmentInCondition sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class AssignmentInConditionUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array(); + + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array( + 29 => 1, + 30 => 1, + 31 => 1, + 32 => 1, + 33 => 1, + 34 => 1, + 35 => 1, + 36 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 41 => 1, + 42 => 1, + 43 => 1, + 44 => 2, + 46 => 1, + 47 => 1, + 50 => 1, + 51 => 1, + 52 => 1, + 53 => 1, + 54 => 1, + 55 => 1, + 56 => 1, + 58 => 1, + 60 => 1, + 63 => 2, + 67 => 1, + 68 => 2, + 71 => 1, + 73 => 1, + 75 => 1, + 79 => 1, + 80 => 1, + 81 => 1, + 83 => 1, + 84 => 1, + 86 => 1, + 87 => 1, + 88 => 1, + 91 => 1, + 95 => 1, + 106 => 1, + 107 => 1, + 108 => 2, + 109 => 1, + 110 => 1, + 111 => 2, + 112 => 3, + ); + + } + +} // End class. diff --git a/WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.inc b/WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.inc new file mode 100644 index 00000000..710099b4 --- /dev/null +++ b/WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.inc @@ -0,0 +1,45 @@ +<?php + +/* + * Test empty statement: two consecutive semicolons without executable code between them. + */ +function_call(); // OK. + +// The below examples are all bad. +function_call();; + +function_call(); +; + +function_call(); +/* some comment */; + +function_call(); +/* some comment */ ; + +?> +<input name="<?php ; something_else(); ?>" /> +<input name="<?php something_else(); ; ?>" /> + +/* + * Test empty statement: no code between PHP open and close tag. + */ +<input name="<?php something_else() ?>" /> <!-- OK. --> +<input name="<?php something_else(); ?>" /> <!-- OK. --> +<input name="<?php /* comment */ ?>" /> <!-- OK. --> + +<input name="<?php ?>" /> <!-- Bad. --> + +<input name="<?php + + +?>" /> <!-- Bad. --> + +<!-- +/* + * Test detecting & fixing a combination of the two above checks. + */ +--> +<?php ; ?> + +<input name="<?php ; ?>" /> diff --git a/WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.inc.fixed b/WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.inc.fixed new file mode 100644 index 00000000..d036c214 --- /dev/null +++ b/WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.inc.fixed @@ -0,0 +1,40 @@ +<?php + +/* + * Test empty statement: two consecutive semicolons without executable code between them. + */ +function_call(); // OK. + +// The below examples are all bad. +function_call(); + +function_call(); + +function_call(); +/* some comment */ + +function_call(); +/* some comment */ + +?> +<input name="<?php something_else(); ?>" /> +<input name="<?php something_else(); ?>" /> + +/* + * Test empty statement: no code between PHP open and close tag. + */ +<input name="<?php something_else() ?>" /> <!-- OK. --> +<input name="<?php something_else(); ?>" /> <!-- OK. --> +<input name="<?php /* comment */ ?>" /> <!-- OK. --> + +<input name="" /> <!-- Bad. --> + +<input name="" /> <!-- Bad. --> + +<!-- +/* + * Test detecting & fixing a combination of the two above checks. + */ +--> + +<input name="" /> diff --git a/WordPress/Tests/Theme/DeprecatedWPConstantsUnitTest.php b/WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.php similarity index 66% rename from WordPress/Tests/Theme/DeprecatedWPConstantsUnitTest.php rename to WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.php index f8b95c57..d4b632a4 100644 --- a/WordPress/Tests/Theme/DeprecatedWPConstantsUnitTest.php +++ b/WordPress/Tests/CodeAnalysis/EmptyStatementUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\CodeAnalysis; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** - * Unit test class for the Theme_DeprecatedWPConstants sniff. + * Unit test class for the EmptyStatement sniff. * * @package WPCS\WordPressCodingStandards - * @since 0.xx.0 + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_Theme_DeprecatedWPConstantsUnitTest extends AbstractSniffUnitTest { +class EmptyStatementUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -21,20 +27,7 @@ class WordPress_Tests_Theme_DeprecatedWPConstantsUnitTest extends AbstractSniffU * @return array <int line number> => <int number of errors> */ public function getErrorList() { - return array( - 9 => ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ? 0 : 1 ), - 13 => 1, - 14 => 1, - 15 => 1, - 16 => 1, - 17 => 1, - 18 => 1, - 19 => 1, - 20 => 1, - 21 => 1, - 22 => 1, - 23 => 1, - ); + return array(); } /** @@ -43,7 +36,18 @@ public function getErrorList() { * @return array <int line number> => <int number of warnings> */ public function getWarningList() { - return array(); + return array( + 9 => 1, + 12 => 1, + 15 => 1, + 18 => 1, + 21 => 1, + 22 => 1, + 31 => 1, + 33 => 1, + 43 => 1, + 45 => 1, + ); } } // End class. diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc new file mode 100644 index 00000000..d7030131 --- /dev/null +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc @@ -0,0 +1,209 @@ +<?php + +$sql = $wpdb->prepare( $sql, $replacements ); // OK - no query available to examine - this will be handled by the PreparedSQL sniff. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", 1, "admin" ); // OK. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", array( 1, "admin" ) ); // OK. +$sql = $wpdb->prepare( 'SELECT * FROM `table` WHERE `column` = %s AND `field` = %d', 'foo', 1337 ); // OK. +$sql = $wpdb->prepare( 'SELECT DATE_FORMAT(`field`, "%%c") FROM `table` WHERE `column` = %s', 'foo' ); // OK. + +/* + * No placeholders, no need to use prepare(). + */ +$sql = $wpdb->prepare( 'SELECT * FROM `table`' ); // Warning. +$sql = $wpdb->prepare( 'SELECT * FROM `table` WHERE id = ' . $id ); // OK - this will be handled by the PreparedSQL sniff. +$sql = $wpdb->prepare( "SELECT * FROM `table` WHERE id = $id" ); // OK - this will be handled by the PreparedSQL sniff. +$sql = $wpdb->prepare( "SELECT * FROM `table` WHERE id = {$id['some%sing']}" ); // OK - this will be handled by the PreparedSQL sniff. +$sql = $wpdb->prepare( 'SELECT * FROM ' . $wpdb->users ); // Warning. +$sql = $wpdb->prepare( "SELECT * FROM `{$wpdb->users}`" ); // Warning. +$sql = $wpdb->prepare( "SELECT * FROM `{$wpdb->users}` WHERE id = $id" ); // OK - this will be handled by the PreparedSQL sniff. + +/* + * No placeholders found, but replacement variable(s) are being passed. + */ +$sql = $wpdb->prepare( 'SELECT * FROM `table`', $something ); // Warning. + +/* + * Test passing invalid replacement placeholder. + */ +$sql = $wpdb->prepare( '%d %1$e %%% % %A %h', 1 ); // Bad x 5. +$sql = $wpdb->prepare( '%%%s', 1 ); // OK. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %1\$d AND user_login = %2\$s", 1, "admin" ); // OK. 2 x warning for unquoted complex placeholders. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %01.2f AND user_login = %10.10X", 1, "admin" ); // Bad x 1 + 1 warning unquoted complex placeholders + 1 warning nr of replacements. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %'.09F AND user_login = %1\$04x", 1, "admin" ); // Bad x 1 + 1 warning unquoted complex placeholders + 1 warning nr of replacements. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = \"%1\$c\" AND user_login = '%2\$e'", 1, "admin" ); // Bad x 2 + 1 warning. +$sql = $wpdb->prepare( 'SELECT * FROM ' . $wpdb->users . ' WHERE id = \'%1\$b\' AND user_login = "%2\$o"', 1, "admin" ); // Bad x 2 + 1 warning. + +/* + * Test passing quoted simple replacement placeholder and unquoted complex placeholder. + */ +$sql = $wpdb->prepare( '"%f"', 1.1 ); // Bad. +$sql = $wpdb->prepare( 'SELECT * FROM `table` WHERE `field` = \'%s\'', 'string' ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM `table` WHERE `id` = \"%d\"", 1 ); // Bad. +$sql = $wpdb->prepare( <<<EOD + SELECT * + FROM `%1\$s` + WHERE id = %2\$d + AND `%3\$s` = "%4\$s" +EOD + , $wpdb->users, 1, 'user_login', "admin" +); // Warning x 3, unquoted complex placeholder. + +/* + * Test passing an incorrect amount of replacement parameters. + */ +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", 1 ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", 1, "admin", $variable ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", array( 1 ) ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", [1, "admin", $variable] ); // Bad. + +$replacements = [1, "admin", $variable]; +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", $replacements ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s", $replacements ); // WPCS: PreparedSQLPlaceholders replacement count OK. + +// Valid test case as found in WP core /wp-admin/includes/export.php +$esses = array_fill( 0, count($post_types), '%s' ); +$where = $wpdb->prepare( "{$wpdb->posts}.post_type IN (" . implode( ',', $esses ) . ')', $post_types ); // Warning. +// Testing that whitelist comment work for this mismatch too. +$where = $wpdb->prepare( "{$wpdb->posts}.post_type IN (" . implode( ',', $esses ) . ')', $post_types ); // WPCS: PreparedSQLPlaceholders replacement count OK. + +/* + * Test correctly recognizing queries using IN in combination with dynamic placeholder creation. + */ +// The proper way to write a query using `IN` won't throw a warning: +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); // OK. + +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s) + AND {$wpdb->posts}.post_status IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ), + IMPLODE( ',', Array_Fill( 0, count($post_statusses), '%s' ) ) + ), + array_merge( $post_types, $post_statusses ) +); // OK. + +$where = $wpdb->prepare( + "{$wpdb->posts}.post_type IN (" + . implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . ") AND {$wpdb->posts}.post_status IN (" + . implode( ',', array_fill( 0, count($post_statusses), '%s' ) ) + . ')', + array_merge( $post_types, $post_statusses ) +); // OK. + +$query = $wpdb->prepare( + sprintf( + 'SELECT COUNT(ID) + FROM `%s` + WHERE ID IN (%s) + AND post_status = "publish"', + $wpdb->posts, + implode( ',', array_fill( 0, count( $post_ids ), '%d' ) ) + ) . ' AND post_type = %s', + array_merge( $post_ids, array( $this->get_current_post_type() ) ), +); // OK. + +$results = $wpdb->get_results( + $wpdb->prepare( ' + SELECT ID + FROM ' . $wpdb->posts . ' + WHERE ID NOT IN( SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = %s AND meta_value = %s ) + AND post_status in( "future", "draft", "pending", "private", "publish" ) + AND post_type in( ' . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . ' ) + LIMIT %d', + $replacements + ), + ARRAY_A +); // OK. + +$query = $wpdb->prepare( + sprintf( + 'SELECT COUNT(ID) + FROM `%s` + WHERE ID in (%s) + AND post_status = "publish"', + $wpdb->posts, + implode( ',', array_fill( 0, count( $post_ids ), '%d' ) ) + ) . ' AND post_type = %s', + array_merge( $post_ids, array( $this->get_current_post_type() ) ), + $another +); // Error - second replacement param is incorrect, with a variable nr of placeholders you always need to pass a replacement array. + +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN ('%s')", + implode( ',', array_fill( 0, count($post_types), '%s' ) ), + ), + array_merge( $post_types, $post_statusses ) +); // Bad x 2 - %s is quoted, so this won't work properly, will throw incorrect nr of replacements error + quotes found. + +$where = $wpdb->prepare( + "{$wpdb->posts}.post_type IN (\"" + . implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . "\") AND {$wpdb->posts}.post_status IN ('" + . implode( ',', array_fill( 0, count($post_statusses), '%s' ) ) + . '\')', + array_merge( $post_types, $post_statusses ) +); // Bad x 2 - quotes between the () for the IN. + +/* + * Test distinguising wildcard _ and %'s in LIKE statements. + */ +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like ); // OK. +$sql = $wpdb->prepare( 'SELECT * FROM ' . $wpdb->posts . ' WHERE post_content LIKE \'a string\'' ); // Warning x 2. Like without wildcard, no need for prepare. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE 'a string'" ); // Warning x 2. Like without wildcard, no need for prepare. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE '%a string' AND post_status = %s", $status ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE 'a string%' AND post_status = %s", $status ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE '%a string%' AND post_status = %s", $status ); // Bad. +$sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE 'a_string' AND post_status = %s", $status ); // Bad. + +// Some VALID test cases as found in plugins published on WP.org. +$comment_id = $wpdb->get_var($wpdb->prepare('SELECT comment_ID FROM ' . $wpdb->comments . ' WHERE comment_post_ID = %d AND comment_agent LIKE %s', intval($post->ID), 'Disqus/1.0:' . $comment_id)); // OK. + +$sql = $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s", self::CACHE_KEY_PREFIX . '%' ); // OK. + +$wpdb->query($wpdb->prepare('UPDATE '.$wpdb->prefix.'posts SET post_content = REPLACE(post_content, %s, %s) WHERE post_type = "page" AND post_content LIKE %s', $meta_before, $meta_after, '%'.$wpdb->esc_like($meta_before).'%')); // OK. + +$query = $wpdb->prepare("DELETE FROM {$wpdb->prefix}options WHERE `option_name` LIKE %s OR `option_name` LIKE %s", "%{$transient_name}%", "%{$transient_timeout_name}%"); // OK. + +// Some INVALID test cases as found in plugins published on WP.org. +$wpdb->prepare( "UPDATE $wpdb->posts SET post_status = 'pending' WHERE (post_type LIKE 'product_variation' or post_type LIKE 'product') AND NOT ID IN (". implode(",", $imported_ids ) .")"); // Error x 1 for `product_variation`; warning x 1 for wrong use of LIKE with `product`; the PreparedSQL sniff will also kick in and throw an error about `$imported_ids`. + +$attachment = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE guid LIKE '%%%s%%' LIMIT 1;", $img_url ) ); // Bad. + +$result = $wpdb->get_col($wpdb->prepare("SELECT guid FROM $wpdb->posts WHERE guid LIKE '%%%s' and post_parent=%d;", $atts['model'], $post->ID )); // Bad. + +$comments = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_agent NOT LIKE 'Disqus/%%'", $post->ID) ); // Bad. + +$sql = $wpdb->prepare( "SELECT count(*) FROM $this->fontsTable WHERE name LIKE '%%%s%%' OR status LIKE '%%%s%%' OR metadata LIKE '%%%s%%'", $search, $search, $search ); // Bad x 3, the PreparedSQL sniff will also kick in and throw an error about `$this`. + +$additional_where = $wpdb->prepare(' AND (network like "%%%s%%" OR ProgramTitle like "%%%s%%" OR TransactionStatus like "%%%s%%" ) ', $search, $search, $search); // Bad x 3. + +$robots_query = $wpdb->prepare( "SELECT name FROM $robots_table WHERE %s LIKE concat('%%',name,'%%')", $http_user_agent ); // Bad, the PreparedSQL sniff will also kick in. + +$sql = $wpdb->prepare('SELECT * FROM ' . $wpdb->avatar_privacy . ' WHERE email LIKE "%s"', $email); // Bad (quotes). + +$res = $wpdb->query( $wpdb->prepare( 'UPDATE ' . $wpdb->posts . ' SET post_name="feed" WHERE post_name LIKE "feed-%" AND LENGTH(post_name)=6 AND post_type=%s', BAWAS_POST_TYPE ) ); // Bad. + +$sql = $wpdb->prepare( "SELECT ID FROM $wpdb->users AS us INNER JOIN $wpdb->usermeta AS mt ON ( us.ID = mt.user_id ) WHERE ( mt.meta_key = 'bbp_last_login' AND mt.meta_value < %s ) AND user_id IN ( SELECT user_id FROM $wpdb->usermeta AS mt WHERE (mt.meta_key = '{$wpdb->prefix}capabilities' AND mt.meta_value LIKE '%%bbp_user%%' ))", $beforegmdate ); // Bad. + +$paged_events = $wpdb->get_results( $wpdb->prepare( "SELECT id as event_id FROM {$bp->events->table_name} WHERE ( name LIKE '{$filter}%%' OR description LIKE '{$filter}%%' ) AND id IN ({$gids}) " . $oldevents . " {$pag_sql}" ) ); // Bad x 2, the PreparedSQL sniff will also kick in and throw six errors. + +$query = $wpdb->prepare( "SELECT language_code FROM {$wpdb->prefix}icl_translations WHERE element_type LIKE '%%post_%%' AND element_id = %d", $post_ID ); // Bad. + +$postID = $wpdb->get_var($wpdb->prepare("SELECT `postID` FROM `".EPDataBase::$table_name."` WHERE `path` like '".$filePath."';")); // OK, the PreparedSQL sniff will kick in and throw four errors. + +$wpdb->query($wpdb->prepare("show tables like '$this->table_name'")) > 0; // OK, the PreparedSQL sniff will kick in. + +$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->wp_options WHERE option_name LIKE '%widget_gigya%'" ) ); // Bad. + +$where .= $wpdb->prepare( " AND `name` LIKE '%%%%" . '%s' . "%%%%' ", $args['name'] ); // Bad x 2. + +$wpdb->query($wpdb->prepare("delete from wp_postmeta where post_id = $target_postId AND meta_key like 'google_snippets'")); // Bad, the PreparedSQL sniff will also kick in and throw an error about `$target_postId`. diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php new file mode 100644 index 00000000..235be3ea --- /dev/null +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php @@ -0,0 +1,98 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\DB; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the PreparedSQLPlaceholders sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class PreparedSQLPlaceholdersUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 28 => 5, + 31 => 1, + 32 => 1, + 33 => 2, + 34 => 2, + 39 => 1, + 40 => 1, + 41 => 1, + 54 => 1, + 141 => 1, + 149 => 1, + 151 => 1, + 162 => 1, + 163 => 1, + 164 => 1, + 165 => 1, + 177 => 1, + 179 => 1, + 181 => 1, + 183 => 1, + 185 => 3, + 187 => 3, + 189 => 1, + 191 => 1, + 193 => 1, + 195 => 1, + 197 => 2, + 199 => 1, + 205 => 1, + 207 => 2, + 209 => 1, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array( + 12 => 1, + 16 => 1, + 17 => 1, + 23 => 1, + 30 => 2, + 31 => 2, + 32 => 2, + 33 => 1, + 34 => 1, + 44 => 1, + 45 => 1, + 46 => 1, + 55 => 1, + 56 => 1, + 57 => 1, + 58 => 1, + 61 => 1, + 66 => 1, + 126 => 1, + 139 => 1, + 160 => 2, + 161 => 2, + 177 => 1, + ); + + } + +} // End class. diff --git a/WordPress/Tests/DB/RestrictedClassesUnitTest.1.inc b/WordPress/Tests/DB/RestrictedClassesUnitTest.1.inc index 493415f8..c5dbdc38 100644 --- a/WordPress/Tests/DB/RestrictedClassesUnitTest.1.inc +++ b/WordPress/Tests/DB/RestrictedClassesUnitTest.1.inc @@ -33,7 +33,7 @@ class OurMysqli implements mysqli {} class TheirMysqli implements \mysqli {} $db5 = new PDO(); -$db6 - new PDO->exec(); +$db6 = ( new PDO() )->exec(); PDO::getAvailableDrivers(); $db7 = new PDOStatement; @@ -49,18 +49,18 @@ $db9 = new \My\DBlayer; $db9 = new \My\DBlayer; // Ok - within excluded group. echo mysqli::$affected_rows; // Error. -class YourMysqli extends \mysqli {} // Error. +class YourMysqliA extends \mysqli {} // Error. // Exclude all groups: // @codingStandardsChangeSetting WordPress.DB.RestrictedClasses exclude test,mysql $db9 = new \My\DBlayer; // Ok - within excluded group. echo mysqli::$affected_rows; // Ok - within excluded group. -class YourMysqli extends \mysqli {} // Ok - within excluded group. +class YourMysqliB extends \mysqli {} // Ok - within excluded group. // Reset group exclusions. // @codingStandardsChangeSetting WordPress.DB.RestrictedClasses exclude false $db9 = new \My\DBlayer; // Error. echo mysqli::$affected_rows; // Error. -class YourMysqli extends \mysqli {} // Error. +class YourMysqliC extends \mysqli {} // Error. diff --git a/WordPress/Tests/DB/RestrictedClassesUnitTest.php b/WordPress/Tests/DB/RestrictedClassesUnitTest.php index bef641cf..ed839781 100644 --- a/WordPress/Tests/DB/RestrictedClassesUnitTest.php +++ b/WordPress/Tests/DB/RestrictedClassesUnitTest.php @@ -7,13 +7,20 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\DB; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use WordPress\AbstractFunctionRestrictionsSniff; + /** * Unit test class for the DB_RestrictedClasses sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.10.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_DB_RestrictedClassesUnitTest extends AbstractSniffUnitTest { +class RestrictedClassesUnitTest extends AbstractSniffUnitTest { /** * Add a number of extra restricted classes to unit test the abstract @@ -25,10 +32,10 @@ class WordPress_Tests_DB_RestrictedClassesUnitTest extends AbstractSniffUnitTest protected function setUp() { parent::setUp(); - WordPress_AbstractFunctionRestrictionsSniff::$unittest_groups = array( + AbstractFunctionRestrictionsSniff::$unittest_groups = array( 'test' => array( - 'type' => 'error', - 'message' => 'Detected usage of %s.', + 'type' => 'error', + 'message' => 'Detected usage of %s.', 'classes' => array( '\My\DBlayer', 'AdoDb\Test*', @@ -38,12 +45,11 @@ protected function setUp() { } /** - * Skip this test on PHP 5.2 as otherwise testing the namespace resolving would fail. - * - * @return bool Whether to skip this test. + * Reset the $groups property. */ - protected function shouldSkipTest() { - return ( PHP_VERSION_ID < 50300 ); + protected function tearDown() { + AbstractFunctionRestrictionsSniff::$unittest_groups = array(); + parent::tearDown(); } /** diff --git a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php index 335e2bdc..5d31280f 100644 --- a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php +++ b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\DB; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the DB_RestrictedFunctions sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.10.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_DB_RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { +class RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Files/FileNameUnitTest.php b/WordPress/Tests/Files/FileNameUnitTest.php index ce1364d0..d5a65940 100644 --- a/WordPress/Tests/Files/FileNameUnitTest.php +++ b/WordPress/Tests/Files/FileNameUnitTest.php @@ -7,14 +7,20 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Files; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the FileName sniff. * * @package WPCS\WordPressCodingStandards + * * @since 2013-06-11 * @since 0.11.0 Actually added tests ;-) + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_Files_FileNameUnitTest extends AbstractSniffUnitTest { +class FileNameUnitTest extends AbstractSniffUnitTest { /** * Error files with the expected nr of errors. @@ -28,14 +34,14 @@ class WordPress_Tests_Files_FileNameUnitTest extends AbstractSniffUnitTest { */ // File names generic. - 'some_file.inc' => 1, - 'SomeFile.inc' => 1, - 'some-File.inc' => 1, + 'some_file.inc' => 1, + 'SomeFile.inc' => 1, + 'some-File.inc' => 1, // Class file names. - 'my-class.inc' => 1, - 'class-different-class.inc' => 1, - 'ClassMyClass.inc' => 2, + 'my-class.inc' => 1, + 'class-different-class.inc' => 1, + 'ClassMyClass.inc' => 2, // Theme specific exceptions in a non-theme context. 'single-my_post_type.inc' => 1, @@ -46,40 +52,38 @@ class WordPress_Tests_Files_FileNameUnitTest extends AbstractSniffUnitTest { */ // Non-strict class names still have to comply with lowercase hyphenated. - 'ClassNonStrictClass.inc' => 1, + 'ClassNonStrictClass.inc' => 1, /* * In /FileNameUnitTests/TestFiles. */ - 'test-sample-phpunit.inc' => 0, - 'test-sample-phpunit6.inc' => 0, - 'test-sample-wpunit.inc' => 0, - // @todo Fix this! False positive, custom property setting not recognized. - // Is issue with unit tests, not with the sniff. If the property is set from the ruleset, it "should" work. - 'test-sample-custom-unit.inc' => 1, + 'test-sample-phpunit.inc' => 0, + 'test-sample-phpunit6.inc' => 0, + 'test-sample-wpunit.inc' => 0, + 'test-sample-custom-unit.inc' => 0, /* * In /FileNameUnitTests/ThemeExceptions. */ // Files in a theme context. - 'front_page.inc' => 1, - 'FrontPage.inc' => 1, - 'author-nice_name.inc' => 1, + 'front_page.inc' => 1, + 'FrontPage.inc' => 1, + 'author-nice_name.inc' => 1, /* * In /FileNameUnitTests/wp-includes. */ // Files containing template tags. - 'general.inc' => 1, + 'general.inc' => 1, /* * In /. */ // Fall-back file in case glob() fails. - 'FileNameUnitTest.inc' => 1, + 'FileNameUnitTest.inc' => 1, ); /** @@ -90,13 +94,8 @@ class WordPress_Tests_Files_FileNameUnitTest extends AbstractSniffUnitTest { * @return string[] */ protected function getTestFiles( $testFileBase ) { - $sep = DIRECTORY_SEPARATOR; - $test_files = glob( dirname( $testFileBase ) . $sep . 'FileNameUnitTests{' . $sep . ',' . $sep . 'TestFiles' . $sep . ',' . $sep . 'ThemeExceptions' . $sep . ',' . $sep . 'wp-includes' . $sep . '}*.inc', GLOB_BRACE ); - - // Adjust the expected results array for PHP 5.2 as PHP 5.2 does not recognize namespaces. - if ( PHP_VERSION_ID < 50300 ) { - $this->expected_results['test-sample-phpunit6.inc'] = 1; - } + $sep = DIRECTORY_SEPARATOR; + $test_files = glob( dirname( $testFileBase ) . $sep . 'FileNameUnitTests{' . $sep . ',' . $sep . '*' . $sep . '}*.inc', GLOB_BRACE ); if ( ! empty( $test_files ) ) { return $test_files; diff --git a/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/ClassNonStrictClass.inc b/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/ClassNonStrictClass.inc index a012de74..c4d4103a 100644 --- a/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/ClassNonStrictClass.inc +++ b/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/ClassNonStrictClass.inc @@ -1,5 +1,7 @@ @codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names false + <?php class Non_Strict_Class {} -@codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names true + +// @codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names true diff --git a/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/non-strict-class.inc b/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/non-strict-class.inc index a012de74..c4d4103a 100644 --- a/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/non-strict-class.inc +++ b/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/non-strict-class.inc @@ -1,5 +1,7 @@ @codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names false + <?php class Non_Strict_Class {} -@codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names true + +// @codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names true diff --git a/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/unrelated-filename.inc b/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/unrelated-filename.inc index f254f499..f7f643d7 100644 --- a/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/unrelated-filename.inc +++ b/WordPress/Tests/Files/FileNameUnitTests/NonStrictClassNames/unrelated-filename.inc @@ -1,5 +1,7 @@ @codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names false + <?php class My_Class {} -@codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names true \ No newline at end of file + +// @codingStandardsChangeSetting WordPress.Files.FileName strict_class_file_names true diff --git a/WordPress/Tests/Files/FileNameUnitTests/TestFiles/test-sample-custom-unit.inc b/WordPress/Tests/Files/FileNameUnitTests/TestFiles/test-sample-custom-unit.inc index cc4901a7..136c2585 100644 --- a/WordPress/Tests/Files/FileNameUnitTests/TestFiles/test-sample-custom-unit.inc +++ b/WordPress/Tests/Files/FileNameUnitTests/TestFiles/test-sample-custom-unit.inc @@ -1,8 +1,5 @@ -<?php -// @codingStandardsChangeSetting WordPress.Files.FileName custom_test_class_whitelist My_TestClass -?> - +@codingStandardsChangeSetting WordPress.Files.FileName custom_test_class_whitelist My_TestClass <?php class TestSample extends My_TestClass {} -// @codingStandardsChangeSetting WordPress.Files.FileName custom_test_class_whitelist false +/* @codingStandardsChangeSetting WordPress.Files.FileName custom_test_class_whitelist false */ diff --git a/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/category-another_slug.inc b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/category-another_slug.inc new file mode 100644 index 00000000..1ab58b4e --- /dev/null +++ b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/category-another_slug.inc @@ -0,0 +1,3 @@ +@codingStandardsChangeSetting WordPress.Files.FileName is_theme true +<?php +/* @codingStandardsChangeSetting WordPress.Files.FileName is_theme false */ diff --git a/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/category-slug.inc b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/category-slug.inc new file mode 100644 index 00000000..1ab58b4e --- /dev/null +++ b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/category-slug.inc @@ -0,0 +1,3 @@ +@codingStandardsChangeSetting WordPress.Files.FileName is_theme true +<?php +/* @codingStandardsChangeSetting WordPress.Files.FileName is_theme false */ diff --git a/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/content-another_slug.inc b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/content-another_slug.inc new file mode 100644 index 00000000..1ab58b4e --- /dev/null +++ b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/content-another_slug.inc @@ -0,0 +1,3 @@ +@codingStandardsChangeSetting WordPress.Files.FileName is_theme true +<?php +/* @codingStandardsChangeSetting WordPress.Files.FileName is_theme false */ diff --git a/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/content-slug.inc b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/content-slug.inc new file mode 100644 index 00000000..1ab58b4e --- /dev/null +++ b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/content-slug.inc @@ -0,0 +1,3 @@ +@codingStandardsChangeSetting WordPress.Files.FileName is_theme true +<?php +/* @codingStandardsChangeSetting WordPress.Files.FileName is_theme false */ diff --git a/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/page-slug_slug.inc b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/page-slug_slug.inc new file mode 100644 index 00000000..1ab58b4e --- /dev/null +++ b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/page-slug_slug.inc @@ -0,0 +1,3 @@ +@codingStandardsChangeSetting WordPress.Files.FileName is_theme true +<?php +/* @codingStandardsChangeSetting WordPress.Files.FileName is_theme false */ diff --git a/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/tag-another_slug.inc b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/tag-another_slug.inc new file mode 100644 index 00000000..1ab58b4e --- /dev/null +++ b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/tag-another_slug.inc @@ -0,0 +1,3 @@ +@codingStandardsChangeSetting WordPress.Files.FileName is_theme true +<?php +/* @codingStandardsChangeSetting WordPress.Files.FileName is_theme false */ diff --git a/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/tag-slug.inc b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/tag-slug.inc new file mode 100644 index 00000000..1ab58b4e --- /dev/null +++ b/WordPress/Tests/Files/FileNameUnitTests/ThemeExceptions/tag-slug.inc @@ -0,0 +1,3 @@ +@codingStandardsChangeSetting WordPress.Files.FileName is_theme true +<?php +/* @codingStandardsChangeSetting WordPress.Files.FileName is_theme false */ diff --git a/WordPress/Tests/Functions/DontExtractUnitTest.php b/WordPress/Tests/Functions/DontExtractUnitTest.php index fa06e359..081e794b 100644 --- a/WordPress/Tests/Functions/DontExtractUnitTest.php +++ b/WordPress/Tests/Functions/DontExtractUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Functions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the DontExtract sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.10.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_Functions_DontExtractUnitTest extends AbstractSniffUnitTest { +class DontExtractUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,7 +28,7 @@ class WordPress_Tests_Functions_DontExtractUnitTest extends AbstractSniffUnitTes */ public function getErrorList() { return array( - 3 => 1, + 3 => 1, ); } diff --git a/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.inc b/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.inc new file mode 100644 index 00000000..788da75d --- /dev/null +++ b/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.inc @@ -0,0 +1,9 @@ +<?php + +ms_cookie_constants(); // OK. +ms_cookie_constants( ); // Bad. +ms_cookie_constants( ); // Bad. + +// These should be ignored as these are handled by the PEAR.Functions.FunctionCallSignature sniff. +ms_cookie_constants($something); +ms_cookie_constants( $something ); diff --git a/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.inc.fixed b/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.inc.fixed new file mode 100644 index 00000000..df5bebca --- /dev/null +++ b/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.inc.fixed @@ -0,0 +1,9 @@ +<?php + +ms_cookie_constants(); // OK. +ms_cookie_constants(); // Bad. +ms_cookie_constants(); // Bad. + +// These should be ignored as these are handled by the PEAR.Functions.FunctionCallSignature sniff. +ms_cookie_constants($something); +ms_cookie_constants( $something ); diff --git a/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.php b/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.php new file mode 100644 index 00000000..b118b158 --- /dev/null +++ b/WordPress/Tests/Functions/FunctionCallSignatureNoParamsUnitTest.php @@ -0,0 +1,46 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\Functions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the FunctionCallSignatureNoParams sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class FunctionCallSignatureNoParamsUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 4 => 1, + 5 => 1, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array(); + + } + +} // End class. diff --git a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc new file mode 100644 index 00000000..2c9ca83f --- /dev/null +++ b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc @@ -0,0 +1,18 @@ +<?php +// @codingStandardsChangeSetting WordPress.NamingConventions.PrefixAllGlobals prefixes acronym,tgmpa + +/* + * OK - not in the global namespace. + */ +namespace Acronym; + +function do_something() {} + +const SOME_CONSTANT = 'value'; + +class Example {} +interface I_Example {} +trait T_Example {} + +// Issue #1056. +define( __NAMESPACE__ . '\PLUGIN_FILE', __FILE__ ); diff --git a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.inc b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.inc new file mode 100644 index 00000000..1dbc0e51 --- /dev/null +++ b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.inc @@ -0,0 +1,324 @@ +<?php + +/* + * Bad: invalid prefix passed + */ +// @codingStandardsChangeSetting WordPress.NamingConventions.PrefixAllGlobals prefixes wp +function wp_do_something() {} + +// @codingStandardsChangeSetting WordPress.NamingConventions.PrefixAllGlobals prefixes ^%& +function ^%&_do_something() {} + +// Now let's set the real prefixes we want to test for. +// @codingStandardsChangeSetting WordPress.NamingConventions.PrefixAllGlobals prefixes acronym,tgmpa + +/* + * Bad - not prefixed. + */ +function do_something() { + global $something, $else; + + $something = 'value'; + $GLOBALS['something'] = 'value'; + $GLOBALS[ 'something' . $else ] = 'value'; + $GLOBALS[ "something_{$else}" ] = 'value'; + $GLOBALS[ "something$else" ] = 'value'; +} + +$var = 'abc'; + +define( 'SOME_CONSTANT', 'value' ); +const SOME_CONSTANT = 'value'; + +class Example {} +interface Example_Interface {} +trait Example_Trait {} + +do_action( 'plugin_action' ); +apply_filters( 'theme_filter', $var ); +do_action( "plugin_action_{$acronym_filter_var}" ); +apply_filters( 'theme_filter_' . $acronym_filter_var ); + + +/* + * OK - prefixed. + */ +function acronym_do_something() { + global $acronym_something, $else; + + $acronym_something = 'value'; + $GLOBALS['acronym_something'] = 'value'; + $GLOBALS[ 'acronym_' . $else ] = 'value'; + $GLOBALS[ "acronym_something_{$else}" ] = 'value'; +} + +$acronym_var = 'abc'; + +define( 'ACRONYM_SOME_CONSTANT', 'value' ); +const ACRONYM_SOME_CONSTANT = 'value'; + +class Acronym_Example {} +interface Acronym_Example_Interface {} +trait Acronym_Example_Trait {} + +do_action( 'acronym_plugin_action' ); +apply_filters( 'acronym_theme_filter', $var ); +do_action( "acronym_plugin_action_{$acronym_filter_var}" ); +apply_filters( 'acronym_theme_filter_' . $acronym_filter_var ); + + +/* + * OK - test secondary prefix. + */ +function tgmpa_do_something() {} + +$tgmpa_var = 'abc'; + +define( 'TGMPA_SOME_CONSTANT', 'value' ); +const TGMPA_SOME_CONSTANT = 'value'; + +class TGMPA_Example {} + +do_action( 'tgmpa_plugin_action' ); +apply_filters( 'tgmpa_theme_filter', $var ); +do_action( "tgmpa_plugin_action_{$acronym_filter_var}" ); + + +/* + * Bad: prefix not correctly used. + */ +function abtgmpa_do_something() {} // Bad. +function tgmpacd_do_something() {} // Bad. + + +/* + * OK - allow for function/var/constant/class etc names to be just and only the prefix. + */ +function acronym() { + global $acronym; + + $acronym = 'value'; + $GLOBALS['acronym'] = 'value'; + $GLOBALS[ 'acronym' . $else ] = 'value'; // Presume the '_' is part of the $else. + $GLOBALS[ "acronym{$else}" ] = 'value'; // Presume the '_' is part of the $else. + $GLOBALS[ "acronym$else" ] = 'value'; // Presume the '_' is part of the $else. +} + +$acronym = 'abc'; + +define( 'ACRONYM', 'value' ); +const ACRONYM = 'value'; + +class Acronym {} +interface Acronym {} +trait Acronym {} + +do_action( 'acronym' ); +apply_filters( 'acronym', $var ); + + +/* + * OK - not in the global namespace. + */ +function acronym_do_something_else( $param = 'default' ) { + $var = 'abc'; + ${$something} = 'value'; +} + +function ( $param ) { + $var = 'abc'; +}; + +class Acronym_Example { + const SOME_CONSTANT = 'value'; + + public $var = 'abc'; + + function do_something( $param = 'default' ) {} +} + +$acronym_class = new class { + const SOME_CONSTANT = 'value'; + + public $var = 'abc'; + + function do_something( $param = 'default' ) {} +}; + +namespace Acronym { + function do_something( $param = 'default' ) {} + + const SOME_CONSTANT = 'value'; + + class Example {} + interface I_Example {} + trait T_Example {} +} + + +/* + * OK - exceptions whitelisted by default. + */ +$_POST['something'] = 'value'; + +do_action_deprecated( 'set_current_user' ); // Deprecated hook, ignored. + +// WP global variables, override warning is handled by another sniff. +function acronym_do_something() { + global $post; + $post = 'value'; + $GLOBALS['post'] = 'value'; +} + +/* + * OK - test class - skips forward. + */ +class Example extends WP_UnitTestCase { + const SOME_CONSTANT = 'value'; + + public $var = 'abc'; + + function do_something() {} +} + +// @codingStandardsChangeSetting WordPress.NamingConventions.PrefixAllGlobals custom_test_class_whitelist My_TestClass +class Test_Class_D extends My_TestClass { + + const SOME_CONSTANT = 'value'; + + public $var = 'abc'; + + function do_something() {} +} +// @codingStandardsChangeSetting WordPress.NamingConventions.PrefixAllGlobals custom_test_class_whitelist false + + +/* + * OK - whitelisted via whitelist comment. + */ +if ( ! function_exists( 'intdiv' ) ) { + // Fill in for a PHP function which is not available in low PHP versions. + function intdiv() { // WPCS: prefix ok. + // Some code. + } +} + +if ( ! defined( 'PHP_VERSION_ID' ) ) { + $acronym_version = explode('.', PHP_VERSION); + define('PHP_VERSION_ID', (int) (($acronym_version[0] * 10000) + ($acronym_version[1] * 100) + $acronym_version[2])); // WPCS: prefix ok. + unset($acronym_version); +} + +$something = 'abc'; // WPCS: prefix ok. + +// Executing a WP core action or filter is sometimes ok. +do_action( 'set_current_user' ); // WPCS: prefix ok. +apply_filters( 'excerpt_edit_pre', $var ); // WPCS: prefix ok. + + +/* + * Issue 915: OK/Bad - backfilled PHP functions will be recognized depending on the PHP version PHPCS runs on + * and the extensions loaded in that version. + */ +if ( ! function_exists( 'mb_strpos' ) ) { + // Fill in for a PHP function which is not always available (extension needs to be loaded). + function mb_strpos() {} +} + +if ( ! function_exists( 'array_column' ) ) { + // Fill in for a PHP function which is not always available - introduced in PHP 5.5. + function array_column() {} +} + +if ( ! defined( 'E_DEPRECATED' ) ) { + define( 'E_DEPRECATED', true ); // Introduced in PHP 5.3.0. +} + +if ( ! class_exists( 'IntlTimeZone' ) ) { + class IntlTimeZone {} // Introduced in PHP 5.5.0. +} + + +/* + * Issue 915: dynamic names. Names starting with a dynamic part or + * which are completely dynamic, will receive a warning. + */ +function acronym_something() { + global $something; + + $GLOBALS[ $something ] = 'value'; // Warning. + $GLOBALS[ "{$something}_something" ] = 'value'; // Warning. +} + +$$something = 'value'; // Warning. +${$something} = 'value'; // Warning. +$$$${$something} = 'value'; // Warning. +${$something}['foo'] = 'value'; // Warning. +${$something}['foo']['bar'] = 'value'; // Warning. +${$something['foo']} = 'value'; // Warning. +$GLOBALS[ $something ] = 'value'; // Warning. +$GLOBALS[ "{$something}_something" ] = 'value'; // Warning. +$GLOBALS[ ${$something} ] = 'value'; // Warning. + +define( ${$something}, 'value' ); // Warning. +define( $something, 'value' ); // Warning. +define( $something . '_CONSTANT', 'value' ); // Warning. +define( "{$something}_CONSTANT", 'value' ); // Warning. +define( $something . '_CONSTANT', 'value' ); // Warning. + +do_action( "{$acronym_filter_var}_hook_name" ); // Warning. +do_action( "{$acronym_filter_var}hook_name" ); // Warning. +do_action( $acronym_filter_var ); // Warning. +do_action( $GLOBALS['something'] ); // Warning. +do_action( ${$acronym_filter_var} ); // Warning. +do_action( $GLOBALS[ ${$something} ] ); // Warning. +apply_filters( $_REQUEST['else'] ); // Warning. + +class Acronym_Dynamic_Hooks { + const FILTER = 'acronym'; + const FILTER_WITH_UNDERSCORE = 'acronym_'; + + protected $filter = 'acronym'; + protected $filter_with_underscore = 'acronym_'; + + public function test() { + global $acronym_filter_var; + ${$this->name} = 'value'; // Warning. + apply_filters( "{$acronym_filter_var}_hook" ); // Warning. + do_action( $acronym_filter_var ); // Warning. + + do_action( $this->filter ); // Warning. + apply_filters( $this->filter_array['key'] ); // Warning. + do_action( "{$this->filter}_hook_name" ); // Warning. + do_action( "{$this->filter_with_underscore}hook_name" ); // Warning. + + apply_filters( self::FILTER ); // Warning. + apply_filters( self::FILTER_WITH_UNDERSCORE . 'hook_name' ); // Warning. + apply_filters( self::FILTER_ARRAY['key'] ); // Warning. + + do_action( $this->parent_property ); // Warning. + } +} + +// Dashes and other non-word characters are ok as a hook name separator after the prefix. +// The rule that these should be underscores is handled by another sniff. +do_action( 'acronym-action' ); // OK. +apply_filters( 'acronym/filter', $var ); // OK. +do_action( "acronym-action-{$acronym_filter_var}" ); // OK. +apply_filters( 'acronym/filter-' . $acronym_filter_var ); // OK. + +// Issue #1056. +define( 'SomeNameSpace\PLUGIN_FILE', __FILE__ ); // OK. +define( '\OtherNameSpace\PLUGIN_FILE', __FILE__ ); // OK. +// OK: unreachable constants. +define( __NAMESPACE__ . '\PLUGIN_FILE', __FILE__ ); +define( '\PLUGIN_FILE', __FILE__ ); + +namespace Testing { + define( 'MY' . __NAMESPACE__, __FILE__ ); // Error, not actually namespaced. + define( 'MY\\' . __NAMESPACE__, __FILE__ ); // OK, even though strangely setup, the constant is in a namespace. +} + +// OK: whitelisted core hooks. +apply_filters( 'widget_title', $title ); +do_action( 'add_meta_boxes' ); diff --git a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php new file mode 100644 index 00000000..00798e48 --- /dev/null +++ b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php @@ -0,0 +1,126 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\NamingConventions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the PrefixAllGlobals sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class PrefixAllGlobalsUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @param string $testFile The name of the file being tested. + * @return array <int line number> => <int number of errors> + */ + public function getErrorList( $testFile = 'PrefixAllGlobalsUnitTest.inc' ) { + + switch ( $testFile ) { + case 'PrefixAllGlobalsUnitTest.inc': + return array( + 1 => 2, // 2 x error for incorrect prefix passed. + 10 => 1, + 18 => 1, + 21 => 1, + 22 => 1, + 23 => 1, + 24 => 1, + 25 => 1, + 28 => 1, + 30 => 1, + 31 => 1, + 33 => 1, + 34 => 1, + 35 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 90 => 1, + 91 => 1, + // Backfills. + 225 => ( function_exists( '\mb_strpos' ) ) ? 0 : 1, + 230 => ( function_exists( '\array_column' ) ) ? 0 : 1, + 234 => ( defined( '\E_DEPRECATED' ) ) ? 0 : 1, + 238 => ( class_exists( '\IntlTimeZone' ) ) ? 0 : 1, + 318 => 1, + ); + + case 'PrefixAllGlobalsUnitTest.1.inc': + // Namespaced - all OK, fall through to the default case. + default: + return array(); + + } // End switch(). + + } // end getErrorList() + + /** + * Returns the lines where warnings should occur. + * + * @param string $testFile The name of the file being tested. + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList( $testFile = 'PrefixAllGlobalsUnitTest.inc' ) { + + switch ( $testFile ) { + case 'PrefixAllGlobalsUnitTest.inc': + return array( + 249 => 1, + 250 => 1, + 253 => 1, + 254 => 1, + 255 => 1, + 256 => 1, + 257 => 1, + 258 => 1, + 259 => 1, + 260 => 1, + 261 => 1, + 263 => 1, + 264 => 1, + 265 => 1, + 266 => 1, + 267 => 1, + 269 => 1, + 270 => 1, + 271 => 1, + 272 => 1, + 273 => 1, + 274 => 1, + 275 => 1, + 286 => 1, + 287 => 1, + 288 => 1, + 290 => 1, + 291 => 1, + 292 => 1, + 293 => 1, + 295 => 1, + 296 => 1, + 297 => 1, + 299 => 1, + ); + + default: + return array(); + + } // End switch(). + + } + +} // End class. diff --git a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc index 4eef0dc3..f8b9d142 100644 --- a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc +++ b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc @@ -43,12 +43,12 @@ class Foo implements ArrayAccess { 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 __call( $a, $b ) {} // Ok. + static function __callStatic( $a, $b ) {} // Ok. + function __get( $a ) {} // Ok. + function __set( $a, $b ) {} // Ok. + function __isset( $a ) {} // Ok. + function __unset( $a ) {} // Ok. function __sleep() {} // Ok. function __wakeup() {} // Ok. function __toString() {} // Ok. diff --git a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php index e7852a90..223f7b3c 100644 --- a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\NamingConventions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the ValidFunctionName sniff. * * @package WPCS\WordPressCodingStandards + * * @since 2013-06-11 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_NamingConventions_ValidFunctionNameUnitTest extends AbstractSniffUnitTest { +class ValidFunctionNameUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,8 +28,8 @@ class WordPress_Tests_NamingConventions_ValidFunctionNameUnitTest extends Abstra */ public function getErrorList() { return array( - 3 => 1, - 9 => 1, + 3 => 1, + 9 => 1, 13 => 1, 15 => 1, 79 => 1, diff --git a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php index 913aa123..a605b898 100644 --- a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\NamingConventions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the ValidHookName sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.10.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_NamingConventions_ValidHookNameUnitTest extends AbstractSniffUnitTest { +class ValidHookNameUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -84,8 +90,8 @@ public function getWarningList( $testFile = 'ValidHookNameUnitTest.inc' ) { switch ( $testFile ) { case 'ValidHookNameUnitTest.inc': return array( - 8 => 1, - 9 => 1, + 8 => 1, + 9 => 1, 10 => 1, 11 => 1, 68 => 1, diff --git a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.inc b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.inc index 0abbf0d1..1e8e9b6d 100644 --- a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.inc +++ b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.inc @@ -5,25 +5,25 @@ $varname = 'hello'; $_varName = 'hello'; // Bad. class MyClass { - $varName = 'hello'; // Bad. - $var_name = 'hello'; - $varname = 'hello'; - $_varName = 'hello'; // Bad. - - public $varName = 'hello'; // Bad. - public $var_name = 'hello'; - public $varname = 'hello'; - public $_varName = 'hello'; // Bad. - - protected $varName = 'hello'; // Bad. - protected $var_name = 'hello'; - protected $varname = 'hello'; - protected $_varName = 'hello'; // Bad. - - private $_varName = 'hello'; // Bad. - private $_var_name = 'hello'; - private $_varname = 'hello'; - private $varName = 'hello'; // Bad. + var $varName = 'hello'; // Bad. + var $var_name = 'hello'; + var $varname = 'hello'; + var $_varName = 'hello'; // Bad. + + public $varNamf = 'hello'; // Bad. + public $var_namf = 'hello'; + public $varnamf = 'hello'; + public $_varNamf = 'hello'; // Bad. + + protected $varNamg = 'hello'; // Bad. + protected $var_namg = 'hello'; + protected $varnamg = 'hello'; + protected $_varNamg = 'hello'; // Bad. + + private $_varNamh = 'hello'; // Bad. + private $_var_namh = 'hello'; + private $_varnamh = 'hello'; + private $varNamh = 'hello'; // Bad. } echo $varName; // Bad. diff --git a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php index 0244f38a..11593697 100644 --- a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\NamingConventions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the ValidVariableName sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.9.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_NamingConventions_ValidVariableNameUnitTest extends AbstractSniffUnitTest { +class ValidVariableNameUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php index b7cc9288..bb448c77 100644 --- a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php @@ -7,14 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the PHP_DevelopmentFunctions sniff. * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_PHP_DevelopmentFunctionsUnitTest extends AbstractSniffUnitTest { +class DevelopmentFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -33,13 +38,13 @@ public function getErrorList() { */ public function getWarningList() { return array( - 3 => 1, - 4 => 1, - 5 => 1, - 6 => 1, - 7 => 1, - 8 => 1, - 9 => 1, + 3 => 1, + 4 => 1, + 5 => 1, + 6 => 1, + 7 => 1, + 8 => 1, + 9 => 1, 10 => 1, 11 => 1, 13 => 1, diff --git a/WordPress/Tests/PHP/DiscourageGotoUnitTest.inc b/WordPress/Tests/PHP/DiscourageGotoUnitTest.inc new file mode 100644 index 00000000..f564215b --- /dev/null +++ b/WordPress/Tests/PHP/DiscourageGotoUnitTest.inc @@ -0,0 +1,18 @@ +<?php + +goto a; +echo 'Foo'; + +a: +echo 'Bar'; + +for($i=0,$j=50; $i<100; $i++) { + while($j--) { + if($j==17) goto end; + } +} +echo "i = $i"; + +end: { + echo 'j hit 17'; +} diff --git a/WordPress/Tests/PHP/DiscourageGotoUnitTest.php b/WordPress/Tests/PHP/DiscourageGotoUnitTest.php new file mode 100644 index 00000000..707e4ff7 --- /dev/null +++ b/WordPress/Tests/PHP/DiscourageGotoUnitTest.php @@ -0,0 +1,46 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the PHP.DiscourageGoto sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class DiscourageGotoUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array(); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array( + 3 => 1, + 6 => 1, + 11 => 1, + 16 => 1, + ); + } + +} diff --git a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc index 755fcdcb..d762b2f8 100644 --- a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc @@ -2,9 +2,9 @@ -add_action( 'widgets_init', create_function( '', // Warning. - 'return register_widget( "time_more_on_time_widget" );' -) ); + + + serialize(); // Warning. unserialize(); // Warning. diff --git a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.php b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.php index 357e7b5e..ba85707b 100644 --- a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the PHP_DiscouragedPHPFunctions sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_PHP_DiscouragedPHPFunctionsUnitTest extends AbstractSniffUnitTest { +class DiscouragedPHPFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -32,7 +38,6 @@ public function getErrorList() { */ public function getWarningList() { return array( - 5 => 1, 9 => 1, 10 => 1, 12 => 1, diff --git a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php index db0f6dca..c3b890f7 100644 --- a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the POSIXFunctions sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.10.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_PHP_POSIXFunctionsUnitTest extends AbstractSniffUnitTest { +class POSIXFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc new file mode 100644 index 00000000..84dbd1d7 --- /dev/null +++ b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc @@ -0,0 +1,5 @@ +<?php + +add_action( 'widgets_init', create_function( '', // Error. + 'return register_widget( "time_more_on_time_widget" );' +) ); diff --git a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php new file mode 100644 index 00000000..1de4bea2 --- /dev/null +++ b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php @@ -0,0 +1,44 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the PHP_DiscouragedPHPFunctions sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class RestrictedPHPFunctionsUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 3 => 1, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array(); + + } + +} // End class. diff --git a/WordPress/Tests/PHP/StrictComparisonsUnitTest.inc b/WordPress/Tests/PHP/StrictComparisonsUnitTest.inc index 1cb1fe41..7c3ce841 100644 --- a/WordPress/Tests/PHP/StrictComparisonsUnitTest.inc +++ b/WordPress/Tests/PHP/StrictComparisonsUnitTest.inc @@ -18,4 +18,14 @@ if ( true != $true ) { // Bad. // Test for whitelisting. if ( true == $true ) { // Loose comparison, OK. echo 'True'; -} \ No newline at end of file +} + +// Test that whitelisting is not too eager. +if ( true == $true ) { + // The line above has a loose comparison, but no whitelist comment. + echo 'True'; +} + +if ( true == $true ) { // Loose comparisons FTW! + echo 'True'; +} diff --git a/WordPress/Tests/PHP/StrictComparisonsUnitTest.php b/WordPress/Tests/PHP/StrictComparisonsUnitTest.php index 1de1ad16..b987b121 100644 --- a/WordPress/Tests/PHP/StrictComparisonsUnitTest.php +++ b/WordPress/Tests/PHP/StrictComparisonsUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the StrictComparisons sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.4.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_PHP_StrictComparisonsUnitTest extends AbstractSniffUnitTest { +class StrictComparisonsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -32,9 +38,11 @@ public function getErrorList() { */ public function getWarningList() { return array( - 3 => 1, + 3 => 1, 10 => 1, 12 => 1, + 24 => 1, + 29 => 1, ); } diff --git a/WordPress/Tests/PHP/StrictInArrayUnitTest.inc b/WordPress/Tests/PHP/StrictInArrayUnitTest.inc index df9372fa..76921436 100644 --- a/WordPress/Tests/PHP/StrictInArrayUnitTest.inc +++ b/WordPress/Tests/PHP/StrictInArrayUnitTest.inc @@ -34,4 +34,6 @@ array_keys( array( '1', 1, true ), 'my_key', true ); // Ok. array_keys( $testing, 'my_key' ); // Warning. array_keys( array( '1', 1, true ), 'my_key' ); // Warning. array_keys( $testing, 'my_key', false ); // Warning. -array_keys( array( '1', 1, true ), 'my_key', false ); // Warning. +array_keys( array( '1', 1, true ), 'my_key', false ); // Warning +in_array( 1, array( '1', 1 ), TRUE ); // Ok. + diff --git a/WordPress/Tests/PHP/StrictInArrayUnitTest.php b/WordPress/Tests/PHP/StrictInArrayUnitTest.php index 487783e2..cf9efce3 100644 --- a/WordPress/Tests/PHP/StrictInArrayUnitTest.php +++ b/WordPress/Tests/PHP/StrictInArrayUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the StrictInArray sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.9.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_PHP_StrictInArrayUnitTest extends AbstractSniffUnitTest { +class StrictInArrayUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -33,9 +39,9 @@ public function getErrorList() { */ public function getWarningList() { return array( - 5 => 1, - 6 => 1, - 7 => 1, + 5 => 1, + 6 => 1, + 7 => 1, 23 => 1, 24 => 1, 25 => 1, diff --git a/WordPress/Tests/PHP/YodaConditionsUnitTest.inc b/WordPress/Tests/PHP/YodaConditionsUnitTest.inc index 5b2cf5e4..53b31fbc 100644 --- a/WordPress/Tests/PHP/YodaConditionsUnitTest.inc +++ b/WordPress/Tests/PHP/YodaConditionsUnitTest.inc @@ -90,3 +90,48 @@ if ( $GLOBALS['wpdb']->num_rows === 0 ) {} // Bad. if ( $true == strtolower( $check ) ) {} // Bad. $update = 'yes' === strtolower( $this->from_post( 'update' ) ); // Ok. +$sample = false !== strpos( $link, '%pagename%' ); // Ok. +$sample = true !== strpos( $link, '%pagename%' ); // Ok. +$sample = null !== strpos( $link, '%pagename%' ); // Ok. +$sample = SOME_CONSTANT !== strpos( $link, '%pagename%' ); // Ok. +$sample = foo() !== strpos( $link, '%pagename%' ); // Ok. +$sample = foo( $var ) !== strpos( $link, '%pagename%' ); // Ok. + +if ( $sample = false !== strpos( $link, '%pagename%' ) ) {} // Ok. +if ( $sample = ( false !== strpos( $link, '%pagename%' ) ) ) {} // Ok. +if ( ( $sample = false ) !== strpos( $link, '%pagename%' ) ) {} // Ok. + +switch ( true ) { + case $sample === 'something': // Bad. + // Do something. + break; + + case 'something' === $sample: // OK. + // Do something. + break; +} + +for ( $i = 0; $i !== 100; $i++ ) {} // Bad. +for ( $i = 0; 100 != $i; $i++ ) {} // OK. + +do { + // Something. +} while ( $sample === false ); // Bad. + +do { + // Something. +} while ( CONSTANT_A === $sample ); // OK. + +while ( $sample === false ) {} // Bad. +while ( false != $sample ) {} // OK. + +$a = ( $sample ) === 'yes'; // OK. + +// Issue #1181 +function is_windows() { + return false !== ( $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : 0 === stripos( PHP_OS, 'WIN' ); // OK. +} + +if ( $something == ( false !== ( $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : 0 === stripos( PHP_OS, 'WIN' ) ) ) {} // Bad. + +if ( ( false !== ( $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : 0 === stripos( PHP_OS, 'WIN' ) ) === $something ) {} // OK. diff --git a/WordPress/Tests/PHP/YodaConditionsUnitTest.php b/WordPress/Tests/PHP/YodaConditionsUnitTest.php index bdf50962..d6593391 100644 --- a/WordPress/Tests/PHP/YodaConditionsUnitTest.php +++ b/WordPress/Tests/PHP/YodaConditionsUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\PHP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the YodaConditions sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_PHP_YodaConditionsUnitTest extends AbstractSniffUnitTest { +class YodaConditionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,19 +28,24 @@ class WordPress_Tests_PHP_YodaConditionsUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 2 => 2, - 4 => 2, - 11 => 1, - 18 => 1, - 25 => 1, - 32 => 1, - 49 => 1, - 55 => 1, - 62 => 1, - 68 => 1, - 84 => 1, - 88 => 1, - 90 => 1, + 2 => 2, + 4 => 2, + 11 => 1, + 18 => 1, + 25 => 1, + 32 => 1, + 49 => 1, + 55 => 1, + 62 => 1, + 68 => 1, + 84 => 1, + 88 => 1, + 90 => 1, + 105 => 1, + 114 => 1, + 119 => 1, + 125 => 1, + 135 => 1, ); } diff --git a/WordPress/Tests/Theme/DeprecatedWPConstantsUnitTest.inc b/WordPress/Tests/Theme/DeprecatedWPConstantsUnitTest.inc deleted file mode 100644 index e9140dd0..00000000 --- a/WordPress/Tests/Theme/DeprecatedWPConstantsUnitTest.inc +++ /dev/null @@ -1,23 +0,0 @@ -<?php - -define( 'STYLESHEETPATH', 'something' ); // Ok - will throw an error about already defined constant anyway. - -if ( defined( 'STYLESHEETPATH' ) ) { // Ok. - // Do something unrelated. -} - -echo \mynamespace\STYLESHEETPATH; // Ok, not WP constant. - -echo My_Class::STYLESHEETPATH; // Ok, not WP constant. - -echo STYLESHEETPATH; // Bad. -$folder = basename( TEMPLATEPATH ); // Bad. -$file = PLUGINDIR . '/js/myfile.js'; // Bad. -echo MUPLUGINDIR; // Bad. -echo HEADER_IMAGE; // Bad. -echo NO_HEADER_TEXT; // Bad. -echo HEADER_TEXTCOLOR; // Bad. -echo HEADER_IMAGE_WIDTH; // Bad. -echo HEADER_IMAGE_HEIGHT; // Bad. -echo BACKGROUND_COLOR; // Bad. -echo BACKGROUND_IMAGE; // Bad. diff --git a/WordPress/Tests/Theme/FileIncludeUnitTest.php b/WordPress/Tests/Theme/FileIncludeUnitTest.php index 562dbe00..bbb53174 100644 --- a/WordPress/Tests/Theme/FileIncludeUnitTest.php +++ b/WordPress/Tests/Theme/FileIncludeUnitTest.php @@ -7,13 +7,17 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Theme; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the Theme_FileInclude sniff. * * @package WPCS\WordPressCodingStandards * @since 0.xx.0 */ -class WordPress_Tests_Theme_FileIncludeUnitTest extends AbstractSniffUnitTest { +class FileIncludeUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -40,4 +44,4 @@ public function getWarningList() { ); } -} // End class. +} diff --git a/WordPress/Tests/Theme/NoAddAdminPagesUnitTest.php b/WordPress/Tests/Theme/NoAddAdminPagesUnitTest.php index 7bd67cd1..5ad1ae12 100644 --- a/WordPress/Tests/Theme/NoAddAdminPagesUnitTest.php +++ b/WordPress/Tests/Theme/NoAddAdminPagesUnitTest.php @@ -7,13 +7,17 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Theme; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the NoAddAdminPages sniff. * * @package WPCS\WordPressCodingStandards * @since 0.xx.0 */ -class WordPress_Tests_Theme_NoAddAdminPagesUnitTest extends AbstractSniffUnitTest { +class NoAddAdminPagesUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,10 +26,10 @@ class WordPress_Tests_Theme_NoAddAdminPagesUnitTest extends AbstractSniffUnitTes */ public function getErrorList() { return array( - 4 => 1, - 5 => 1, - 6 => 1, - 9 => 1, + 4 => 1, + 5 => 1, + 6 => 1, + 9 => 1, 12 => 1, 13 => 1, 14 => 1, @@ -47,4 +51,4 @@ public function getWarningList() { return array(); } -} // End class. +} diff --git a/WordPress/Tests/Theme/NoAutoGenerateUnitTest.php b/WordPress/Tests/Theme/NoAutoGenerateUnitTest.php index 6cea5e98..dd66f8da 100644 --- a/WordPress/Tests/Theme/NoAutoGenerateUnitTest.php +++ b/WordPress/Tests/Theme/NoAutoGenerateUnitTest.php @@ -7,13 +7,17 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Theme; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the Theme_NoAutoGenerate sniff. * * @package WPCS\WordPressCodingStandards * @since 0.xx.0 */ -class WordPress_Tests_Theme_NoAutoGenerateUnitTest extends AbstractSniffUnitTest { +class NoAutoGenerateUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -66,4 +70,4 @@ public function getWarningList() { return array(); } -} // End class. +} diff --git a/WordPress/Tests/Theme/NoFaviconUnitTest.php b/WordPress/Tests/Theme/NoFaviconUnitTest.php index 5057965a..ad561e73 100644 --- a/WordPress/Tests/Theme/NoFaviconUnitTest.php +++ b/WordPress/Tests/Theme/NoFaviconUnitTest.php @@ -7,13 +7,17 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Theme; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the NoFavicon sniff. * * @package WPCS\WordPressCodingStandards * @since 0.xx.0 */ -class WordPress_Tests_Theme_NoFaviconUnitTest extends AbstractSniffUnitTest { +class NoFaviconUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,8 +26,8 @@ class WordPress_Tests_Theme_NoFaviconUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 8 => 1, - 9 => 1, + 8 => 1, + 9 => 1, 10 => 1, 11 => 1, 12 => 1, @@ -35,8 +39,8 @@ public function getErrorList() { 21 => 1, 24 => 1, 25 => 1, - 29 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. - 30 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. + 29 => 1, + 30 => 1, ); } @@ -49,4 +53,4 @@ public function getWarningList() { return array(); } -} // End class. +} diff --git a/WordPress/Tests/Theme/NoTitleTagUnitTest.php b/WordPress/Tests/Theme/NoTitleTagUnitTest.php index e0a19f89..750b46bf 100644 --- a/WordPress/Tests/Theme/NoTitleTagUnitTest.php +++ b/WordPress/Tests/Theme/NoTitleTagUnitTest.php @@ -7,13 +7,17 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Theme; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the NoTitleTag sniff. * * @package WPCS\WordPressCodingStandards * @since 0.xx.0 */ -class WordPress_Tests_Theme_NoTitleTagUnitTest extends AbstractSniffUnitTest { +class NoTitleTagUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,22 +26,20 @@ class WordPress_Tests_Theme_NoTitleTagUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 7 => 1, - 9 => 1, + 7 => 1, + 9 => 1, 19 => 1, 24 => 1, 26 => 1, 28 => 1, - // PHP 5.2 has an issue tokenizing `<s` so splits the string into two. - 30 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 2, + 30 => 1, 34 => 1, 37 => 1, 40 => 1, 43 => 1, - 47 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. - 50 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. + 47 => 1, + 50 => 1, ); - } /** @@ -47,7 +49,6 @@ public function getErrorList() { */ public function getWarningList() { return array(); - } -} // End class. +} diff --git a/WordPress/Tests/Theme/PluginTerritoryFunctionsUnitTest.php b/WordPress/Tests/Theme/PluginTerritoryFunctionsUnitTest.php index 5cc8d310..02cba2e0 100644 --- a/WordPress/Tests/Theme/PluginTerritoryFunctionsUnitTest.php +++ b/WordPress/Tests/Theme/PluginTerritoryFunctionsUnitTest.php @@ -7,13 +7,17 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Theme; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the PluginTerritoryFunctions sniff. * * @package WPCS\WordPressCodingStandards * @since 0.xx.0 */ -class WordPress_Tests_Theme_PluginTerritoryFunctionsUnitTest extends AbstractSniffUnitTest { +class PluginTerritoryFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -28,7 +32,6 @@ public function getErrorList() { 6 => 1, 7 => 1, ); - } /** @@ -38,7 +41,6 @@ public function getErrorList() { */ public function getWarningList() { return array(); - } -} // End class. +} diff --git a/WordPress/Tests/VIP/AdminBarRemovalUnitTest.php b/WordPress/Tests/VIP/AdminBarRemovalUnitTest.php index 18aaf168..7adbae60 100644 --- a/WordPress/Tests/VIP/AdminBarRemovalUnitTest.php +++ b/WordPress/Tests/VIP/AdminBarRemovalUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the AdminBarRemoval sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_AdminBarRemovalUnitTest extends AbstractSniffUnitTest { +class AdminBarRemovalUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -37,9 +43,9 @@ public function getErrorList( $testFile = '' ) { 21 => 1, 26 => 1, 32 => 1, - 56 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. - 57 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. - 58 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. + 56 => 1, + 57 => 1, + 58 => 1, 68 => 1, 69 => 1, 70 => 1, @@ -54,7 +60,7 @@ public function getErrorList( $testFile = '' ) { case 'AdminBarRemovalUnitTest.css': return array( - 15 => 1, + 15 => 1, 16 => 1, 17 => 1, 22 => 1, diff --git a/WordPress/Tests/VIP/CronIntervalUnitTest.inc b/WordPress/Tests/VIP/CronIntervalUnitTest.inc index 3a4e44c1..aeae496a 100644 --- a/WordPress/Tests/VIP/CronIntervalUnitTest.inc +++ b/WordPress/Tests/VIP/CronIntervalUnitTest.inc @@ -13,11 +13,11 @@ add_filter( 'cron_schedules', 'my_add_weekly'); // Error: 6 min. class Foo { - function __construct() { + public function __construct() { add_filter( 'cron_schedules', array( $this, 'my_add_quickly' ) ); // Error: 10 min. } - function my_add_quickly( $schedules ) { + public function my_add_quickly( $schedules ) { $schedules['every_10_mins'] = array( 'interval' => 10 * 60, 'display' => __( 'Once every 10 minutes' ) @@ -80,3 +80,51 @@ add_filter( 'cron_schedules', function ( $schedules ) { ]; return $schedules; } ); // Warning: time undetermined. + +// @codingStandardsChangeSetting WordPress.VIP.CronInterval min_interval 600 +add_filter( 'cron_schedules', function ( $schedules ) { + $schedules['every_2_mins'] = array( + 'interval' => 2 * 60, + 'display' => __( 'Once every 2 minutes' ) + ); + return $schedules; +} ); // Error: 2 min. +add_filter( 'cron_schedules', function ( $schedules ) { + $schedules['every_10_mins'] = array( + 'interval' => 10 * 60, + 'display' => __( 'Once every 10 minutes' ) + ); + return $schedules; +} ); // OK: 10 min. +add_filter( 'cron_schedules', function ( $schedules ) { + $schedules['every_hour'] = [ + 'interval' => HOUR_IN_SECONDS, + 'display' => __( 'Once every hour' ) + ]; + return $schedules; +} ); // OK: > 10 min. + +// @codingStandardsChangeSetting WordPress.VIP.CronInterval min_interval 1800 +add_filter( 'cron_schedules', function ( $schedules ) { + $schedules['every_2_mins'] = array( + 'interval' => 2 * 60, + 'display' => __( 'Once every 2 minutes' ) + ); + return $schedules; +} ); // Error: 2 min. +add_filter( 'cron_schedules', function ( $schedules ) { + $schedules['every_15_mins'] = array( + 'interval' => 15 * 60, + 'display' => __( 'Once every 15 minutes' ) + ); + return $schedules; +} ); // Error: 15 min. +add_filter( 'cron_schedules', function ( $schedules ) { + $schedules['every_hour'] = [ + 'interval' => HOUR_IN_SECONDS, + 'display' => __( 'Once every hour' ) + ]; + return $schedules; +} ); // Ok: > 30 min. + +// @codingStandardsChangeSetting WordPress.VIP.CronInterval min_interval 900 diff --git a/WordPress/Tests/VIP/CronIntervalUnitTest.php b/WordPress/Tests/VIP/CronIntervalUnitTest.php index ce5bdd17..8b12122f 100644 --- a/WordPress/Tests/VIP/CronIntervalUnitTest.php +++ b/WordPress/Tests/VIP/CronIntervalUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the CronInterval sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_CronIntervalUnitTest extends AbstractSniffUnitTest { +class CronIntervalUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,13 +28,16 @@ class WordPress_Tests_VIP_CronIntervalUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 12 => 1, - 17 => 1, - 37 => 1, - 43 => 1, - 53 => 1, - 56 => 1, // False positive. - 67 => 1, + 12 => 1, + 17 => 1, + 37 => 1, + 43 => 1, + 53 => 1, + 56 => 1, // False positive. + 67 => 1, + 85 => 1, + 108 => 1, + 115 => 1, ); } diff --git a/WordPress/Tests/VIP/DirectDatabaseQueryUnitTest.inc b/WordPress/Tests/VIP/DirectDatabaseQueryUnitTest.inc index 638bd625..617c3de3 100644 --- a/WordPress/Tests/VIP/DirectDatabaseQueryUnitTest.inc +++ b/WordPress/Tests/VIP/DirectDatabaseQueryUnitTest.inc @@ -122,7 +122,7 @@ $b = function () { // @codingStandardsChangeSetting WordPress.VIP.DirectDatabaseQuery customCacheGetFunctions my_cacheget // @codingStandardsChangeSetting WordPress.VIP.DirectDatabaseQuery customCacheSetFunctions my_cacheset,my_other_cacheset // @codingStandardsChangeSetting WordPress.VIP.DirectDatabaseQuery customCacheDeleteFunctions my_cachedel -function cache_custom() { +function cache_customA() { global $wpdb; $quux = my_cacheget( 'quux' ); @@ -132,7 +132,7 @@ function cache_custom() { } } -function cache_custom() { +function cache_customB() { global $wpdb; $quux = my_cacheget( 'quux' ); @@ -142,7 +142,7 @@ function cache_custom() { } } -function cache_custom() { +function cache_customC() { global $wpdb; $wpdb->query( 'SELECT X FROM Y' ); // DB call ok; OK. @@ -152,7 +152,7 @@ function cache_custom() { // @codingStandardsChangeSetting WordPress.VIP.DirectDatabaseQuery customCacheSetFunctions my_cacheset // @codingStandardsChangeSetting WordPress.VIP.DirectDatabaseQuery customCacheDeleteFunctions false -function cache_custom() { +function cache_customD() { global $wpdb; $quux = my_cacheget( 'quux' ); @@ -162,7 +162,7 @@ function cache_custom() { } } -function cache_custom() { +function cache_customE() { global $wpdb; $quux = my_cacheget( 'quux' ); @@ -172,7 +172,7 @@ function cache_custom() { } } -function cache_custom() { +function cache_customF() { global $wpdb; $wpdb->query( 'SELECT X FROM Y' ); // DB call ok; Bad. @@ -182,7 +182,7 @@ function cache_custom() { // @codingStandardsChangeSetting WordPress.VIP.DirectDatabaseQuery customCacheGetFunctions false // @codingStandardsChangeSetting WordPress.VIP.DirectDatabaseQuery customCacheSetFunctions false -function cache_custom() { +function cache_customG() { global $wpdb; $quux = my_cacheget( 'quux' ); @@ -244,7 +244,7 @@ function custom_modify_term_relationship() { } // Test Nowdocs and Heredocs -function foo() { +function foofoo() { global $wpdb; $listofthings = $wpdb->get_col( <<<'EOD' @@ -264,7 +264,7 @@ EOD return $listofthings; } -function baz() { +function bazbaz() { global $wpdb; $baz = wp_cache_get( 'baz' ); @@ -278,7 +278,7 @@ EOD } } -function cache_add_instead_of_set() { +function cache_add_instead_of_setter() { global $wpdb; $baz = wp_cache_get( 'baz' ); diff --git a/WordPress/Tests/VIP/DirectDatabaseQueryUnitTest.php b/WordPress/Tests/VIP/DirectDatabaseQueryUnitTest.php index f1e8419a..41a8d786 100644 --- a/WordPress/Tests/VIP/DirectDatabaseQueryUnitTest.php +++ b/WordPress/Tests/VIP/DirectDatabaseQueryUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the DirectDatabaseQuery sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_DirectDatabaseQueryUnitTest extends AbstractSniffUnitTest { +class DirectDatabaseQueryUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -35,7 +41,7 @@ public function getErrorList() { 190 => 1, 250 => 1, 257 => 1, - 274 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. + 274 => 1, ); } diff --git a/WordPress/Tests/VIP/FileSystemWritesDisallowUnitTest.php b/WordPress/Tests/VIP/FileSystemWritesDisallowUnitTest.php index 5686a7a2..f4937b37 100644 --- a/WordPress/Tests/VIP/FileSystemWritesDisallowUnitTest.php +++ b/WordPress/Tests/VIP/FileSystemWritesDisallowUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the FileSystemWritesDisallow sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_FileSystemWritesDisallowUnitTest extends AbstractSniffUnitTest { +class FileSystemWritesDisallowUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/VIP/OrderByRandUnitTest.php b/WordPress/Tests/VIP/OrderByRandUnitTest.php index 5074d859..ddcb3f6e 100644 --- a/WordPress/Tests/VIP/OrderByRandUnitTest.php +++ b/WordPress/Tests/VIP/OrderByRandUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the OrderByRand sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.9.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_OrderByRandUnitTest extends AbstractSniffUnitTest { +class OrderByRandUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/VIP/PluginMenuSlugUnitTest.php b/WordPress/Tests/VIP/PluginMenuSlugUnitTest.php index 32308a50..5a8307c2 100644 --- a/WordPress/Tests/VIP/PluginMenuSlugUnitTest.php +++ b/WordPress/Tests/VIP/PluginMenuSlugUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the PluginMenuSlug sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_PluginMenuSlugUnitTest extends AbstractSniffUnitTest { +class PluginMenuSlugUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,10 +28,9 @@ class WordPress_Tests_VIP_PluginMenuSlugUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 3 => 1, - 5 => 1, - 9 => 2, - 14 => ( PHP_VERSION_ID < 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NS_SEPARATOR. + 3 => 1, + 5 => 1, + 9 => 2, ); } diff --git a/WordPress/Tests/VIP/PostsPerPageUnitTest.inc b/WordPress/Tests/VIP/PostsPerPageUnitTest.inc index 9fd7d8aa..75330d6e 100644 --- a/WordPress/Tests/VIP/PostsPerPageUnitTest.inc +++ b/WordPress/Tests/VIP/PostsPerPageUnitTest.inc @@ -16,3 +16,15 @@ $query_args['posts_per_page'] = '1'; // Ok. $query_args['posts_per_page'] = '-1'; // Bad. $query_args['my_posts_per_page'] = -1; // Ok. + +// @codingStandardsChangeSetting WordPress.VIP.PostsPerPage posts_per_page 50 + $query_args['posts_per_page'] = 50; // OK. + $query_args['posts_per_page'] = 100; // Bad. + $query_args['posts_per_page'] = 200; // Bad. + $query_args['posts_per_page'] = 300; // Bad. +// @codingStandardsChangeSetting WordPress.VIP.PostsPerPage posts_per_page 200 + $query_args['posts_per_page'] = 50; // OK. + $query_args['posts_per_page'] = 100; // OK. + $query_args['posts_per_page'] = 200; // OK. + $query_args['posts_per_page'] = 300; // Bad. +// @codingStandardsChangeSetting WordPress.VIP.PostsPerPage posts_per_page 100 diff --git a/WordPress/Tests/VIP/PostsPerPageUnitTest.php b/WordPress/Tests/VIP/PostsPerPageUnitTest.php index 5dfb472a..7b38b81e 100644 --- a/WordPress/Tests/VIP/PostsPerPageUnitTest.php +++ b/WordPress/Tests/VIP/PostsPerPageUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the PostsPerPage sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_PostsPerPageUnitTest extends AbstractSniffUnitTest { +class PostsPerPageUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -25,9 +31,13 @@ public function getErrorList() { 4 => 1, 5 => 1, 6 => 1, - 11 => 2, + 11 => 2, 13 => 1, 16 => 1, + 22 => 1, + 23 => 1, + 24 => 1, + 29 => 1, ); } diff --git a/WordPress/Tests/VIP/RestrictedFunctionsUnitTest.inc b/WordPress/Tests/VIP/RestrictedFunctionsUnitTest.inc index 981e04eb..8a8b2420 100644 --- a/WordPress/Tests/VIP/RestrictedFunctionsUnitTest.inc +++ b/WordPress/Tests/VIP/RestrictedFunctionsUnitTest.inc @@ -31,34 +31,34 @@ $y = Bar::add_role(); // Ok. \add_role(); // Error. -get_term_link( $term ); // Error. +wpcom_vip_get_term_link( $term ); // Error. get_page_by_path( $path ); // Error. get_page_by_title( $page_title ); // Error. -get_term_by( $field, $value, $taxonomy ); // Error. +wpcom_vip_get_term_by( $field, $value, $taxonomy ); // Error. -get_category_by_slug( $slug ); // Error. +wpcom_vip_get_category_by_slug( $slug ); // Error. url_to_postid( $url ); // Error. attachment_url_to_postid( $url ); // Error. wpcom_vip_attachment_url_to_postid( $url ); // Ok. -get_tag_link(); // Error. -get_category_link(); // Error. -get_cat_ID(); // Error. +get_tag_link(); // Ok. +get_category_link(); // Ok. +get_cat_ID(); // Ok. url_to_post_id(); // Error. get_posts(); // Warning. wp_get_recent_posts(); // Warning. get_children(); // Warning. -wp_get_post_terms(); // Error. -wp_get_post_categories(); // Error. -wp_get_post_tags(); // Error. -wp_get_object_terms(); // Error. + + + + term_exists(); // Error. count_user_posts(); // Error. wp_old_slug_redirect(); // Error. diff --git a/WordPress/Tests/VIP/RestrictedFunctionsUnitTest.php b/WordPress/Tests/VIP/RestrictedFunctionsUnitTest.php index 169bff90..9853667b 100644 --- a/WordPress/Tests/VIP/RestrictedFunctionsUnitTest.php +++ b/WordPress/Tests/VIP/RestrictedFunctionsUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the VIP_RestrictedFunctions sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { +class RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -24,7 +30,6 @@ public function getErrorList() { return array( 3 => 1, 17 => 1, - 30 => ( PHP_VERSION_ID >= 50300 ) ? 0 : 1, 32 => 1, 34 => 1, 36 => 1, @@ -33,14 +38,7 @@ public function getErrorList() { 42 => 1, 44 => 1, 46 => 1, - 49 => 1, - 50 => 1, - 51 => 1, 52 => 1, - 58 => 1, - 59 => 1, - 60 => 1, - 61 => 1, 62 => 1, 63 => 1, 64 => 1, diff --git a/WordPress/Tests/VIP/RestrictedVariablesUnitTest.php b/WordPress/Tests/VIP/RestrictedVariablesUnitTest.php index afece734..c26a5929 100644 --- a/WordPress/Tests/VIP/RestrictedVariablesUnitTest.php +++ b/WordPress/Tests/VIP/RestrictedVariablesUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the VIP_RestrictedVariables sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_RestrictedVariablesUnitTest extends AbstractSniffUnitTest { +class RestrictedVariablesUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/VIP/SessionFunctionsUsageUnitTest.php b/WordPress/Tests/VIP/SessionFunctionsUsageUnitTest.php index 37890a39..85de6384 100644 --- a/WordPress/Tests/VIP/SessionFunctionsUsageUnitTest.php +++ b/WordPress/Tests/VIP/SessionFunctionsUsageUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the SessionFunctionsUsage sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_SessionFunctionsUsageUnitTest extends AbstractSniffUnitTest { +class SessionFunctionsUsageUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/VIP/SessionVariableUsageUnitTest.php b/WordPress/Tests/VIP/SessionVariableUsageUnitTest.php index 102afe5a..1455824d 100644 --- a/WordPress/Tests/VIP/SessionVariableUsageUnitTest.php +++ b/WordPress/Tests/VIP/SessionVariableUsageUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the SessionVariableUsage sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_SessionVariableUsageUnitTest extends AbstractSniffUnitTest { +class SessionVariableUsageUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/VIP/SlowDBQueryUnitTest.inc b/WordPress/Tests/VIP/SlowDBQueryUnitTest.inc index cf40bacb..6c8791aa 100644 --- a/WordPress/Tests/VIP/SlowDBQueryUnitTest.inc +++ b/WordPress/Tests/VIP/SlowDBQueryUnitTest.inc @@ -28,10 +28,11 @@ $test = array( // Single-line statements. 'tax_query' => array(), // Bad. + 'tax_query' => array(), // WPCS: slow query ok. 'tax_query' => array(), // WPCS: tax_query ok. // Multi-line statement. - 'tax_query' => array( // WPCS: tax_query ok. + 'tax_query' => array( // WPCS: slow query ok. array( 'taxonomy' => 'foo', ), diff --git a/WordPress/Tests/VIP/SlowDBQueryUnitTest.php b/WordPress/Tests/VIP/SlowDBQueryUnitTest.php index 57a0c4b5..ee659e54 100644 --- a/WordPress/Tests/VIP/SlowDBQueryUnitTest.php +++ b/WordPress/Tests/VIP/SlowDBQueryUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the SlowDBQuery sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_SlowDBQueryUnitTest extends AbstractSniffUnitTest { +class SlowDBQueryUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -38,6 +44,7 @@ public function getWarningList() { 16 => 1, 19 => 2, 30 => 1, + 32 => 1, // Warning about deprecated whitelist comment. ); } diff --git a/WordPress/Tests/VIP/SuperGlobalInputUsageUnitTest.inc b/WordPress/Tests/VIP/SuperGlobalInputUsageUnitTest.inc index ecb13156..44911785 100644 --- a/WordPress/Tests/VIP/SuperGlobalInputUsageUnitTest.inc +++ b/WordPress/Tests/VIP/SuperGlobalInputUsageUnitTest.inc @@ -18,3 +18,6 @@ $_REQUEST['wp_customize'] = 'on'; // Ok. // Issue: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues/542 if ( isset( $_GET['updated'] ) ) { // input var okay ?> + // Do something. +<?php +} diff --git a/WordPress/Tests/VIP/SuperGlobalInputUsageUnitTest.php b/WordPress/Tests/VIP/SuperGlobalInputUsageUnitTest.php index f5df99fa..cbd8cabe 100644 --- a/WordPress/Tests/VIP/SuperGlobalInputUsageUnitTest.php +++ b/WordPress/Tests/VIP/SuperGlobalInputUsageUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the SuperGlobalInputUsage sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_SuperGlobalInputUsageUnitTest extends AbstractSniffUnitTest { +class SuperGlobalInputUsageUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -32,7 +38,7 @@ public function getErrorList() { */ public function getWarningList() { return array( - 3 => 1, + 3 => 1, 13 => 1, 15 => 1, ); diff --git a/WordPress/Tests/VIP/TimezoneChangeUnitTest.php b/WordPress/Tests/VIP/TimezoneChangeUnitTest.php index a88b4923..7a8d9f35 100644 --- a/WordPress/Tests/VIP/TimezoneChangeUnitTest.php +++ b/WordPress/Tests/VIP/TimezoneChangeUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the TimezoneChange sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_TimezoneChangeUnitTest extends AbstractSniffUnitTest { +class TimezoneChangeUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,7 +28,7 @@ class WordPress_Tests_VIP_TimezoneChangeUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 3 => 1, + 3 => 1, ); } diff --git a/WordPress/Tests/VIP/ValidatedSanitizedInputUnitTest.inc b/WordPress/Tests/VIP/ValidatedSanitizedInputUnitTest.inc index 1d41fc92..b551dc30 100644 --- a/WordPress/Tests/VIP/ValidatedSanitizedInputUnitTest.inc +++ b/WordPress/Tests/VIP/ValidatedSanitizedInputUnitTest.inc @@ -160,3 +160,6 @@ output( <<<EOD some string {$_POST[some_var]} {$_GET['evil']} EOD ); // Bad x2. + +if ( ( $_POST['foo'] ?? 'post' ) === 'post' ) {} // OK. +if ( ( $_POST['foo'] <=> 'post' ) === 0 ) {} // OK. diff --git a/WordPress/Tests/VIP/ValidatedSanitizedInputUnitTest.php b/WordPress/Tests/VIP/ValidatedSanitizedInputUnitTest.php index 774ea935..46c30fc0 100644 --- a/WordPress/Tests/VIP/ValidatedSanitizedInputUnitTest.php +++ b/WordPress/Tests/VIP/ValidatedSanitizedInputUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\VIP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the ValidatedSanitizedInput sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_VIP_ValidatedSanitizedInputUnitTest extends AbstractSniffUnitTest { +class ValidatedSanitizedInputUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,20 +28,20 @@ class WordPress_Tests_VIP_ValidatedSanitizedInputUnitTest extends AbstractSniffU */ public function getErrorList() { return array( - 5 => 3, - 7 => 1, - 10 => 1, - 20 => 1, - 33 => 3, - 65 => 1, - 79 => 1, - 80 => 1, - 81 => 1, - 82 => 1, - 85 => 1, - 90 => 1, - 93 => 1, - 96 => 1, + 5 => 3, + 7 => 1, + 10 => 1, + 20 => 1, + 33 => 3, + 65 => 1, + 79 => 1, + 80 => 1, + 81 => 1, + 82 => 1, + 85 => 1, + 90 => 1, + 93 => 1, + 96 => 1, 100 => 2, 101 => 1, 104 => 2, diff --git a/WordPress/Tests/Variables/GlobalVariablesUnitTest.inc b/WordPress/Tests/Variables/GlobalVariablesUnitTest.inc index 88396213..b61fabc9 100644 --- a/WordPress/Tests/Variables/GlobalVariablesUnitTest.inc +++ b/WordPress/Tests/Variables/GlobalVariablesUnitTest.inc @@ -129,3 +129,5 @@ add_filter( 'comments_open', new class { return 'page' === $page->post_type; } }, 10, 2 ); + +$GLOBALS['totals'] ??= 10; // Bad. diff --git a/WordPress/Tests/Variables/GlobalVariablesUnitTest.php b/WordPress/Tests/Variables/GlobalVariablesUnitTest.php index 1c5a40e9..20444f09 100644 --- a/WordPress/Tests/Variables/GlobalVariablesUnitTest.php +++ b/WordPress/Tests/Variables/GlobalVariablesUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Variables; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the GlobalVariables sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_Variables_GlobalVariablesUnitTest extends AbstractSniffUnitTest { +class GlobalVariablesUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -33,6 +39,7 @@ public function getErrorList() { 54 => 1, 95 => 1, 128 => 1, + 133 => 1, ); } diff --git a/WordPress/Tests/Variables/VariableRestrictionsUnitTest.inc b/WordPress/Tests/Variables/VariableRestrictionsUnitTest.inc index 6df1a8a5..163699b1 100644 --- a/WordPress/Tests/Variables/VariableRestrictionsUnitTest.inc +++ b/WordPress/Tests/Variables/VariableRestrictionsUnitTest.inc @@ -8,7 +8,7 @@ $foo->bar(); // Ignored, this is a function not a variable. $foo->bar_method(); // Ignored. -FOO::var; // Matches: 'FOO::var'. +FOO::vars; // Matches: 'FOO::var'. FOO::var_test; @@ -16,7 +16,7 @@ FOO::reg; // Matches: 'FOO::reg*'. FOO::regex; // Matches: 'FOO::reg*'. -FOO::var(); // Ignored. +FOO::vars(); // Ignored. FOO::$static; // Matches: 'FOO::$static'. @@ -26,7 +26,7 @@ $foo["test"]; // Matches: '$foo['test']' AND $foo['test']. bar( $tallyho ); // Matches: '$tallyho'. test( $bar->bar ); // Matches: '$bar->bar'. -BAR::var; // Matches: 'BAR::var'. +BAR::vars; // Matches: 'BAR::var'. /* * Test exclude property. @@ -39,7 +39,7 @@ FOO::regex; // Ok - within excluded group. bar( $tallyho ); // Error. test( $bar->bar ); // Error. -BAR::var; // Error. +BAR::vars; // Error. // Exclude all groups: // @codingStandardsChangeSetting WordPress.Variables.VariableRestrictions exclude test,another @@ -49,7 +49,7 @@ FOO::regex; // Ok - within excluded group. bar( $tallyho ); // Ok - within excluded group. test( $bar->bar ); // Ok - within excluded group. -BAR::var; // Ok - within excluded group. +BAR::vars; // Ok - within excluded group. // Reset group exclusions. // @codingStandardsChangeSetting WordPress.Variables.VariableRestrictions exclude false @@ -59,4 +59,4 @@ FOO::regex; // Error. bar( $tallyho ); // Error. test( $bar->bar ); // Error. -BAR::var; // Error. +BAR::vars; // Error. diff --git a/WordPress/Tests/Variables/VariableRestrictionsUnitTest.php b/WordPress/Tests/Variables/VariableRestrictionsUnitTest.php index 354ab051..cbbf02f2 100644 --- a/WordPress/Tests/Variables/VariableRestrictionsUnitTest.php +++ b/WordPress/Tests/Variables/VariableRestrictionsUnitTest.php @@ -7,13 +7,20 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\Variables; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use WordPress\AbstractVariableRestrictionsSniff; + /** * Unit test class for the VariableRestrictions sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_Variables_VariableRestrictionsUnitTest extends AbstractSniffUnitTest { +class VariableRestrictionsUnitTest extends AbstractSniffUnitTest { /** * Fill in the $groups property to test the abstract class. @@ -21,13 +28,13 @@ class WordPress_Tests_Variables_VariableRestrictionsUnitTest extends AbstractSni protected function setUp() { parent::setUp(); - WordPress_AbstractVariableRestrictionsSniff::$groups = array( + AbstractVariableRestrictionsSniff::$groups = array( 'test' => array( 'type' => 'error', 'message' => 'Detected usage of %s', 'object_vars' => array( '$foo->bar', - 'FOO::var', + 'FOO::vars', 'FOO::reg*', 'FOO::$static', ), @@ -43,7 +50,7 @@ protected function setUp() { 'message' => 'Detected usage of %s', 'object_vars' => array( '$bar->bar', - 'BAR::var', + 'BAR::vars', 'BAR::reg*', 'BAR::$static', ), @@ -57,6 +64,14 @@ protected function setUp() { ); } + /** + * Reset the $groups property. + */ + protected function tearDown() { + AbstractVariableRestrictionsSniff::$groups = array(); + parent::tearDown(); + } + /** * Returns the lines where errors should occur. * @@ -64,8 +79,8 @@ protected function setUp() { */ public function getErrorList() { return array( - 3 => 1, - 5 => 1, + 3 => 1, + 5 => 1, 11 => 1, 15 => 1, 17 => 1, diff --git a/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php b/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php index f84f69e2..aa79f262 100644 --- a/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php +++ b/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php @@ -7,14 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the WP_AlternativeFunctions sniff. * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WP_AlternativeFunctionsUnitTest extends AbstractSniffUnitTest { +class AlternativeFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/CapitalPDangitUnitTest.inc b/WordPress/Tests/WP/CapitalPDangitUnitTest.inc new file mode 100644 index 00000000..20783f50 --- /dev/null +++ b/WordPress/Tests/WP/CapitalPDangitUnitTest.inc @@ -0,0 +1,182 @@ +<?php + +// Bad: This is a comment with WORDPRESS spelled incorrectly. + +/* Bad: This is a comment with wordpress spelled incorrectly. */ + +/* + * Bad: This is a comment with Wordpress spelled incorrectly. + */ + +/** + * Function comment + * + * @param string $wordpress OK: Here the incorrect spelling is OK to comply with the variable name rules. + */ +function somethingA( $wordpress ) {} // OK. + +/** + * Function comment + * + * @param string $my_wordpress_test OK: Here the incorrect spelling is OK to comply with the variable name rules. + */ +function somethingB( $my_wordpress_test ) {} // OK. + +/** + * Bad: In this comment wordPresss should be fixed. + * + * @param string $test Bad: In this comment word press should be fixed. + */ +function somethingC( $test ) {} // OK. + +function wordpress_function() {} // OK - comply with the function name rules. + +class Wordpress_Something {} // Bad. +class Something_Word_press_Something {} // Bad. +class Something_wordpressss {} // Bad. +class WordPress_Something {} // OK. +class Something_WordPress {} // OK. + +echo 'This is an explanation about wordpress.'; // Bad. +echo "This is an {$explanation} about wordpress."; // Bad. + +// Bad. +echo <<<EOD +This is an {$explanation} about wordpress. +EOD; + +echo 'http://wordpress.org/something'; // OK - part of a URL. +?> + +<div class="copyright"><?php printf( wp_kses_post( __( 'Powered by <a href="%s">WordPress</a>', 'theme-slug' ) ), 'https://wordpress.org/' ); ?></div><!-- OK. --> + +<p>Here we have an inline HTML tag with wordpress spelled incorrectly.</p><!-- Bad. --> +<p>Here we have an inline HTML tag with WordPress spelled correctly.</p><!-- OK. --> + +<p>Here we have an inline HTML tag with a URL, of course this should be coded differently, but even when it isn't, it should be ignored, so here goes: http://wordpress.org/ spelled incorrectly.</p><!-- OK. --> + +<p>Here we have an inline HTML tag with wordpressers spelled incorrectly.</p><!-- OK, part of another word. --> + +<p>Here we have an inline HTML tag with word press spelled incorrectly.</p><!-- Bad. --> +<p>Here we have an inline HTML tag with word-press spelled incorrectly.</p><!-- Bad. --> +<p>Here we have an inline HTML tag with word - presssss spelled incorrectly.</p><!-- Bad. --> + +<p class="fa-wordpress">In this case it's a CSS class name and we should leave well alone.</p><!-- OK. --> +<p class="wordpress-class">CSS class, but also wordPres spelled incorrectly in the text.</p><!-- Bad. --> +<p class="wordpress-class">Same again, this time with same spelling in both the class as well as in the text wordpress.</p><!-- Bad. --> + +<p>And lets have another test with wordpress spelled incorrectly more than once. Wordpress, wordPress, word pres.</p><!-- Bad. --> + +<?php +/* + * Some additional examples found in WP core. + */ +$first_comment_email = ! empty( $first_comment_email ) ? $first_comment_email : 'wapuu@wordpress.example'; // OK. + +$wordpress_rules = $xpath->query('/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]'); // OK. + +// If we don't have an email from the input headers default to wordpress@$sitename <= OK. +$from_email = 'wordpress@' . $sitename; // OK. + +if ( $counts['wordpress'] ) { // OK. + /* translators: 1: Number of updates available to WordPress */ + $titles['wordpress'] = sprintf( __( '%d WordPress Update'), $counts['wordpress'] ); // OK. +} + +?> +<td><input name="dbname" id="dbname" type="text" size="25" value="wordpress" /></td><!-- OK. --> + +<ol id="authors"><form action="?import=wordpress&step=2&id=" method="post"><input type="hidden" name="_wpnonce" value="855ae98911" /><!-- OK. --> + +<?php +/* + * More examples found in themes which should be accounted for. + * Based on a run of this sniff against 180 recently updated themes (7500+ files). + */ + +/** + * @wordpress-plugin <= OK. + * Plugin Name: TGM Plugin Activation + * + * @package Wordpress <= Bad. + * + * @link OK: http://justintadlock.com/archives/2011/07/01/captions-in-wordpress + */ + +if ( file_exists( ABSPATH . 'wp-content/plugins/wordpress-importer/wordpress-importer.php' ) ) { // OK. + include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); + + return is_plugin_active( 'wordpress-importer/wordpress-importer.php' ); // OK. +} + +$check = array( + 'wordpress-importer' => array( 'installed' => false, 'active' => false ), // OK. + 'widget-importer-exporter' => array( 'installed' => false, 'active' => false ) +); + +$default_editor_settings = [ + 'textarea_name' => $textarea_name, + 'media_buttons' => false, + 'tinymce' => [ 'plugins' => 'wordpress' ] // OK. +]; + + $wp_customize->add_control( + new Arouse_Custom_Content( + $wp_customize, + 'arouse_documentation_link', + array( + 'section' => 'arouse_theme_info', + 'label' => __( 'Arouse Documentation', 'arouse' ), + 'content' => __( '<a class="button" href="http://themezhut.com/arouse-wordpress-theme-documentation/" target="_blank">Read the documentation.</a>', 'arouse' ), // OK. + ) + ) + ); + +?> + + <a href="https://wordpress.org/support/theme/{{ data.theme_slug }}/reviews/#new-post" class="button button-primary activello-wordpress"><span class="dashicons dashicons-wordpress"></span>Review this theme on w.org</a><!-- OK. --> + + <p><?php _e( 'This page will help you get up and running quickly with <strong>Adamos</strong>. Please use the <a href="https://wordpress.org/support/theme/adamos">Wordpress Support Forums</a> if you have experience issues with this theme.', 'adamos' ); ?></p><!-- Bad. --> + +<a target="_blank" href="<?php echo esc_url( 'https://www.themeinprogress.com/alhena-free-responsive-corporate-wordpress-theme/?ref=2&campaign=alhena-notice' ); ?>" class="button"><?php _e( 'Upgrade to Alhena Premium', 'alhena-lite' ); ?></a><!-- OK. --> + +<?php + comment_form( array( + 'fields' => apply_filters( 'comment_form_default_fields', $fields ), + /* translators: %s: wordpress login url */ // Bad, but false negative as within an array. + 'must_log_in' => '<p class="must-log-in">' . sprintf( __( 'You must be <a href="%s">logged in</a> to post a comment.' , 'annina' ), wp_login_url( apply_filters( 'the_permalink', get_permalink( ) ) ) ) . '</p>', + )); + +$wl_theme_options['service_3_icons']="fa fa-wordpress"; // OK. + +if ( get_theme_mod('header_icon', 'fa-wordpress') ) echo '<i class="fa ' .esc_attr(get_theme_mod('header_icon' , 'fa-wordpress')) . '"></i>'; // OK. + +/** + * Bootstrap styled Caption shortcode. + * Hat tip: http://justintadlock.com/archives/2011/07/01/captions-in-wordpress <= OK. + */ + +$render = '<div class="redux-field-locked"><div class="redux-locked-inner' . (empty($message) ? ' empty' : '') . '"><a target="_blank" href="' . $t4p_url . 'evolve-multipurpose-wordpress-theme/" class="el el-lock"> </a>' . $message . '</div></div>' . $render; // OK. + +$installed = self::check_plugin_is_installed( 'wordpress-importer' ); // OK. + + +/* + * Test whitelisting + */ +echo 'This is an explanation about wordpress.'; // WPCS: spelling ok. + +/* + * Test fixer with an ignored and a fixable misspelling in the same line. + */ +?> +<p class="wordpress" href="http://x.org/?something=wordpress">The first two should be ignored for the purpose of replacing, this wordpress however should be fixed and this wordpress too.</p><!-- Bad. --> + +<?php // POT filename should be ignored. ?> +wordpress.pot + +<?php +// Bad. +$text = <<<'EOD' +This is an explanation about word-press. +EOD; diff --git a/WordPress/Tests/WP/CapitalPDangitUnitTest.inc.fixed b/WordPress/Tests/WP/CapitalPDangitUnitTest.inc.fixed new file mode 100644 index 00000000..7dcc1666 --- /dev/null +++ b/WordPress/Tests/WP/CapitalPDangitUnitTest.inc.fixed @@ -0,0 +1,182 @@ +<?php + +// Bad: This is a comment with WordPress spelled incorrectly. + +/* Bad: This is a comment with WordPress spelled incorrectly. */ + +/* + * Bad: This is a comment with WordPress spelled incorrectly. + */ + +/** + * Function comment + * + * @param string $wordpress OK: Here the incorrect spelling is OK to comply with the variable name rules. + */ +function somethingA( $wordpress ) {} // OK. + +/** + * Function comment + * + * @param string $my_wordpress_test OK: Here the incorrect spelling is OK to comply with the variable name rules. + */ +function somethingB( $my_wordpress_test ) {} // OK. + +/** + * Bad: In this comment WordPress should be fixed. + * + * @param string $test Bad: In this comment WordPress should be fixed. + */ +function somethingC( $test ) {} // OK. + +function wordpress_function() {} // OK - comply with the function name rules. + +class Wordpress_Something {} // Bad. +class Something_Word_press_Something {} // Bad. +class Something_wordpressss {} // Bad. +class WordPress_Something {} // OK. +class Something_WordPress {} // OK. + +echo 'This is an explanation about WordPress.'; // Bad. +echo "This is an {$explanation} about WordPress."; // Bad. + +// Bad. +echo <<<EOD +This is an {$explanation} about WordPress. +EOD; + +echo 'http://wordpress.org/something'; // OK - part of a URL. +?> + +<div class="copyright"><?php printf( wp_kses_post( __( 'Powered by <a href="%s">WordPress</a>', 'theme-slug' ) ), 'https://wordpress.org/' ); ?></div><!-- OK. --> + +<p>Here we have an inline HTML tag with WordPress spelled incorrectly.</p><!-- Bad. --> +<p>Here we have an inline HTML tag with WordPress spelled correctly.</p><!-- OK. --> + +<p>Here we have an inline HTML tag with a URL, of course this should be coded differently, but even when it isn't, it should be ignored, so here goes: http://wordpress.org/ spelled incorrectly.</p><!-- OK. --> + +<p>Here we have an inline HTML tag with wordpressers spelled incorrectly.</p><!-- OK, part of another word. --> + +<p>Here we have an inline HTML tag with WordPress spelled incorrectly.</p><!-- Bad. --> +<p>Here we have an inline HTML tag with WordPress spelled incorrectly.</p><!-- Bad. --> +<p>Here we have an inline HTML tag with WordPress spelled incorrectly.</p><!-- Bad. --> + +<p class="fa-wordpress">In this case it's a CSS class name and we should leave well alone.</p><!-- OK. --> +<p class="wordpress-class">CSS class, but also WordPress spelled incorrectly in the text.</p><!-- Bad. --> +<p class="wordpress-class">Same again, this time with same spelling in both the class as well as in the text WordPress.</p><!-- Bad. --> + +<p>And lets have another test with WordPress spelled incorrectly more than once. WordPress, WordPress, WordPress.</p><!-- Bad. --> + +<?php +/* + * Some additional examples found in WP core. + */ +$first_comment_email = ! empty( $first_comment_email ) ? $first_comment_email : 'wapuu@wordpress.example'; // OK. + +$wordpress_rules = $xpath->query('/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]'); // OK. + +// If we don't have an email from the input headers default to wordpress@$sitename <= OK. +$from_email = 'wordpress@' . $sitename; // OK. + +if ( $counts['wordpress'] ) { // OK. + /* translators: 1: Number of updates available to WordPress */ + $titles['wordpress'] = sprintf( __( '%d WordPress Update'), $counts['wordpress'] ); // OK. +} + +?> +<td><input name="dbname" id="dbname" type="text" size="25" value="wordpress" /></td><!-- OK. --> + +<ol id="authors"><form action="?import=wordpress&step=2&id=" method="post"><input type="hidden" name="_wpnonce" value="855ae98911" /><!-- OK. --> + +<?php +/* + * More examples found in themes which should be accounted for. + * Based on a run of this sniff against 180 recently updated themes (7500+ files). + */ + +/** + * @wordpress-plugin <= OK. + * Plugin Name: TGM Plugin Activation + * + * @package WordPress <= Bad. + * + * @link OK: http://justintadlock.com/archives/2011/07/01/captions-in-wordpress + */ + +if ( file_exists( ABSPATH . 'wp-content/plugins/wordpress-importer/wordpress-importer.php' ) ) { // OK. + include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); + + return is_plugin_active( 'wordpress-importer/wordpress-importer.php' ); // OK. +} + +$check = array( + 'wordpress-importer' => array( 'installed' => false, 'active' => false ), // OK. + 'widget-importer-exporter' => array( 'installed' => false, 'active' => false ) +); + +$default_editor_settings = [ + 'textarea_name' => $textarea_name, + 'media_buttons' => false, + 'tinymce' => [ 'plugins' => 'wordpress' ] // OK. +]; + + $wp_customize->add_control( + new Arouse_Custom_Content( + $wp_customize, + 'arouse_documentation_link', + array( + 'section' => 'arouse_theme_info', + 'label' => __( 'Arouse Documentation', 'arouse' ), + 'content' => __( '<a class="button" href="http://themezhut.com/arouse-wordpress-theme-documentation/" target="_blank">Read the documentation.</a>', 'arouse' ), // OK. + ) + ) + ); + +?> + + <a href="https://wordpress.org/support/theme/{{ data.theme_slug }}/reviews/#new-post" class="button button-primary activello-wordpress"><span class="dashicons dashicons-wordpress"></span>Review this theme on w.org</a><!-- OK. --> + + <p><?php _e( 'This page will help you get up and running quickly with <strong>Adamos</strong>. Please use the <a href="https://wordpress.org/support/theme/adamos">WordPress Support Forums</a> if you have experience issues with this theme.', 'adamos' ); ?></p><!-- Bad. --> + +<a target="_blank" href="<?php echo esc_url( 'https://www.themeinprogress.com/alhena-free-responsive-corporate-wordpress-theme/?ref=2&campaign=alhena-notice' ); ?>" class="button"><?php _e( 'Upgrade to Alhena Premium', 'alhena-lite' ); ?></a><!-- OK. --> + +<?php + comment_form( array( + 'fields' => apply_filters( 'comment_form_default_fields', $fields ), + /* translators: %s: wordpress login url */ // Bad, but false negative as within an array. + 'must_log_in' => '<p class="must-log-in">' . sprintf( __( 'You must be <a href="%s">logged in</a> to post a comment.' , 'annina' ), wp_login_url( apply_filters( 'the_permalink', get_permalink( ) ) ) ) . '</p>', + )); + +$wl_theme_options['service_3_icons']="fa fa-wordpress"; // OK. + +if ( get_theme_mod('header_icon', 'fa-wordpress') ) echo '<i class="fa ' .esc_attr(get_theme_mod('header_icon' , 'fa-wordpress')) . '"></i>'; // OK. + +/** + * Bootstrap styled Caption shortcode. + * Hat tip: http://justintadlock.com/archives/2011/07/01/captions-in-wordpress <= OK. + */ + +$render = '<div class="redux-field-locked"><div class="redux-locked-inner' . (empty($message) ? ' empty' : '') . '"><a target="_blank" href="' . $t4p_url . 'evolve-multipurpose-wordpress-theme/" class="el el-lock"> </a>' . $message . '</div></div>' . $render; // OK. + +$installed = self::check_plugin_is_installed( 'wordpress-importer' ); // OK. + + +/* + * Test whitelisting + */ +echo 'This is an explanation about wordpress.'; // WPCS: spelling ok. + +/* + * Test fixer with an ignored and a fixable misspelling in the same line. + */ +?> +<p class="wordpress" href="http://x.org/?something=wordpress">The first two should be ignored for the purpose of replacing, this WordPress however should be fixed and this WordPress too.</p><!-- Bad. --> + +<?php // POT filename should be ignored. ?> +wordpress.pot + +<?php +// Bad. +$text = <<<'EOD' +This is an explanation about WordPress. +EOD; diff --git a/WordPress/Tests/WP/CapitalPDangitUnitTest.php b/WordPress/Tests/WP/CapitalPDangitUnitTest.php new file mode 100644 index 00000000..dc5c651a --- /dev/null +++ b/WordPress/Tests/WP/CapitalPDangitUnitTest.php @@ -0,0 +1,69 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the CapitalPDangit sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class CapitalPDangitUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array(); + + } // end getErrorList() + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + + return array( + 3 => 1, + 5 => 1, + 8 => 1, + 26 => 1, + 28 => 1, + 34 => 1, + 35 => 1, + 36 => 1, + 40 => 1, + 41 => 1, + 45 => 1, + 53 => 1, + 60 => 1, + 61 => 1, + 62 => 1, + 65 => 1, + 66 => 1, + 68 => 1, + 101 => 1, + 139 => 1, + 146 => 0, // False negative. + 173 => 1, + 181 => 1, + ); + + } + +} // End class. diff --git a/WordPress/Tests/WP/DeprecatedClassesUnitTest.inc b/WordPress/Tests/WP/DeprecatedClassesUnitTest.inc new file mode 100644 index 00000000..fcd27c2f --- /dev/null +++ b/WordPress/Tests/WP/DeprecatedClassesUnitTest.inc @@ -0,0 +1,19 @@ +<?php + +// DEPRECATED WORDPRESS CLASSES. + +/* + * Error. + */ +/* ============ WP 3.1 ============ */ +$a = new WP_User_Search; +$a = new \WP_User_Search(); +echo WP_User_Search::$users_per_page; +echo \WP_User_Search::prepare_query(); +class My_User_Search extends WP_User_Search {} +class Our_User_Search implements WP_User_Search {} +$a = (new WP_User_Search())->query(); + +/* + * Warning. + */ diff --git a/WordPress/Tests/WP/DeprecatedClassesUnitTest.php b/WordPress/Tests/WP/DeprecatedClassesUnitTest.php new file mode 100644 index 00000000..d5e9ca69 --- /dev/null +++ b/WordPress/Tests/WP/DeprecatedClassesUnitTest.php @@ -0,0 +1,44 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the WP_DeprecatedClasses sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class DeprecatedClassesUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array_fill( 9, 7, 1 ); + + } // End getErrorList(). + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array(); + + } + +} // End class. diff --git a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.inc b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.inc index 03dd465a..8cdfda91 100644 --- a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.inc +++ b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.inc @@ -1,211 +1,265 @@ <?php - // DEPRECATED WORDPRESS FUNCTIONS. -// Error. - -// The methods are being checked till https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues/731 is solved. -find_base_dir(); // WP_Filesystem_Base::find_base_dir() use WP_Filesystem::abspath instead. -get_base_dir(); // WP_Filesystem_Base::get_base_dir() use WP_Filesystem::abspath instead. -the_category_id(); +/* + * Error. + */ +/* ============ WP 0.71 ============ */ the_category_head(); +the_category_ID(); +/* ============ WP 1.2 ============ */ permalink_link(); +/* ============ WP 1.5 ============ */ start_wp(); +/* ============ WP 1.5.1 ============ */ get_postdata(); -previous_post(); +/* ============ WP 2.0 ============ */ +create_user(); next_post(); -user_can_create_post(); +previous_post(); user_can_create_draft(); -user_can_edit_post(); +user_can_create_post(); user_can_delete_post(); -user_can_set_post_date(); -user_can_edit_post_comments(); user_can_delete_post_comments(); +user_can_edit_post(); +user_can_edit_post_comments(); +user_can_edit_post_date(); user_can_edit_user(); -create_user(); -get_linksbyname(); -wp_get_linksbyname(); -get_linkobjectsbyname(); -get_linkobjects(); -get_linksbyname_withrating(); -get_links_withrating(); -get_autotoggle(); -list_cats(); -wp_list_cats(); +user_can_set_post_date(); +/* ============ WP 2.1 ============ */ dropdown_cats(); -list_authors(); -wp_get_post_cats(); -wp_set_post_cats(); get_archives(); get_author_link(); -link_pages(); -get_settings(); -wp_get_links(); +get_autotoggle(); +get_link(); +get_linkcatname(); +get_linkobjectsbyname(); +get_linkobjects(); +get_linkrating(); get_links(); get_links_list(); +get_links_withrating(); +get_linksbyname(); +get_linksbyname_withrating(); +get_settings(); +link_pages(); links_popup_script(); -get_linkrating(); -get_linkcatname(); -get_link(); +list_authors(); +list_cats(); tinymce_include(); +wp_get_links(); +wp_get_linksbyname(); +wp_get_post_cats(); +wp_list_cats(); +wp_set_post_cats(); +/* ============ WP 2.2 ============ */ comments_rss(); +/* ============ WP 2.3 ============ */ permalink_single_rss(); +/* ============ WP 2.5 ============ */ comments_rss_link(); -get_category_rss_link(); -get_author_rss_link(); -get_the_attachment_link(); -get_attachment_icon_src(); +documentation_link(); get_attachment_icon(); +get_attachment_icon_src(); get_attachment_innerHTML(); -documentation_link(); +get_author_rss_link(); +get_category_rss_link(); +get_the_attachment_link(); gzip_compression(); -wp_setcookie(); +wp_clearcookie(); wp_get_cookie_login(); wp_login(); +wp_setcookie(); +/* ============ WP 2.6 ============ */ dropdown_categories(); dropdown_link_categories(); +/* ============ WP 2.7 ============ */ get_commentdata(); +find_base_dir(); // Issue #731 - method WP_Filesystem_Base::find_base_dir() use WP_Filesystem::abspath instead. +get_base_dir(); // Issue #731 - method WP_Filesystem_Base::get_base_dir() use WP_Filesystem::abspath instead. +/* ============ WP 2.8 ============ */ +__ngettext(); +__ngettext_noop(); +attribute_escape(); +get_author_name(); get_catname(); get_category_children(); +get_the_author_aim(); get_the_author_description(); -the_author_description(); -get_the_author_login(); +get_the_author_email(); get_the_author_firstname(); -the_author_firstname(); +get_the_author_icq(); +get_the_author_ID(); get_the_author_lastname(); -the_author_lastname(); +get_the_author_login(); +get_the_author_msn(); get_the_author_nickname(); -the_author_nickname(); -get_the_author_email(); +get_the_author_url(); +get_the_author_yim(); +js_escape(); +register_sidebar_widget(); +register_widget_control(); +sanitize_url(); +the_author_aim(); +the_author_description(); the_author_email(); -get_the_author_icq(); +the_author_firstname(); the_author_icq(); -get_the_author_yim(); -the_author_yim(); -get_the_author_msn(); +the_author_ID(); +the_author_lastname(); +the_author_login(); the_author_msn(); -get_the_author_aim(); -the_author_aim(); -get_author_name(); -get_the_author_url(); +the_author_nickname(); the_author_url(); -get_the_author_ID(); -the_author_ID(); -__ngettext(); -__ngettext_noop(); -sanitize_url(); -js_escape(); -wp_specialchars(); -attribute_escape(); -register_sidebar_widget(); +the_author_yim(); unregister_sidebar_widget(); -register_widget_control(); unregister_widget_control(); -the_content_rss(); -make_url_footnote(); +wp_specialchars(); +/* ============ WP 2.9 ============ */ _c(); +_nc(); +get_real_file_to_edit(); +make_url_footnote(); +the_content_rss(); translate_with_context(); -nc(); -get_alloptions(); +/* ============ WP 3.0 ============ */ +activate_sitewide_plugin(); +add_option_update_handler(); +automatic_feed_links(); clean_url(); +clear_global_post_cache(); +codepress_footer_js(); +codepress_get_lang(); +deactivate_sitewide_plugin(); delete_usermeta(); -get_usermeta(); -update_usermeta(); -automatic_feed_links(); -get_profile(); -get_usernumposts(); funky_javascript_callback(); funky_javascript_fix(); +generate_random_password(); +get_alloptions(); +get_blog_list(); +get_most_active_blogs(); +get_profile(); +get_user_details(); +get_usermeta(); +get_usernumposts(); +graceful_fail(); +is_main_blog(); +is_site_admin(); is_taxonomy(); is_term(); -wp_dropdown_cats(); -add_option_update_handler(); +is_wpmu_sitewide_plugin(); +mu_options(); remove_option_update_handler(); -codepress_get_lang(); -codepress_footer_js(); +set_current_user(); +update_usermeta(); use_codepress(); +validate_email(); +wp_dropdown_cats(); wp_shrink_dimensions(); -is_plugin_page(); -update_category_cache(); -get_users_of_blog(); +wpmu_checkAvailableSpace(); +wpmu_menu(); +/* ============ WP 3.1 ============ */ get_author_user_ids(); +get_dashboard_blog(); get_editable_authors(); get_editable_user_ids(); get_nonauthor_user_ids(); -WP_User_Search(); -get_others_unpublished_posts(); get_others_drafts(); get_others_pending(); -wp_timezone_supported(); -wp_dashboard_quick_press(); -wp_tiny_mce(); -wp_preload_dialogs(); -wp_print_editor_js(); -wp_quicktags(); +get_others_unpublished_posts(); +get_users_of_blog(); +install_themes_feature_list(); +is_plugin_page(); +update_category_cache(); +/* ============ WP 3.2 ============ */ favorite_actions(); -the_editor(); -get_user_metavalues(); -sanitize_user_object(); +wp_dashboard_quick_press_output(); +wp_timezone_supported(); +/* ============ WP 3.3 ============ */ +add_contextual_help(); get_boundary_post_rel_link(); -start_post_rel_link(); get_index_rel_link(); -index_rel_link(); get_parent_post_rel_link(); -parent_post_rel_link(); -wp_admin_bar_dashboard_view_site_menu(); +get_user_by_email(); +get_user_metavalues(); +get_userdatabylogin(); +index_rel_link(); is_blog_user(); -debug_fopen(); -debug_fwrite(); -debug_fclose(); -screen_layout(); -screen_options(); -screen_meta(); -media_upload_image(); media_upload_audio(); -media_upload_video(); media_upload_file(); -type_url_form_image(); +media_upload_image(); +media_upload_video(); +parent_post_rel_link(); +sanitize_user_object(); +screen_layout(); +screen_meta(); +screen_options(); +start_post_rel_link(); +the_editor(); type_url_form_audio(); -type_url_form_video(); type_url_form_file(); -add_contextual_help(); -get_themes(); -get_theme(); -get_current_theme(); -clean_pre(); -add_custom_image_header(); -remove_custom_image_header(); +type_url_form_image(); +type_url_form_video(); +wp_admin_bar_dashboard_view_site_menu(); +wp_preload_dialogs(); +wp_print_editor_js(); +wp_quicktags(); +wp_tiny_mce(); +wpmu_admin_do_redirect(); +wpmu_admin_redirect_add_updated_param(); +/* ============ WP 3.4 ============ */ add_custom_background(); -remove_custom_background(); -get_theme_data(); -update_page_cache(); +add_custom_image_header(); clean_page_cache(); +clean_pre(); +current_theme_info(); +debug_fclose(); +debug_fopen(); +debug_fwrite(); +display_theme(); get_allowed_themes(); get_broken_themes(); -current_theme_info(); +get_current_theme(); +get_site_allowed_themes(); +get_theme(); +get_theme_data(); +get_themes(); +logIO(); +remove_custom_background(); +remove_custom_image_header(); +update_page_cache(); wp_explain_nonce(); -sticky_class(); +wpmu_get_blog_allowedthemes(); +/* ============ WP 3.5 ============ */ +_flip_image_resource(); _get_post_ancestors(); -wp_load_image(); -image_resize(); -wp_get_single_post(); -user_pass_ok(); -_save_post_hook(); -gd_edit_image_support(); _insert_into_post_button(); _media_button(); -get_post_to_edit(); +_rotate_image_resource(); +_save_post_hook(); +image_resize(); +gd_edit_image_support(); get_default_page_to_edit(); +get_post_to_edit(); +get_udims(); +sticky_class(); +user_pass_ok(); +wp_cache_reset(); wp_create_thumbnail(); +wp_get_single_post(); +wp_load_image(); +/* ============ WP 3.6 ============ */ get_user_id_from_string(); wp_convert_bytes_to_hr(); wp_nav_menu_locations_meta_box(); +/* ============ WP 3.7 ============ */ +_search_terms_tidy(); +get_blogaddress_by_domain(); the_attachment_links(); wp_update_core(); wp_update_plugin(); wp_update_theme(); -_search_terms_tidy(); -get_blogaddress_by_domain(); +/* ============ WP 3.8 ============ */ get_screen_icon(); screen_icon(); wp_dashboard_incoming_links(); @@ -217,44 +271,64 @@ wp_dashboard_recent_comments_control(); wp_dashboard_secondary(); wp_dashboard_secondary_control(); wp_dashboard_secondary_output(); -rich_edit_exists(); +/* ============ WP 3.9 ============ */ +_relocate_children(); default_topic_count_text(); format_to_post(); get_current_site_name(); +rich_edit_exists(); wpmu_current_site(); -_relocate_children(); +/* ============ WP 4.0 ============ */ get_all_category_ids(); like_escape(); url_is_accessable_via_ssl(); -prepare_control(); -add_tab(); -remove_tab(); -print_tab_image(); -setup_widget_addition_previews(); -prepreview_added_sidebars_widgets(); -prepreview_added_widget_instance(); -remove_prepreview_filters(); -preview_theme(); -_preview_theme_template_filter(); +/* ============ WP 4.1 ============ */ +add_tab(); // Issue #731 - method, not function. +prepare_control(); // Issue #731 - method, not function. +print_tab_image(); // Issue #731 - method, not function. +remove_tab(); // Issue #731 - method, not function. +/* ============ WP 4.2 ============ */ +prepreview_added_sidebars_widgets(); // Issue #731 - method, not function. +prepreview_added_widget_instance(); // Issue #731 - method, not function. +remove_prepreview_filters(); // Issue #731 - method, not function. +setup_widget_addition_previews(); // Issue #731 - method, not function. +/* ============ WP 4.3 ============ */ _preview_theme_stylesheet_filter(); +_preview_theme_template_filter(); +preview_theme(); preview_theme_ob_filter(); preview_theme_ob_filter_callback(); -wp_richedit_pre(); wp_ajax_wp_fullscreen_save_post(); - -// Warning. -flush_widget_cache(); // WP_Widget_Recent_Comments::flush_widget_cache() -post_permalink(); -force_ssl_login(); +wp_richedit_pre(); +/* ============ WP 4.4 ============ */ create_empty_blog(); +force_ssl_login(); get_admin_users_for_domain(); +post_permalink(); wp_get_http(); -is_comments_popup(); +flush_widget_cache(); // Issue #731 - method WP_Widget_Recent_Comments::flush_widget_cache() + +/* + * Warning. + */ +/* ============ WP 4.5 ============ */ add_object_page(); add_utility_page(); -get_comments_popup_template(); comments_popup_script(); -popuplinks(); +get_comments_popup_template(); get_currentuserinfo(); +is_comments_popup(); +popuplinks(); +/* ============ WP 4.6 ============ */ +post_form_autocomplete_off(); wp_embed_handler_googlevideo(); wp_get_sites(); +/* ============ WP 4.7 ============ */ +_sort_nav_menu_items(); +_usort_terms_by_ID(); +_usort_terms_by_name(); +get_paged_template(); +wp_get_network(); +wp_kses_js_entities(); +/* ============ WP 4.8 ============ */ +wp_dashboard_plugins_output(); diff --git a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php index 951804c3..957ce678 100644 --- a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php +++ b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php @@ -7,14 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the WP_DeprecatedFunctions sniff. * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WP_DeprecatedFunctionsUnitTest extends AbstractSniffUnitTest { +class DeprecatedFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,8 +27,20 @@ class WordPress_Tests_WP_DeprecatedFunctionsUnitTest extends AbstractSniffUnitTe * @return array <int line number> => <int number of errors> */ public function getErrorList() { - return array_fill( 8, 236, 1 ); + $errors = array_fill( 8, 302, 1 ); + + // Unset the lines related to version comments. + unset( + $errors[10], $errors[12], $errors[14], $errors[16], $errors[29], + $errors[55], $errors[57], $errors[59], $errors[73], $errors[76], + $errors[80], $errors[118], $errors[125], $errors[161], $errors[174], + $errors[178], $errors[210], $errors[233], $errors[251], $errors[255], + $errors[262], $errors[274], $errors[281], $errors[285], $errors[290], + $errors[295], $errors[303] + ); + + return $errors; } /** @@ -32,8 +49,15 @@ public function getErrorList() { * @return array <int line number> => <int number of warnings> */ public function getWarningList() { - return array_fill( 246, 15, 1 ); + $warnings = array_fill( 315, 20, 1 ); + + // Unset the lines related to version comments. + unset( + $warnings[322], $warnings[326], $warnings[333] + ); + + return $warnings; } } // End class. diff --git a/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc b/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc new file mode 100644 index 00000000..ba7c2a9c --- /dev/null +++ b/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc @@ -0,0 +1,66 @@ +<?php + +// All will be OK as the default value is used. + +wp_title_rss( '–' ); // 1st. +wp_get_sidebars_widgets( true ); // 1st. +wp_new_user_notification( '', null, '' ); // 2nd. +the_attachment_link( '', false, false, false ); // 3rd. +update_blog_option( '', '', '', null ); // 4th. +wp_install( '', '', '', '', '' ); // 5th. +get_category_parents( '', '', '', '', array() ); // 5th. +get_category_parents( '', '', '', '', [] ); // 5th. + +// Method names within a class should be fine. + +Theme_Object::wp_title_rss( 'home' ); // Ok. +$this->wp_title_rss( 'siteurl' ); // Ok. +$theme_object->wp_title_rss( 'text_direction' ); // Ok. + +// All will give an Error even though they have a dynamic variable. + +wp_new_user_notification( '', $variable ); +wp_new_user_notification( '', function_name() ); +wp_new_user_notification( '', $this->method_name() ); + +// All will give an ERROR. The functions are ordered alphabetically. + +add_option( '', '', [] ); +add_option( '', '', 1.23 ); +add_option( '', '', 10 ); +add_option( '', '', false ); +add_option( '', '', 'deprecated' ); +comments_link( 'deprecated', 'deprecated' ); +comments_number( '', '', '', 'deprecated' ); +convert_chars( '', 'deprecated' ); +discover_pingback_server_uri( '', 'deprecated' ); +get_delete_post_link( '', 'deprecated' ); +get_last_updated( 'deprecated' ); +get_the_author( 'deprecated' ); +get_user_option( '', '', 'deprecated' ); +get_wp_title_rss( 'deprecated' ); +is_email( '', 'deprecated' ); +is_email( '', 'false' ); // False as a string not bool. +load_plugin_textdomain( '', 'deprecated' ); +safecss_filter_attr( '', 'deprecated' ); +the_attachment_link( '', '', 'deprecated' ); +the_author( 'deprecated', 'deprecated' ); +the_author_posts_link( 'deprecated' ); +trackback_rdf( 'deprecated' ); +trackback_url( 'deprecated' ); +update_blog_option( '', '', '', 'deprecated' ); +update_user_status( '', '', '', 'deprecated' ); +wp_get_http_headers( '', 'deprecated' ); +wp_get_sidebars_widgets( 'deprecated' ); +wp_install( '', '', '', '', 'deprecated' ); +wp_new_user_notification( '', 'deprecated' ); +wp_notify_postauthor( '', 'deprecated' ); +wp_notify_postauthor( '', 'null' ); // Null as a string not null. +wp_title_rss( 'deprecated' ); +wp_upload_bits( '', 'deprecated' ); +xfn_check( '', '', 'deprecated' ); + +// All will give an WARNING as they have been deprecated after WP 4.5. + +get_category_parents( '', '', '', '', array( 'deprecated') ); +unregister_setting( '', '', '', 'deprecated' ); diff --git a/WordPress/Tests/WP/DeprecatedParametersUnitTest.php b/WordPress/Tests/WP/DeprecatedParametersUnitTest.php new file mode 100644 index 00000000..b1884ac8 --- /dev/null +++ b/WordPress/Tests/WP/DeprecatedParametersUnitTest.php @@ -0,0 +1,55 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the DeprecatedParameters sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class DeprecatedParametersUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + $errors = array_fill( 28, 34, 1 ); + + $errors[22] = 1; + $errors[23] = 1; + $errors[24] = 1; + + // Override number of errors. + $errors[33] = 2; + $errors[47] = 2; + + return $errors; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array( + 65 => 1, + 66 => 1, + ); + } + +} // End class. diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc new file mode 100644 index 00000000..d5e5f3a6 --- /dev/null +++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc @@ -0,0 +1,72 @@ +<?php + +/* + * Make sure that global constants are correctly identified. + * + * The below are all ok. + */ +// Ok, not a (global) constant. +namespace STYLESHEETPATH {} +namespace MY\OTHER\STYLESHEETPATH\NS {} +use STYLESHEETPATH; +use Something, STYLESHEETPATH, SomethingElse; +class STYLESHEETPATH { + const STYLESHEETPATH = 'something'; + private function STYLESHEETPATH() {} +} +class ABC extends STYLESHEETPATH {} +class DEF implements STYLESHEETPATH {} +interface STYLESHEETPATH {} +trait STYLESHEETPATH {} +$a = new STYLESHEETPATH; +$a = new STYLESHEETPATH(); +function STYLESHEETPATH() {} +echo STYLESHEETPATH(); +echo My\UsedAsNamespace\STYLESHEETPATH\something; +My\UsedAsNamespace\STYLESHEETPATH\something::something_else(); +if ( $abc instanceof STYLESHEETPATH ) {} + +goto STYLESHEETPATH; +// Something. +STYLESHEETPATH: +// Something. + +echo \mynamespace\STYLESHEETPATH; +echo My_Class::STYLESHEETPATH; +echo $this->STYLESHEETPATH; +use const SomeNamespace\STYLESHEETPATH as SSP; // PHP 5.6+ +use const SomeNamespace\{STYLESHEETPATH, TEMPLATEPATH}; // PHP 7.0+ +define( 'My\STYLESHEETPATH', 'something' ); + +// Ok, not usage of the constant as such. +if ( defined( 'STYLESHEETPATH' ) ) { // Ok. + // Do something unrelated. +} + + +/* + * These are all bad. + */ +echo STYLESHEETPATH; +echo \STYLESHEETPATH; // Global constant. +$folder = basename( TEMPLATEPATH ); +include PLUGINDIR . '/js/myfile.js'; +echo MUPLUGINDIR; +echo HEADER_IMAGE; +echo NO_HEADER_TEXT; +echo HEADER_TEXTCOLOR; +echo HEADER_IMAGE_WIDTH; +echo HEADER_IMAGE_HEIGHT; +echo BACKGROUND_COLOR; +echo BACKGROUND_IMAGE; + +use const STYLESHEETPATH as SSP; +use const ABC as STYLESHEETPATH; + +switch( STYLESHEETPATH ) { + case STYLESHEETPATH: + break; +} + +define( 'STYLESHEETPATH', 'something' ); +const STYLESHEETPATH = 'something'; diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php new file mode 100644 index 00000000..3e60ba4b --- /dev/null +++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the WP_DiscouragedConstants sniff. + * + * @package WPCS\WordPressCodingStandards + * @since 0.14.0 + */ +class DiscouragedConstantsUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array(); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array( + 50 => 1, + 51 => 1, + 52 => 1, + 53 => 1, + 54 => 1, + 55 => 1, + 56 => 1, + 57 => 1, + 58 => 1, + 59 => 1, + 60 => 1, + 61 => 1, + 63 => 1, + 64 => 1, + 66 => 1, + 67 => 1, + 71 => 1, + 72 => 1, + ); + } + +} // End class. diff --git a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php index 60c7c266..d7e441fc 100644 --- a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php +++ b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php @@ -7,14 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the WP_DiscouragedFunctions sniff. * * @package WPCS\WordPressCodingStandards * * @since 0.11.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WP_DiscouragedFunctionsUnitTest extends AbstractSniffUnitTest { +class DiscouragedFunctionsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/EnqueuedResourcesUnitTest.php b/WordPress/Tests/WP/EnqueuedResourcesUnitTest.php index 63c47741..28da1f87 100644 --- a/WordPress/Tests/WP/EnqueuedResourcesUnitTest.php +++ b/WordPress/Tests/WP/EnqueuedResourcesUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the EnqueuedResources sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WP_EnqueuedResourcesUnitTest extends AbstractSniffUnitTest { +class EnqueuedResourcesUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,10 +28,10 @@ class WordPress_Tests_WP_EnqueuedResourcesUnitTest extends AbstractSniffUnitTest */ public function getErrorList() { return array( - 1 => 1, - 2 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 has a bug tokenizing inline HTML / `<s`. - 6 => 1, - 7 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 has a bug tokenizing inline HTML / `<s`. + 1 => 1, + 2 => 1, + 6 => 1, + 7 => 1, 10 => 1, 11 => 1, 13 => 1, @@ -34,10 +40,10 @@ public function getErrorList() { 17 => 1, 20 => 1, 21 => 1, - 25 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize double quoted T_HEREDOC. - 26 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize double quoted T_HEREDOC. - 30 => 1, // PHPCS on PHP 5.2 does not recognize T_NOWDOC, but sees this as a literal string anyway. - 31 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. + 25 => 1, + 26 => 1, + 30 => 1, + 31 => 1, ); } diff --git a/WordPress/Tests/WP/I18nUnitTest.1.inc b/WordPress/Tests/WP/I18nUnitTest.1.inc index 2d9a38c7..14412b5e 100644 --- a/WordPress/Tests/WP/I18nUnitTest.1.inc +++ b/WordPress/Tests/WP/I18nUnitTest.1.inc @@ -1,104 +1,163 @@ <?php -/* - * Test sniffing for translator comments. - */ -/* Basic test ****************************************************************/ -__( 'No placeholders here.', 'my-slug' ); // Ok, no placeholders, so no translators comment needed. -__( 'There are %1$d monkeys in the %2$s', 'my-slug' ); // Bad - no translators comment. +// @codingStandardsChangeSetting WordPress.WP.I18n check_translator_comments false +__( 'string' ); // OK - no text domain known, so not checked. +__( 'string', 'something' ); // OK - no text domain known, so not checked. -/* Testing different comment styles ******************************************/ +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain my-slug -/* translators: %d: number of cats. */ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - single line /* */ style. - - /* translators: %d: number of cats. */ - _n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - single line /* */ style, indented code. - -// translators: %d: number of cats. -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - single line // style. - -/* translators: - - number of monkeys, - - location. */ -esc_html__( 'There are %1$d monkeys in the %2$s', 'my-slug' ); // OK - multi-line /* */ style. - -/* - * translators: %d: number of cats. - */ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - multi-line /* */ style. - -/* - * translators: %d: number of cats. - * This is a multiline comment, - * But it also has * at the start - of some lines ;-) -*/ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - inconsistent multi-line /* */ style. - -/** - * translators: %d: number of cats. - */ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // Bad - docblock style. - - -/* Testing comment content ****************************************************/ - -/* %d: number of cats. */ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // Bad - doesn't start with 'translators: '. - -/* this is for translators: %d: number of cats. */ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // Bad - doesn't *start* with 'translators:' - -/* Translators: %d: number of cats. */ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - Capitalized translators. - -/* TRANSLATORS: %d: number of cats. */ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - All caps translators. - - -/* Testing comment placement ***************************************************/ - -/* translators: %d: number of cats. */ - - -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - only whitespace between. - -/* Some other comment. */ -/* translators: %d: number of cats. */ -_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK. - -// translators: 1: number; 2: string. -// Some other comment. -esc_attr_e( 'Text to translate to %1$d languages. Another %2$s placeholder', 'my-slug' ); // Bad - translators comment has to be the first comment before the function call. - -printf( - /* translators: number of monkeys, location. */ - __( 'There are %1$d monkeys in the %2$s', 'my-slug' ), - (int) $number, - esc_html( $string ) -); // Ok. - -/* translators: number of monkeys, location. */ -printf( - __( 'There are %1$d monkeys in the %2$s', 'my-slug' ), - (int) $number, - esc_html( $string ) -); // Bad - comment not directly before line containing the gettext call. - -/* translators: number of monkeys, location. */ -printf( __( 'There are %1$d monkeys in the %2$s', 'my-slug' ), intval( $number ), esc_html( $string ) ); // Ok - comment is directly before line containing the gettext call. - -/* translators: number of monkeys, location. */ -printf( __( - 'There are %1$d monkeys in the %2$s', 'my-slug' ), - intval( $number ), - esc_html( $string ) -); // Ok - comment is directly before line containing the gettext call. - -// Issue 776 - regex issue. -__( 'foo 100% bar', 'my-slug' ); // Ok, not a placeholder. - -// Issue #830. -_e(); // Bad. +__( 'string' ); // Bad - no text domain passed. +__( 'string', 'something' ); // Bad - text domain mismatch. +__( 'string', "my-slug-too" ); // Bad - text domain mismatch. + +__( "hell$varo", 'my-slug' ); // Bad, shouldn't use a string with variables. +__( "hell\$varo", 'my-slug' ); // OK, Variable is not interpolated. +__( "hell\\$varo", 'my-slug' ); // Bad, is interpolated. +__( "hell\\\$varo", 'my-slug' ); // OK, variable is escaped. + +__( $var, 'my-slug' ); // Bad, shouldn't use variables. + +__( 'string', SOMETHING ); // Bad, shouldn't use CONSTANTS. + +__( 'string' . $var, 'my-slug' ); // Bad, shouldn't use variable for string. +__( $var . 'string', 'my-slug' ); // Bad, shouldn't use variable for string. + +__( SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for string. +__( 'string' . SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for string. +__( SOMETHING . 'string', 'my-slug' ); // Bad, shouldn't use variable for string. + +__( 'string', $domain ); // Bad, shouldn't use variable for domain. +__( 'string', 'my' . $domain ); // Bad, shouldn't use variable for domain. +__( 'string', $domain . 'my-slug' ); // Bad, shouldn't use variable for domain. + +__( 'string', 'my-slug' ); // Good. +_x( 'string', 'context', 'my-slug' ); // Good. + +_x( 'string', $var, 'my-slug' ); // Bad, shouldn't use variable for context. +_x( 'string', 'context' . $var, 'my-slug' ); // Bad, shouldn't use variable for context. +_x( 'string', $var . 'context', 'my-slug' ); // Bad, shouldn't use variable for context. + +_x( 'string', SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for context. +_x( 'string', SOMETHING . 'context', 'my-slug' ); // Bad, shouldn't use constant for context. +_x( 'string', 'context' . SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for context. + +_n( 'I have %d cat.', 'I have %d cats.', $number ); // Bad, no text domain. +_n( 'I have %d cat.', 'I have %d cats.', $number, 'my-slug' ); // OK. +_n( 'I have %d cat.', 'I have %d cats.', $number, "illegal $string" ); // Bad. +_n( 'I have %d cat.', 'I have %d cats.', $number, SOMETHING ); // Bad. + +_n_noop( 'I have %d cat.', 'I have %d cats.' ); // Bad, no text domain. +_n_noop( 'I have %d cat.', 'I have %d cats.', 'my-slug' ); // OK. +_n_noop( 'I have %d cat.', 'I have %d cats.', "illegal $string" ); // Bad. +_n_noop( 'I have %d cat.', 'I have %d cats.', SOMETHING ); // Bad. + +_nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.' ); // Bad, no text domain. +_nx( 'I have %d cat.', 'I have %d cats.', $number, $context ); // Bad, no text domain, variable context. +_nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.', 'my-slug' ); // OK. +_nx( 'I have %d cat.', 'I have %d cats.', $number, $context, 'my-slug' ); // Bad. +_nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.', "illegal $string" ); // Bad. +_nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.', SOMETHING ); // Bad. + +_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.' ); // Bad, no text domain. +_nx_noop( 'I have %d cat.', 'I have %d cats.', $context ); // Bad, no text domain, variable context. +_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug' ); // OK. +_nx_noop( 'I have %d cat.', 'I have %d cats.', $context, 'my-slug' ); // Bad. +_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', "illegal $string" ); // Bad. +_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', SOMETHING ); // Bad. + +translate( 'foo', 'my-slug' ); // Bad, low-level API function. +translate_with_gettext_context( 'foo', 'bar', 'my-slug' ); // Bad, low-level API function. + +_( 'foo', 'my-slug' ); // Bad. + +__( 'foo', 'my-slug', 'too-many-args' ); // Bad. +_x( 'string', 'context', 'my-slug', 'too-many-args' ); // Bad. +_n( 'I have %d cat.', 'I have %d cats.', $number, 'my-slug', 'too-many-args' ); // Bad. +_n_noop( 'I have %d cat.', 'I have %d cats.', 'my-slug', 'too-many-args' ); // Bad. +_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug', 'too-many-args' ); // Bad. + +// Make sure that multi-line string literals are accepted. +_nx( 'I have +%d cat.', 'I have +%d cats.', $number, 'Not +really.', 'my-slug' ); // OK. + +// Ensure lack of spaces doesn't cause i18n error. +_n_noop('I have %d cat.', 'I have %d cats.', 'my-slug'); // OK. + +// Dollar sign in literal string is not interpolated, so OK. +_n_noop( 'I have %d cat.', 'I have %d cats literal-string-so-$variable-not-interpolated.', 'my-slug' ); // OK. + +// Multiple placeholders should have orderable placeholders. +__( 'There are %d monkeys in the %s', 'my-slug' ); // Multiple arguments should be numbered. +__( 'There are %1$d monkeys in the %2$s', 'my-slug' ); // OK. +_n( 'There is %d monkey in the %s', 'There are %d monkeys in the %s', $number, 'my-slug' ); // Multiple arguments should be numbered. +_n( 'There is %1$d monkey in the %2$s', 'In the %2$s there are %1$d monkeys', $number, 'my-slug' ); // OK. + +// The singular form should use placeholders if the plural does. +// https://codex.wordpress.org/I18n_for_WordPress_Developers#Plurals +_n( 'I have a cat.', 'I have %d cats.', $number, 'my-slug' ); // Bad, singular should have placeholder. +_n_noop( 'I have a cat.', 'I have %d cats.', 'my-slug' ); // Bad, singular should have placeholder. +_nx( 'I have a cat.', 'I have %d cats.', $number, 'Not really.', 'my-slug' ); // Bad, singular should have placeholder. +_nx_noop( 'I have a cat.', 'I have %d cats.', 'Not really.', 'my-slug' ); // Bad, singular should have placeholder. + +__( '%s', 'my-slug' ); // Bad, don't waste translator's time. +__( '%1$s%2$s', 'my-slug' ); // Bad, don't waste translator's time. +_n( 'I have %d cat.', '%d', $number, 'my-slug' ); // Bad, move the logic out of the translation. +__( '\'%s\'', 'my-slug' ); // OK (ish. this is a technical test, not a great string). + +// Issue #681. +__( 'String with a literal %%', 'my-slug'); // Ok, replacement would be a single %. +__( 'String with a literal %% and a %s placeholder', 'my-slug'); // Ok. +__( 'A replacement variable can not start with "%%cf_" or "%%ct_" as these are reserved for the WPSEO standard variable variables for custom fields and custom taxonomies. Try making your variable name unique.', 'my-slug' ); // Ok. + +// Issue #698 - fix for auto-fixing placeholder ordering. +__( '%s - %s - %s - %s', 'my-slug' ); // Bad - Multiple of the same placeholders. +__( '%d - %d - %d - %d', 'my-slug' ); // Bad - Multiple of the same placeholders. +__( '%% - %b - %c - %d - %e - %E - %f - %F - %g - %G - %o - %s - %u - %x - %X', 'my-slug' ); // Bad - All simple variations. +__( "%d for %d 'item'", 'my-slug' ); // Bad - Multiple of the same placeholders in a double quoted string. +__( '%04d for %02d item', 'my-slug' ); // Bad - Placeholder with other specifier, but no position. +__( "%04d for %'.9d item", 'my-slug' ); // Bad - Placeholder with other specifier, but no position, double quoted string. + +// Related to issue #698 - mixed ordered and non-ordered placeholders. +__( '%1$d for %d item', 'my-slug' ); // Bad. +__( '%1$d for %d and %d item', 'my-slug' ); // Bad. + +// Nowdoc syntax. +__( <<<'EOD' +%1$d for %d item +EOD +, 'my-slug' ); // Bad. + +// Multi-line heredoc syntax. +_nx( <<<'EOD' +I have +%d cat and %d +dog. +EOD +, <<<'EOD' +I have +%d cats and %d +dogs. +EOD +, $number, <<<'EOD' +Not +really. +EOD +, 'my-slug' ); // OK. + +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain my-slug,default +__( "String default text domain.", "my-slug" ); // Ok. +__( "String default text domain.", "default" ); // Ok. +__( "String default text domain.", 'something' ); // Bad, text-domain mismatch. +__( 'String default text domain.' ); // Warning, use default. + +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain default +__( 'String default text domain.', 'my-slug' ); // Bad because text_domain is only 'default'. +__( 'String default text domain.', 'default' ); // Warning, text domain can be omitted. +__( 'String default text domain.', /* Explicitly set for reason. */ 'default' ); // Warning, text domain can be omitted (not auto-fixable). +__( 'String default text domain.' ); // Ok because default domain is 'default' and it matches one of the supplied configured text domains. + +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain false +// @codingStandardsChangeSetting WordPress.WP.I18n check_translator_comments true diff --git a/WordPress/Tests/WP/I18nUnitTest.inc.fixed b/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed similarity index 85% rename from WordPress/Tests/WP/I18nUnitTest.inc.fixed rename to WordPress/Tests/WP/I18nUnitTest.1.inc.fixed index 228b285c..bce01afe 100644 --- a/WordPress/Tests/WP/I18nUnitTest.inc.fixed +++ b/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed @@ -1,7 +1,17 @@ <?php + // @codingStandardsChangeSetting WordPress.WP.I18n check_translator_comments false -__( "hell$varo", 'my-slug' ); // Bad, shouldn't use a string with variables. +__( 'string' ); // OK - no text domain known, so not checked. +__( 'string', 'something' ); // OK - no text domain known, so not checked. + +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain my-slug + +__( 'string' ); // Bad - no text domain passed. +__( 'string', 'something' ); // Bad - text domain mismatch. +__( 'string', "my-slug-too" ); // Bad - text domain mismatch. + +__( "hell$varo", 'my-slug' ); // Bad, shouldn't use a string with variables. __( "hell\$varo", 'my-slug' ); // OK, Variable is not interpolated. __( "hell\\$varo", 'my-slug' ); // Bad, is interpolated. __( "hell\\\$varo", 'my-slug' ); // OK, variable is escaped. @@ -11,35 +21,25 @@ __( $var, 'my-slug' ); // Bad, shouldn't use variables. __( 'string', SOMETHING ); // Bad, shouldn't use CONSTANTS. __( 'string' . $var, 'my-slug' ); // Bad, shouldn't use variable for string. - __( $var . 'string', 'my-slug' ); // Bad, shouldn't use variable for string. __( SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for string. - __( 'string' . SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for string. - __( SOMETHING . 'string', 'my-slug' ); // Bad, shouldn't use variable for string. __( 'string', $domain ); // Bad, shouldn't use variable for domain. - __( 'string', 'my' . $domain ); // Bad, shouldn't use variable for domain. - __( 'string', $domain . 'my-slug' ); // Bad, shouldn't use variable for domain. __( 'string', 'my-slug' ); // Good. - _x( 'string', 'context', 'my-slug' ); // Good. _x( 'string', $var, 'my-slug' ); // Bad, shouldn't use variable for context. - _x( 'string', 'context' . $var, 'my-slug' ); // Bad, shouldn't use variable for context. - _x( 'string', $var . 'context', 'my-slug' ); // Bad, shouldn't use variable for context. _x( 'string', SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for context. - _x( 'string', SOMETHING . 'context', 'my-slug' ); // Bad, shouldn't use constant for context. - _x( 'string', 'context' . SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for context. _n( 'I have %d cat.', 'I have %d cats.', $number ); // Bad, no text domain. @@ -53,7 +53,7 @@ _n_noop( 'I have %d cat.', 'I have %d cats.', "illegal $string" ); // Bad. _n_noop( 'I have %d cat.', 'I have %d cats.', SOMETHING ); // Bad. _nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.' ); // Bad, no text domain. -_nx( 'I have %d cat.', 'I have %d cats.', $number, $context ); // Bad. +_nx( 'I have %d cat.', 'I have %d cats.', $number, $context ); // Bad, no text domain, variable context. _nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.', 'my-slug' ); // OK. _nx( 'I have %d cat.', 'I have %d cats.', $number, $context, 'my-slug' ); // Bad. _nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.', "illegal $string" ); // Bad. @@ -112,10 +112,6 @@ __( 'String with a literal %%', 'my-slug'); // Ok, replacement would be a single __( 'String with a literal %% and a %s placeholder', 'my-slug'); // Ok. __( 'A replacement variable can not start with "%%cf_" or "%%ct_" as these are reserved for the WPSEO standard variable variables for custom fields and custom taxonomies. Try making your variable name unique.', 'my-slug' ); // Ok. -// The domain 'default' was also added to the text domains. -__( 'String default text domain.', 'default' ); // Ok. -__( "String default text domain.", "default" ); // Ok. - // Issue #698 - fix for auto-fixing placeholder ordering. __( '%1$s - %2$s - %3$s - %4$s', 'my-slug' ); // Bad - Multiple of the same placeholders. __( '%1$d - %2$d - %3$d - %4$d', 'my-slug' ); // Bad - Multiple of the same placeholders. @@ -151,5 +147,17 @@ really. EOD , 'my-slug' ); // OK. +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain my-slug,default +__( "String default text domain.", "my-slug" ); // Ok. +__( "String default text domain.", "default" ); // Ok. +__( "String default text domain.", 'something' ); // Bad, text-domain mismatch. +__( 'String default text domain.' ); // Warning, use default. + +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain default +__( 'String default text domain.', 'my-slug' ); // Bad because text_domain is only 'default'. +__( 'String default text domain.' ); // Warning, text domain can be omitted. +__( 'String default text domain.', /* Explicitly set for reason. */ 'default' ); // Warning, text domain can be omitted (not auto-fixable). +__( 'String default text domain.' ); // Ok because default domain is 'default' and it matches one of the supplied configured text domains. +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain false // @codingStandardsChangeSetting WordPress.WP.I18n check_translator_comments true diff --git a/WordPress/Tests/WP/I18nUnitTest.2.inc b/WordPress/Tests/WP/I18nUnitTest.2.inc new file mode 100644 index 00000000..0c6a5a5c --- /dev/null +++ b/WordPress/Tests/WP/I18nUnitTest.2.inc @@ -0,0 +1,106 @@ +<?php +/* + * Test sniffing for translator comments. + */ +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain my-slug + +/* Basic test ****************************************************************/ +__( 'No placeholders here.', 'my-slug' ); // Ok, no placeholders, so no translators comment needed. +__( 'There are %1$d monkeys in the %2$s', 'my-slug' ); // Bad - no translators comment. + +/* Testing different comment styles ******************************************/ + +/* translators: %d: number of cats. */ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - single line /* */ style. + + /* translators: %d: number of cats. */ + _n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - single line /* */ style, indented code. + +// translators: %d: number of cats. +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - single line // style. + +/* translators: + - number of monkeys, + - location. */ +esc_html__( 'There are %1$d monkeys in the %2$s', 'my-slug' ); // OK - multi-line /* */ style. + +/* + * translators: %d: number of cats. + */ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - multi-line /* */ style. + +/* + * translators: %d: number of cats. + * This is a multiline comment, + * But it also has * at the start + of some lines ;-) +*/ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - inconsistent multi-line /* */ style. + +/** + * translators: %d: number of cats. + */ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // Bad - docblock style. + + +/* Testing comment content ****************************************************/ + +/* %d: number of cats. */ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // Bad - doesn't start with 'translators: '. + +/* this is for translators: %d: number of cats. */ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // Bad - doesn't *start* with 'translators:' + +/* Translators: %d: number of cats. */ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - Capitalized translators. + +/* TRANSLATORS: %d: number of cats. */ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - All caps translators. + + +/* Testing comment placement ***************************************************/ + +/* translators: %d: number of cats. */ + + +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK - only whitespace between. + +/* Some other comment. */ +/* translators: %d: number of cats. */ +_n_noop( 'I have %d cat.', "I have %d cats.", 'my-slug' ); // OK. + +// translators: 1: number; 2: string. +// Some other comment. +esc_attr_e( 'Text to translate to %1$d languages. Another %2$s placeholder', 'my-slug' ); // Bad - translators comment has to be the first comment before the function call. + +printf( + /* translators: number of monkeys, location. */ + __( 'There are %1$d monkeys in the %2$s', 'my-slug' ), + (int) $number, + esc_html( $string ) +); // Ok. + +/* translators: number of monkeys, location. */ +printf( + __( 'There are %1$d monkeys in the %2$s', 'my-slug' ), + (int) $number, + esc_html( $string ) +); // Bad - comment not directly before line containing the gettext call. + +/* translators: number of monkeys, location. */ +printf( __( 'There are %1$d monkeys in the %2$s', 'my-slug' ), intval( $number ), esc_html( $string ) ); // Ok - comment is directly before line containing the gettext call. + +/* translators: number of monkeys, location. */ +printf( __( + 'There are %1$d monkeys in the %2$s', 'my-slug' ), + intval( $number ), + esc_html( $string ) +); // Ok - comment is directly before line containing the gettext call. + +// Issue 776 - regex issue. +__( 'foo 100% bar', 'my-slug' ); // Ok, not a placeholder. + +// Issue #830. +_e(); // Bad. + +// @codingStandardsChangeSetting WordPress.WP.I18n text_domain false diff --git a/WordPress/Tests/WP/I18nUnitTest.3.inc b/WordPress/Tests/WP/I18nUnitTest.3.inc new file mode 100644 index 00000000..bac1dadc --- /dev/null +++ b/WordPress/Tests/WP/I18nUnitTest.3.inc @@ -0,0 +1,21 @@ +<?php + +/* + * Test that all checks relating to the text domain still work. + * Text domain for this test file is checked as if it were passed + * from the command-line. + */ +__( 'string', 'something' ); // OK. + +__( 'string' ); // Bad, no text domain. +_n( 'I have a cat.', 'I have two cats.', $number ); // Bad, no text domain. + +__( 'string', SOMETHING ); // Bad, shouldn't use CONSTANTS. +__( 'string', $domain ); // Bad, shouldn't use variable for domain. +__( 'string', 'something' . $domain ); // Bad, shouldn't use variable for domain. +__( 'string', $domain . 'something' ); // Bad, shouldn't use variable for domain. +__( 'string', "$domain" ); // Bad, shouldn't use variable for domain. +__( 'string', "something-$domain" ); // Bad, shouldn't use variable for domain. + +__( 'string', 'something-else' ); // Bad, text domain mismatch. +__( 'string', "something-else" ); // Bad, text domain mismatch. diff --git a/WordPress/Tests/WP/I18nUnitTest.inc b/WordPress/Tests/WP/I18nUnitTest.inc deleted file mode 100644 index 79d1d32b..00000000 --- a/WordPress/Tests/WP/I18nUnitTest.inc +++ /dev/null @@ -1,155 +0,0 @@ -<?php -// @codingStandardsChangeSetting WordPress.WP.I18n check_translator_comments false -__( "hell$varo", 'my-slug' ); // Bad, shouldn't use a string with variables. - -__( "hell\$varo", 'my-slug' ); // OK, Variable is not interpolated. -__( "hell\\$varo", 'my-slug' ); // Bad, is interpolated. -__( "hell\\\$varo", 'my-slug' ); // OK, variable is escaped. - -__( $var, 'my-slug' ); // Bad, shouldn't use variables. - -__( 'string', SOMETHING ); // Bad, shouldn't use CONSTANTS. - -__( 'string' . $var, 'my-slug' ); // Bad, shouldn't use variable for string. - -__( $var . 'string', 'my-slug' ); // Bad, shouldn't use variable for string. - -__( SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for string. - -__( 'string' . SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for string. - -__( SOMETHING . 'string', 'my-slug' ); // Bad, shouldn't use variable for string. - -__( 'string', $domain ); // Bad, shouldn't use variable for domain. - -__( 'string', 'my' . $domain ); // Bad, shouldn't use variable for domain. - -__( 'string', $domain . 'my-slug' ); // Bad, shouldn't use variable for domain. - -__( 'string', 'my-slug' ); // Good. - -_x( 'string', 'context', 'my-slug' ); // Good. - -_x( 'string', $var, 'my-slug' ); // Bad, shouldn't use variable for context. - -_x( 'string', 'context' . $var, 'my-slug' ); // Bad, shouldn't use variable for context. - -_x( 'string', $var . 'context', 'my-slug' ); // Bad, shouldn't use variable for context. - -_x( 'string', SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for context. - -_x( 'string', SOMETHING . 'context', 'my-slug' ); // Bad, shouldn't use constant for context. - -_x( 'string', 'context' . SOMETHING, 'my-slug' ); // Bad, shouldn't use constant for context. - -_n( 'I have %d cat.', 'I have %d cats.', $number ); // Bad, no text domain. -_n( 'I have %d cat.', 'I have %d cats.', $number, 'my-slug' ); // OK. -_n( 'I have %d cat.', 'I have %d cats.', $number, "illegal $string" ); // Bad. -_n( 'I have %d cat.', 'I have %d cats.', $number, SOMETHING ); // Bad. - -_n_noop( 'I have %d cat.', 'I have %d cats.' ); // Bad, no text domain. -_n_noop( 'I have %d cat.', 'I have %d cats.', 'my-slug' ); // OK. -_n_noop( 'I have %d cat.', 'I have %d cats.', "illegal $string" ); // Bad. -_n_noop( 'I have %d cat.', 'I have %d cats.', SOMETHING ); // Bad. - -_nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.' ); // Bad, no text domain. -_nx( 'I have %d cat.', 'I have %d cats.', $number, $context ); // Bad. -_nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.', 'my-slug' ); // OK. -_nx( 'I have %d cat.', 'I have %d cats.', $number, $context, 'my-slug' ); // Bad. -_nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.', "illegal $string" ); // Bad. -_nx( 'I have %d cat.', 'I have %d cats.', $number, 'Not really.', SOMETHING ); // Bad. - -_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.' ); // Bad, no text domain. -_nx_noop( 'I have %d cat.', 'I have %d cats.', $context ); // Bad, no text domain, variable context. -_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug' ); // OK. -_nx_noop( 'I have %d cat.', 'I have %d cats.', $context, 'my-slug' ); // Bad. -_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', "illegal $string" ); // Bad. -_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', SOMETHING ); // Bad. - -translate( 'foo', 'my-slug' ); // Bad, low-level API function. -translate_with_gettext_context( 'foo', 'bar', 'my-slug' ); // Bad, low-level API function. - -_( 'foo', 'my-slug' ); // Bad. - -__( 'foo', 'my-slug', 'too-many-args' ); // Bad. -_x( 'string', 'context', 'my-slug', 'too-many-args' ); // Bad. -_n( 'I have %d cat.', 'I have %d cats.', $number, 'my-slug', 'too-many-args' ); // Bad. -_n_noop( 'I have %d cat.', 'I have %d cats.', 'my-slug', 'too-many-args' ); // Bad. -_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug', 'too-many-args' ); // Bad. - -// Make sure that multi-line string literals are accepted. -_nx( 'I have -%d cat.', 'I have -%d cats.', $number, 'Not -really.', 'my-slug' ); // OK. - -// Ensure lack of spaces doesn't cause i18n error. -_n_noop('I have %d cat.', 'I have %d cats.', 'my-slug'); // OK. - -// Dollar sign in literal string is not interpolated, so OK. -_n_noop( 'I have %d cat.', 'I have %d cats literal-string-so-$variable-not-interpolated.', 'my-slug' ); // OK. - -// Multiple placeholders should have orderable placeholders. -__( 'There are %d monkeys in the %s', 'my-slug' ); // Multiple arguments should be numbered. -__( 'There are %1$d monkeys in the %2$s', 'my-slug' ); // OK. -_n( 'There is %d monkey in the %s', 'There are %d monkeys in the %s', $number, 'my-slug' ); // Multiple arguments should be numbered. -_n( 'There is %1$d monkey in the %2$s', 'In the %2$s there are %1$d monkeys', $number, 'my-slug' ); // OK. - -// The singular form should use placeholders if the plural does. -// https://codex.wordpress.org/I18n_for_WordPress_Developers#Plurals -_n( 'I have a cat.', 'I have %d cats.', $number, 'my-slug' ); // Bad, singular should have placeholder. -_n_noop( 'I have a cat.', 'I have %d cats.', 'my-slug' ); // Bad, singular should have placeholder. -_nx( 'I have a cat.', 'I have %d cats.', $number, 'Not really.', 'my-slug' ); // Bad, singular should have placeholder. -_nx_noop( 'I have a cat.', 'I have %d cats.', 'Not really.', 'my-slug' ); // Bad, singular should have placeholder. - -__( '%s', 'my-slug' ); // Bad, don't waste translator's time. -__( '%1$s%2$s', 'my-slug' ); // Bad, don't waste translator's time. -_n( 'I have %d cat.', '%d', $number, 'my-slug' ); // Bad, move the logic out of the translation. -__( '\'%s\'', 'my-slug' ); // OK (ish. this is a technical test, not a great string). - -// Issue #681. -__( 'String with a literal %%', 'my-slug'); // Ok, replacement would be a single %. -__( 'String with a literal %% and a %s placeholder', 'my-slug'); // Ok. -__( 'A replacement variable can not start with "%%cf_" or "%%ct_" as these are reserved for the WPSEO standard variable variables for custom fields and custom taxonomies. Try making your variable name unique.', 'my-slug' ); // Ok. - -// The domain 'default' was also added to the text domains. -__( 'String default text domain.', 'default' ); // Ok. -__( "String default text domain.", "default" ); // Ok. - -// Issue #698 - fix for auto-fixing placeholder ordering. -__( '%s - %s - %s - %s', 'my-slug' ); // Bad - Multiple of the same placeholders. -__( '%d - %d - %d - %d', 'my-slug' ); // Bad - Multiple of the same placeholders. -__( '%% - %b - %c - %d - %e - %E - %f - %F - %g - %G - %o - %s - %u - %x - %X', 'my-slug' ); // Bad - All simple variations. -__( "%d for %d 'item'", 'my-slug' ); // Bad - Multiple of the same placeholders in a double quoted string. -__( '%04d for %02d item', 'my-slug' ); // Bad - Placeholder with other specifier, but no position. -__( "%04d for %'.9d item", 'my-slug' ); // Bad - Placeholder with other specifier, but no position, double quoted string. - -// Related to issue #698 - mixed ordered and non-ordered placeholders. -__( '%1$d for %d item', 'my-slug' ); // Bad. -__( '%1$d for %d and %d item', 'my-slug' ); // Bad. - -// Nowdoc syntax. -__( <<<'EOD' -%1$d for %d item -EOD -, 'my-slug' ); // Bad. - -// Multi-line heredoc syntax. -_nx( <<<'EOD' -I have -%d cat and %d -dog. -EOD -, <<<'EOD' -I have -%d cats and %d -dogs. -EOD -, $number, <<<'EOD' -Not -really. -EOD -, 'my-slug' ); // OK. - - -// @codingStandardsChangeSetting WordPress.WP.I18n check_translator_comments true diff --git a/WordPress/Tests/WP/I18nUnitTest.php b/WordPress/Tests/WP/I18nUnitTest.php index a2864005..4eff21a0 100644 --- a/WordPress/Tests/WP/I18nUnitTest.php +++ b/WordPress/Tests/WP/I18nUnitTest.php @@ -7,20 +7,54 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use WordPress\PHPCSHelper; + /** * Unit test class for the I18n sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.10.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WP_I18nUnitTest extends AbstractSniffUnitTest { +class I18nUnitTest extends AbstractSniffUnitTest { /** - * Fill in the $text_domain property to test domain check functionality. + * Get a list of CLI values to set before the file is tested. + * + * Used by PHPCS 2.x. + * + * @param string $testFile The name of the file being tested. + * + * @return array */ - protected function setUp() { - parent::setUp(); - PHP_CodeSniffer::setConfigData( 'text_domain', 'my-slug,default', true ); + public function getCliValues( $testFile ) { + // Test overruling the text domain from the command line for one test file. + if ( 'I18nUnitTest.3.inc' === $testFile ) { + PHPCSHelper::set_config_data( 'text_domain', 'something', true ); + } + + return array(); + } + + /** + * Set CLI values before the file is tested. + * + * Used by PHPCS 3.x. + * + * @param string $testFile The name of the file being tested. + * @param \PHP_CodeSniffer\Config $config The config data for the test run. + * + * @return void + */ + public function setCliValues( $testFile, $config ) { + // Test overruling the text domain from the command line for one test file. + if ( 'I18nUnitTest.3.inc' === $testFile ) { + $config->setConfigData( 'text_domain', 'something', true ); + } } /** @@ -29,53 +63,56 @@ protected function setUp() { * @param string $testFile The name of the file being tested. * @return array <int line number> => <int number of errors> */ - public function getErrorList( $testFile = 'I18nUnitTest.inc' ) { + public function getErrorList( $testFile = '' ) { switch ( $testFile ) { - case 'I18nUnitTest.inc': + case 'I18nUnitTest.1.inc': return array( - 3 => 1, - 6 => 1, - 9 => 1, - 11 => 1, - 13 => 1, - 15 => 1, - 17 => 1, - 19 => 1, - 21 => 1, - 23 => 1, - 25 => 1, - 27 => 1, - 33 => 1, - 35 => 1, - 37 => 1, - 39 => 1, - 41 => 1, - 43 => 1, - 45 => 1, - 47 => 1, - 48 => 1, - 50 => 1, - 52 => 1, - 53 => 1, - 55 => 1, - 56 => 2, - 58 => 1, - 59 => 1, - 60 => 1, - 62 => 1, - 63 => 2, - 65 => 1, - 66 => 1, - 67 => 1, - 72 => 1, - 74 => 1, - 75 => 1, - 76 => 1, - 77 => 1, - 78 => 1, - 93 => 1, - 95 => 2, + 10 => 1, + 11 => 1, + 12 => 1, + 14 => 1, + 16 => 1, + 19 => 1, + 21 => 1, + 23 => 1, + 24 => 1, + 26 => 1, + 27 => 1, + 28 => 1, + 30 => 1, + 31 => 1, + 32 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 41 => 1, + 42 => 1, + 43 => 1, + 45 => 1, + 47 => 1, + 48 => 1, + 50 => 1, + 52 => 1, + 53 => 1, + 55 => 1, + 56 => 2, + 58 => 1, + 59 => 1, + 60 => 1, + 62 => 1, + 63 => 2, + 65 => 1, + 66 => 1, + 67 => 1, + 72 => 1, + 74 => 1, + 75 => 1, + 76 => 1, + 77 => 1, + 78 => 1, + 93 => 1, + 95 => 2, 100 => 1, 101 => 1, 102 => 1, @@ -83,25 +120,41 @@ public function getErrorList( $testFile = 'I18nUnitTest.inc' ) { 105 => 1, 106 => 1, 107 => 1, + 116 => 1, + 117 => 1, + 118 => 1, + 119 => 1, 120 => 1, 121 => 1, - 122 => 1, - 123 => 1, 124 => 1, 125 => 1, 128 => 1, - 129 => 1, - 132 => ( PHP_VERSION_ID >= 50300 ) ? 1 : 2, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. - 138 => 1, - 143 => 1, - 148 => 1, + 134 => 1, + 139 => 1, + 144 => 1, + 153 => 1, + 157 => 1, ); - case 'I18nUnitTest.1.inc': + case 'I18nUnitTest.2.inc': return array( 104 => 2, ); + case 'I18nUnitTest.3.inc': + return array( + 10 => 1, + 11 => 1, + 13 => 1, + 14 => 1, + 15 => 1, + 16 => 1, + 17 => 1, + 18 => 1, + 20 => 1, + 21 => 1, + ); + default: return array(); @@ -115,21 +168,24 @@ public function getErrorList( $testFile = 'I18nUnitTest.inc' ) { * @param string $testFile The name of the file being tested. * @return array <int line number> => <int number of warnings> */ - public function getWarningList( $testFile = 'I18nUnitTest.inc' ) { + public function getWarningList( $testFile = '' ) { switch ( $testFile ) { - case 'I18nUnitTest.inc': + case 'I18nUnitTest.1.inc': return array( - 69 => 1, - 70 => 1, + 69 => 1, + 70 => 1, 100 => 1, 101 => 1, 102 => 1, 103 => 1, + 154 => 1, + 158 => 1, + 159 => 1, ); - case 'I18nUnitTest.1.inc': + case 'I18nUnitTest.2.inc': return array( - 8 => 1, + 9 => 1, 43 => 1, 49 => 1, 52 => 1, diff --git a/WordPress/Tests/WP/PreparedSQLUnitTest.inc b/WordPress/Tests/WP/PreparedSQLUnitTest.inc index 6da97cf7..86ae5bd2 100644 --- a/WordPress/Tests/WP/PreparedSQLUnitTest.inc +++ b/WordPress/Tests/WP/PreparedSQLUnitTest.inc @@ -81,3 +81,30 @@ ND , $wpdb->postmeta, implode( ',', array_fill( 0, count( $post_ids ), '%d' ) ) ), $post_ids ) ); // OK. + +wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Bad. + +$wpdb->query( // Some arbitrary comment. + "SELECT * + FROM $wpdb->posts + WHERE post_title LIKE '" . $escaped_var . "';" +); // Bad x 1. + +$wpdb->query( + "SELECT * + FROM $wpdb->posts + WHERE post_title LIKE '" . $escaped_var . "';" +); // WPCS: unprepared SQL OK. + +$wpdb->query( // WPCS: unprepared SQL OK. + "SELECT * + FROM $wpdb->posts + WHERE post_title LIKE '" . $escaped_var . "';" +); + +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . (int) $foo . ";" ); // Ok. + +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE value = " . (float) $foo . ";" ); // Ok. + +// Don't throw an error during live coding. +wpdb::prepare( "SELECT * FROM $wpdb->posts diff --git a/WordPress/Tests/WP/PreparedSQLUnitTest.php b/WordPress/Tests/WP/PreparedSQLUnitTest.php index a9a26e02..d6b75eda 100644 --- a/WordPress/Tests/WP/PreparedSQLUnitTest.php +++ b/WordPress/Tests/WP/PreparedSQLUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WP; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the PreparedSQL sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.8.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WP_PreparedSQLUnitTest extends AbstractSniffUnitTest { +class PreparedSQLUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -23,12 +29,12 @@ class WordPress_Tests_WP_PreparedSQLUnitTest extends AbstractSniffUnitTest { * @return array <int line number> => <int number of errors> */ public function getErrorList() { - $errors = array( - 3 => 1, - 4 => 1, - 5 => 1, - 7 => 1, - 8 => 1, + return array( + 3 => 1, + 4 => 1, + 5 => 1, + 7 => 1, + 8 => 1, 16 => 1, 17 => 1, 18 => 1, @@ -37,24 +43,9 @@ public function getErrorList() { 54 => 1, 64 => 1, 71 => 1, + 85 => 1, + 90 => 1, ); - - // Deal with PHP 5.2 not recognizing quoted heredoc openers, nor nowdoc syntax. - // These are all false positives! - if ( PHP_VERSION_ID < 50300 ) { - $errors[68] = 2; - $errors[69] = 2; - $errors[70] = 2; - $errors[71] = 4; - $errors[75] = 2; - $errors[76] = 7; - $errors[77] = 4; - $errors[78] = 5; - $errors[79] = 7; - $errors[80] = 1; - } - - return $errors; } /** diff --git a/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc b/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc new file mode 100644 index 00000000..0f3f7140 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc @@ -0,0 +1,161 @@ +<?php + +/* + * Make sure the sniff does not act on structures it shouldn't act on. + * All parentheses have extra spacing around it to test this properly. + */ +$b = functioncall( $something ) ; +$b = function( $something ) {}; +$c = myFunction( $arg1 , $arg2 = array( ) ); + +function something( $param ) {} + +$d = new MyClass( ); +$e = new class( ) {}; + +try { +} catch( Exception ) { +} + +include( PATH . 'file.php' ); + +if ( in_array( $arg1, array( 'foo','bar' ) ) ) {} +isset( $abc ); +unset( $abc ); +empty( $abc ); +eval( $abc ); +exit( $abc ); +clone( $_date1 <= $_date2 ? $_date1 : $_date2 ); +declare( ticks=1 ); +list( $post_mime_types, $avail_post_mime_types ) = wp_edit_attachments_query( $q ); +throw( $e ); +yield from ( function(){} ); + +$obj->{$var}( $foo,$bar ); + +(function( $a, $b ) { + return function( $c, $d ) use ( $a, $b ) { + echo $a, $b, $c, $d; + }; +})( 'a','b' )( 'c','d' ); + +$closure( $foo,$bar ); +$var = $closure() + $closure( $foo,$bar ) + self::$closure( $foo,$bar ); + +class Test { + public static function baz( $foo, $bar ) { + $a = new self( $foo,$bar ); + $b = new static( $foo,$bar ); + } +} + +/* + * Test warning for empty parentheses. + */ +$a = 4 + (); // Warning. +$a = 4 + ( ); // Warning. +$a = 4 + (/* Not empty */); + +/* + * Test the actual sniff. + */ +if ((null !== $extra) && ($row->extra !== $extra)) {} + +if (( null !== $extra ) && ( $row->extra !== $extra )) {} // Bad x 4. + +if (( null !== $extra // Bad x 1. + && is_int($extra)) + && ( $row->extra !== $extra // Bad x 1. + || $something ) // Bad x 1. +) {} + +if (( null !== $extra ) // Bad x 2. + && ( $row->extra !== $extra ) // Bad x 2. +) {} + +$a = (null !== $extra); +$a = ( null !== $extra ); // Bad x 2. + +$sx = $vert ? ($w - 1) : 0; + +$this->is_overloaded = ( ( ini_get("mbstring.func_overload") & 2 ) != 0 ) && function_exists('mb_substr'); // Bad x 4. + +$image->flip( ($operation->axis & 1) != 0, ($operation->axis & 2) != 0 ); + +if ( $success && ('nothumb' == $target || 'all' == $target) ) {} + +$directory = ('/' == $file[ strlen($file)-1 ]); + +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing spacingInside 1 +if ((null !== $extra) && ($row->extra !== $extra)) {} // Bad x 4. + +if (( null !== $extra ) && ( $row->extra !== $extra )) {} + +if (( null !== $extra // Bad x 1. + && is_int($extra)) // Bad x 1. + && ( $row->extra !== $extra + || $something ) // Bad x 1. +) {} + +if ((null !== $extra) // Bad x 2. + && ($row->extra !== $extra) // Bad x 2. +) {} + +$a = (null !== $extra); // Bad x 2. +$a = ( null !== $extra ); + +$sx = $vert ? ($w - 1) : 0; // Bad x 2. + +$this->is_overloaded = ((ini_get("mbstring.func_overload") & 2) != 0) && function_exists('mb_substr'); // Bad x 4. + +$image->flip( ($operation->axis & 1) != 0, ($operation->axis & 2) != 0 ); // Bad x 4. + +if ( $success && ('nothumb' == $target || 'all' == $target) ) {} // Bad x 2. + +$directory = ('/' == $file[ strlen($file)-1 ]); // Bad x 2. + +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing spacingInside 0 + +/* + * Test handling of ignoreNewlines. + */ +if ( + ( + null !== $extra + ) && ( + $row->extra !== $extra + ) +) {} // Bad x 4, 1 x line 123, 2 x line 125, 1 x line 127. + + +$a = ( + null !== $extra +); // Bad x 2, 1 x line 131, 1 x line 133. + +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing spacingInside 1 +if ( + ( + null !== $extra + ) && ( + $row->extra !== $extra + ) +) {} // Bad x 4, 1 x line 137, 2 x line 139, 1 x line 141. + +$a = ( + null !== $extra +); // Bad x 2, 1 x line 144, 1 x line 146. +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing spacingInside 0 + +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing ignoreNewlines true +if ( + ( + null !== $extra + ) && ( + $row->extra !== $extra + ) +) {} + +$a = ( + null !== $extra +); +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing ignoreNewlines false diff --git a/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc.fixed b/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc.fixed new file mode 100644 index 00000000..64c9edd4 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc.fixed @@ -0,0 +1,149 @@ +<?php + +/* + * Make sure the sniff does not act on structures it shouldn't act on. + * All parentheses have extra spacing around it to test this properly. + */ +$b = functioncall( $something ) ; +$b = function( $something ) {}; +$c = myFunction( $arg1 , $arg2 = array( ) ); + +function something( $param ) {} + +$d = new MyClass( ); +$e = new class( ) {}; + +try { +} catch( Exception ) { +} + +include( PATH . 'file.php' ); + +if ( in_array( $arg1, array( 'foo','bar' ) ) ) {} +isset( $abc ); +unset( $abc ); +empty( $abc ); +eval( $abc ); +exit( $abc ); +clone( $_date1 <= $_date2 ? $_date1 : $_date2 ); +declare( ticks=1 ); +list( $post_mime_types, $avail_post_mime_types ) = wp_edit_attachments_query( $q ); +throw( $e ); +yield from ( function(){} ); + +$obj->{$var}( $foo,$bar ); + +(function( $a, $b ) { + return function( $c, $d ) use ( $a, $b ) { + echo $a, $b, $c, $d; + }; +})( 'a','b' )( 'c','d' ); + +$closure( $foo,$bar ); +$var = $closure() + $closure( $foo,$bar ) + self::$closure( $foo,$bar ); + +class Test { + public static function baz( $foo, $bar ) { + $a = new self( $foo,$bar ); + $b = new static( $foo,$bar ); + } +} + +/* + * Test warning for empty parentheses. + */ +$a = 4 + (); // Warning. +$a = 4 + ( ); // Warning. +$a = 4 + (/* Not empty */); + +/* + * Test the actual sniff. + */ +if ((null !== $extra) && ($row->extra !== $extra)) {} + +if ((null !== $extra) && ($row->extra !== $extra)) {} // Bad x 4. + +if ((null !== $extra // Bad x 1. + && is_int($extra)) + && ($row->extra !== $extra // Bad x 1. + || $something) // Bad x 1. +) {} + +if ((null !== $extra) // Bad x 2. + && ($row->extra !== $extra) // Bad x 2. +) {} + +$a = (null !== $extra); +$a = (null !== $extra); // Bad x 2. + +$sx = $vert ? ($w - 1) : 0; + +$this->is_overloaded = ((ini_get("mbstring.func_overload") & 2) != 0) && function_exists('mb_substr'); // Bad x 4. + +$image->flip( ($operation->axis & 1) != 0, ($operation->axis & 2) != 0 ); + +if ( $success && ('nothumb' == $target || 'all' == $target) ) {} + +$directory = ('/' == $file[ strlen($file)-1 ]); + +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing spacingInside 1 +if (( null !== $extra ) && ( $row->extra !== $extra )) {} // Bad x 4. + +if (( null !== $extra ) && ( $row->extra !== $extra )) {} + +if (( null !== $extra // Bad x 1. + && is_int($extra) ) // Bad x 1. + && ( $row->extra !== $extra + || $something ) // Bad x 1. +) {} + +if (( null !== $extra ) // Bad x 2. + && ( $row->extra !== $extra ) // Bad x 2. +) {} + +$a = ( null !== $extra ); // Bad x 2. +$a = ( null !== $extra ); + +$sx = $vert ? ( $w - 1 ) : 0; // Bad x 2. + +$this->is_overloaded = ( ( ini_get("mbstring.func_overload") & 2 ) != 0 ) && function_exists('mb_substr'); // Bad x 4. + +$image->flip( ( $operation->axis & 1 ) != 0, ( $operation->axis & 2 ) != 0 ); // Bad x 4. + +if ( $success && ( 'nothumb' == $target || 'all' == $target ) ) {} // Bad x 2. + +$directory = ( '/' == $file[ strlen($file)-1 ] ); // Bad x 2. + +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing spacingInside 0 + +/* + * Test handling of ignoreNewlines. + */ +if ( + (null !== $extra) && ($row->extra !== $extra) +) {} // Bad x 4, 1 x line 123, 2 x line 125, 1 x line 127. + + +$a = (null !== $extra); // Bad x 2, 1 x line 131, 1 x line 133. + +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing spacingInside 1 +if ( + ( null !== $extra ) && ( $row->extra !== $extra ) +) {} // Bad x 4, 1 x line 137, 2 x line 139, 1 x line 141. + +$a = ( null !== $extra ); // Bad x 2, 1 x line 144, 1 x line 146. +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing spacingInside 0 + +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing ignoreNewlines true +if ( + ( + null !== $extra + ) && ( + $row->extra !== $extra + ) +) {} + +$a = ( + null !== $extra +); +// @codingStandardsChangeSetting WordPress.WhiteSpace.ArbitraryParenthesesSpacing ignoreNewlines false diff --git a/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.php new file mode 100644 index 00000000..4219f293 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\WhiteSpace; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the ArbitraryParenthesesSpacing sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class ArbitraryParenthesesSpacingUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 64 => 4, + 66 => 1, + 68 => 1, + 69 => 1, + 72 => 2, + 73 => 2, + 77 => 2, + 81 => 4, + 90 => 4, + 94 => 1, + 95 => 1, + 97 => 1, + 100 => 2, + 101 => 2, + 104 => 2, + 107 => 2, + 109 => 4, + 111 => 4, + 113 => 2, + 115 => 2, + 123 => 1, + 125 => 2, + 127 => 1, + 131 => 1, + 133 => 1, + 137 => 1, + 139 => 2, + 141 => 1, + 144 => 1, + 146 => 1, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array( + 55 => 1, + 56 => 1, + ); + + } + +} // End class. diff --git a/WordPress/Tests/WhiteSpace/CastStructureSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/CastStructureSpacingUnitTest.php index 0f3e9e78..b3b95994 100644 --- a/WordPress/Tests/WhiteSpace/CastStructureSpacingUnitTest.php +++ b/WordPress/Tests/WhiteSpace/CastStructureSpacingUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WhiteSpace; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the CastStructureSpacing sniff. * * @package WPCS\WordPressCodingStandards + * * @since 0.3.0 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WhiteSpace_CastStructureSpacingUnitTest extends AbstractSniffUnitTest { +class CastStructureSpacingUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,13 +28,13 @@ class WordPress_Tests_WhiteSpace_CastStructureSpacingUnitTest extends AbstractSn */ public function getErrorList() { return array( - 3 => 2, - 6 => 2, - 9 => 2, - 12 => 2, - 15 => 2, - 18 => 2, - 21 => 2, + 3 => 2, + 6 => 2, + 9 => 2, + 12 => 2, + 15 => 2, + 18 => 2, + 21 => 2, ); } diff --git a/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc b/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc index dbd0d4e4..081409f4 100644 --- a/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc +++ b/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc @@ -46,14 +46,14 @@ if ( false ) : else : endif; // @codingStandardsChangeSetting WordPress.WhiteSpace.ControlStructureSpacing spaces_before_closure_open_paren 1 -function($arg){} // Bad. -function ( $arg ) { +$a = function($arg){}; // Bad. +$a = function ( $arg ) { // Ok. -} +}; -function () { +$a = function () { // Ok. -} +}; function something($arg){} // Bad. function foo( $arg ) { @@ -69,17 +69,17 @@ function and_another() {} // Bad, space before function name prohibited. function bar() {} // Bad. function baz() {} // Bad. -function test() +function testA() {} // Bad. function &return_by_ref() {} // Ok. // @codingStandardsChangeSetting WordPress.WhiteSpace.ControlStructureSpacing spaces_before_closure_open_paren 0 -function() {} // Ok. -function( $arg ) {} // Ok. -function($arg){} // Bad. -function () {} // Bad. +$a = function() {}; // Ok. +$a = function( $arg ) {}; // Ok. +$a = function($arg){}; // Bad. +$a = function () {}; // Bad. $closureWithArgsAndVars = function( $arg1, $arg2 ) use ( $var1, $var2 ) {}; // Ok. $closureWithArgsAndVars = function ( $arg1, $arg2 ) use ( $var1, $var2 ) {}; // Bad. @@ -102,8 +102,8 @@ use Foo\Admin; // @codingStandardsChangeSetting WordPress.WhiteSpace.ControlStructureSpacing spaces_before_closure_open_paren -1 -function( $arg ) {} // Ok. -function ( $arg ) {} // Ok. +$a = function( $arg ) {}; // Ok. +$a = function ( $arg ) {}; // Ok. $closureWithArgsAndVars = function( $arg1, $arg2 ) use ( $var1, $var2 ) {}; // Ok. $closureWithArgsAndVars = function ( $arg1, $arg2 ) use ( $var1, $var2 ) {}; // Ok. @@ -167,7 +167,7 @@ if ( $foo ) { /** * Comment */ - class bar() { + class bar { }//end class @@ -177,7 +177,7 @@ if ( $foo ) { // Check for too many spaces as long as the next non-blank token is on the same line. function test( $blah ) {} // Bad. -function( $bar ) {} // Bad. +$a = function( $bar ) {}; // Bad. if ( 'abc' === $test ) { // Bad. echo 'hi'; @@ -193,7 +193,7 @@ while ( $blah ) { // Bad. echo 'bye bye'; } -for ( $i = 0; $i < 1; $++ ) { // Bad. +for ( $i = 0; $i < 1; $i++ ) { // Bad. echo 'hi'; } @@ -211,3 +211,60 @@ if ( ) { // Ok. echo 'bye'; } + +// Bug #976 - the case of the disappearing comment. +if ( isset( $submenu_file ) ) { + if ( $submenu_file == $sub_item[2] ) { + $class[] = 'current'; + } +// If plugin_page is set the parent must either match the current page or not physically exist. +// This allows plugin pages with the same hook to exist under different parents. +} else { + $class[] = 'current'; +} + +// Test finding & fixing blank line after control structure. +if ( $one ) { +} +elseif ( $two ) { +} +// else if something +else if ( $three ) { +} // else do something +else { +} + +do { +} +// Comment +while ( $a === $b ); + +if ( $foo ) { + try { + // Something + } catch ( Exception $e ) { + // Something + } + + +} + +if ( $foo ) { + try { + // Something + } catch ( Exception $e ) { + // Something + }//end try/catch <- Bad: "blank line after". + + +} + +if ( $foo ) { + try { // Bad. + // Something + } catch ( Exception $e ) { + // Something + } // End try/catch <- Bad: "blank line after". + + +} diff --git a/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed b/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed index 62753b57..7abd37f7 100644 --- a/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed +++ b/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed @@ -44,14 +44,14 @@ if ( false ) : else : endif; // @codingStandardsChangeSetting WordPress.WhiteSpace.ControlStructureSpacing spaces_before_closure_open_paren 1 -function ( $arg ) {} // Bad. -function ( $arg ) { +$a = function ( $arg ) {}; // Bad. +$a = function ( $arg ) { // Ok. -} +}; -function () { +$a = function () { // Ok. -} +}; function something( $arg ) {} // Bad. function foo( $arg ) { @@ -66,16 +66,16 @@ function another() {} // Bad, space before open parenthesis prohibited. function and_another() {} // Bad, space before function name prohibited. function bar() {} // Bad. function baz() {} // Bad. -function test() {} // Bad. +function testA() {} // Bad. function &return_by_ref() {} // Ok. // @codingStandardsChangeSetting WordPress.WhiteSpace.ControlStructureSpacing spaces_before_closure_open_paren 0 -function() {} // Ok. -function( $arg ) {} // Ok. -function( $arg ) {} // Bad. -function() {} // Bad. +$a = function() {}; // Ok. +$a = function( $arg ) {}; // Ok. +$a = function( $arg ) {}; // Bad. +$a = function() {}; // Bad. $closureWithArgsAndVars = function( $arg1, $arg2 ) use ( $var1, $var2 ) {}; // Ok. $closureWithArgsAndVars = function( $arg1, $arg2 ) use ( $var1, $var2 ) {}; // Bad. @@ -98,8 +98,8 @@ use Foo\Admin; // @codingStandardsChangeSetting WordPress.WhiteSpace.ControlStructureSpacing spaces_before_closure_open_paren -1 -function( $arg ) {} // Ok. -function ( $arg ) {} // Ok. +$a = function( $arg ) {}; // Ok. +$a = function ( $arg ) {}; // Ok. $closureWithArgsAndVars = function( $arg1, $arg2 ) use ( $var1, $var2 ) {}; // Ok. $closureWithArgsAndVars = function ( $arg1, $arg2 ) use ( $var1, $var2 ) {}; // Ok. @@ -162,7 +162,7 @@ if ( $foo ) { /** * Comment */ - class bar() { + class bar { }//end class @@ -172,7 +172,7 @@ if ( $foo ) { // Check for too many spaces as long as the next non-blank token is on the same line. function test( $blah ) {} // Bad. -function( $bar ) {} // Bad. +$a = function( $bar ) {}; // Bad. if ( 'abc' === $test ) { // Bad. echo 'hi'; @@ -188,7 +188,7 @@ while ( $blah ) { // Bad. echo 'bye bye'; } -for ( $i = 0; $i < 1; $++ ) { // Bad. +for ( $i = 0; $i < 1; $i++ ) { // Bad. echo 'hi'; } @@ -206,3 +206,54 @@ if ( ) { // Ok. echo 'bye'; } + +// Bug #976 - the case of the disappearing comment. +if ( isset( $submenu_file ) ) { + if ( $submenu_file == $sub_item[2] ) { + $class[] = 'current'; + } +// If plugin_page is set the parent must either match the current page or not physically exist. +// This allows plugin pages with the same hook to exist under different parents. +} else { + $class[] = 'current'; +} + +// Test finding & fixing blank line after control structure. +if ( $one ) { +} +elseif ( $two ) { +} +// else if something +else if ( $three ) { +} // else do something +else { +} + +do { +} +// Comment +while ( $a === $b ); + +if ( $foo ) { + try { + // Something + } catch ( Exception $e ) { + // Something + } +} + +if ( $foo ) { + try { + // Something + } catch ( Exception $e ) { + // Something + }//end try/catch <- Bad: "blank line after". +} + +if ( $foo ) { + try { // Bad. + // Something + } catch ( Exception $e ) { + // Something + } // End try/catch <- Bad: "blank line after". +} diff --git a/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php index d4a5230b..a5b9537f 100644 --- a/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php +++ b/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php @@ -7,24 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WhiteSpace; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the ControlStructureSpacing sniff. * * @package WPCS\WordPressCodingStandards + * * @since 2013-06-11 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WhiteSpace_ControlStructureSpacingUnitTest extends AbstractSniffUnitTest { - - /** - * Skip this test on PHP 5.2. - * - * @since 0.9.0 - * - * @return bool Whether to skip this test. - */ - protected function shouldSkipTest() { - return ( PHP_VERSION_ID < 50300 ); - } +class ControlStructureSpacingUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -33,28 +28,28 @@ protected function shouldSkipTest() { */ public function getErrorList() { $ret = array( - 4 => 2, - 17 => 2, - 29 => 5, - 37 => 1, - 41 => 1, - 42 => 1, - 49 => 5, - 58 => 3, - 67 => 1, - 68 => 1, - 69 => 1, - 71 => 1, - 72 => 1, - 81 => 3, - 82 => 1, - 85 => 1, - 91 => 2, - 92 => 1, - 94 => 1, - 95 => 1, - 97 => 1, - 98 => 1, + 4 => 2, + 17 => 2, + 29 => 5, + 37 => 1, + 41 => 1, + 42 => 1, + 49 => 5, + 58 => 3, + 67 => 1, + 68 => 1, + 69 => 1, + 71 => 1, + 72 => 1, + 81 => 3, + 82 => 1, + 85 => 1, + 91 => 2, + 92 => 1, + 94 => 1, + 95 => 1, + 97 => 1, + 98 => 1, 135 => 2, 137 => 5, 144 => 1, @@ -67,6 +62,9 @@ public function getErrorList() { 192 => 1, 196 => 2, 200 => 2, + 247 => 1, + 257 => 1, + 267 => 1, ); /* diff --git a/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.inc b/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.inc new file mode 100644 index 00000000..5d7178c1 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.inc @@ -0,0 +1,39 @@ +<?php + +/** + * @param int $var Description. + * @param string $string Another description. + */ + + $expected = ( $column - 1 ); + $found = ( $this->tokens[ $closer ]['column'] - 1 ); + $error = 'Array closer not aligned correctly; expected %s space(s) but found %s'; + $data = array( + $expected_value => 'data', + $found => 'more_data', + ); + +/** + * @param int $var Description - Bad: alignment using tabs. + * @param string $string Another description. + */ + + $expected = ( $column - 1 ); + $found = ( $this->tokens[ $closer ]['column'] - 1 ); // Bad. + $error = 'Array closer not aligned correctly; expected %s space(s) but found %s'; // Bad. + $data = array( // Bad. + $expected_value => 'data', + $found => 'more_data', // Bad. + ); + +/* + * Test that the tab replacements do not negatively influence the existing mid-line alignments. + */ +$a = true; +$aa = true; +$aaa = true; +$aaaa = true; +$aaaaa = true; +$aaaaaa = true; +$aaaaaaa = true; +$aaaaaaaa = true; diff --git a/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.inc.fixed b/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.inc.fixed new file mode 100644 index 00000000..20f2877f --- /dev/null +++ b/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.inc.fixed @@ -0,0 +1,39 @@ +<?php + +/** + * @param int $var Description. + * @param string $string Another description. + */ + + $expected = ( $column - 1 ); + $found = ( $this->tokens[ $closer ]['column'] - 1 ); + $error = 'Array closer not aligned correctly; expected %s space(s) but found %s'; + $data = array( + $expected_value => 'data', + $found => 'more_data', + ); + +/** + * @param int $var Description - Bad: alignment using tabs. + * @param string $string Another description. + */ + + $expected = ( $column - 1 ); + $found = ( $this->tokens[ $closer ]['column'] - 1 ); // Bad. + $error = 'Array closer not aligned correctly; expected %s space(s) but found %s'; // Bad. + $data = array( // Bad. + $expected_value => 'data', + $found => 'more_data', // Bad. + ); + +/* + * Test that the tab replacements do not negatively influence the existing mid-line alignments. + */ +$a = true; +$aa = true; +$aaa = true; +$aaaa = true; +$aaaaa = true; +$aaaaaa = true; +$aaaaaaa = true; +$aaaaaaaa = true; diff --git a/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php b/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php new file mode 100644 index 00000000..8761423c --- /dev/null +++ b/WordPress/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\WhiteSpace; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the DisallowInlineTabs sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.12.0 + * @since 0.13.0 Class name changed: this class is now namespaced. + */ +class DisallowInlineTabsUnitTest extends AbstractSniffUnitTest { + + /** + * The tab width to use during testing. + * + * @var int + */ + private $tab_width = 4; + + /** + * Get a list of CLI values to set before the file is tested. + * + * Used by PHPCS 2.x. + * + * @param string $testFile The name of the file being tested. + * + * @return array + */ + public function getCliValues( $testFile ) { + return array( '--tab-width=' . $this->tab_width ); + } + + /** + * Set CLI values before the file is tested. + * + * Used by PHPCS 3.x. + * + * @param string $testFile The name of the file being tested. + * @param \PHP_CodeSniffer\Config $config The config data for the test run. + * + * @return void + */ + public function setCliValues( $testFile, $config ) { + $config->tabWidth = $this->tab_width; + } + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 17 => 1, + 22 => 1, + 23 => 1, + 24 => 1, + 26 => 1, + 32 => 1, + 33 => 1, + 34 => 1, + 35 => 1, + 36 => 1, + 37 => 1, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array(); + + } + +} // End class. diff --git a/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.inc b/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.inc index fc9dab41..bad89cc0 100644 --- a/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.inc +++ b/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.inc @@ -1,55 +1,66 @@ <?php -$posts = get_posts( 'cat=3' ); -// Bad, no operator spacing. -for ( $i=0; $i<sizeof( $posts ); $i++ ) { - // ... -} - -// Good. -for ( $i = 0; $i < sizeof( $posts ); $i++ ) { - // ... -} -// Good. -if ( ! $var ) { - // ... -} -// Bad. -if ( !$var ) { - // ... -} - -while ( have_posts() ) { - $ok = ( - ! is_author() - || - is_front_page() - ); -} - -// All OK. +// Boolean not operator: All OK. if ( 'bb' !== 'bb' ) { if ( - empty($_GET['refid']) && - empty($_GET['nolinks']) && - ! is_page_template('page_strategy-center.php') && - ! is_page_template('page_confirmation.php') && - ! is_page_template('page_debartolo.php') && - ! is_singular('offer') + empty( $_GET['refid'] ) && + empty( $_GET['nolinks'] ) && + ! is_page_template( 'page_strategy-center.php' ) && + ! is_page_template( 'page_confirmation.php' ) && + ! is_page_template( 'page_debartolo.php' ) && + ! is_singular( 'offer' ) ) { hello(); } } +// Good. +if ( ! $var ) { + // ... +} + // Bad. -for ( $i = 0; $i < sizeof( $posts ); $i++ ) { +if (!$var ) { // ... } + // Bad. -if ( ! $var ) { +if ( ! $var ) { // ... } -// Ok: Test skipping of default values in function declarations. -function my_test( $a=true, $b = 123, $c= 'string' ) {} -$a = function ( $a=true, $b = 123, $c= 'string' ) {}; +// Logical operators: Ok. +if ( $a === $b && $b === $c ) {} +if ( $a === $b || $b === $c ) {} +if ( $a === $b and $b === $c ) {} +if ( $a === $b or $b === $c ) {} +if ( $a === $b xor $b === $c ) {} + +// Logical operators: Too little space. +if ( $a === $b&&$b === $c ) {} +if ( $a === $b||$b === $c ) {} +if ( $a === {$b}and$b === $c ) {} +if ( $a === {$b}or$b === $c ) {} +if ( $a === {$b}xor$b === $c ) {} + +// Logical operators: Too much space. +if ( $a === $b && $b === $c ) {} +if ( $a === $b || $b === $c ) {} +if ( $a === $b and $b === $c ) {} +if ( $a === $b or $b === $c ) {} +if ( $a === $b xor $b === $c ) {} + +// Logical operators: Multi-line, OK. +if ( $a === $b + && $b === $c +) {} +if ( + $a === $b + || + $b === $c +) {} +if ( $a === $b + and $b === $c ) {} + +if ( $a === $b or + $b === $c ) {} diff --git a/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed b/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed index 377b17d5..4a3214f5 100644 --- a/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed +++ b/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed @@ -1,55 +1,66 @@ <?php -$posts = get_posts( 'cat=3' ); -// Bad, no operator spacing. -for ( $i = 0; $i < sizeof( $posts ); $i++ ) { - // ... +// Boolean not operator: All OK. +if ( 'bb' !== 'bb' ) { + if ( + empty( $_GET['refid'] ) && + empty( $_GET['nolinks'] ) && + ! is_page_template( 'page_strategy-center.php' ) && + ! is_page_template( 'page_confirmation.php' ) && + ! is_page_template( 'page_debartolo.php' ) && + ! is_singular( 'offer' ) + ) { + hello(); + } } -// Good. -for ( $i = 0; $i < sizeof( $posts ); $i++ ) { - // ... -} // Good. if ( ! $var ) { // ... } + // Bad. if ( ! $var ) { // ... } -while ( have_posts() ) { - $ok = ( - ! is_author() - || - is_front_page() - ); -} - -// All OK. -if ( 'bb' !== 'bb' ) { - if ( - empty($_GET['refid']) && - empty($_GET['nolinks']) && - ! is_page_template('page_strategy-center.php') && - ! is_page_template('page_confirmation.php') && - ! is_page_template('page_debartolo.php') && - ! is_singular('offer') - ) { - hello(); - } -} - -// Bad. -for ( $i = 0; $i < sizeof( $posts ); $i++ ) { - // ... -} // Bad. if ( ! $var ) { // ... } -// Ok: Test skipping of default values in function declarations. -function my_test( $a=true, $b = 123, $c= 'string' ) {} -$a = function ( $a=true, $b = 123, $c= 'string' ) {}; +// Logical operators: Ok. +if ( $a === $b && $b === $c ) {} +if ( $a === $b || $b === $c ) {} +if ( $a === $b and $b === $c ) {} +if ( $a === $b or $b === $c ) {} +if ( $a === $b xor $b === $c ) {} + +// Logical operators: Too little space. +if ( $a === $b && $b === $c ) {} +if ( $a === $b || $b === $c ) {} +if ( $a === {$b} and $b === $c ) {} +if ( $a === {$b} or $b === $c ) {} +if ( $a === {$b} xor $b === $c ) {} + +// Logical operators: Too much space. +if ( $a === $b && $b === $c ) {} +if ( $a === $b || $b === $c ) {} +if ( $a === $b and $b === $c ) {} +if ( $a === $b or $b === $c ) {} +if ( $a === $b xor $b === $c ) {} + +// Logical operators: Multi-line, OK. +if ( $a === $b + && $b === $c +) {} +if ( + $a === $b + || + $b === $c +) {} +if ( $a === $b + and $b === $c ) {} + +if ( $a === $b or + $b === $c ) {} diff --git a/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.php index 3f16ae4e..b9468eff 100644 --- a/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.php +++ b/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.php @@ -7,13 +7,21 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\WhiteSpace; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the OperatorSpacing sniff. * * @package WPCS\WordPressCodingStandards + * * @since 2013-06-11 + * @since 0.12.0 Now only tests the WPCS specific addition of T_BOOLEAN_NOT. + * The rest of the sniff is unit tested upstream. + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_WhiteSpace_OperatorSpacingUnitTest extends AbstractSniffUnitTest { +class OperatorSpacingUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,10 +30,18 @@ class WordPress_Tests_WhiteSpace_OperatorSpacingUnitTest extends AbstractSniffUn */ public function getErrorList() { return array( - 5 => 4, - 18 => 1, - 45 => 1, - 49 => 1, + 23 => 2, + 28 => 2, + 40 => 2, + 41 => 2, + 42 => 2, + 43 => 2, + 44 => 2, + 47 => 2, + 48 => 2, + 49 => 2, + 50 => 2, + 51 => 2, ); } diff --git a/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.1.inc b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.1.inc new file mode 100644 index 00000000..7ac67c0a --- /dev/null +++ b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.1.inc @@ -0,0 +1,56 @@ +<?php + + function exampleFunctionA() {} // OK: [tab]. + +/* + * OK: [tab][space][space][space][space]. + * The four spaces will be replaced by a tab by the upstream sniff. + */ + function exampleFunctionB() {} + +/* + * OK: [tab][space][space][tab][space][tab]. + * The space replacement here will be handled by the upstream sniff. + */ + function exampleFunctionC() {} + + /** + * OK: Doc comments are indented with tabs and one space. + * + * @var string <= Bad. + * @access private + */ + + /* + * OK: Multi-line comments are indented with tabs and one space. + * + * <= Bad. + */ + + function exampleFunctionD() {} // Bad: [tab][space]. + function exampleFunctionE() {} // Bad: [tab][space][space]. + function exampleFunctionF() {} // Bad: [tab][space][space][space]. + + function exampleFunctionG() {} // WPCS: precision alignment ok. + +?> + + <p> + Bad: Some text with precision alignment. + </p> + +<?php + +// Testing empty line within a comment. +/* + +*/ + +// Testing that incorrectly aligned docblocks and multi-line inline comments do not trigger errors. + /** + * Provision some options + */ + + /* + * Provision some options + */ diff --git a/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.2.inc b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.2.inc new file mode 100644 index 00000000..b8dc1a9d --- /dev/null +++ b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.2.inc @@ -0,0 +1,44 @@ +@codingStandardsChangeSetting WordPress.WhiteSpace.PrecisionAlignment ignoreAlignmentTokens T_COMMENT,T_DOC_COMMENT_WHITESPACE,T_INLINE_HTML,T_FUNCTION + +<?php + + function exampleFunctionA() {} // OK: [tab]. + +/* + * OK: [tab][space][space][space][space]. + * The four spaces will be replaced by a tab by the upstream sniff. + */ + function exampleFunctionB() {} + +/* + * OK: [tab][space][space][tab][space][tab]. + * The space replacement here will be handled by the upstream sniff. + */ + function exampleFunctionC() {} + + /** + * OK: Doc comments are indented with tabs and one space. + * + * @var string <= Bad. + * @access private + */ + + /* + * OK: Multi-line comments are indented with tabs and one space. + * + * <= Bad. + */ + + function exampleFunctionD() {} // Bad: [tab][space]. + function exampleFunctionE() {} // Bad: [tab][space][space]. + function exampleFunctionF() {} // Bad: [tab][space][space][space]. + +?> + + <p> + Bad: Some text with precision alignment. + </p> + +<?php + +// @codingStandardsChangeSetting WordPress.WhiteSpace.PrecisionAlignment ignoreAlignmentTokens T_NONE diff --git a/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.3.inc b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.3.inc new file mode 100644 index 00000000..8d6287f7 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.3.inc @@ -0,0 +1,14 @@ +<?php +/* +Plugin Name: Hello Dolly +Plugin URI: http://wordpress.org/# +Description: This is not just a plugin, it symbolizes the hope and enthusiasm of an entire generation summed up in two words sung most famously by Louis Armstrong: Hello, Dolly. When activated you will randomly see a lyric from <cite>Hello, Dolly</cite> in the upper right of your admin screen on every page. +Author: Matt Mullenweg +Version: 1.5.1 +Author URI: http://ma.tt/ +Text Domain: hello-dolly + +*/ + +// Test for +?> diff --git a/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.css b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.css new file mode 100644 index 00000000..a4cf5166 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.css @@ -0,0 +1,5 @@ +#login-container { + margin-left: -225px; + width: 450px; + height: 450px; /* Bad. */ +} diff --git a/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.js b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.js new file mode 100644 index 00000000..986fb2f7 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.js @@ -0,0 +1,7 @@ +var x = { + abc: 1, + zyz: 2, + mno: { + abc: 4 + }, +} diff --git a/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.php b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.php new file mode 100644 index 00000000..57fcbbd4 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/PrecisionAlignmentUnitTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\WhiteSpace; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the PrecisionAlignment sniff. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class PrecisionAlignmentUnitTest extends AbstractSniffUnitTest { + + /** + * The tab width to use during testing. + * + * @var int + */ + private $tab_width = 4; + + /** + * Get a list of CLI values to set before the file is tested. + * + * Used by PHPCS 2.x. + * + * @param string $testFile The name of the file being tested. + * + * @return array + */ + public function getCliValues( $testFile ) { + return array( '--tab-width=' . $this->tab_width ); + } + + /** + * Set CLI values before the file is tested. + * + * Used by PHPCS 3.x. + * + * @param string $testFile The name of the file being tested. + * @param \PHP_CodeSniffer\Config $config The config data for the test run. + * + * @return void + */ + public function setCliValues( $testFile, $config ) { + $config->tabWidth = $this->tab_width; + } + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array(); + } + + /** + * Returns the lines where warnings should occur. + * + * @param string $testFile The name of the file being tested. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList( $testFile = '' ) { + switch ( $testFile ) { + case 'PrecisionAlignmentUnitTest.1.inc': + return array( + 20 => 1, + 27 => 1, + 30 => 1, + 31 => 1, + 32 => 1, + 39 => 1, + ); + + case 'PrecisionAlignmentUnitTest.css': + return array( + 4 => 1, + ); + + case 'PrecisionAlignmentUnitTest.js': + return array( + 4 => 1, + 5 => 1, + 6 => 1, + ); + + case 'PrecisionAlignmentUnitTest.2.inc': + case 'PrecisionAlignmentUnitTest.3.inc': + default: + return array(); + } + } + +} // End class. diff --git a/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.inc b/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.inc new file mode 100644 index 00000000..ec7f4067 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.inc @@ -0,0 +1,12 @@ +<?php + +/* + * Test that the sniff does *not* throw incorrect errors for whitespace before semi-colons in + * "empty" parts of a `for` control structure. + */ +for ($i = 1; ; $i++) {} +for ( ; $ptr >= 0; $ptr-- ) {} +for ( ; ; ) {} + +// But it should when the whitespace is between a condition and a semi-colon. +for ( $i = 1 ; ; $i++ ) {} diff --git a/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.inc.fixed b/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.inc.fixed new file mode 100644 index 00000000..0a0820f1 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.inc.fixed @@ -0,0 +1,12 @@ +<?php + +/* + * Test that the sniff does *not* throw incorrect errors for whitespace before semi-colons in + * "empty" parts of a `for` control structure. + */ +for ($i = 1; ; $i++) {} +for ( ; $ptr >= 0; $ptr-- ) {} +for ( ; ; ) {} + +// But it should when the whitespace is between a condition and a semi-colon. +for ( $i = 1; ; $i++ ) {} diff --git a/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.php new file mode 100644 index 00000000..0de15603 --- /dev/null +++ b/WordPress/Tests/WhiteSpace/SemicolonSpacingUnitTest.php @@ -0,0 +1,48 @@ +<?php +/** + * Unit test class for WordPress Coding Standard. + * + * @package WPCS\WordPressCodingStandards + * @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace WordPress\Tests\WhiteSpace; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +/** + * Unit test class for the SemicolonSpacing sniff. + * + * Only the WPCS specific difference with the upstream sniff is being tested. + * The upstream sniff is tested upstream. + * + * @package WPCS\WordPressCodingStandards + * + * @since 0.14.0 + */ +class SemicolonSpacingUnitTest extends AbstractSniffUnitTest { + + /** + * Returns the lines where errors should occur. + * + * @return array <int line number> => <int number of errors> + */ + public function getErrorList() { + return array( + 12 => 1, + ); + + } + + /** + * Returns the lines where warnings should occur. + * + * @return array <int line number> => <int number of warnings> + */ + public function getWarningList() { + return array(); + + } + +} // End class. diff --git a/WordPress/Tests/XSS/EscapeOutputUnitTest.inc b/WordPress/Tests/XSS/EscapeOutputUnitTest.inc index 8c1f6a11..c215e4a3 100644 --- a/WordPress/Tests/XSS/EscapeOutputUnitTest.inc +++ b/WordPress/Tests/XSS/EscapeOutputUnitTest.inc @@ -24,7 +24,7 @@ function custom_column_display( $column, $post_id ) { global $post; switch ( $column ) { - case 'some_number' : + case 'some_number' : echo (int) $test; echo (int) get_post_meta( $post_id, SOME_NUMBER, true ); break; @@ -218,3 +218,42 @@ echo 1.234; // Ok. echo ( 1.234 + 10 + 2.5 ); // Ok. echo 10 % 2; // Ok. echo 8 * 1.2; // Ok. + +?> +<?= $var ?><!-- Bad. --> +<?= esc_html( $var ); ?><!-- Ok. --> +<?= $var['foo']; ?><!-- Bad. --> +<?= $var->foo ?><!-- Bad. --> +<?php + +// Issue #933. OK. +function do_footer_nav() { + echo \wp_kses_post( + \genesis_get_nav_menu( + [ + 'menu_class' => 'menu genesis-nav-menu menu-footer', + 'theme_location' => 'footer', + ] + ) + ); +} + +?><?php echo $html_fragment // XSS pass. ?><?php + +echo // WPCS: XSS ok. + esc_html( $something ), + $something_else, + esc_html( $something_more ); + +echo esc_html( $something ), + $something_else, + esc_html( $something_more ); // WPCS: XSS ok. + +_ex( 'Something', 'context' ); // Bad. +_ex( $some_nasty_var, 'context' ); // Bad. +echo esc_html_x( 'Something', 'context' ); // Ok. +echo esc_html_x( $some_nasty_var, 'context' ); // Ok. + +?> + <input type="hidden" name="some-action" value="<?php echo esc_attr_x( 'none', 'context' ); ?>" /><!-- OK. --> +<?php diff --git a/WordPress/Tests/XSS/EscapeOutputUnitTest.php b/WordPress/Tests/XSS/EscapeOutputUnitTest.php index 3eded370..065e3b5f 100644 --- a/WordPress/Tests/XSS/EscapeOutputUnitTest.php +++ b/WordPress/Tests/XSS/EscapeOutputUnitTest.php @@ -7,13 +7,19 @@ * @license https://opensource.org/licenses/MIT MIT */ +namespace WordPress\Tests\XSS; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the EscapeOutput sniff. * * @package WPCS\WordPressCodingStandards + * * @since 2013-06-11 + * @since 0.13.0 Class name changed: this class is now namespaced. */ -class WordPress_Tests_XSS_EscapeOutputUnitTest extends AbstractSniffUnitTest { +class EscapeOutputUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -22,22 +28,22 @@ class WordPress_Tests_XSS_EscapeOutputUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 17 => 1, - 19 => 1, - 36 => 1, - 39 => 1, - 40 => 1, - 41 => 1, - 43 => 1, - 46 => 1, - 53 => 1, - 59 => 1, - 60 => 1, - 65 => 1, - 68 => 1, - 71 => 1, - 73 => 1, - 75 => 1, + 17 => 1, + 19 => 1, + 36 => 1, + 39 => 1, + 40 => 1, + 41 => 1, + 43 => 1, + 46 => 1, + 53 => 1, + 59 => 1, + 60 => 1, + 65 => 1, + 68 => 1, + 71 => 1, + 73 => 1, + 75 => 1, 101 => 1, 103 => 1, 111 => 1, @@ -63,7 +69,11 @@ public function getErrorList() { 205 => 1, 206 => 1, 207 => 1, - 212 => ( PHP_VERSION_ID < 50300 ) ? 1 : 0, // PHPCS on PHP 5.2 does not recognize T_NOWDOC. + 223 => 1, + 225 => 1, + 226 => 1, + 252 => 1, + 253 => 1, ); } // end getErrorList() diff --git a/WordPress/ruleset.xml b/WordPress/ruleset.xml index 6fb17425..5acf9334 100644 --- a/WordPress/ruleset.xml +++ b/WordPress/ruleset.xml @@ -1,17 +1,18 @@ <?xml version="1.0"?> -<ruleset name="WordPress"> +<ruleset name="WordPress" namespace="WordPress"> <description>WordPress Coding Standards</description> + <autoload>./PHPCSAliases.php</autoload> + <rule ref="WordPress-Core"/> <rule ref="WordPress-Docs"/> <rule ref="WordPress-Extra"/> <rule ref="WordPress-VIP"/> <rule ref="WordPress.PHP.DiscouragedPHPFunctions"> - <!-- From "Extra": The create_function group is excluded as WP core still supports PHP 5.2 and 5.2 does not support anonymous functions. --> <!-- From "VIP": The obfuscation group is excluded as there are plenty of legitimate uses for the base64 functions. --> <properties> - <property name="exclude" value="create_function,obfuscation" /> + <property name="exclude" value="obfuscation"/> </properties> </rule> </ruleset> diff --git a/bin/phpcs.xml b/bin/phpcs.xml index 6e933911..c5c5b4cb 100644 --- a/bin/phpcs.xml +++ b/bin/phpcs.xml @@ -2,16 +2,30 @@ <ruleset name="WordPress Coding Standards"> <description>The Coding standard for the WordPress Coding Standards itself.</description> - <!-- Exclude one sniff which is nothing but a slimmed down copy of an upstream sniff. - Leaving the code standard of this sniff alone, will facilitate easier syncing - with the upstream sniff now and in the future. --> - <exclude-pattern>/WordPress/Sniffs/Arrays/ArrayDeclarationSniff.php</exclude-pattern> + <arg value="sp"/> + <arg name="extensions" value="php"/> + + <!-- Exclude the code in the PHPCS 2.x test files copied in from PHPCS. --> + <exclude-pattern>/Test/AllTests.php</exclude-pattern> + <exclude-pattern>/Test/Standards/*.php</exclude-pattern> + + <!-- Exclude Composer vendor directory. --> + <exclude-pattern>*/vendor/*</exclude-pattern> <rule ref="WordPress-Extra"> - <exclude name="WordPress.Files.FileName" /> - <exclude name="WordPress.NamingConventions.ValidVariableName" /> + <exclude name="WordPress.Files.FileName"/> + <exclude name="WordPress.NamingConventions.ValidVariableName"/> </rule> - <rule ref="WordPress-Docs" /> + <rule ref="WordPress-Docs"/> + + <!-- Enforce PSR1 compatible namespaces. --> + <rule ref="PSR1.Classes.ClassDeclaration"/> + + <rule ref="WordPress.Arrays.MultipleStatementAlignment"> + <properties> + <property name="alignMultilineItems" value="!=100"/> + </properties> + </rule> </ruleset> diff --git a/bin/pre-commit b/bin/pre-commit index 24de124e..4b3e665d 100755 --- a/bin/pre-commit +++ b/bin/pre-commit @@ -11,4 +11,4 @@ fi find . \( -name '*.php' \) -exec php -lf {} \; -phpunit --filter WordPress "$phpcs_root/tests/AllTests.php" +PHPCS_DIR=$phpcs_root phpunit --bootstrap="./Test/phpcs3-bootstrap.php" --filter WordPress "$phpcs_root/tests/AllTests.php" diff --git a/composer.json b/composer.json index aced619d..4a1523a1 100644 --- a/composer.json +++ b/composer.json @@ -11,12 +11,19 @@ } ], "require" : { - "squizlabs/php_codesniffer": "^2.8.1" + "php" : ">=5.3", + "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2" + }, + "suggest" : { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3" }, "minimum-stability" : "RC", "support" : { - "issues": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues" + "issues": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues", + "wiki" : "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki", + "source": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards" }, + "type" : "phpcodesniffer-standard", "scripts" : { "post-install-cmd": "\"vendor/bin/phpcs\" --config-set installed_paths ../../..", "post-update-cmd" : "\"vendor/bin/phpcs\" --config-set installed_paths ../../.." diff --git a/phpcs.xml.dist.sample b/phpcs.xml.dist.sample new file mode 100644 index 00000000..2e960507 --- /dev/null +++ b/phpcs.xml.dist.sample @@ -0,0 +1,84 @@ +<?xml version="1.0"?> +<ruleset name="Example Project"> + <description>A custom set of rules to check for a WPized WordPress project</description> + + <!-- Exclude WP Core folders and files from being checked. --> + <exclude-pattern>/docroot/wp-admin/*</exclude-pattern> + <exclude-pattern>/docroot/wp-includes/*</exclude-pattern> + <exclude-pattern>/docroot/wp-*.php</exclude-pattern> + <exclude-pattern>/docroot/index.php</exclude-pattern> + <exclude-pattern>/docroot/xmlrpc.php</exclude-pattern> + <exclude-pattern>/docroot/wp-content/plugins/*</exclude-pattern> + + <!-- Exclude the Composer Vendor directory. --> + <exclude-pattern>/vendor/*</exclude-pattern> + + <!-- Exclude the Node Modules directory. --> + <exclude-pattern>/node_modules/*</exclude-pattern> + + <!-- Exclude minified Javascript files. --> + <exclude-pattern>*.min.js</exclude-pattern> + + <!-- Include the WordPress-Extra standard. --> + <rule ref="WordPress-Extra"> + <!-- + We may want a middle ground though. The best way to do this is add the + entire ruleset, then rule by rule, remove ones that don't suit a project. + We can do this by running `phpcs` with the '-s' flag, which allows us to + see the names of the sniffs reporting errors. + Once we know the sniff names, we can opt to exclude sniffs which don't + suit our project like so. + + The below two examples just show how you can exclude rules. + They are not intended as advice about which sniffs to exclude. + --> + + <!-- + <exclude name="WordPress.WhiteSpace.ControlStructureSpacing"/> + <exclude name="WordPress.XSS.EscapeOutput"/> + --> + </rule> + + <!-- Let's also check that everything is properly documented. --> + <rule ref="WordPress-Docs"/> + + <!-- Add in some extra rules from other standards. --> + <rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/> + <rule ref="Generic.Commenting.Todo"/> + + <!-- Check for PHP cross-version compatibility. --> + <!-- + To enable this, the PHPCompatibility standard needs + to be installed. + See the readme for installation instructions: + https://github.com/wimg/PHPCompatibility + --> + <!-- + <config name="testVersion" value="5.2-99.0"/> + <rule ref="PHPCompatibility"/> + --> + + <!-- + To get the optimal benefits of using WPCS, we should add a couple of + custom properties. + Adjust the values of these properties to fit our needs. + + For information on additional custom properties available, check out + the wiki: + https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties + --> + <config name="minimum_supported_wp_version" value="4.5"/> + + <rule ref="WordPress.WP.I18n"> + <properties> + <property name="text_domain" type="array" value="my-textdomain,library-textdomain"/> + </properties> + </rule> + + <rule ref="WordPress.NamingConventions.PrefixAllGlobals"> + <properties> + <property name="prefixes" type="array" value="my_prefix"/> + </properties> + </rule> + +</ruleset> diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..95f66654 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd" + backupGlobals="true" + bootstrap="./Test/bootstrap.php" + beStrictAboutTestsThatDoNotTestAnything="false" + colors="true"> +</phpunit> diff --git a/project.ruleset.xml.example b/project.ruleset.xml.example deleted file mode 100644 index 8893a4ea..00000000 --- a/project.ruleset.xml.example +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0"?> -<ruleset name="Example Project"> - <description>A custom set of rules to check for a WPized WordPress project</description> - - <exclude-pattern>/docroot/wp-admin/*</exclude-pattern> - <exclude-pattern>/docroot/wp-includes/*</exclude-pattern> - <exclude-pattern>/docroot/wp-*.php</exclude-pattern> - <exclude-pattern>/docroot/index.php</exclude-pattern> - <exclude-pattern>/docroot/xmlrpc.php</exclude-pattern> - <exclude-pattern>/docroot/wp-content/plugins/*</exclude-pattern> - <exclude-pattern>*.twig</exclude-pattern> - - <rule ref="Squiz.PHP.CommentedOutCode"/> - <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"/> - <rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/> - <rule ref="Generic.Commenting.Todo"/> - <rule ref="Generic.ControlStructures.InlineControlStructure"/> - - <!-- - We may also want to to include all the rules in a standard - --> - <rule ref="WordPress-Core"> - <!-- - We may want a middle ground though. The best way to do this is add the - entire ruleset, then rule by rule, remove ones that don't suit a project. We - can do this by running `phpcs` with the '-s' flag, to see the names of the - different Sniffs, as their rules are broken. From here, we can opt to - exclude problematic sniffs like so. - --> - - <exclude name="WordPress.WhiteSpace.ControlStructureSpacing" /> - <exclude name="WordPress.XSS.EscapeOutput" /> - </rule> -</ruleset>