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 `=` are used.
+- Updated the list of WP globals which is used by both the `WordPress.Variables.GlobalVariables` and the `WordPress.NamingConventions.PrefixAllGlobals` sniffs.
+- Updated the information on using a custom ruleset and associated naming conventions in the Readme.
+- Updated the [custom ruleset example](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/blob/develop/phpcs.xml.dist.sample) to provide a better starting point and renamed the file to follow current PHPCS best practices.
+- Various inline documentation improvements.
+- Updated the link to the PHPStorm documentation in the Readme.
+- Various textual improvements to the Readme.
+- Minor improvements to the build script.
+
+### Removed
+- `Squiz.Commenting.LongConditionClosingComment` sniff from the `WordPress-Core` ruleset. This rule has been removed from the WP Coding Standards handbook.
+- The exclusion of the `Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace` error from the `WordPress-Core` ruleset.
+- The exclusion of the `PEAR.Functions.FunctionCallSignature.ContentAfterOpenBracket` and `PEAR.Functions.FunctionCallSignature.CloseBracketLine` error from the `WordPress-Core` ruleset when used in combination with the fixer, i.e. `phpcbf`. The exclusions remain in place for `phpcs` runs.
+- `wp_get_post_terms()`, `wp_get_post_categories()`, `wp_get_post_tags()` and `wp_get_object_terms()` from the `WordPress.VIP.RestrictedFunctions` sniff as these functions are now cached natively since WP 4.7.
+
+### Fixed
+- The `WordPress.Array.ArrayDeclarationSpacing` could be overeager when fixing associative arrays to be multi-line. Non-associative single-line arrays which contained a nested associative array would also be auto-fixed by the sniff, while only the nested associated array should be fixed.
+- The `WordPress.Files.FileName` sniff did not play nice with IDEs passing a filename to PHPCS via `--stdin-path=`.
+- The `WordPress.Files.FileName` sniff was being triggered on code passed via `stdin` where there is no file name to examine.
+- The `WordPress.PHP.YodaConditions` sniff would give a false positive for the result of a condition being assigned to a variable.
+- The `WordPress.VIP.RestrictedVariables` sniff was potentially underreporting issues when the variables being restricted were a combination of variables, object properties and array members.
+- The auto-fixer in the `WordPress.WhiteSpace.ControlStructureSpacing` sniff which deals with "blank line after control structure" issues could cause comments at the end of control structures to be removed.
+- The `WordPress.WP.DeprecatedFunctions` sniff was reporting the wrong WP version for the deprecation of a number of functions.
+- The `WordPress.WP.EnqueuedResources` sniff would potentially underreport issues in certain circumstances.
+- The `WordPress.XSS.EscapeOutput` sniff will no now longer report issues when it encounters a `__DIR__`, `(unset)` cast or a floating point number, and will correctly disregard more arithmetic operators when deciding whether to report an issue or not.
+- The [whitelisting of errors using flags](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Whitelisting-code-which-flags-errors) was sometimes a bit too eager and could accidentally whitelist code which was not intended to be whitelisted.
+- Various (potential) `Undefined variable`, `Undefined index` and `Undefined offset` notices.
+- Grammer in one of the `WordPress.WP.I18n` error messages.
+
+
## [0.11.0] - 2017-03-20
### Important notes for end-users:
@@ -30,9 +193,9 @@ For more detailed information on the most significant changes, please refer to P
You are also encouraged to check the file history of any WPCS classes you extend.
### Added
-- `WordPress.WP.DeprecatedFunctions` sniff to the `WordPress-Extra` ruleset to check for usage of deprecated WP version and show errors/warnings depending on a `minimum_supported_version` which [can be passed to the sniff from a custom ruleset](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#minimum-wp-version-to-check-for-usage-of-deprecated-functions-and-function-parameters). The default value for the `minimum_supported_version` property is three versions before the current WP version.
+- `WordPress.WP.DeprecatedFunctions` sniff to the `WordPress-Extra` ruleset to check for usage of deprecated WP version and show errors/warnings depending on a `minimum_supported_version` which [can be passed to the sniff from a custom ruleset](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#minimum-wp-version-to-check-for-usage-of-deprecated-functions-classes-and-function-parameters). The default value for the `minimum_supported_version` property is three versions before the current WP version.
- `WordPress.WP.I18n`: ability to check for missing _translators comments_ when a I18n function call contains translatable text strings containing placeholders. This check will also verify that the _translators comment_ is correctly placed in the code and uses the correct comment type for optimal compatibility with the various tools available to create `.pot` files.
-- `WordPress.WP.I18n`: ability to pass the `text_domain` to check for [from the command line](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#setting-text_domain-from-the-command-line).
+- `WordPress.WP.I18n`: ability to pass the `text_domain` to check for [from the command line](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#setting-text_domain-from-the-command-line-wpcs-0110).
- `WordPress.Arrays.ArrayDeclarationSpacing`: check + fixer for single line associative arrays. The [handbook](https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation) states that these should always be multi-line.
- `WordPress.Files.FileName`: verification that files containing a class reflect this in the file name as per the core guidelines. This particular check can be disabled in a custom ruleset by setting the new [`strict_class_file_names` property](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#disregard-class-file-name-rules).
- `WordPress.Files.FileName`: verification that files in `/wp-includes/` containing template tags - annotated with `@subpackage Template` in the file header - use the `-template` suffix.
@@ -74,7 +237,7 @@ You are also encouraged to check the file history of any WPCS classes you extend
- `WordPress.NamingConventions.ValidVariableName`: The `customVariablesWhitelist` property which could be passed from the ruleset has been renamed to [`customPropertiesWhitelist`](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#mixed-case-property-name-exceptions) as it is only usable to whitelist class properties.
- `WordPress.WP.I18n`: now allows for an array of text domain names to be passed to the `text_domain` property from a custom ruleset.
- `WordPress.WhiteSpace.CastStructureSpacing`: the error level for the checks in this sniff has been raised from `warning` to `error`.
-- `WordPress.Variables.GlobalVariables`: will no longer throw errors if the global variable override is done from within a test method. Whether something is considered a "test method" is based on whether the method is in a class which extends a predefined set of known unit test classes. This list can be enhanced by setting the [`custom_test_class_whitelist` property](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#global-variable-overloads-in-unit-tests) in your ruleset.
+- `WordPress.Variables.GlobalVariables`: will no longer throw errors if the global variable override is done from within a test method. Whether something is considered a "test method" is based on whether the method is in a class which extends a predefined set of known unit test classes. This list can be enhanced by setting the [`custom_test_class_whitelist` property](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#custom-unit-test-classes) in your ruleset.
- The `WordPress.Arrays.ArrayDeclaration` sniff has been split into two sniffs: `WordPress.Arrays.ArrayDeclaration` and `WordPress.Arrays.ArrayDeclarationSpacing` for better compatibility with PHPCS upstream.
- The `WordPress.Arrays.ArrayDeclaration` sniff has been synced with the PHPCS upstream version to get the benefit of some bug fixes and improvements which had been made upstream since the sniff was originally copied over.
- The `WordPress.VIP.FileSystemWritesDisallow`, `WordPress.VIP.TimezoneChange` and `WordPress.VIP.SessionFunctionsUsage` sniffs now extend the `WordPress_AbstractFunctionRestrictionsSniff`.
@@ -366,7 +529,12 @@ See the comparison for full list.
Initial tagged release.
-[Unreleased]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.11.0...HEAD
+[Unreleased]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/master...HEAD
+[0.14.1]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.14.0...0.14.1
+[0.14.0]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.13.1...0.14.0
+[0.13.1]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.13.0...0.13.1
+[0.13.0]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.12.0...0.13.0
+[0.12.0]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.11.0...0.12.0
[0.11.0]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.10.0...0.11.0
[0.10.0]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.9.0...0.10.0
[0.9.0]: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/compare/0.8.0...0.9.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 30093967..00000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,141 +0,0 @@
-# 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. Before reporting a bug, you should check what sniff an error is coming from. Running `phpcs` with the `-s` flag, which will show the names of the sniffs with each error. 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).
-
-# 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 then serve provide a second round of Travis CI checks (especially for any hotfixes pushed directly to the `develop` branch), and 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
-class WordPress_Sniffs_CSRF_NonceVerificationSniff extends WordPress_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
-
-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