diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 4c60892b8b..337db8e8fa 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,6 +1,8 @@ +# Contributing to the WordPress Coding Standards + Hi, thank you for your interest in contributing to the WordPress Coding Standards! We look forward to working with you. -# Reporting Bugs +## Reporting Bugs Please search the repo to see if your issue has been reported already and if so, comment in that issue instead of opening a new one. @@ -9,7 +11,7 @@ Running `phpcs` with the `-s` flag will show the name of the sniff with each err 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 +### Upstream Issues Since WordPressCS employs many sniffs that are part of PHP_CodeSniffer itself or PHPCSExtra, sometimes an issue will be caused by a bug in PHPCS or PHPCSExtra and not in WordPressCS 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 or PHPCSExtra. @@ -17,40 +19,61 @@ If the error message in question doesn't come from a sniff whose name starts wit * Bugs for sniffs starting with `Generic`, `PEAR`, `PSR1`, `PSR2`, `PSR12`, `Squiz` or `Zend` should be [reported to PHPCS](https://github.com/PHPCSStandards/PHP_CodeSniffer/issues). * Bugs for sniffs starting with `Modernize`, `NormalizedArrays` or `Universal` should be [reported to PHPCSExtra](https://github.com/PHPCSStandards/PHPCSExtra/issues). -# Contributing patches and new features +## Contributing patches and new features + +### Tips for Successful PRs + +We welcome contributions from everyone, and want your PR to have the best chance of being reviewed and merged. To help with this, please keep the following in mind: + +* **Respect copyright and licensing.** + Only submit code that you have written yourself or that comes from sources where the license clearly allows inclusion. Submitting code that infringes on copyright or licensing terms puts both you and the project at legal risk, and such contributions cannot be accepted. + +* **Do not submit AI-generated code.** + Pull requests containing AI-generated code are not acceptable. Beyond copyright and licensing uncertainties, AI-generated contributions consistently require disproportionate amounts of maintainer time to review, correct, or rewrite. This wastes limited project resources and slows progress for everyone. Submitting AI-generated code may be treated as a violation of our [Code of Conduct](../CODE_OF_CONDUCT.md). -## Branches +* **Focus on quality and clarity.** + Take time to explain *why* the change is needed, and include tests or examples where appropriate. Clear, self-written explanations make it more straightforward for reviewers to understand what you are trying to achieve. + +* **Think about long-term maintainability.** + Code should align with WordPress Coding Standards and be written in a way that others can readily read, understand, and maintain. + +* **Be collaborative.** + If you're unsure about an approach, open an issue first to start a conversation. + +By following these tips, you'll save time for both yourself and the maintainers — and increase the likelihood that your contribution can be merged smoothly. + +### Branches Ongoing development will be done in the `develop` branch with merges to `main` once considered stable. To contribute an improvement to this project, fork the repo, run `composer install`, make your changes to the code, run the unit tests and code style checks by running `composer check-all`, and if all is good, 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`. -# Considerations when writing sniffs +## Considerations when writing sniffs -## Public properties +### 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 behavior. 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/WordPress-Coding-Standards/wiki/Customizable-sniff-properties) with the relevant details once your PR has been merged into the `develop` branch. -# Unit Testing +## Unit Testing -## Pre-requisites +### Pre-requisites * WordPress-Coding-Standards -* PHP_CodeSniffer 3.13.0 or higher +* PHP_CodeSniffer 3.13.4 or higher * PHPCSUtils 1.1.0 or higher -* PHPCSExtra 1.4.0 or higher -* PHPUnit 4.x - 9.x +* PHPCSExtra 1.5.0 or higher +* PHPUnit 8.x - 9.x The WordPress Coding Standards use the `PHP_CodeSniffer` native unit test framework for unit testing the sniffs. -## Getting ready to test +### Getting ready to test Presuming you have cloned WordPressCS for development, to run the unit tests you need to make sure you have run `composer install` from the root directory of your WordPressCS git clone. -## Custom develop setups +### Custom develop setups If you are developing with a stand-alone PHP_CodeSniffer (git clone) installation and want to use that git clone to test WordPressCS, there are three extra things you need to do: 1. Install [PHPCSUtils](https://github.com/PHPCSStandards/PHPCSUtils). @@ -73,7 +96,7 @@ If you are developing with a stand-alone PHP_CodeSniffer (git clone) installatio ``` -## Running the unit tests +### Running the unit tests From the root of your WordPressCS install, run the unit tests like so: ```bash @@ -85,32 +108,32 @@ phpunit --filter WordPress /path/to/PHP_CodeSniffer/tests/AllTests.php Expected output: ``` -PHPUnit 9.6.15 by Sebastian Bergmann and contributors. +PHPUnit 9.6.26 by Sebastian Bergmann and contributors. -Runtime: PHP 8.3.0 +Runtime: PHP 8.4.12 Configuration: /WordPressCS/phpunit.xml.dist -......................................................... 57 / 57 (100%) +............................................................ 60 / 60 (100%) -201 sniff test files generated 744 unique error codes; 50 were fixable (6%) +210 sniff test files generated 775 unique error codes; 50 were fixable (6%) -Time: 10.19 seconds, Memory: 40.00 MB +Time: 00:03.396, Memory: 60.00 MB -OK (57 tests, 0 assertions) +OK (60 tests, 6 assertions) ``` -## Unit Testing conventions +### 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? +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/TypeCastsSniff.php` sniff has its unit test class defined in `WordPress/Tests/PHP/TypeCastsUnitTest.php` which checks the `WordPress/Tests/PHP/TypeCastsUnitTest.inc` test case file. See the file naming convention? -Lets take a look at what's inside `POSIXFunctionsUnitTest.php`: +Lets take a look at what's inside `TypeCastsUnitTest.php`: ```php namespace WordPressCS\WordPress\Tests\PHP; use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; -final class POSIXFunctionsUnitTest extends AbstractSniffUnitTest { +final class TypeCastsUnitTest extends AbstractSniffUnitTest { /** * Returns the lines where errors should occur. @@ -119,13 +142,12 @@ final class POSIXFunctionsUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( + 10 => 1, + 11 => 1, 13 => 1, - 16 => 1, - 18 => 1, - 20 => 1, - 22 => 1, - 24 => 1, 26 => 1, + 27 => 1, + 28 => 1, ); } @@ -133,44 +155,46 @@ final class POSIXFunctionsUnitTest extends AbstractSniffUnitTest { } ``` -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`. Similarly, the `getWarningList()` method must return an array of line numbers with the number of expected warnings. +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/TypeCastsUnitTest.inc`. Similarly, the `getWarningList()` method must return an array of line numbers with the number of expected warnings. If you run the following from the root directory of your WordPressCS clone: ```sh -$ "vendor/bin/phpcs" --standard=Wordpress -s ./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) --------------------------------------------------------------------------------- +$ "vendor/bin/phpcs" --standard=Wordpress -s ./WordPress/Tests/PHP/TypeCastsUnitTest.inc --sniffs=WordPress.PHP.TypeCasts ... +---------------------------------------------------------------------------------------------------- +FOUND 6 ERRORS AND 4 WARNINGS AFFECTING 10 LINES +---------------------------------------------------------------------------------------------------- + 10 | ERROR | [x] Normalized type keywords must be used; expected "(float)" but found "(double)" + | | (WordPress.PHP.TypeCasts.DoubleRealFound) + 11 | ERROR | [x] Normalized type keywords must be used; expected "(float)" but found "(real)" + | | (WordPress.PHP.TypeCasts.DoubleRealFound) + 13 | ERROR | [ ] Using the "(unset)" cast is forbidden as the type cast is removed in PHP 8.0. + | | Use the "unset()" language construct instead. + | | (WordPress.PHP.TypeCasts.UnsetFound) + 15 | WARNING | [ ] Using binary casting is strongly discouraged. Found: "(binary)" + | | (WordPress.PHP.TypeCasts.BinaryFound) + 16 | WARNING | [ ] Using binary casting is strongly discouraged. Found: "b" + | | (WordPress.PHP.TypeCasts.BinaryFound) + 17 | WARNING | [ ] Using binary casting is strongly discouraged. Found: "b" + | | (WordPress.PHP.TypeCasts.BinaryFound) + 26 | ERROR | [x] Normalized type keywords must be used; expected "(float)" but found "(double)" + | | (WordPress.PHP.TypeCasts.DoubleRealFound) + 27 | ERROR | [x] Normalized type keywords must be used; expected "(float)" but found "(real)" + | | (WordPress.PHP.TypeCasts.DoubleRealFound) + 28 | ERROR | [ ] Using the "(unset)" cast is forbidden as the type cast is removed in PHP 8.0. + | | Use the "unset()" language construct instead. + | | (WordPress.PHP.TypeCasts.UnsetFound) + 29 | WARNING | [ ] Using binary casting is strongly discouraged. Found: "(binary)" + | | (WordPress.PHP.TypeCasts.BinaryFound) +---------------------------------------------------------------------------------------------------- +PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY +---------------------------------------------------------------------------------------------------- ``` 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 +### Code Standards for this project The sniffs and test files - not test _case_ files! - for WordPressCS should be written such that they pass the `WordPress-Extra` and the `WordPress-Docs` code standards using the custom ruleset as found in `/.phpcs.xml.dist`. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 97c58b7822..2b9891a7f0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,3 +15,9 @@ updates: prefix: "GH Actions:" labels: - "Type: Chores/Cleanup" + groups: + action-runners: + applies-to: version-updates + update-types: + - "minor" + - "patch" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..e33b84ad9d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,34 @@ + + + + +# Description + + + +## Suggested changelog entry + + + +## Related issues/external references + +Fixes # diff --git a/.github/release-checklist.md b/.github/release-checklist.md index a45db59b23..37426e0d86 100644 --- a/.github/release-checklist.md +++ b/.github/release-checklist.md @@ -36,6 +36,7 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH ### Release prep +- [ ] Double-check that all PRs which were merged since the last release have a milestone attached to it. - [ ] Add changelog for the release - PR #xxx :pencil2: Remember to add a release link at the bottom! - [ ] Update `README` (if applicable) - PR #xxx @@ -45,7 +46,7 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH - [ ] Merge this PR. - [ ] Make sure all CI builds are green. -- [ ] Tag and create a release against `main` (careful, GH defaults to `develop`!) & copy & paste the changelog to it. +- [ ] Tag and create a release against `main` (careful, GH defaults to `develop`!) & copy & paste the changelog to it. :pencil2: Check if anything from the link collection at the bottom of the changelog needs to be copied in! - Remove square brackets from all ticket links or make them proper full links (as GH markdown parser doesn't parse these correctly). - Change all contributor links to full inline links (as GH markdown parser on the Releases page doesn't parse these correctly). @@ -62,13 +63,16 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH ### Publicize - [ ] [Major releases only] Publish post about the release on Make WordPress. -- [ ] Tweet, toot, etc about the release. -- [ ] Post about it in Slack. -- [ ] Submit for ["Month in WordPress"][month-in-wp]. +- [ ] Tweet, toot, etc about the release from your personal account (there is no official WPCS account). +- [ ] Post about it in #core channel on the WordPress.org Slack. + :pencil2: No need to post in the #core-coding-standard channel as that gets an automated release notification anyway. + - [ ] Optionally post in #plugin-review if a sniff was added in a release which was requested by the plugin review team. + - [ ] Optionally post in #core-docs if significant updates were made to the documentation ruleset. +- [ ] Create a Marketing team ["amplify request"][amplify-request]. - [ ] Submit for the ["Monthy Dev Roundup"][dev-roundup]. [phpcs-releases]: https://github.com/PHPCSStandards/PHP_CodeSniffer/releases [phpcsutils-releases]: https://github.com/PHPCSStandards/PHPCSUtils/releases [phpcsextra-releases]: https://github.com/PHPCSStandards/PHPCSExtra/releases -[month-in-wp]: https://make.wordpress.org/community/month-in-wordpress-submissions/ +[amplify-request]: https://github.com/WordPress/Marketing-Team/issues/new?template=2-request-for-amplification-template.yml [dev-roundup]: https://github.com/WordPress/developer-blog-content/issues?q=is%3Aissue+label%3A%22Monthly+Roundup%22 diff --git a/.github/workflows/basic-qa.yml b/.github/workflows/basic-qa.yml index f43a2843bb..10a1ec6263 100644 --- a/.github/workflows/basic-qa.yml +++ b/.github/workflows/basic-qa.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: true env: - PHPCS_DEV: 'dev-master' + PHPCS_DEV: '3.x-dev' UTILS_DEV: 'dev-develop' EXTRA_DEV: 'dev-develop' @@ -26,10 +26,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 with: php-version: 'latest' coverage: none @@ -48,7 +50,7 @@ jobs: phpcsstandards/phpcsextra:"${{ env.EXTRA_DEV }}" - name: Install Composer dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -64,38 +66,38 @@ jobs: # Validate the Ruleset XML files. # @link http://xmlsoft.org/xmllint.html - name: Validate the WordPress rulesets - uses: phpcsstandards/xmllint-validate@v1 + uses: phpcsstandards/xmllint-validate@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 with: pattern: "./*/ruleset.xml" xsd-file: "vendor/squizlabs/php_codesniffer/phpcs.xsd" - name: Validate the sample ruleset - uses: phpcsstandards/xmllint-validate@v1 + uses: phpcsstandards/xmllint-validate@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 with: pattern: "phpcs.xml.dist.sample" xsd-file: "vendor/squizlabs/php_codesniffer/phpcs.xsd" # Validate the Documentation XML files. - name: Validate documentation against schema - uses: phpcsstandards/xmllint-validate@v1 + uses: phpcsstandards/xmllint-validate@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 with: pattern: "./WordPress/Docs/*/*Standard.xml" xsd-file: "vendor/phpcsstandards/phpcsdevtools/DocsXsd/phpcsdocs.xsd" - name: Validate Project PHPCS ruleset against schema - uses: phpcsstandards/xmllint-validate@v1 + uses: phpcsstandards/xmllint-validate@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 with: pattern: ".phpcs.xml.dist" xsd-file: "vendor/squizlabs/php_codesniffer/phpcs.xsd" - name: "Validate PHPUnit config for use with PHPUnit 8" - uses: phpcsstandards/xmllint-validate@v1 + uses: phpcsstandards/xmllint-validate@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 with: pattern: "phpunit.xml.dist" xsd-file: "vendor/phpunit/phpunit/schema/8.5.xsd" - name: "Validate PHPUnit config for use with PHPUnit 9" - uses: phpcsstandards/xmllint-validate@v1 + uses: phpcsstandards/xmllint-validate@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 with: pattern: "phpunit.xml.dist" xsd-file: "vendor/phpunit/phpunit/schema/9.2.xsd" @@ -115,7 +117,9 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false # Updating the lists can fail intermittently, typically after Microsoft has released a new package. # This should not be blocking for this job, so ignore any errors from this step. @@ -129,7 +133,7 @@ jobs: # Show XML violations inline in the file diff. - name: Enable showing XML issues inline - uses: korelstar/xmllint-problem-matcher@v1 + uses: korelstar/xmllint-problem-matcher@1bd292d642ddf3d369d02aaa8b262834d61198c0 # v1.2.0 - name: Check the code-style consistency of the xml files run: | @@ -153,10 +157,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false - name: Set up PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 with: php-version: ${{ matrix.php }} # Allow for PHP deprecation notices. @@ -176,7 +182,7 @@ jobs: run: composer config --unset lock - name: Install Composer dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 with: composer-options: --no-dev # Bust the cache at least once a month - output format: YYYY-MM. @@ -234,10 +240,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false - name: Install PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 with: php-version: 'latest' coverage: none @@ -247,7 +255,7 @@ jobs: # Dependencies need to be installed to make sure the PHPCS and PHPUnit classes are recognized. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer - name: Install Composer dependencies - uses: "ramsey/composer-install@v3" + uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -262,7 +270,9 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3" # v6.0.0 + with: + persist-credentials: false - name: "Search for misspellings" - uses: "crate-ci/typos@v1" + uses: "crate-ci/typos@626c4bedb751ce0b7f03262ca97ddda9a076ae1c" # v1.39.2 diff --git a/.github/workflows/manage-labels.yml b/.github/workflows/manage-labels.yml index 56de1c5ac0..a0385ae6fa 100644 --- a/.github/workflows/manage-labels.yml +++ b/.github/workflows/manage-labels.yml @@ -12,12 +12,12 @@ on: jobs: on-pr-merge: runs-on: ubuntu-latest - if: github.repository_owner == 'WordPress' && github.event.pull_request.merged == true + if: github.event.repository.fork == false && github.event.pull_request.merged == true name: Clean up labels on PR merge steps: - - uses: mondeja/remove-labels-gh-action@v2 + - uses: mondeja/remove-labels-gh-action@b7118e4ba5dca74acf1059b3cb7660378ff9ab1a # v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} labels: | @@ -26,12 +26,12 @@ jobs: on-pr-close: runs-on: ubuntu-latest - if: github.repository_owner == 'WordPress' && github.event_name == 'pull_request_target' && github.event.pull_request.merged == false + if: github.event.repository.fork == false && github.event_name == 'pull_request_target' && github.event.pull_request.merged == false name: Clean up labels on PR close steps: - - uses: mondeja/remove-labels-gh-action@v2 + - uses: mondeja/remove-labels-gh-action@b7118e4ba5dca74acf1059b3cb7660378ff9ab1a # v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} labels: | @@ -41,12 +41,12 @@ jobs: on-issue-close: runs-on: ubuntu-latest - if: github.repository_owner == 'WordPress' && github.event.issue.state == 'closed' + if: github.event.repository.fork == false && github.event.issue.state == 'closed' name: Clean up labels on issue close steps: - - uses: mondeja/remove-labels-gh-action@v2 + - uses: mondeja/remove-labels-gh-action@b7118e4ba5dca74acf1059b3cb7660378ff9ab1a # v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} labels: | diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index 6372e6e853..5ee925db9e 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -23,22 +23,24 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [ '5.4', 'latest' ] + php: [ '7.2', 'latest' ] dependencies: [ 'lowest', 'stable' ] name: QTest - PHP ${{ matrix.php }} on PHPCS ${{ matrix.dependencies }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false - name: Set up PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 with: php-version: ${{ matrix.php }} # With stable PHPCS dependencies, allow for PHP deprecation notices. # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. - ini-values: error_reporting=-1, display_errors=On + ini-values: error_reporting=-1, display_errors=On, display_startup_errors=On coverage: ${{ github.ref_name == 'develop' && 'xdebug' || 'none' }} - name: Enable creation of `composer.lock` file @@ -46,7 +48,7 @@ jobs: run: composer config --unset lock - name: Install Composer dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -64,16 +66,16 @@ jobs: run: composer lint - name: Run the unit tests without code coverage - if: ${{ github.repository_owner != 'WordPress' || github.ref_name != 'develop' }} + if: ${{ github.event.repository.fork == true || github.ref_name != 'develop' }} run: composer run-tests - name: Run the unit tests with code coverage - if: ${{ github.repository_owner == 'WordPress' && github.ref_name == 'develop' }} + if: ${{ github.event.repository.fork == false && github.ref_name == 'develop' }} run: composer coverage - name: Send coverage report to Codecov - if: ${{ success() && github.repository_owner == 'WordPress' && github.ref_name == 'develop' }} - uses: codecov/codecov-action@v5 + if: ${{ success() && github.event.repository.fork == false && github.ref_name == 'develop' }} + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 with: files: ./build/logs/clover.xml fail_ci_if_error: true diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 87b871363e..2bcae652cc 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -15,7 +15,7 @@ concurrency: cancel-in-progress: true env: - PHPCS_DEV: 'dev-master' + PHPCS_DEV: '3.x-dev' UTILS_DEV: 'dev-develop' EXTRA_DEV: 'dev-develop' @@ -26,49 +26,40 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [ '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.5' ] + php: [ '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.6' ] dependencies: [ 'lowest', 'stable' ] extensions: [ '' ] coverage: [false] include: - - php: '7.2' - dependencies: 'stable' - extensions: ':mbstring' # = Disable Mbstring. - coverage: true # Make sure coverage is recorded for this too. - # Run code coverage builds against high/low PHP and high/low PHPCS. - - php: '5.4' + - php: '7.2' dependencies: 'stable' extensions: '' coverage: true - - php: '5.4' + - php: '7.2' dependencies: 'lowest' extensions: '' coverage: true - - php: '8.4' + - php: '8.5' dependencies: 'stable' extensions: '' coverage: true - - php: '8.4' + - php: '8.5' dependencies: 'lowest' extensions: '' coverage: true # Test against dev versions of all dependencies with select PHP versions for early detection of issues. - - php: '5.4' - dependencies: 'dev' - extensions: '' - coverage: false - - php: '7.0' + - php: '7.2' dependencies: 'dev' extensions: '' coverage: false - - php: '7.4' + - php: '8.1' dependencies: 'dev' extensions: '' coverage: false - - php: '8.4' + - php: '8.5' dependencies: 'dev' extensions: '' coverage: false @@ -79,11 +70,13 @@ jobs: name: PHP ${{ matrix.php }} on PHPCS ${{ matrix.dependencies }} - continue-on-error: ${{ matrix.php == '8.5' }} + continue-on-error: ${{ matrix.php == '8.6' }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false # With stable PHPCS dependencies, allow for PHP deprecation notices. # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. @@ -91,13 +84,13 @@ jobs: id: set_ini run: | if [ "${{ matrix.dependencies }}" != "dev" ]; then - echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> "$GITHUB_OUTPUT" + echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On, display_startup_errors=On' >> "$GITHUB_OUTPUT" else - echo 'PHP_INI=error_reporting=-1, display_errors=On' >> "$GITHUB_OUTPUT" + echo 'PHP_INI=error_reporting=-1, display_errors=On, display_startup_errors=On' >> "$GITHUB_OUTPUT" fi - name: Set up PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 with: php-version: ${{ matrix.php }} ini-values: ${{ steps.set_ini.outputs.PHP_INI }} @@ -117,7 +110,7 @@ jobs: run: composer config --unset lock - name: Install Composer dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -135,16 +128,16 @@ jobs: run: composer lint -- --checkstyle | cs2pr - name: Run the unit tests without code coverage - if: ${{ matrix.coverage == false }} + if: ${{ matrix.coverage == false || github.event.repository.fork == true }} run: composer run-tests - name: Run the unit tests with code coverage - if: ${{ matrix.coverage == true }} + if: ${{ matrix.coverage == true && github.event.repository.fork == false }} run: composer coverage - name: Send coverage report to Codecov - if: ${{ success() && matrix.coverage == true }} - uses: codecov/codecov-action@v5 + if: ${{ success() && matrix.coverage == true && github.event.repository.fork == false }} + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 with: files: ./build/logs/clover.xml fail_ci_if_error: true diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index cf03cc18c1..b14c3862b0 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -50,24 +50,27 @@ - - - - + - - - - - - - - + + + + + + + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b3c71aa66..d08d7fbc7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,69 @@ This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a _No documentation available about unreleased changes as of yet._ +## [3.3.0] - 2025-11-25 + +### Added +- Support for attributes on anonymous classes (PHP 8.0) and `readonly` anonymous classes (PHP 8.3) to the `WordPress.Security.EscapeOutput` sniff. Props [@rodrigoprimo]. [#2559] +- Support for handling "exit as a function call" (PHP 8.4) to the `WordPress.Security.EscapeOutput` sniff. [#2563] +- WordPress-Extra: the following sniffs have been added to the ruleset: `Universal.Attributes.BracketSpacing` and `Universal.Attributes.DisallowAttributeParentheses`. [#2646] + +### Changed +- The minimum supported PHP version is now PHP 7.2 (was PHP 5.4). [#2614] +- The minimum required `PHP_CodeSniffer` version to 3.13.4 (was 3.13.0). [#2630] +- The minimum required `PHPCSExtra` version to 1.5.0 (was 1.4.0). [#2646] +- The default value for `minimum_wp_version`, as used by a [number of sniffs detecting usage of deprecated WP features](https://github.com/WordPress/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#various-sniffs-set-the-minimum-supported-wp-version), has been updated to `6.6`. [#2656] +- `WordPress.DB.DirectDatabaseQuery` will now recognize more caching functions, like the `wp_cache_*_multiple()` functions as added in WordPress 6.0 and the `wp_cache_*_salted()` functions as added in WordPress 6.9. [#2654] +- `WordPress.NamingConventions.PrefixAllGlobals` has been updated to recognize pluggable functions introduced in WP up to WP 6.9.0. [#2652] +- `WordPress.WP.ClassNameCase` has been updated to recognize classes introduced in WP up to WP 6.9.0. [#2652] +- `WordPress.WP.DeprecatedFunctions` now detects functions deprecated in WordPress up to WP 6.9.0. [#2652] +- `WordPress.WP.DeprecatedParameters` now detects parameters deprecated in WordPress up to WP 6.9.0. [#2652] +- `WordPress.Security.ValidatedSanitizedInput`: improved the clarity of the error message for the `InputNotValidated` error code. Props [@rodrigoprimo]. [#2642] +- README: updated `testVersion` recommendations for PHPCompatibility. Props [@johnjago]. [#2471] +- Example ruleset: updated the `minimum_wp_version` and `testVersion` recommendations. [#2608] +- All sniffs are now also being tested against PHP 8.5 for consistent sniff results. [#2649] +- Various housekeeping, including documentation and test improvements. Includes contributions by [@rodrigoprimo]. + +### Deprecated +- The WordPress.PHP.POSIXFunctions sniff (as it is no longer relevant). [#2616] + +### Removed +- `wp_kses_allowed_html()` from the list of escaping functions. [#2566] + This affects the `WordPress.Security.EscapeOutput` sniff. + +### Fixed +- `WordPress.DB.DirectDatabaseQuery`: false positive when function call to caching functions did not use the canonical function name. Props [@rodrigoprimo]. [#2613] +- `WordPress.DB.DirectDatabaseQuery`: potential false negative when a class property or constant would mirror the name of one of the caching functions. Props [@rodrigoprimo]. [#2615] +- `WordPress.DB.PreparedSQL`: false positive for correctly escaped SQL snippets when the function call did not use the canonical function name. Props [@rodrigoprimo]. [#2570] +- `WordPress.DB.PreparedSQLPlaceholders`: improved handling of fully qualified calls to global functions. Props [@rodrigoprimo]. [#2569] +- `WordPress.Security.EscapeOutput`: expanded protection against false positives for `*::class`. Props [@rodrigoprimo]. [#2605] +- `WordPress.Security.NonceVerification`: false positive when nonce checking function call did not use the canonical function name. Props [@rodrigoprimo]. [#2572] +- `WordPress.WP.EnqueuedResourceParameters`: the sniff could cause a PHP 8.5 deprecation notice if the _code under scan_ contained one of the deprecated type casts. [#2573] +- `WordPress.WP.EnqueuedResourceParameters`: improved recognition of non-lowercase and fully qualified `true`/`false`/`null` when passed as the `$ver` parameter value. Props [@rodrigoprimo]. [#2630] + +[#2471]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2471 +[#2559]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2559 +[#2563]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2563 +[#2566]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2566 +[#2569]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2569 +[#2570]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2570 +[#2572]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2572 +[#2573]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2573 +[#2605]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2605 +[#2608]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2608 +[#2613]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2613 +[#2614]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2614 +[#2615]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2615 +[#2616]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2616 +[#2630]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2630 +[#2642]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2642 +[#2646]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2646 +[#2649]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2649 +[#2652]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2652 +[#2654]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2654 +[#2656]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2656 + + ## [3.2.0] - 2025-07-24 ### Added @@ -15,7 +78,7 @@ _No documentation available about unreleased changes as of yet._ This sniff warns when `get_*_meta()` and `get_metadata*()` functions are used with the `$meta_key`/`$key` param, but without the `$single` parameter as this could lead to unexpected behavior due to the different return types. - `WordPress-Extra`: the following additional sniffs have been added to the ruleset: `Generic.Strings.UnnecessaryHeredoc` and `Generic.WhiteSpace.HereNowdocIdentifierSpacing`. [#2534] - The `rest_sanitize_boolean()` functions to the list of known "sanitizing" functions. Props [@westonruter]. [#2530] -- End-user documentation to the following existing sniffs: `WordPress.DB.PreparedSQL` (props [@jaymcp], [#2454]), `WordPress.NamingConventions.ValidFunctionName` (props [@richardkorthuis] and [@rodrigoprimo], [#2452], [#2531]), `WordPress.NamingConventions.ValidVariableName` (props [@richardkorthuis], [#2457]). +- End-user documentation to the following existing sniffs: `WordPress.DB.PreparedSQL` (props [@jaymcp], [#2454]), `WordPress.NamingConventions.ValidFunctionName` (props [@richardkorthuis] and [@rodrigoprimo], [#2452], [#2531]), `WordPress.NamingConventions.ValidVariableName` (props [@richardkorthuis], [#2457]), `WordPress.PHP.DontExtract` (props [@aiolachiara], [#2456]). This documentation can be exposed via the [`PHP_CodeSniffer` `--generator=...` command-line argument](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Usage). ### Changed @@ -39,10 +102,7 @@ _No documentation available about unreleased changes as of yet._ - Various housekeeping, including documentation and test improvements. Includes contributions by [@rodrigoprimo] and [@szepeviktor]. - All sniffs are now also being tested against PHP 8.4 for consistent sniff results. [#2511] -### Deprecated - ### Removed - - The `Generic.Functions.CallTimePassByReference` has been removed from the `WordPress-Extra` ruleset. Props [@rodrigoprimo]. [#2536] This sniff was dated anyway and deprecated in PHP_CodeSniffer. If you need to check if your code is PHP cross-version compatible, use the [PHPCompatibility] standard instead. @@ -54,6 +114,7 @@ _No documentation available about unreleased changes as of yet._ [#2465]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2465 [#2452]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2452 [#2454]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2454 +[#2456]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2456 [#2457]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2457 [#2479]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2479 [#2500]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2500 @@ -78,7 +139,6 @@ _No documentation available about unreleased changes as of yet._ - The `sanitize_locale_name()` function to the list of known "sanitize & unslash" functions. Props [@Chouby] ### Changed - - The minimum required `PHP_CodeSniffer` version to 3.9.0 (was 3.7.2). - The minimum required `PHPCSUtils` version to 1.0.10 (was 1.0.8). - The minimum required `PHPCSExtra` version to 1.2.1 (was 1.1.0). @@ -95,24 +155,20 @@ _No documentation available about unreleased changes as of yet._ - Various housekeeping, includes a contribution from [@rodrigoprimo]. ### Fixed - - `WordPress.WP.PostsPerPage` could potentially result in an `Internal.Exception` when encountering a query string which doesn't include the value for `posts_per_page` in the query string. Props [@anomiex] for reporting. ## [3.0.1] - 2023-09-14 ### Added - - In WordPressCS 3.0.0, the functionality of the `WordPress.Security.EscapeOutput` sniff was updated to report unescaped message parameters passed to exceptions created in `throw` statements. This specific violation now has a separate error code: `ExceptionNotEscaped`. This will allow users to ignore or exclude that specific error code. Props [@anomiex]. The error code(s) for other escaping issues flagged by the sniff remain unchanged. ### Changed - - Updated the CI workflow to test the example ruleset for issues. - Funding files and updates in the Readme about funding the project. ### Fixed - - Fixed a sniff name in the `phpcs.xml.dist.sample` file (case-sensitive sniff name). Props [@dawidurbanski]. @@ -134,7 +190,6 @@ In all cases, please read the complete changelog carefully before you upgrade. ### Added - - Dependencies on the following packages: [PHPCSUtils](https://phpcsutils.com/), [PHPCSExtra](https://github.com/PHPCSStandards/PHPCSExtra) and the [Composer PHPCS plugin]. - A best effort has been made to add support for the new PHP syntaxes/features to all WordPressCS native sniffs and utility functions (or to verify/improve existing support). While support in external sniffs used by WordPressCS has not be exhaustively verified, a lot of work has been done to try and add support for new PHP syntaxes to those as well. @@ -221,7 +276,6 @@ More information is available in the [Upgrade Guide to WordPressCS 3.0.0 for Dev ### Changed - - As of this version, installation via Composer is the only supported manner of installation. Installing in a different manner (git clone/PEAR/PHAR) is still possible, but no longer supported. - The minimum required `PHP_CodeSniffer` version to 3.7.2 (was 3.3.1). @@ -310,9 +364,7 @@ More information is available in the [Upgrade Guide to WordPressCS 3.0.0 for Dev - `AbstractFunctionRestrictionsSniff`: The `whitelist` key in the `$groups` array property has been renamed to `allow`. - The `WordPress.NamingConventions.ValidFunctionName` sniff no longer extends the similar PHPCS native `PEAR` sniff. - ### Removed - - Support for the deprecated, old-style WordPressCS native ignore annotations. Use the PHPCS native selective ignore annotations instead. - The following WordPressCS native sniffs have been removed: - The `WordPress.Arrays.CommaAfterArrayItem` sniff (replaced by the `NormalizedArrays.Arrays.CommaAfterLast` and the `Universal.WhiteSpace.CommaSpacing` sniffs). @@ -368,9 +420,7 @@ More information is available in the [Upgrade Guide to WordPressCS 3.0.0 for Dev - `WordPressCS\WordPress\Sniff::valid_direct_scope()` method (use the `PHPCSUtils\Utils\Scopes::validDirectScope()` method instead). - Unused dev-only files in the (now removed) `bin` directory. - ### Fixed - - All sniffs which, in one way or another, check whether code represents a short list or a short array will now do so more accurately. This fixes various false positives and false negatives. - Sniffs supporting the `minimum_wp_version` property (previously `minimum_supported_version`) will no longer throw a "passing null to non-nullable" deprecation notice on PHP 8.1+. @@ -692,7 +742,6 @@ If you are a maintainer of an external standard based on WordPressCS and any of ### Changes since 2.0.0-RC1 #### Fixed - - `WordPress-Extra`: Reverted back to including the `Squiz.WhiteSpace.LanguageConstructSpacing` sniff instead of the new `Generic.WhiteSpace.LanguageConstructSpacing` sniff as the new sniff is not (yet) available when the PEAR install of PHPCS is used. ### Changes since 1.2.1 @@ -1666,6 +1715,7 @@ Initial tagged release. [PHPCompatibility]: https://github.com/PHPCompatibility/PHPCompatibility [Unreleased]: https://github.com/WordPress/WordPress-Coding-Standards/compare/main...HEAD +[3.3.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.2.0...3.3.0 [3.2.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.1.0...3.2.0 [3.1.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.0.1...3.1.0 [3.0.1]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.0.0...3.0.1 @@ -1699,17 +1749,22 @@ Initial tagged release. [2013-10-06]: https://github.com/WordPress/WordPress-Coding-Standards/compare/2013-06-11...2013-10-06 [@anomiex]: https://github.com/anomiex +[@aiolachiara]: https://github.com/aiolachiara [@Chouby]: https://github.com/Chouby [@ckanitz]: https://github.com/ckanitz [@craigfrancis]: https://github.com/craigfrancis [@davidperezgar]: https://github.com/davidperezgar [@dawidurbanski]: https://github.com/dawidurbanski [@desrosj]: https://github.com/desrosj +[@dingo-d]: https://github.com/dingo-d [@fredden]: https://github.com/fredden +[@GaryJones]: https://github.com/GaryJones [@grappler]: https://github.com/grappler [@Ipstenu]: https://github.com/Ipstenu [@jaymcp]: https://github.com/jaymcp [@JDGrimes]: https://github.com/JDGrimes +[@johnjago]: https://github.com/johnjago +[@jrfnl]: https://github.com/jrfnl [@khacoder]: https://github.com/khacoder [@Luc45]: https://github.com/Luc45 [@marconmartins]: https://github.com/marconmartins diff --git a/README.md b/README.md index 859b2f778c..5ebe6c660c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![codecov.io](https://codecov.io/gh/WordPress/WordPress-Coding-Standards/graph/badge.svg?token=UzFYn0RzVG&branch=develop)](https://codecov.io/gh/WordPress/WordPress-Coding-Standards?branch=develop) [![Minimum PHP Version](https://img.shields.io/packagist/php-v/wp-coding-standards/wpcs.svg?maxAge=3600)](https://packagist.org/packages/wp-coding-standards/wpcs) -[![Tested on PHP 5.4 to 8.4](https://img.shields.io/badge/tested%20on-PHP%205.4%20|%205.5%20|%205.6%20|%207.0%20|%207.1%20|%207.2%20|%207.3%20|%207.4%20|%208.0%20|%208.1%20|%208.2%20|%208.3%20|%208.4-green.svg?maxAge=2419200)](https://github.com/WordPress/WordPress-Coding-Standards/actions/workflows/unit-tests.yml) +[![Tested on PHP 7.2 to 8.5](https://img.shields.io/badge/tested%20on-PHP%207.2%20|%207.3%20|%207.4%20|%208.0%20|%208.1%20|%208.2%20|%208.3%20|%208.4%20|%208.5-green.svg?maxAge=2419200)](https://github.com/WordPress/WordPress-Coding-Standards/actions/workflows/unit-tests.yml) [![License: MIT](https://poser.pugx.org/wp-coding-standards/wpcs/license)](https://github.com/WordPress/WordPress-Coding-Standards/blob/develop/LICENSE) [![Total Downloads](https://poser.pugx.org/wp-coding-standards/wpcs/downloads)](https://packagist.org/packages/wp-coding-standards/wpcs/stats) @@ -48,12 +48,12 @@ This project is a collection of [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) rules (sniffs) to validate code developed for WordPress. It ensures code quality and adherence to coding conventions, especially the official [WordPress Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/). -This project needs funding. [Find out how you can help](#funding). +**This project needs funding. [Find out how you can help](#funding).** ## Minimum Requirements The WordPress Coding Standards package requires: -* PHP 5.4 or higher with the following extensions enabled: +* PHP 7.2 or higher with the following extensions enabled: - [Filter](https://www.php.net/book.filter) - [libxml](https://www.php.net/book.libxml) - [Tokenizer](https://www.php.net/book.tokenizer) @@ -157,13 +157,13 @@ The [PHPCompatibilityWP](https://github.com/PHPCompatibility/PHPCompatibilityWP) Install either as a separate ruleset and run it separately against your code or add it to your custom ruleset, like so: ```xml - + *\.php$ ``` -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 `7.0-` 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 `7.2-` to support the same PHP versions as WordPress Core supports. For more information about setting the `testVersion`, see: * [PHPCompatibility: Sniffing your code for compatibility with specific PHP version(s)](https://github.com/PHPCompatibility/PHPCompatibility#sniffing-your-code-for-compatibility-with-specific-php-versions) diff --git a/WordPress-Core/ruleset.xml b/WordPress-Core/ruleset.xml index f97bd73d90..c8f1bedfae 100644 --- a/WordPress-Core/ruleset.xml +++ b/WordPress-Core/ruleset.xml @@ -827,10 +827,6 @@ Ref: https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/#regular-expressions ############################################################################# --> - - - diff --git a/WordPress-Extra/ruleset.xml b/WordPress-Extra/ruleset.xml index f2b1b9e5ba..a939d1eb95 100644 --- a/WordPress-Extra/ruleset.xml +++ b/WordPress-Extra/ruleset.xml @@ -202,4 +202,10 @@ + + + + + + diff --git a/WordPress/Helpers/EscapingFunctionsTrait.php b/WordPress/Helpers/EscapingFunctionsTrait.php index 3df484ff62..b3fbf61d57 100644 --- a/WordPress/Helpers/EscapingFunctionsTrait.php +++ b/WordPress/Helpers/EscapingFunctionsTrait.php @@ -90,7 +90,6 @@ trait EscapingFunctionsTrait { 'urlencode_deep' => true, 'urlencode' => true, 'wp_json_encode' => true, - 'wp_kses_allowed_html' => true, 'wp_kses_data' => true, 'wp_kses_one_attr' => true, 'wp_kses_post' => true, diff --git a/WordPress/Helpers/ListHelper.php b/WordPress/Helpers/ListHelper.php index 99373b2aec..656757346a 100644 --- a/WordPress/Helpers/ListHelper.php +++ b/WordPress/Helpers/ListHelper.php @@ -9,8 +9,8 @@ namespace WordPressCS\WordPress\Helpers; -use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Files\File; +use PHPCSUtils\Exceptions\UnexpectedTokenType; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\Lists; @@ -67,7 +67,7 @@ public static function get_list_variables( File $phpcsFile, $stackPtr ) { try { $assignments = Lists::getAssignments( $phpcsFile, $stackPtr ); - } catch ( RuntimeException $e ) { + } catch ( UnexpectedTokenType $e ) { // Parse error/live coding. return array(); } diff --git a/WordPress/Helpers/MinimumWPVersionTrait.php b/WordPress/Helpers/MinimumWPVersionTrait.php index 74f0a2370b..b651e80bb6 100644 --- a/WordPress/Helpers/MinimumWPVersionTrait.php +++ b/WordPress/Helpers/MinimumWPVersionTrait.php @@ -79,7 +79,7 @@ trait MinimumWPVersionTrait { * * @var string WordPress version. */ - private $default_minimum_wp_version = '6.5'; + private $default_minimum_wp_version = '6.6'; /** * Overrule the minimum supported WordPress version with a command-line/config value. diff --git a/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php b/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php index 590f6478ea..3271a41874 100644 --- a/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php +++ b/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php @@ -19,7 +19,7 @@ /** * Flag Database direct queries. * - * @link https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#direct-database-queries + * @link https://docs.wpvip.com/php_codesniffer/warnings/#h-direct-database-queries * * @since 0.3.0 * @since 0.6.0 Removed the add_unique_message() function as it is no longer needed. @@ -82,7 +82,10 @@ final class DirectDatabaseQuerySniff extends Sniff { * @var array */ protected $cacheGetFunctions = array( - 'wp_cache_get' => true, + 'wp_cache_get' => true, + 'wp_cache_get_multiple' => true, + 'wp_cache_get_multiple_salted' => true, + 'wp_cache_get_salted' => true, ); /** @@ -95,8 +98,12 @@ final class DirectDatabaseQuerySniff extends Sniff { * @var array */ protected $cacheSetFunctions = array( - 'wp_cache_set' => true, - 'wp_cache_add' => true, + 'wp_cache_add' => true, + 'wp_cache_add_multiple' => true, + 'wp_cache_set' => true, + 'wp_cache_set_multiple' => true, + 'wp_cache_set_multiple_salted' => true, + 'wp_cache_set_salted' => true, ); /** @@ -109,18 +116,21 @@ final class DirectDatabaseQuerySniff extends 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, - 'clean_object_term_cache' => true, - 'clean_page_cache' => true, - 'clean_post_cache' => true, - 'clean_term_cache' => true, - 'clean_user_cache' => true, + 'wp_cache_delete' => true, + 'wp_cache_delete_multiple' => true, + 'wp_cache_flush_group' => true, + 'wp_cache_flush_runtime' => 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, ); /** @@ -229,18 +239,25 @@ public function process_token( $stackPtr ) { for ( $i = ( $scopeStart + 1 ); $i < $scopeEnd; $i++ ) { if ( \T_STRING === $this->tokens[ $i ]['code'] ) { + $nextNonEmpty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); - if ( isset( $this->cacheDeleteFunctions[ $this->tokens[ $i ]['content'] ] ) ) { + if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $nextNonEmpty ]['code'] ) { + continue; + } + + $content = strtolower( $this->tokens[ $i ]['content'] ); + + if ( isset( $this->cacheDeleteFunctions[ $content ] ) ) { if ( \in_array( $method, array( 'query', 'update', 'replace', 'delete' ), true ) ) { $cached = true; break; } - } elseif ( isset( $this->cacheGetFunctions[ $this->tokens[ $i ]['content'] ] ) ) { + } elseif ( isset( $this->cacheGetFunctions[ $content ] ) ) { $wp_cache_get = true; - } elseif ( isset( $this->cacheSetFunctions[ $this->tokens[ $i ]['content'] ] ) ) { + } elseif ( isset( $this->cacheSetFunctions[ $content ] ) ) { if ( $wp_cache_get ) { $cached = true; @@ -277,6 +294,8 @@ protected function mergeFunctionLists() { $this->cacheGetFunctions ); + $this->cacheGetFunctions = array_change_key_case( $this->cacheGetFunctions ); + $this->addedCustomFunctions['cacheget'] = $this->customCacheGetFunctions; } @@ -286,6 +305,8 @@ protected function mergeFunctionLists() { $this->cacheSetFunctions ); + $this->cacheSetFunctions = array_change_key_case( $this->cacheSetFunctions ); + $this->addedCustomFunctions['cacheset'] = $this->customCacheSetFunctions; } @@ -295,6 +316,8 @@ protected function mergeFunctionLists() { $this->cacheDeleteFunctions ); + $this->cacheDeleteFunctions = array_change_key_case( $this->cacheDeleteFunctions ); + $this->addedCustomFunctions['cachedelete'] = $this->customCacheDeleteFunctions; } } diff --git a/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php b/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php index 27bb4f8489..ba72cabc3c 100644 --- a/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php +++ b/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php @@ -266,8 +266,13 @@ public function process_token( $stackPtr ) { unset( $sprintf_parameters, $valid_sprintf, $last_param ); } elseif ( 'implode' === strtolower( $this->tokens[ $i ]['content'] ) ) { + $ignore_tokens = Tokens::$emptyTokens + array( + \T_STRING_CONCAT => \T_STRING_CONCAT, + \T_NS_SEPARATOR => \T_NS_SEPARATOR, + ); + $prev = $this->phpcsFile->findPrevious( - Tokens::$emptyTokens + array( \T_STRING_CONCAT => \T_STRING_CONCAT ), + $ignore_tokens, ( $i - 1 ), $query['start'], true diff --git a/WordPress/Sniffs/DB/PreparedSQLSniff.php b/WordPress/Sniffs/DB/PreparedSQLSniff.php index 7b6529ef90..05f0efc0db 100644 --- a/WordPress/Sniffs/DB/PreparedSQLSniff.php +++ b/WordPress/Sniffs/DB/PreparedSQLSniff.php @@ -207,10 +207,11 @@ static function ( $symbol ) { } if ( \T_STRING === $this->tokens[ $this->i ]['code'] ) { + $content_lowercase = strtolower( $this->tokens[ $this->i ]['content'] ); if ( - isset( $this->SQLEscapingFunctions[ $this->tokens[ $this->i ]['content'] ] ) - || isset( $this->SQLAutoEscapedFunctions[ $this->tokens[ $this->i ]['content'] ] ) + isset( $this->SQLEscapingFunctions[ $content_lowercase ] ) + || isset( $this->SQLAutoEscapedFunctions[ $content_lowercase ] ) ) { // Find the opening parenthesis. diff --git a/WordPress/Sniffs/DB/SlowDBQuerySniff.php b/WordPress/Sniffs/DB/SlowDBQuerySniff.php index 5cbd99ac61..061a3e7de8 100644 --- a/WordPress/Sniffs/DB/SlowDBQuerySniff.php +++ b/WordPress/Sniffs/DB/SlowDBQuerySniff.php @@ -14,7 +14,7 @@ /** * Flag potentially slow queries. * - * @link https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#uncached-pageload + * @link https://docs.wpvip.com/php_codesniffer/warnings/#h-functions-that-use-joins-taxonomy-relation-queries-cat-tax-queries-subselects-or-api-calls * * @since 0.3.0 * @since 0.13.0 Class name changed: this class is now namespaced. diff --git a/WordPress/Sniffs/DateTime/RestrictedFunctionsSniff.php b/WordPress/Sniffs/DateTime/RestrictedFunctionsSniff.php index 279da5735c..16c279b9bc 100644 --- a/WordPress/Sniffs/DateTime/RestrictedFunctionsSniff.php +++ b/WordPress/Sniffs/DateTime/RestrictedFunctionsSniff.php @@ -29,7 +29,7 @@ public function getGroups() { /* * Disallow the changing the timezone. * - * @link https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#manipulating-the-timezone-server-side + * @link https://docs.wpvip.com/php_codesniffer/errors/#h-manipulating-the-timezone-server-side */ 'timezone_change' => array( 'type' => 'error', diff --git a/WordPress/Sniffs/Files/FileNameSniff.php b/WordPress/Sniffs/Files/FileNameSniff.php index 72c3e3d44d..2cdbd96d74 100644 --- a/WordPress/Sniffs/Files/FileNameSniff.php +++ b/WordPress/Sniffs/Files/FileNameSniff.php @@ -10,8 +10,8 @@ namespace WordPressCS\WordPress\Sniffs\Files; use PHPCSUtils\Tokens\Collections; +use PHPCSUtils\Utils\FilePath; use PHPCSUtils\Utils\ObjectDeclarations; -use PHPCSUtils\Utils\TextStrings; use WordPressCS\WordPress\Helpers\IsUnitTestTrait; use WordPressCS\WordPress\Sniff; @@ -151,8 +151,7 @@ public function register() { * normal file processing. */ public function process_token( $stackPtr ) { - // Usage of `stripQuotes` is to ensure `stdin_path` passed by IDEs does not include quotes. - $file = TextStrings::stripQuotes( $this->phpcsFile->getFileName() ); + $file = FilePath::getName( $this->phpcsFile ); if ( 'STDIN' === $file ) { return $this->phpcsFile->numTokens; } @@ -197,7 +196,7 @@ public function process_token( $stackPtr ) { $this->check_filename_has_class_prefix( $class_ptr, $file_name ); } - if ( false !== strpos( $file, \DIRECTORY_SEPARATOR . 'wp-includes' . \DIRECTORY_SEPARATOR ) + if ( false !== strpos( $file, '/wp-includes/' ) && false === $class_ptr ) { $this->check_filename_for_template_suffix( $stackPtr, $file_name ); diff --git a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php index 686075042c..fb5b1bdb02 100644 --- a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php +++ b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php @@ -149,7 +149,7 @@ final class PrefixAllGlobalsSniff extends AbstractFunctionParameterSniff { * Only overrulable constants are listed, i.e. those defined within core within * a `if ( ! defined() ) {}` wrapper. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 1.0.0 * @since 3.0.0 Renamed from `$whitelisted_core_constants` to `$allowed_core_constants`. @@ -203,7 +203,7 @@ final class PrefixAllGlobalsSniff extends AbstractFunctionParameterSniff { * * Note: deprecated functions should still be included in this list as plugins may support older WP versions. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0. * @@ -342,7 +342,11 @@ final class PrefixAllGlobalsSniff extends AbstractFunctionParameterSniff { 'wp_cache_flush_group' => true, 'wp_cache_flush_runtime' => true, 'wp_cache_get_multiple' => true, + 'wp_cache_get_multiple_salted' => true, + 'wp_cache_get_salted' => true, 'wp_cache_set_multiple' => true, + 'wp_cache_set_multiple_salted' => true, + 'wp_cache_set_salted' => true, 'wp_cache_supports' => true, 'wp_check_password' => true, 'wp_clear_auth_cookie' => true, @@ -392,7 +396,7 @@ final class PrefixAllGlobalsSniff extends AbstractFunctionParameterSniff { * * Note: deprecated classes should still be included in this list as plugins may support older WP versions. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0. * @@ -407,6 +411,7 @@ final class PrefixAllGlobalsSniff extends AbstractFunctionParameterSniff { 'TwentyTwenty_Walker_Comment' => true, 'TwentyTwenty_Walker_Page' => true, 'Twenty_Twenty_One_Customize' => true, + 'WP_Block_Cloner' => true, 'WP_User_Search' => true, 'wp_atom_server' => true, // Deprecated. ); diff --git a/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php b/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php index 15d58edf9d..f5cb955b03 100644 --- a/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php +++ b/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php @@ -63,7 +63,7 @@ final class ValidPostTypeSlugSniff extends AbstractFunctionParameterSniff { * * Source: {@link https://developer.wordpress.org/reference/functions/register_post_type/#reserved-post-types} * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 2.2.0 * @@ -172,8 +172,8 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p ); // Warn for dynamic parts in the slug parameter. - if ( 'T_DOUBLE_QUOTED_STRING' === $this->tokens[ $string_pos ]['type'] - || ( 'T_HEREDOC' === $this->tokens[ $string_pos ]['type'] + if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $string_pos ]['code'] + || ( \T_HEREDOC === $this->tokens[ $string_pos ]['code'] && strpos( $this->tokens[ $string_pos ]['content'], '$' ) !== false ) ) { $this->phpcsFile->addWarning( diff --git a/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php b/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php index 6202f2b5e3..9d41e8b2ae 100644 --- a/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php +++ b/WordPress/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -29,7 +29,7 @@ * @since 2.0.0 Now offers name suggestions for variables in violation. * * Last synced with base class January 2022 at commit 4b49a952bf0e2c3863d0a113256bae0d7fe63d52. - * @link https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php + * @link https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/HEAD/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php */ final class ValidVariableNameSniff extends PHPCS_AbstractVariableSniff { diff --git a/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php b/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php index 72f77ad551..a6fedf78a9 100644 --- a/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php +++ b/WordPress/Sniffs/PHP/POSIXFunctionsSniff.php @@ -9,6 +9,7 @@ namespace WordPressCS\WordPress\Sniffs\PHP; +use PHP_CodeSniffer\Sniffs\DeprecatedSniff; use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff; /** @@ -22,8 +23,37 @@ * `WordPress.VIP.RestrictedFunctions` and the * `WordPress.PHP.DiscouragedPHPFunctions` sniffs. * @since 0.13.0 Class name changed: this class is now namespaced. + * + * @deprecated 3.3.0 Use the PHPCompatibility standard instead. */ -final class POSIXFunctionsSniff extends AbstractFunctionRestrictionsSniff { +final class POSIXFunctionsSniff extends AbstractFunctionRestrictionsSniff implements DeprecatedSniff { + + /** + * Provide the version number in which the sniff was deprecated. + * + * @return string + */ + public function getDeprecationVersion() { + return 'WordPressCS v3.3.0'; + } + + /** + * Provide the version number in which the sniff will be removed. + * + * @return string + */ + public function getRemovalVersion() { + return 'WordPressCS v4.0.0'; + } + + /** + * Provide a custom message to display with the deprecation. + * + * @return string + */ + public function getDeprecationMessage() { + return 'To scan for PHP cross-version compatibility issues, use the PHPCompatibility standard instead.'; + } /** * Groups of functions to restrict. diff --git a/WordPress/Sniffs/PHP/YodaConditionsSniff.php b/WordPress/Sniffs/PHP/YodaConditionsSniff.php index 150993c6a4..cff816119f 100644 --- a/WordPress/Sniffs/PHP/YodaConditionsSniff.php +++ b/WordPress/Sniffs/PHP/YodaConditionsSniff.php @@ -43,10 +43,9 @@ public function register() { $starters = Tokens::$booleanOperators; $starters += Tokens::$assignmentTokens; + $starters += Collections::ternaryOperators(); $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; diff --git a/WordPress/Sniffs/Security/EscapeOutputSniff.php b/WordPress/Sniffs/Security/EscapeOutputSniff.php index 7861940caa..e0714e36c4 100644 --- a/WordPress/Sniffs/Security/EscapeOutputSniff.php +++ b/WordPress/Sniffs/Security/EscapeOutputSniff.php @@ -200,38 +200,51 @@ public function process_token( $stackPtr ) { return parent::process_token( $stackPtr ); case \T_EXIT: - $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); - if ( false === $next_non_empty - || \T_OPEN_PARENTHESIS !== $this->tokens[ $next_non_empty ]['code'] - || isset( $this->tokens[ $next_non_empty ]['parenthesis_closer'] ) === false - ) { - // Live coding/parse error or an exit/die which doesn't pass a status code. Ignore. + $params = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); + if ( empty( $params ) ) { + // Live coding/parse error or an exit/die which doesn't pass a status. Ignore. return; } - // $end is not examined, so make sure the parentheses are balanced. - $start = $next_non_empty; - $end = ( $this->tokens[ $next_non_empty ]['parenthesis_closer'] + 1 ); - break; + // There should only be one parameter ($status), but just to be on the safe side. + foreach ( $params as $param ) { + $this->check_code_is_escaped( $param['start'], ( $param['end'] + 1 ) ); + } + + // Skip to the end of the last found parameter. + return ( $param['end'] + 1 ); case \T_THROW: // Find the open parentheses, while stepping over the exception creation tokens. - $ignore = Tokens::$emptyTokens; - $ignore += Collections::namespacedNameTokens(); - $ignore += Collections::functionCallTokens(); - $ignore += Collections::objectOperators(); - - $next_relevant = $this->phpcsFile->findNext( $ignore, ( $stackPtr + 1 ), null, true ); - if ( false === $next_relevant ) { - return; - } - - if ( \T_NEW === $this->tokens[ $next_relevant ]['code'] ) { + $ignore = Tokens::$emptyTokens; + $ignore += Collections::namespacedNameTokens(); + $ignore += Collections::functionCallTokens(); + $ignore += Collections::objectOperators(); + $ignore[ \T_READONLY ] = \T_READONLY; + + $next_relevant = $stackPtr; + do { $next_relevant = $this->phpcsFile->findNext( $ignore, ( $next_relevant + 1 ), null, true ); if ( false === $next_relevant ) { return; } - } + + if ( \T_NEW === $this->tokens[ $next_relevant ]['code'] ) { + continue; + } + + // Skip over attribute declarations when searching for the open parenthesis. + if ( \T_ATTRIBUTE === $this->tokens[ $next_relevant ]['code'] ) { + if ( isset( $this->tokens[ $next_relevant ]['attribute_closer'] ) === false ) { + return; + } + + $next_relevant = $this->tokens[ $next_relevant ]['attribute_closer']; + continue; + } + + break; + } while ( $next_relevant < ( $this->phpcsFile->numTokens - 1 ) ); if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $next_relevant ]['code'] || isset( $this->tokens[ $next_relevant ]['parenthesis_closer'] ) === false @@ -602,8 +615,13 @@ protected function check_code_is_escaped( $start, $end, $code = 'OutputNotEscape if ( \T_STRING === $this->tokens[ $i ]['code'] || \T_VARIABLE === $this->tokens[ $i ]['code'] || isset( Collections::ooHierarchyKeywords()[ $this->tokens[ $i ]['code'] ] ) + || \T_NAMESPACE === $this->tokens[ $i ]['code'] ) { - $double_colon = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), $end, true ); + $skip_tokens = Tokens::$emptyTokens; + $skip_tokens[ \T_STRING ] = \T_STRING; + $skip_tokens[ \T_NS_SEPARATOR ] = \T_NS_SEPARATOR; + + $double_colon = $this->phpcsFile->findNext( $skip_tokens, ( $i + 1 ), $end, true ); if ( false !== $double_colon && \T_DOUBLE_COLON === $this->tokens[ $double_colon ]['code'] ) { diff --git a/WordPress/Sniffs/Security/NonceVerificationSniff.php b/WordPress/Sniffs/Security/NonceVerificationSniff.php index 0241147327..06b941fc79 100644 --- a/WordPress/Sniffs/Security/NonceVerificationSniff.php +++ b/WordPress/Sniffs/Security/NonceVerificationSniff.php @@ -309,8 +309,10 @@ private function has_nonce_check( $stackPtr, array $cache_keys, $allow_nonce_aft continue; } + $content_lc = \strtolower( $this->tokens[ $i ]['content'] ); + // If this is one of the nonce verification functions, we can bail out. - if ( isset( $this->nonceVerificationFunctions[ $this->tokens[ $i ]['content'] ] ) ) { + if ( isset( $this->nonceVerificationFunctions[ $content_lc ] ) ) { /* * Now, make sure it is a call to a global function. */ @@ -416,6 +418,8 @@ protected function mergeFunctionLists() { $this->nonceVerificationFunctions ); + $this->nonceVerificationFunctions = array_change_key_case( $this->nonceVerificationFunctions ); + $this->addedCustomNonceFunctions = $this->customNonceVerificationFunctions; } } diff --git a/WordPress/Sniffs/Security/PluginMenuSlugSniff.php b/WordPress/Sniffs/Security/PluginMenuSlugSniff.php index 0deac9e82c..7efb287f8c 100644 --- a/WordPress/Sniffs/Security/PluginMenuSlugSniff.php +++ b/WordPress/Sniffs/Security/PluginMenuSlugSniff.php @@ -15,7 +15,7 @@ /** * Warn about __FILE__ for page registration. * - * @link https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#using-__file__-for-page-registration + * @link https://docs.wpvip.com/php_codesniffer/warnings/#h-using-file-for-page-registration * * @since 0.3.0 * @since 0.11.0 Refactored to extend the new WordPressCS native diff --git a/WordPress/Sniffs/Security/ValidatedSanitizedInputSniff.php b/WordPress/Sniffs/Security/ValidatedSanitizedInputSniff.php index 6f46261b56..45ea8ce896 100644 --- a/WordPress/Sniffs/Security/ValidatedSanitizedInputSniff.php +++ b/WordPress/Sniffs/Security/ValidatedSanitizedInputSniff.php @@ -23,7 +23,7 @@ /** * Flag any non-validated/sanitized input ( _GET / _POST / etc. ). * - * @link https://github.com/WordPress/WordPress-Coding-Standards/issues/69 + * @link https://github.com/WordPress/WordPress-Coding-Standards/issues/72 * * @since 0.3.0 * @since 0.4.0 This class now extends the WordPressCS native `Sniff` class. @@ -175,7 +175,7 @@ static function ( $var_name ) { if ( false === $validated ) { $this->phpcsFile->addError( - 'Detected usage of a possibly undefined superglobal array index: %s. Use isset() or empty() to check the index exists before using it', + 'Detected usage of a possibly undefined superglobal array index: %s. Check that the array index exists before using it.', $stackPtr, 'InputNotValidated', $error_data diff --git a/WordPress/Sniffs/Utils/I18nTextDomainFixerSniff.php b/WordPress/Sniffs/Utils/I18nTextDomainFixerSniff.php index af176326a6..1d85ea0e1d 100644 --- a/WordPress/Sniffs/Utils/I18nTextDomainFixerSniff.php +++ b/WordPress/Sniffs/Utils/I18nTextDomainFixerSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\BackCompat\Helper; +use PHPCSUtils\Utils\FilePath; use PHPCSUtils\Utils\GetTokensAsString; use PHPCSUtils\Utils\PassedParameters; use PHPCSUtils\Utils\TextStrings; @@ -675,7 +676,7 @@ public function process_comments( $stackPtr ) { $headers = $this->plugin_headers; $type = 'plugin'; - $file = TextStrings::stripQuotes( $this->phpcsFile->getFileName() ); + $file = FilePath::getName( $this->phpcsFile ); if ( 'STDIN' === $file ) { return; } diff --git a/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php b/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php index afd98fd2ac..40fb0c61c2 100644 --- a/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php +++ b/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php @@ -259,7 +259,7 @@ public function process_matched_token( $stackPtr, $group_name, $matched_content case 'file_get_contents': /* * Using `wp_remote_get()` will only work for remote URLs. - * See if we can determine is this function call is for a local file and if so, bow out. + * See if we can determine if this function call is for a local file and if so, bow out. */ $params = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); diff --git a/WordPress/Sniffs/WP/CapabilitiesSniff.php b/WordPress/Sniffs/WP/CapabilitiesSniff.php index fc2b0b34a9..758359f9c6 100644 --- a/WordPress/Sniffs/WP/CapabilitiesSniff.php +++ b/WordPress/Sniffs/WP/CapabilitiesSniff.php @@ -173,7 +173,7 @@ final class CapabilitiesSniff extends AbstractFunctionParameterSniff { * * List is sorted alphabetically. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0 * diff --git a/WordPress/Sniffs/WP/ClassNameCaseSniff.php b/WordPress/Sniffs/WP/ClassNameCaseSniff.php index 1c3ce88f24..ef8170e275 100644 --- a/WordPress/Sniffs/WP/ClassNameCaseSniff.php +++ b/WordPress/Sniffs/WP/ClassNameCaseSniff.php @@ -25,7 +25,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { * * Note: this list will be enhanced in the class constructor. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0 * @@ -115,6 +115,10 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'Walker_Page', 'Walker_PageDropdown', 'WP', + 'WP_Abilities_Registry', + 'WP_Ability', + 'WP_Ability_Categories_Registry', + 'WP_Ability_Category', 'WP_Admin_Bar', 'WP_Ajax_Response', 'WP_Ajax_Upgrader_Skin', @@ -124,6 +128,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'WP_Block', 'WP_Block_Bindings_Registry', 'WP_Block_Bindings_Source', + 'WP_Block_Cloner', 'WP_Block_Editor_Context', 'WP_Block_List', 'WP_Block_Metadata_Registry', @@ -132,6 +137,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'WP_Block_Parser_Frame', 'WP_Block_Pattern_Categories_Registry', 'WP_Block_Patterns_Registry', + 'WP_Block_Processor', 'WP_Block_Styles_Registry', 'WP_Block_Supports', 'WP_Block_Template', @@ -269,6 +275,9 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'WP_Privacy_Policy_Content', 'WP_Privacy_Requests_Table', 'WP_Query', + 'WP_REST_Abilities_V1_Categories_Controller', + 'WP_REST_Abilities_V1_List_Controller', + 'WP_REST_Abilities_V1_Run_Controller', 'WP_REST_Application_Passwords_Controller', 'WP_REST_Attachments_Controller', 'WP_REST_Autosaves_Controller', @@ -423,7 +432,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { * * Note: this list will be enhanced in the class constructor. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0 * @@ -455,7 +464,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { * * Note: this list will be enhanced in the class constructor. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.1.0 * @@ -477,7 +486,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { * * Note: this list will be enhanced in the class constructor. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0 * @@ -513,7 +522,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { * * Note: this list will be enhanced in the class constructor. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0 * @@ -521,9 +530,16 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { * The constructor will add the lowercased class name as a key to each entry. */ private $phpmailer_classes = array( + // Classes. + 'PHPMailer\\PHPMailer\\DSNConfigurator', 'PHPMailer\\PHPMailer\\Exception', + 'PHPMailer\\PHPMailer\\OAuth', 'PHPMailer\\PHPMailer\\PHPMailer', + 'PHPMailer\\PHPMailer\\POP3', 'PHPMailer\\PHPMailer\\SMTP', + + // Interfaces. + 'PHPMailer\\PHPMailer\\OAuthTokenProvider', ); /** @@ -531,7 +547,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { * * Note: this list will be enhanced in the class constructor. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0 * @@ -675,7 +691,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { * * Note: this list will be enhanced in the class constructor. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 3.0.0 * @@ -690,6 +706,8 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'SimplePie\Cache\Base', 'SimplePie\Cache\DataCache', 'SimplePie\Cache\NameFilter', + 'SimplePie\HTTP\Client', + 'SimplePie\HTTP\Response', 'SimplePie\RegistryAware', // Classes, SimplePie v1. @@ -750,7 +768,12 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'SimplePie\Exception', 'SimplePie\File', 'SimplePie\Gzdecode', + 'SimplePie\HTTP\ClientException', + 'SimplePie\HTTP\FileClient', 'SimplePie\HTTP\Parser', + 'SimplePie\HTTP\Psr7Response', + 'SimplePie\HTTP\Psr18Client', + 'SimplePie\HTTP\RawTextResponse', 'SimplePie\IRI', 'SimplePie\Item', 'SimplePie\Locator', diff --git a/WordPress/Sniffs/WP/CronIntervalSniff.php b/WordPress/Sniffs/WP/CronIntervalSniff.php index fd48e1a2f2..a93315daa9 100644 --- a/WordPress/Sniffs/WP/CronIntervalSniff.php +++ b/WordPress/Sniffs/WP/CronIntervalSniff.php @@ -22,7 +22,7 @@ /** * Flag cron schedules less than 15 minutes. * - * @link https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#cron-schedules-less-than-15-minutes-or-expensive-events + * @link https://docs.wpvip.com/php_codesniffer/warnings/#h-cron-schedules-less-than-15-minutes-or-expensive-events * * @since 0.3.0 * @since 0.11.0 - Extends the WordPressCS native `Sniff` class. diff --git a/WordPress/Sniffs/WP/DeprecatedClassesSniff.php b/WordPress/Sniffs/WP/DeprecatedClassesSniff.php index 8f67cb48f4..eec0d8f7cc 100644 --- a/WordPress/Sniffs/WP/DeprecatedClassesSniff.php +++ b/WordPress/Sniffs/WP/DeprecatedClassesSniff.php @@ -41,7 +41,7 @@ final class DeprecatedClassesSniff extends AbstractClassRestrictionsSniff { * * Version numbers should be fully qualified. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @var array */ diff --git a/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php b/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php index 745b9ea206..6b74d6294b 100644 --- a/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php +++ b/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php @@ -43,7 +43,7 @@ final class DeprecatedFunctionsSniff extends AbstractFunctionRestrictionsSniff { * To retrieve a function list for comparison, the following tool is available: * https://github.com/JDGrimes/wp-deprecated-code-scanner * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @var array */ @@ -1708,6 +1708,18 @@ final class DeprecatedFunctionsSniff extends AbstractFunctionRestrictionsSniff { 'alt' => 'wp_enqueue_classic_theme_styles()', 'version' => '6.8.0', ), + + // WP 6.9.0. + // Note: the deprecation of the (polyfilled) `utf8_encode()` and `utf8_decode()` functions is deliberately + // not listed here as these are polyfills for the PHP native functions, not WP native functions. + 'seems_utf8' => array( + 'alt' => 'wp_is_valid_utf8()', + 'version' => '6.9.0', + ), + 'wp_print_auto_sizes_contain_css_fix' => array( + 'alt' => 'wp_enqueue_img_auto_sizes_contain_css_fix()', + 'version' => '6.9.0', + ), ); /** diff --git a/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php b/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php index a4013892e0..1eefb6596c 100644 --- a/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php +++ b/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php @@ -43,7 +43,7 @@ final class DeprecatedParameterValuesSniff extends AbstractFunctionParameterSnif * looking for `_deprecated_argument()`. * The list is sorted alphabetically by function name. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 1.0.0 * @since 3.0.0 The format of the value has changed to support function calls diff --git a/WordPress/Sniffs/WP/DeprecatedParametersSniff.php b/WordPress/Sniffs/WP/DeprecatedParametersSniff.php index 9db68986e3..265035f249 100644 --- a/WordPress/Sniffs/WP/DeprecatedParametersSniff.php +++ b/WordPress/Sniffs/WP/DeprecatedParametersSniff.php @@ -50,7 +50,7 @@ final class DeprecatedParametersSniff extends AbstractFunctionParameterSniff { * * The functions are ordered alphabetically. * - * {@internal To be updated after every major release. Last updated for WordPress 6.8.1.} + * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} * * @since 0.12.0 * @@ -86,6 +86,13 @@ final class DeprecatedParametersSniff extends AbstractFunctionParameterSniff { 'version' => '5.9.0', ), ), + '_wp_can_use_pcre_u' => array( + 1 => array( + 'name' => 'set', + 'value' => null, + 'version' => '6.9.0', + ), + ), '_wp_post_revision_fields' => array( 2 => array( 'name' => 'deprecated', diff --git a/WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php b/WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php index 6e4385a923..28b4ba8d6b 100644 --- a/WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php +++ b/WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php @@ -15,10 +15,11 @@ use WordPressCS\WordPress\AbstractFunctionParameterSniff; /** - * This checks the enqueued 4th and 5th parameters to make sure the version and in_footer are set. + * This checks that the 4th ($ver) parameter is set for all enqueued resources and that the 5th ($in_footer) parameter + * is set for wp_register_script() and wp_enqueue_script(). * * If a source ($src) value is passed, then version ($ver) needs to have non-falsy value. - * If a source ($src) value is passed a check for in footer ($in_footer), warn the user if the value is falsy. + * If a source ($src) value is passed, then it is recommended to explicitly set the $in_footer parameter. * * @link https://developer.wordpress.org/reference/functions/wp_register_script/ * @link https://developer.wordpress.org/reference/functions/wp_enqueue_script/ @@ -53,20 +54,21 @@ final class EnqueuedResourceParametersSniff extends AbstractFunctionParameterSni ); /** - * False + the empty tokens array. + * False + T_NS_SEPARATOR + the empty tokens array. * * This array is enriched with the $emptyTokens array in the register() method. * * @var array */ private $false_tokens = array( - \T_FALSE => \T_FALSE, + \T_FALSE => \T_FALSE, + \T_NS_SEPARATOR => \T_NS_SEPARATOR, // Needed to handle fully qualified \false (PHPCS 3.x). ); /** * Token codes which are "safe" to accept to determine whether a version would evaluate to `false`. * - * This array is enriched with the several of the PHPCS token arrays in the register() method. + * This array is enriched with several of the PHPCS token arrays in the register() method. * * @var array */ @@ -88,7 +90,8 @@ final class EnqueuedResourceParametersSniff extends AbstractFunctionParameterSni /** * Returns an array of tokens this test wants to listen for. * - * Overloads and calls the parent method to allow for adding additional tokens to the $safe_tokens property. + * Overloads and calls the parent method to allow for adding additional tokens to the + * $false_tokens and $safe_tokens properties. * * @return array */ @@ -139,7 +142,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p } } - if ( false === $version_param || 'null' === $version_param['clean'] ) { + if ( false === $version_param || strtolower( ltrim( $version_param['clean'], '\\' ) ) === 'null' ) { $type = 'script'; if ( strpos( $matched_content, '_style' ) !== false ) { $type = 'style'; @@ -165,8 +168,8 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p /* * In footer Check * - * Check to make sure that $in_footer is set to true. - * It will warn the user to make sure it is intended. + * Check to make sure that $in_footer is explicitly set. + * Warn the user if it is not set. * * Only wp_register_script and wp_enqueue_script need this check, * as this parameter is not available to wp_register_style and wp_enqueue_style. @@ -227,6 +230,30 @@ protected function is_falsy( $start, $end ) { continue; } + // Make sure that when deprecated casts are used in the code under scan and the sniff is run on PHP 8.5, + // the eval() won't cause a deprecation notice, borking the scan of the file. + if ( \PHP_VERSION_ID >= 80500 ) { + if ( \T_INT_CAST === $this->tokens[ $i ]['code'] ) { + $code_string .= '(int)'; + continue; + } + + if ( \T_DOUBLE_CAST === $this->tokens[ $i ]['code'] ) { + $code_string .= '(float)'; + continue; + } + + if ( \T_BOOL_CAST === $this->tokens[ $i ]['code'] ) { + $code_string .= '(bool)'; + continue; + } + + if ( \T_BINARY_CAST === $this->tokens[ $i ]['code'] ) { + $code_string .= '(string)'; + continue; + } + } + $code_string .= $this->tokens[ $i ]['content']; } diff --git a/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php b/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php index c7ed63c303..28142e8aac 100644 --- a/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php +++ b/WordPress/Sniffs/WP/EnqueuedResourcesSniff.php @@ -9,8 +9,8 @@ namespace WordPressCS\WordPress\Sniffs\WP; -use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Exceptions\ValueError; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\TextStrings; use WordPressCS\WordPress\Sniff; @@ -18,8 +18,6 @@ /** * Makes sure scripts and styles are enqueued and not explicitly echo'd. * - * @link https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#inline-resources - * * @since 0.3.0 * @since 0.12.0 This class now extends the WordPressCS native `Sniff` class. * @since 0.13.0 Class name changed: this class is now namespaced. @@ -54,7 +52,7 @@ public function process_token( $stackPtr ) { try { $end_ptr = TextStrings::getEndOfCompleteTextString( $this->phpcsFile, $stackPtr ); $content = TextStrings::getCompleteTextString( $this->phpcsFile, $stackPtr ); - } catch ( RuntimeException $e ) { + } catch ( ValueError $e ) { // Parse error/live coding. return; } diff --git a/WordPress/Sniffs/WP/GetMetaSingleSniff.php b/WordPress/Sniffs/WP/GetMetaSingleSniff.php index 243f272507..76b576d109 100644 --- a/WordPress/Sniffs/WP/GetMetaSingleSniff.php +++ b/WordPress/Sniffs/WP/GetMetaSingleSniff.php @@ -33,6 +33,48 @@ final class GetMetaSingleSniff extends AbstractFunctionParameterSniff { */ const METRIC_NAME = 'get_*meta() function called with $single parameter'; + /** + * Relevant signature structure for generic meta functions. + * + * These functions use 'meta_key' as the parameter name at position 3 and + * 'single' as the parameter name at position 4. + * + * @since 3.3.0 + * + * @var array> + */ + private const GENERIC_META_FUNCTIONS_FORMAT = array( + 'condition' => array( + 'param_name' => 'meta_key', + 'position' => 3, + ), + 'recommended' => array( + 'param_name' => 'single', + 'position' => 4, + ), + ); + + /** + * Relevant signature structure for specific meta functions. + * + * These functions use 'key' as the parameter name at position 2 and + * 'single' as the parameter name at position 3. + * + * @since 3.3.0 + * + * @var array> + */ + private const SPECIFIC_META_FUNCTIONS_FORMAT = array( + 'condition' => array( + 'param_name' => 'key', + 'position' => 2, + ), + 'recommended' => array( + 'param_name' => 'single', + 'position' => 3, + ), + ); + /** * The group name for this group of functions. * @@ -45,10 +87,6 @@ final class GetMetaSingleSniff extends AbstractFunctionParameterSniff { /** * List of functions this sniff should examine. * - * {@internal Once support for PHP < 5.6 is dropped, it is possible to create two class constants - * representing the two different signatures of get meta functions to remove the duplication - * of the name and position of the parameters.} - * * @link https://developer.wordpress.org/reference/functions/get_comment_meta/ * @link https://developer.wordpress.org/reference/functions/get_metadata/ * @link https://developer.wordpress.org/reference/functions/get_metadata_default/ @@ -66,86 +104,14 @@ final class GetMetaSingleSniff extends AbstractFunctionParameterSniff { * the relevant parameters. */ protected $target_functions = array( - 'get_comment_meta' => array( - 'condition' => array( - 'param_name' => 'key', - 'position' => 2, - ), - 'recommended' => array( - 'param_name' => 'single', - 'position' => 3, - ), - ), - 'get_metadata' => array( - 'condition' => array( - 'param_name' => 'meta_key', - 'position' => 3, - ), - 'recommended' => array( - 'param_name' => 'single', - 'position' => 4, - ), - ), - 'get_metadata_default' => array( - 'condition' => array( - 'param_name' => 'meta_key', - 'position' => 3, - ), - 'recommended' => array( - 'param_name' => 'single', - 'position' => 4, - ), - ), - 'get_metadata_raw' => array( - 'condition' => array( - 'param_name' => 'meta_key', - 'position' => 3, - ), - 'recommended' => array( - 'param_name' => 'single', - 'position' => 4, - ), - ), - 'get_post_meta' => array( - 'condition' => array( - 'param_name' => 'key', - 'position' => 2, - ), - 'recommended' => array( - 'param_name' => 'single', - 'position' => 3, - ), - ), - 'get_site_meta' => array( - 'condition' => array( - 'param_name' => 'key', - 'position' => 2, - ), - 'recommended' => array( - 'param_name' => 'single', - 'position' => 3, - ), - ), - 'get_term_meta' => array( - 'condition' => array( - 'param_name' => 'key', - 'position' => 2, - ), - 'recommended' => array( - 'param_name' => 'single', - 'position' => 3, - ), - ), - 'get_user_meta' => array( - 'condition' => array( - 'param_name' => 'key', - 'position' => 2, - ), - 'recommended' => array( - 'param_name' => 'single', - 'position' => 3, - ), - ), + 'get_comment_meta' => self::SPECIFIC_META_FUNCTIONS_FORMAT, + 'get_metadata' => self::GENERIC_META_FUNCTIONS_FORMAT, + 'get_metadata_default' => self::GENERIC_META_FUNCTIONS_FORMAT, + 'get_metadata_raw' => self::GENERIC_META_FUNCTIONS_FORMAT, + 'get_post_meta' => self::SPECIFIC_META_FUNCTIONS_FORMAT, + 'get_site_meta' => self::SPECIFIC_META_FUNCTIONS_FORMAT, + 'get_term_meta' => self::SPECIFIC_META_FUNCTIONS_FORMAT, + 'get_user_meta' => self::SPECIFIC_META_FUNCTIONS_FORMAT, ); /** diff --git a/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php b/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php index ab2aa8bd91..934c3e686b 100644 --- a/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php +++ b/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php @@ -174,7 +174,7 @@ public function process_token( $stackPtr ) { protected function process_list_assignment( $stackPtr ) { $list_open_close = Lists::getOpenClose( $this->phpcsFile, $stackPtr ); if ( false === $list_open_close ) { - // Short array, not short list. + // Live coding or short array, not short list. return; } diff --git a/WordPress/Sniffs/WP/PostsPerPageSniff.php b/WordPress/Sniffs/WP/PostsPerPageSniff.php index e2575cbd43..6c62b920d9 100644 --- a/WordPress/Sniffs/WP/PostsPerPageSniff.php +++ b/WordPress/Sniffs/WP/PostsPerPageSniff.php @@ -16,7 +16,7 @@ /** * Flag returning high or infinite posts_per_page. * - * @link https://vip.wordpress.com/documentation/vip-go/code-review-blockers-warnings-notices/#no-limit-queries + * @link https://docs.wpvip.com/php_codesniffer/warnings/#h-no-limit-queries * * @since 0.3.0 * @since 0.13.0 Class name changed: this class is now namespaced. diff --git a/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php b/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php index f9b5038b74..f1770754b7 100644 --- a/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php +++ b/WordPress/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php @@ -26,7 +26,7 @@ * Last synced with base class 2021-11-20 at commit 7f11ffc8222b123c06345afd3261221561c3bb29. * Note: This class has diverged quite far from the original. All the same, checking occasionally * to see if there are upstream fixes made from which this sniff can benefit, is warranted. - * @link https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php + * @link https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/HEAD/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php */ final class ControlStructureSpacingSniff extends Sniff { diff --git a/WordPress/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php b/WordPress/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php index a832b4f2c6..9ad532eb3d 100644 --- a/WordPress/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php +++ b/WordPress/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php @@ -20,7 +20,7 @@ * - When the `::` operator is used in `::class`, no new line(s) before or after the object operator are allowed. * * @since 3.0.0 - * @link https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/src/Standards/Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php + * @link https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/HEAD/src/Standards/Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php */ final class ObjectOperatorSpacingSniff extends Squiz_ObjectOperatorSpacingSniff { diff --git a/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php index 2caa937df0..5e1ceb66be 100644 --- a/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php +++ b/WordPress/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -31,7 +31,7 @@ * @since 0.13.0 Class name changed: this class is now namespaced. * * Last verified with base class June 2023 at commit 085b1e091b0f2e451333c0bc26dd50bba39402c4. - * @link https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php + * @link https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/HEAD/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php */ final class OperatorSpacingSniff extends PHPCS_Squiz_OperatorSpacingSniff { diff --git a/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.inc b/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.1.inc similarity index 62% rename from WordPress/Tests/DB/DirectDatabaseQueryUnitTest.inc rename to WordPress/Tests/DB/DirectDatabaseQueryUnitTest.1.inc index b08d6bea2f..b8081ec578 100644 --- a/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.inc +++ b/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.1.inc @@ -21,13 +21,13 @@ function bar() { function baz() { global $wpdb; - $baz = wp_cache_get( 'baz' ); + $baz = WP_CACHE_GET( 'baz' ); if ( false !== $baz ) { $wpdb->query( 'ALTER TABLE TO ADD SOME FIELDS' ); // Warning x 2. $wpdb->query( $wpdb->prepare( 'CREATE TABLE ' ) ); // Warning x 2. $wpdb->query( 'SELECT QUERY' ); // Warning. $baz = $wpdb->get_results( $wpdb->prepare( 'SELECT X FROM Y ' ) ); // Warning. - wp_cache_set( 'baz', $baz ); + WP_cache_SET( 'baz', $baz ); } } @@ -66,7 +66,7 @@ function cache_delete_only() { $wpdb->get_row( 'SELECT X FROM Y' ); // Warning x 2. $wpdb->get_col( 'SELECT X FROM Y' ); // Warning x 2. - wp_cache_delete( 'key', 'group' ); + WP_CACHE_DELETE( 'key', 'group' ); } // It is OK to use the wp_cache_add() function instead of wp_cache_set(). @@ -93,7 +93,7 @@ function cache_add_instead_of_set() { $b = function () { global $wpdb; - if ( ! ( $listofthings = wp_cache_get( $foo ) ) ) { + if ( ! ( $listofthings = \Wp_Cache_Get( $foo ) ) ) { $listofthings = $wpdb->get_col( 'SELECT something FROM somewhere WHERE someotherthing = 1' ); // Warning. wp_cache_set( 'foo', $listofthings ); } @@ -333,6 +333,176 @@ function method_names_are_caseinsensitive() { $autoload = $wpdb->Get_Var( $wpdb->Prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option_name ) ); // Warning x 2. } -// Live coding/parse error test. -// This must be the last test in the file. -$wpdb->get_col( ' +// phpcs:set WordPress.DB.DirectDatabaseQuery customCacheGetFunctions[] MY_cacheget +// phpcs:set WordPress.DB.DirectDatabaseQuery customCacheSetFunctions[] my_CACHESET +// phpcs:set WordPress.DB.DirectDatabaseQuery customCacheDeleteFunctions[] MY_cachedel +function cache_custom_mixed_case_A() { + global $wpdb; + $quux = my_cacheget( 'quux' ); + if ( false !== $quux ) { + $wpdb->get_results( 'SELECT X FROM Y' ); // Warning direct DB call. + my_cacheset( 'key', 'group' ); + } +} + +function cache_custom_mixed_case_B() { + global $wpdb; + $wpdb->query( 'SELECT X FROM Y' ); // Warning direct DB call. + my_cachedel( 'key', 'group' ); +} + +// phpcs:set WordPress.DB.DirectDatabaseQuery customCacheGetFunctions[] +// phpcs:set WordPress.DB.DirectDatabaseQuery customCacheSetFunctions[] +// phpcs:set WordPress.DB.DirectDatabaseQuery customCacheDeleteFunctions[] + +// Protect against false negatives where the cache function names are used as the content +// of a T_STRING token that is not a function call. +function notCacheFunctionCalls() { + global $wpdb; + + $bar->wp_cache_get = 'something'; + $listofthings = $wpdb->get_col( 'SELECT something FROM somewhere WHERE someotherthing = 1' ); // Warning x 2. + $foo = wp_cache_set; + + return $listofthings; +} + +// The sniff deliberately does not distinguish between calls to cache functions and calls to methods with the same name as the functions, +// as those method calls are likely custom cache functions. +function methodNamesSameAsCacheFunctions() { + global $wpdb, $bar; + + if ( ! ( $listofthings = $bar->wp_cache_get( 'foo' ) ) ) { + $listofthings = $wpdb->get_col( 'SELECT something FROM somewhere WHERE someotherthing = 1' ); // Warning direct DB call. + $bar->wp_cache_set( 'foo', $listofthings ); + } + + return $listofthings; +} + +/* + * Safeguard correct handling of namespaced function calls. The sniff deliberately does not distinguish between calls to + * WP global cache functions and calls to namespaced functions that mirror the name of the WP global cache functions as + * those are likely custom cache functions. This is consistent with the behavior for method calls (see the + * methodNamesSameAsCacheFunctions() test above). + */ +function callToFullyQualifiedCacheGetSet() { + global $wpdb; + + if ( ! ( $listofthings = \wp_cache_get( $foo ) ) ) { + $listofthings = $wpdb->get_col( 'SELECT something FROM somewhere WHERE someotherthing = 1' ); // Warning direct DB call. + \wp_cache_set( 'foo', $listofthings ); + } + + return $listofthings; +} + +function callToQualifiedNamespacedCacheGetSet() { + global $wpdb; + + if ( ! ( $listofthings = MyNamespace\wp_cache_get( $foo ) ) ) { + $listofthings = $wpdb->get_col( 'SELECT something FROM somewhere WHERE someotherthing = 1' ); // Warning direct DB call. + MyNamespace\wp_cache_add( 'foo', $listofthings ); + } + + return $listofthings; +} + +function callToFullyQualifiedNamespacedCacheGetSet() { + global $wpdb; + + if ( ! ( $listofthings = \MyNamespace\wp_cache_get( $foo ) ) ) { + $listofthings = $wpdb->get_col( 'SELECT something FROM somewhere WHERE someotherthing = 1' ); // Warning direct DB call. + \MyNamespace\wp_cache_set( 'foo', $listofthings ); + } + + return $listofthings; +} + +function callToRelativeNamespacedCacheGetSet() { + global $wpdb; + + if ( ! ( $listofthings = namespace\wp_cache_get( $foo ) ) ) { + $listofthings = $wpdb->get_col( 'SELECT something FROM somewhere WHERE someotherthing = 1' ); // Warning direct DB call. + namespace\wp_cache_add( 'foo', $listofthings ); + } + + return $listofthings; +} + +function callToMultiLevelNamespaceRelativeCacheGetSet() { + global $wpdb; + + if ( ! ( $listofthings = namespace\Sub\wp_cache_get( $foo ) ) ) { + $listofthings = $wpdb->get_col( 'SELECT something FROM somewhere WHERE someotherthing = 1' ); // Warning direct DB call. + namespace\Sub\wp_cache_set( 'foo', $listofthings ); + } + + return $listofthings; +} + +function callToFullyQualifiedCacheDelete() { + global $wpdb; + + $wpdb->query( 'SELECT X FROM Y' ); // Warning direct DB call. + \wp_cache_delete( 'key', 'group' ); +} + +function callToQualifiedNamespacedCacheDelete() { + global $wpdb; + + $wpdb->query( 'SELECT X FROM Y' ); // Warning direct DB call. + MyNamespace\clean_attachment_cache( 1 ); +} + +function callToFullyQualifiedNamespacedCacheDelete() { + global $wpdb; + + $wpdb->query( 'SELECT X FROM Y' ); // Warning direct DB call. + \MyNamespace\CLEAN_POST_CACHE( 1 ); +} + +function callToRelativeNamespacedCacheDelete() { + global $wpdb; + + $wpdb->query( 'SELECT X FROM Y' ); // Warning direct DB call. + namespace\clean_user_cache( 1 ); +} + +function callToMultiLevelNamespaceRelativeCacheDelete() { + global $wpdb; + + $wpdb->query( 'SELECT X FROM Y' ); // Warning direct DB call. + namespace\Sub\clean_blog_cache( 1 ); +} + +// Handle more caching functions. +function cache_multiple() { + global $wpdb; + $data = wp_cache_get_multiple( ['keyA', 'keyB'] ); + if ( false !== $data ) { + $data = $wpdb->get_results( $query ); // Warning direct DB call. + \wp_cache_add_multiple( $data ); + } +} + +function cache_multiple_salted() { + global $wpdb; + $data = \wp_cache_get_multiple_salted( ...$params ); + if ( false !== $data ) { + $data = $wpdb->get_col( $query ); // Warning direct DB call. + wp_cache_set_multiple_salted( ...$params ); + } +} + +function cache_delete_multiple() { + global $wpdb; + $wpdb->replace( $query ); // Warning direct DB call. + WP_cache_delete_multiple( ['keyA', 'keyB'] ); +} + +function cache_flush_group() { + global $wpdb; + $wpdb->delete( $query ); // Warning direct DB call. + wp_cache_flush_group( 'group' ); +} diff --git a/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.2.inc b/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.2.inc new file mode 100644 index 0000000000..ed8c6b75a2 --- /dev/null +++ b/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.2.inc @@ -0,0 +1,8 @@ +get_col( ' diff --git a/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.php b/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.php index e12828a1aa..5a4fda7f4a 100644 --- a/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.php +++ b/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.php @@ -35,62 +35,87 @@ public function getErrorList() { /** * Returns the lines where warnings should occur. * + * @param string $testFile The name of the test file being run. + * * @return array Key is the line number, value is the number of expected warnings. */ - public function getWarningList() { - return array( - 5 => 2, - 12 => 1, - 26 => 2, - 27 => 2, - 28 => 1, - 29 => 1, - 38 => 2, - 44 => 2, - 60 => 1, - 61 => 1, - 62 => 1, - 63 => 1, - 65 => 2, - 66 => 2, - 67 => 2, - 80 => 1, - 81 => 1, - 82 => 1, - 83 => 1, - 84 => 1, - 85 => 1, - 86 => 1, - 97 => 1, - 114 => 1, - 123 => 1, - 130 => 1, - 141 => 1, - 150 => 2, - 157 => 2, - 168 => 2, - 175 => 1, - 180 => 1, - 185 => 1, - 190 => 1, - 195 => 1, - 200 => 1, - 205 => 1, - 210 => 1, - 215 => 1, - 220 => 1, - 228 => 2, - 235 => 2, - 251 => 1, - 252 => 1, - 265 => 1, - 269 => 1, - 281 => 1, - 287 => 2, - 288 => 1, - 300 => 1, - 306 => 2, - 333 => 2, - ); + public function getWarningList( $testFile = '' ) { + switch ( $testFile ) { + case 'DirectDatabaseQueryUnitTest.1.inc': + return array( + 5 => 2, + 12 => 1, + 26 => 2, + 27 => 2, + 28 => 1, + 29 => 1, + 38 => 2, + 44 => 2, + 60 => 1, + 61 => 1, + 62 => 1, + 63 => 1, + 65 => 2, + 66 => 2, + 67 => 2, + 80 => 1, + 81 => 1, + 82 => 1, + 83 => 1, + 84 => 1, + 85 => 1, + 86 => 1, + 97 => 1, + 114 => 1, + 123 => 1, + 130 => 1, + 141 => 1, + 150 => 2, + 157 => 2, + 168 => 2, + 175 => 1, + 180 => 1, + 185 => 1, + 190 => 1, + 195 => 1, + 200 => 1, + 205 => 1, + 210 => 1, + 215 => 1, + 220 => 1, + 228 => 2, + 235 => 2, + 251 => 1, + 252 => 1, + 265 => 1, + 269 => 1, + 281 => 1, + 287 => 2, + 288 => 1, + 300 => 1, + 306 => 2, + 333 => 2, + 343 => 1, + 350 => 1, + 364 => 2, + 376 => 1, + 393 => 1, + 404 => 1, + 415 => 1, + 426 => 1, + 437 => 1, + 447 => 1, + 454 => 1, + 461 => 1, + 468 => 1, + 475 => 1, + 484 => 1, + 493 => 1, + 500 => 1, + 506 => 1, + ); + default: + return array(); + } } } diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc index 0bbb2cf290..601877bfa4 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc @@ -148,7 +148,7 @@ $where = $wpdb->prepare( "{$wpdb->posts}.post_type IN (\"" . implode( ',', array_fill( 0, count($post_types), '%s' ) ) . "\") AND {$wpdb->posts}.post_status IN ('" - . implode( ',', array_fill( 0, count($post_statusses), '%s' ) ) + . \implode( ',', array_fill( 0, count($post_statusses), '%s' ) ) . '\')', array_merge( $post_types, $post_statusses ) ); // Bad x 2 - quotes between the () for the IN. diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc index 97122aa4e2..1f1a49076c 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc @@ -30,7 +30,7 @@ $all_post_meta = $wpdb->get_results( $wpdb->prepare( sprintf( ), $post_ids ) ); $wpdb->query( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . esc_sql( $foo ) . "';" ); // Ok. -$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . absint( $foo ) . ";" ); // Ok. +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . ABSINT( $foo ) . ";" ); // Ok. // Test multi-line strings. $all_post_meta = $wpdb->get_results( $wpdb->prepare( sprintf( @@ -79,7 +79,7 @@ $all_post_meta = $wpdb->get_results( $wpdb->prepare( sprintf( <<<'ND' AND `post_id` IN (%s) ND , $wpdb->postmeta, - IMPLODE( ',', array_fill( 0, count( $post_ids ), '%d' ) ) + IMPLODE( ',', array_fill( 0, COUNT( $post_ids ), '%d' ) ) ), $post_ids ) ); // OK. wpdb::prepare( "SELECT * FROM $wpdb?->posts WHERE post_title LIKE '" . foo() . "';" ); // Bad. diff --git a/WordPress/Tests/DB/RestrictedClassesUnitTest.php b/WordPress/Tests/DB/RestrictedClassesUnitTest.php index 23877253df..a07ace04d8 100644 --- a/WordPress/Tests/DB/RestrictedClassesUnitTest.php +++ b/WordPress/Tests/DB/RestrictedClassesUnitTest.php @@ -32,13 +32,10 @@ final class RestrictedClassesUnitTest extends AbstractSniffUnitTest { * Note: as that class extends the abstract FunctionRestrictions class, that's * where we are passing the parameters to. * - * @before - * * @return void */ - protected function enhanceGroups() { - parent::setUpPrerequisites(); - + protected function setUp(): void { + parent::setUp(); AbstractFunctionRestrictionsSniff::$unittest_groups = array( 'test' => array( 'type' => 'error', @@ -54,11 +51,9 @@ protected function enhanceGroups() { /** * Reset the $groups property. * - * @after - * * @return void */ - protected function resetGroups() { + protected function tearDown(): void { AbstractFunctionRestrictionsSniff::$unittest_groups = array(); parent::tearDown(); } diff --git a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.inc b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.inc index bb976bf8fe..3bebe1fdc2 100644 --- a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.inc +++ b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.inc @@ -94,3 +94,11 @@ WP_Date_Query::build_mysql_datetime(); // Ok. myFictionFunction(); // Bad. myFictional(); // OK. Myfictional(); // OK. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\mysql_connect(); +MyNamespace\mysql_connect(); +\MyNamespace\mysql_connect(); +namespace\mysql_connect(); // The sniff should start flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php index 73702af626..7b730c5f59 100644 --- a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php +++ b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php @@ -27,13 +27,10 @@ final class RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { * Add a number of extra restricted functions to unit test the abstract * AbstractFunctionRestrictionsSniff class. * - * @before - * * @return void */ - protected function enhanceGroups() { + protected function setUp(): void { parent::setUp(); - AbstractFunctionRestrictionsSniff::$unittest_groups = array( 'test-empty-functions-array' => array( 'type' => 'error', @@ -52,11 +49,9 @@ protected function enhanceGroups() { /** * Reset the $groups property. * - * @after - * * @return void */ - protected function resetGroups() { + protected function tearDown(): void { AbstractFunctionRestrictionsSniff::$unittest_groups = array(); parent::tearDown(); } @@ -68,54 +63,56 @@ protected function resetGroups() { */ public function getErrorList() { return array( - 25 => 1, - 26 => 1, - 27 => 1, - 28 => 1, - 29 => 1, - 30 => 1, - 31 => 1, - 32 => 1, - 33 => 1, + 25 => 1, + 26 => 1, + 27 => 1, + 28 => 1, + 29 => 1, + 30 => 1, + 31 => 1, + 32 => 1, + 33 => 1, + + 36 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 41 => 1, + 42 => 1, + 43 => 1, + 44 => 1, - 36 => 1, - 37 => 1, - 38 => 1, - 39 => 1, - 40 => 1, - 41 => 1, - 42 => 1, - 43 => 1, - 44 => 1, + 47 => 1, + 48 => 1, + 49 => 1, + 50 => 1, + 51 => 1, - 47 => 1, - 48 => 1, - 49 => 1, - 50 => 1, - 51 => 1, + 54 => 1, + 55 => 1, + 56 => 1, + 57 => 1, - 54 => 1, - 55 => 1, - 56 => 1, - 57 => 1, + 60 => 1, - 60 => 1, + 63 => 1, - 63 => 1, + 66 => 1, + 67 => 1, + 68 => 1, + 69 => 1, + 70 => 1, + 71 => 1, + 72 => 1, + 73 => 1, + 74 => 1, + 75 => 1, + 76 => 1, - 66 => 1, - 67 => 1, - 68 => 1, - 69 => 1, - 70 => 1, - 71 => 1, - 72 => 1, - 73 => 1, - 74 => 1, - 75 => 1, - 76 => 1, + 94 => 1, - 94 => 1, + 101 => 1, ); } diff --git a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.inc b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.inc index 25227b5178..f96842c9e7 100644 --- a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.inc +++ b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.inc @@ -7,3 +7,11 @@ $date->setTimezone( new DateTimeZone( 'America/Toronto' ) ); // Yay! $post_data['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( __( 'F j, Y' ), $now ), date( __( 'g:i a' ), $now ) ); // Error. $post_data['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), gmdate( __( 'F j, Y' ), $now ), gmdate( __( 'g:i a' ), $now ) ); // OK. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\date_default_timezone_set( 'Foo/Bar' ); +MyNamespace\date_default_timezone_set( 'Foo/Bar' ); +\MyNamespace\date_default_timezone_set( 'Foo/Bar' ); +namespace\date_default_timezone_set( 'Foo/Bar' ); // The sniff should start flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php index f5e160bc62..a2f29c2364 100644 --- a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php +++ b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php @@ -27,8 +27,9 @@ final class RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 3 => 1, - 8 => 2, + 3 => 1, + 8 => 2, + 14 => 1, ); } diff --git a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc index 9ce8176937..707468c137 100644 --- a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc +++ b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc @@ -137,7 +137,7 @@ class Acronym_Example { function do_something( $param = 'default' ) {} } -$acronym_class = new class { +$acronym_class = new readonly class { const SOME_CONSTANT = 'value'; public $var = 'abc'; @@ -260,10 +260,10 @@ $GLOBALS[ $something ] = 'value'; // Warning. $GLOBALS[ "{$something}_something" ] = 'value'; // Warning. $GLOBALS[ ${$something} ] = 'value'; // Warning. -define( ${$something}, 'value' ); // Warning. +DEFINE( ${$something}, 'value' ); // Warning. define( $something, 'value' ); // Warning. define( $something . '_CONSTANT', 'value' ); // Warning. -define( "{$something}_CONSTANT", 'value' ); // Warning. +\Define( "{$something}_CONSTANT", 'value' ); // Warning. define( $something . '_CONSTANT', 'value' ); // Warning. do_action( "{$acronym_filter_var}_hook_name" ); // Warning. @@ -673,4 +673,26 @@ class WP_Atom_Server { } } +/* + * Safeguard that PHP 8.4+ asymmetric visibility properties don't lead to false positives. + * Including those defined using constructor property promotion. + */ +class Acronym_AsymmetricVisibilityProperties { + public private(set) string $bar = 'bar'; // Ok. + + public function __construct(public protected(set) int $foo = 0) {} // Ok. +} + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\define('SOME_GLOBAL', [ 1, 2, 3 ]); // Bad. +MyNamespace\define('SOME_GLOBAL', [ 1, 2, 3 ]); // Ok. +\MyNamespace\define('SOME_GLOBAL', [ 1, 2, 3 ]); // Ok. +namespace\define('SOME_GLOBAL', [ 1, 2, 3 ]); // Ok. The sniff should start flagging this once it can resolve relative namespaces. +\do_action( 'plugin_action' ); // Bad. +MyNamespace\do_action( 'plugin_action' ); // Ok. +\MyNamespace\apply_filters( 'plugin_filter', $variable ); // Ok. +namespace\do_action_ref_array( 'plugin_action', array( $variable ) ); // Ok. The sniff should start flagging this once it can resolve relative namespaces. + // phpcs:set WordPress.NamingConventions.PrefixAllGlobals prefixes[] diff --git a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.3.inc b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.3.inc index be1eb3627f..e910701dba 100644 --- a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.3.inc +++ b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.3.inc @@ -12,7 +12,7 @@ class Some_Test extends \PHPUnit_Framework_TestCase { } } -$acronym_test = new class extends \PHPUnit_Framework_TestCase { +$acronym_test = new class extends \phpunit_framework_testcase { public function testPass() { define( 'SOME_GLOBAL', '4.0.0' ); @@ -21,4 +21,36 @@ $acronym_test = new class extends \PHPUnit_Framework_TestCase { } }; +// Test namespace resolution when the sniff checks the extending class. +class Extends_Namespaced_Class_Not_WP_Test extends WP_Font_Face_UnitTestCase { + + public function testPass() { + do_action( 'some-action', $something ); + } +} + +/* + * Safeguard correct handling of namespaced extending classes. + */ +class Extends_Partially_Qualified_Not_WP_Test extends MyNamespace\WP_UnitTestCase_Base { + + public function testPass() { + do_action( 'some-action', $something ); + } +} + +class Extends_Fully_Qualified_Not_WP_Test extends \MyNamespace\WP_Ajax_UnitTestCase { + + public function testPass() { + do_action( 'some-action', $something ); + } +} + +class Extends_Relative_Namespace_Not_WP_Test extends namespace\WP_Canonical_UnitTestCase { + + public function testPass() { + do_action( 'some-action', $something ); + } +} + // phpcs:set WordPress.NamingConventions.PrefixAllGlobals prefixes[] diff --git a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php index b9beb09ff6..b93ad6216d 100644 --- a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php +++ b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php @@ -96,6 +96,16 @@ public function getErrorList( $testFile = 'PrefixAllGlobalsUnitTest.1.inc' ) { 616 => 1, 617 => 1, 633 => 1, + 689 => 1, + 693 => 1, + ); + + case 'PrefixAllGlobalsUnitTest.3.inc': + return array( + 28 => 1, + 38 => 1, + 45 => 1, + 52 => 1, ); case 'PrefixAllGlobalsUnitTest.4.inc': @@ -106,8 +116,6 @@ public function getErrorList( $testFile = 'PrefixAllGlobalsUnitTest.1.inc' ) { case 'PrefixAllGlobalsUnitTest.2.inc': // Namespaced - all OK, fall through to the default case. - case 'PrefixAllGlobalsUnitTest.3.inc': - // Test class - non-prefixed constant is fine, fall through to the default case. default: return array(); } diff --git a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.1.inc similarity index 96% rename from WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc rename to WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.1.inc index 6a06ba3cab..4fa7a05ed8 100644 --- a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.inc +++ b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.1.inc @@ -223,6 +223,11 @@ function lähtöaika() {} // OK. function lÄhtÖaika() {} // Bad, but only handled by the sniff if Mbstring is available. function lÄhtOaika() {} // Bad, handled via transliteration of non-ASCII chars if Mbstring is not available. -// Live coding/parse error. -// This has to be the last test in the file. -function +/* + * Safeguard that PHP 8.3+ readonly anonymous classes are handled correctly. + */ +$anon_class = new readonly class() { + public function camelCase() {} // Bad. + protected function __something() {} // Bad. + private function snake_case() {} // Ok. +}; diff --git a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.2.inc b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.2.inc new file mode 100644 index 0000000000..4f73e110db --- /dev/null +++ b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.2.inc @@ -0,0 +1,8 @@ + Key is the line number, value is the number of expected errors. */ - public function getErrorList() { - return array( - 3 => 1, - 9 => 1, - 13 => 1, - 15 => 1, - 79 => 2, - 80 => 2, - 81 => 2, - 82 => 2, - 83 => 2, - 84 => 2, - 85 => 2, - 86 => 2, - 87 => 2, - 88 => 2, - 89 => 2, - 106 => 2, - 116 => 1, - 117 => 1, - 157 => 2, - 183 => 1, - 184 => 1, - 185 => 1, - 199 => 1, - 208 => 2, - 210 => 1, - 223 => function_exists( 'mb_strtolower' ) ? 1 : 0, - 224 => 1, - ); + public function getErrorList( $testFile = '' ) { + switch ( $testFile ) { + case 'ValidFunctionNameUnitTest.1.inc': + return array( + 3 => 1, + 9 => 1, + 13 => 1, + 15 => 1, + 79 => 2, + 80 => 2, + 81 => 2, + 82 => 2, + 83 => 2, + 84 => 2, + 85 => 2, + 86 => 2, + 87 => 2, + 88 => 2, + 89 => 2, + 106 => 2, + 116 => 1, + 117 => 1, + 157 => 2, + 183 => 1, + 184 => 1, + 185 => 1, + 199 => 1, + 208 => 2, + 210 => 1, + 223 => function_exists( 'mb_strtolower' ) ? 1 : 0, + 224 => 1, + 230 => 1, + 231 => 1, + ); + + default: + return array(); + } } /** diff --git a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.1.inc b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.1.inc index 830c3552d6..466c23e522 100644 --- a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.1.inc +++ b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.1.inc @@ -6,9 +6,9 @@ prefix_do_action( 'someAction' ); // Ok - not WP do_action. // Check for incorrect word separators. do_action( "admin_head-$hook_suffix" ); // Warning - use underscore. -do_action( 'admin_head.media.upload_popup' ); // Warning - use underscore. +DO_ACTION( 'admin_head.media.upload_popup' ); // Warning - use underscore. apply_filters( "bulk_actions {$this->screen->id}", $this->_actions ); // Warning - use underscore. -apply_filters( "current_theme/supports-{$feature}", true, $args, $_wp_theme_features[$feature] ); // Warning - use underscore. +\Apply_Filters( "current_theme/supports-{$feature}", true, $args, $_wp_theme_features[$feature] ); // Warning - use underscore. // Simple strings. do_action( "adminHead" ); // Error - use lowercase. @@ -121,3 +121,14 @@ do_action( 'admin_head_' . $fn( 'UPPERCASE', 'wrong-delimiter' ) . '_action' ); do_action_ref_array( hook: 'My-Hook', args: $args ); // OK. Well, not really, but using the wrong parameter name, so not our concern. do_action_ref_array( args: $args, hook_name: 'my_hook', ); // OK. do_action_ref_array( args: $args, hook_name: 'My-Hook', ); // Error - use lowercase + warning about dash. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\apply_filters( 'adminHead', $variable ); // Error. +MyNamespace\do_action( 'adminHead' ); // Ok. +\MyNamespace\do_action_ref_array( 'adminHead', array( $variable ) ); // Ok. +namespace\apply_filters_ref_array( 'adminHead', array( $variable ) ); // Ok. The sniff should start flagging this once it can resolve relative namespaces. +apply_filters( 'admin_head_' . MyNamespace\my_function('UPPERCASE') . '_action', $variable ); // Ok. +do_action( 'admin_head_' . \MyNamespace\my_function('UPPERCASE') . '_action' ); // Ok. +do_action_ref_array( 'admin_head_' . namespace\my_function('UPPERCASE') . '_action', array( $variable ) ); // Ok. diff --git a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php index aac97da195..98649fb620 100644 --- a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php @@ -75,6 +75,7 @@ public function getErrorList( $testFile = 'ValidHookNameUnitTest.1.inc' ) { 114 => 1, 115 => 1, 123 => 1, + 128 => 1, ); case 'ValidHookNameUnitTest.2.inc': diff --git a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.inc b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.inc index a8cac33a54..ec7c009cef 100644 --- a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.inc +++ b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.inc @@ -237,3 +237,11 @@ class Has_Mixed_Case_Property { $lähtöaika = true; // OK. $lÄhtÖaika = true; // Bad, but only handled by the sniff if Mbstring is available. $lÄhtOaika = true; // Bad, handled via transliteration of non-ASCII chars if Mbstring is not available. + +/* + * Safeguard that the sniff handles PHP 8.4+ asymmetric visibility properties correctly. + */ +class Acronym_AsymmetricVisibilityProperties { + public private(set) string $valid_name = 'bar'; // Ok. + public(set) string $invalidName = 'bar'; // Bad. +} diff --git a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php index 68b2b64a5c..0ac884c991 100644 --- a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php @@ -98,6 +98,7 @@ public function getErrorList() { 227 => 1, 238 => function_exists( 'mb_strtolower' ) ? 1 : 0, 239 => 1, + 246 => 1, ); } diff --git a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc index b07e7a8ce6..ada6fa9b4a 100644 --- a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc @@ -35,5 +35,11 @@ phpinfo(); // Error. Wrapper_Class::var_dump(); // OK, not the native PHP function. $wrapper ->var_dump(); // OK, not the native PHP function. -namespace\var_dump(); // OK as long as the file is namespaced. -MyNamespace\var_dump(); // OK, namespaced function. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\var_dump( $value ); +MyNamespace\var_dump( $value ); +\MyNamespace\var_dump( $value ); +namespace\var_dump( $value ); // The sniff should start flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php index 8be090a5bf..f3e953545c 100644 --- a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php @@ -52,6 +52,7 @@ public function getWarningList() { 24 => 1, 33 => 1, 34 => 1, + 42 => 1, ); } } diff --git a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc index 9a17c8a6d8..84b7378b65 100644 --- a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc @@ -1,12 +1,12 @@ 1, 9 => 1, 10 => 1, 12 => 1, diff --git a/WordPress/Tests/PHP/DontExtractUnitTest.inc b/WordPress/Tests/PHP/DontExtractUnitTest.inc index 9ed5c3d2b2..7cf000403c 100644 --- a/WordPress/Tests/PHP/DontExtractUnitTest.inc +++ b/WordPress/Tests/PHP/DontExtractUnitTest.inc @@ -7,3 +7,11 @@ my_extract(); // Ok. My_Object::extract(); // Ok. $this->extract(); // Ok. $my_object->extract(); // Ok. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\extract( array( 'a' => 1 ) ); +MyNamespace\extract( array( 'a' => 1 ) ); +\MyNamespace\extract( array( 'a' => 1 ) ); +namespace\extract( array( 'a' => 1 ) ); // The sniff should start flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/PHP/DontExtractUnitTest.php b/WordPress/Tests/PHP/DontExtractUnitTest.php index 3efee7c9a8..f9a1df7494 100644 --- a/WordPress/Tests/PHP/DontExtractUnitTest.php +++ b/WordPress/Tests/PHP/DontExtractUnitTest.php @@ -29,7 +29,8 @@ final class DontExtractUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 3 => 1, + 3 => 1, + 14 => 1, ); } diff --git a/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc b/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc index f007d150bd..27b3598537 100644 --- a/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc +++ b/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc @@ -12,7 +12,7 @@ if (@in_array($array, $needle)) { // Bad. } // File extension. -if ( @&file_exists( $filename ) && @ /*comment*/ is_readable( $filename ) ) { +if ( @&file_exists( $filename ) && @ /*comment*/ IS_READABLE( $filename ) ) { $file = @ \file( $filename ); } @@ -22,7 +22,7 @@ $fp = @fopen('https://www.example.com', 'r', false); // Directory extension. if (@is_dir($dir)) { - if ($dh = @\opendir($dir)) { + if ($dh = @\OPENDIR($dir)) { while (($file = @readdir($dh)) !== false) { // Bad. echo "filename: $file : filetype: " . @\filetype($dir . $file) . "\n"; } @@ -84,3 +84,11 @@ $decoded = @hex2bin( $data ); // phpcs:set WordPress.PHP.NoSilencedErrors context_length 0 echo @some_userland_function( $param ); // Bad. // phpcs:set WordPress.PHP.NoSilencedErrors context_length 6 + +/* + * Safeguard correct handling of namespaced function calls (fully qualified is already tested above). + */ +$file = @MyNS\MyClass::file_get_contents( $file ); // Bad. +$file = @MyNS\MyClass\file_exists( $file ); // Bad. +$file = @namespace\MyNS\MyClass::file( $file ); // Bad. +$file = @namespace\is_dir( $dir ); // The sniff should stop flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.php b/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.php index 7a576b992c..038c2c8d71 100644 --- a/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.php +++ b/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.php @@ -61,6 +61,10 @@ public function getWarningList() { 71 => 1, 78 => 1, 85 => 1, + 91 => 1, + 92 => 1, + 93 => 1, + 94 => 1, ); } } diff --git a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc index 785779e02b..b3bb11a71c 100644 --- a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc @@ -24,3 +24,11 @@ list( $year, $month, $day ) = split( ':', $date ); // Bad, split has been deprec $title_parts = spliti( ' ', get_the_title(), 4 ); // Bad, spliti also deprecated. Use preg_split instead. sql_regcase( 'Foo - bar.'); // Bad. Deprecated. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\split( ':', $date ); +MyNamespace\split( ':', $date ); +\MyNamespace\split( ':', $date ); +namespace\split( ':', $date ); // The sniff should start flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php index 7dbfcd6100..d4086c3e49 100644 --- a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php @@ -35,6 +35,7 @@ public function getErrorList() { 22 => 1, 24 => 1, 26 => 1, + 31 => 1, ); } diff --git a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc index 84dbd1d7b3..09465fa6c5 100644 --- a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc @@ -3,3 +3,11 @@ add_action( 'widgets_init', create_function( '', // Error. 'return register_widget( "time_more_on_time_widget" );' ) ); + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\create_function('', 'return;'); +MyNamespace\create_function('', 'return;'); +\MyNamespace\create_function('', 'return;'); +namespace\create_function('', 'return;'); // The sniff should start flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php index d930ba4329..5cfa75ae51 100644 --- a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php @@ -27,7 +27,8 @@ final class RestrictedPHPFunctionsUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return array( - 3 => 1, + 3 => 1, + 10 => 1, ); } diff --git a/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc b/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc index 5309a4693b..52c3a59976 100644 --- a/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc +++ b/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc @@ -538,7 +538,7 @@ _deprecated_function( __METHOD__, 'x.x.x', ClassName::class ); // OK. die( self::CLASS . ' has been abandoned' ); // OK. _deprecated_function( __METHOD__, 'x.x.x', parent::Class ); // OK. _deprecated_function( __METHOD__, 'x.x.x', static::class ); // OK. -echo 'Do not use ' . $object::class ); // OK. +echo 'Do not use ' . $object::class; // OK. /* * Examine the parameters passed for exception creation via throw. @@ -655,3 +655,35 @@ echo ''; // Bad. echo ''; // Bad. echo ''; // OK, well not really, typo in param name, but that's not our concern. echo ''; // Bad. + +// PHP 8.4: exit/die using named parameters. +exit( status: esc_html( $foo ) ); // Ok. +die( status: esc_html( $foo ) ); // Ok. + +exit( status: $foo ); // Bad. +die( status: $foo ); // Bad. + +/* + * Issue https://github.com/WordPress/WordPress-Coding-Standards/issues/2552 + * Ensure that readonly anonymous classes and anonymous classes with attributes are handled + * correctly when part of a throw statement. + */ +throw new #[MyAttribute] readonly class( esc_html( $message ) ) extends Exception {}; // Good. +throw new readonly class( $unescaped ) {}; // Bad. +throw new #[MyAttribute] class( $unescaped ) extends Exception {}; // Bad. +throw new +#[Attribute1] +/* some comment */ +#[Attribute2('text', 10)] +readonly class( $unescaped ) {}; // Bad. + +/* + * Safeguard correct handling of all types of namespaced function calls when *::class is used. + * + * Note: using ::class on fully qualified or namespace relative class names doesn't provide + * any real value in practice. + */ +_deprecated_function( __METHOD__, 'x.x.x', \ClassName::class ); // OK. +die( \MyNamespace\ClassName::class . ' has been abandoned' ); // OK. +echo 'Do not use ' . MyNamespace\ClassName::class; // OK. +_deprecated_function( __METHOD__, 'x.x.x', namespace\ClassName::class ); // OK. diff --git a/WordPress/Tests/Security/EscapeOutputUnitTest.21.inc b/WordPress/Tests/Security/EscapeOutputUnitTest.21.inc new file mode 100644 index 0000000000..249b60917a --- /dev/null +++ b/WordPress/Tests/Security/EscapeOutputUnitTest.21.inc @@ -0,0 +1,8 @@ + 1, 655 => 1, 657 => 1, + 663 => 1, + 664 => 1, + 672 => 1, + 673 => 1, + 678 => 1, ); case 'EscapeOutputUnitTest.6.inc': diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc index f9f98799f6..fc688e09de 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc @@ -250,7 +250,7 @@ function allow_for_unslash_before_noncecheck() { } function allow_for_unslash_in_sanitization() { - $var = sanitize_text_field( wp_unslash( $_POST['foo'] ) ); // OK. + $var = sanitize_text_field( WP_UNSLASH( $_POST['foo'] ) ); // OK. wp_verify_nonce( $var ); echo $var; } @@ -314,7 +314,7 @@ function allow_in_array_key_exists_before_noncecheck() { } function allow_in_key_exists_before_noncecheck() { - if (key_exists('foo', $_POST['subset']) === false) { // OK. + if (Key_Exists('foo', $_POST['subset']) === false) { // OK. return; } @@ -369,7 +369,7 @@ function disallow_for_non_array_comparison_in_condition() { } function allow_for_array_comparison_in_condition_with_named_params() { - if ( array_keys( filter_value: 'my_action', array: $_GET['actions'], strict: true, ) ) { // OK. + if ( \array_KEYS( filter_value: 'my_action', array: $_GET['actions'], strict: true, ) ) { // OK. check_admin_referer( 'foo' ); foo(); } @@ -486,3 +486,206 @@ enum MyEnum { echo $_POST['foo']; // OK. } } + +// Good, has a nonce check. Ensure the check is case-insensitive as function names are case-insensitive in PHP. +function ajax_process() { + CHECK_AJAX_REFERER( 'something' ); + + update_post_meta( (int) $_POST['id'], 'a_key', $_POST['a_value'] ); +} + +// phpcs:set WordPress.Security.NonceVerification customNonceVerificationFunctions[] MIXED_case_NAME +function non_ascii_characters() { + MIXED_case_NAME( $_POST['something'] ); // Passing $_POST to ensure the sniff bails correctly for variables inside the nonce verification function. + + update_post_meta( (int) $_POST['id'], 'a_key', $_POST['a_value'] ); +} +// phpcs:set WordPress.Security.NonceVerification customNonceVerificationFunctions[] + +/* + * Test case handling of non-ASCII characters in function names. + */ +// phpcs:set WordPress.Security.NonceVerification customNonceVerificationFunctions[] déjà_vu +function same_function_same_case() { + déjà_vu( 'something' ); // Ok. + + update_post_meta( (int) $_POST['id'], 'a_key', $_POST['a_value'] ); +} + +function same_function_different_case() { + DéJà_VU( 'something' ); // Ok. + + update_post_meta( (int) $_POST['id'], 'a_key', $_POST['a_value'] ); +} + +function different_function_name() { + dÉjÀ_vu( 'something' ); // Bad, dÉjÀ_vu() and déjà_vu() are NOT the same function. + + update_post_meta( (int) $_POST['id'], 'a_key', $_POST['a_value'] ); +} +// phpcs:set WordPress.Security.NonceVerification customNonceVerificationFunctions[] + +function test_fully_qualified_call_to_global_nonce_verification_function() { + if ( ! IS_NUMERIC( $_POST['foo'] ) ) { // OK. + return; + } + + \wp_verify_nonce( 'some_action' ); +} + +function test_namespace_relative_call_to_global_nonce_verification_function() { + if ( ! IS_NUMERIC( $_POST['foo'] ) ) { // Bad, but should become ok once the sniff is able to resolve relative namespaces. + return; + } + + namespace\wp_verify_nonce( 'some_action' ); +} + +function test_namespaced_calls_to_incorrect_nonce_verification_functions() { + if ( ! is_numeric( $_POST['foo'] ) ) { // Bad - none of the below are the WP global functions, so no nonce verification. + return; + } + + MyNamespace\wp_verify_nonce( 'some_action' ); + \MyNamespace\check_admin_referer( 'some_action' ); + namespace\Sub\check_ajax_referer( 'some_action' ); +} + +// phpcs:set WordPress.Security.NonceVerification customNonceVerificationFunctions[] my_nonce_check + +function test_namespace_relative_call_to_custom_nonce_verification_function() { + if ( ! is_int( $_POST['foo'] ) ) { // Bad, but should become ok once the sniff is able to resolve relative namespaces. + return; + } + + namespace\my_nonce_check( 'some_action' ); +} + +function test_namespaced_calls_to_incorrect_custom_nonce_verification_functions() { + if ( ! is_string( $_POST['foo'] ) ) { // Bad - none of the below are a custom nonce verification function. + return; + } + + MyNamespace\my_nonce_check( 'some_action' ); + \MyNamespace\my_nonce_check( 'some_action' ); + namespace\Sub\my_nonce_check( 'some_action' ); +} + +// phpcs:set WordPress.Security.NonceVerification customNonceVerificationFunctions[] + +function allow_fully_qualified_key_exists_functions() { + if ( \array_key_exists('foo', $_POST) === false ) { // OK. + return; + } + + \WP_VERIFY_NONCE( 'some_action' ); +} + +function allow_fully_qualified_key_exists_functions_with_mixed_case() { + if ( \Key_Exists('foo', $_POST) === false ) { // OK. + return; + } + + wp_verify_nonce( 'some_action' ); +} + +function allow_namespace_relative_call_to_global_key_exists_functions() { + if ( namespace\array_key_exists('foo', $_POST) === false ) { // Bad, but should become ok once the sniff is able to resolve relative namespaces. + return; + } + + wp_verify_nonce( 'some_action' ); +} + +function disallow_namespaced_key_exists_functions() { + if ( MyNamespace\array_key_exists( 'foo', $_POST ) === false // Bad. + || \MyNamespace\key_exists( 'foo', $_POST ) === false // Bad. + || namespace\Sub\key_exists( 'foo', $_POST ) === false // Bad. + ) { + return; + } + + wp_verify_nonce( 'some_action' ); +} + +function allow_fully_qualified_type_test_functions() { + if ( ! \is_numeric( $_POST['foo'] ) ) { // OK. + return; + } + + \wp_verify_nonce( 'some_action' ); +} + +function allow_fully_qualified_type_test_functions_uppercase() { + if ( ! \IS_int( $_POST['foo'] ) ) { // OK. + return; + } + + \wp_verify_nonce( 'some_action' ); +} + +function allow_namespace_relative_call_to_global_type_test_functions() { + if ( ! namespace\is_numeric( $_POST['foo'] ) ) { // Bad, but should become ok once the sniff is able to resolve relative namespaces. + return; + } + + \wp_verify_nonce( 'some_action' ); +} + +function disallow_namespaced_type_test_functions() { + if ( ! MyNamespace\is_bool( $_POST['foo'] ) // Bad. + || ! \MyNamespace\is_object( $_POST['foo'] ) // Bad. + || ! namespace\Sub\is_string( $_POST['foo'] ) ) // Bad. + { + return; + } + + \wp_verify_nonce( 'some_action' ); +} + +function allow_fully_qualified_array_comparison_functions() { + if ( \in_array( $_GET['action'], $valid_actions, true ) ) { // OK. + check_admin_referer( 'foo' ); + } +} + +function allow_namespace_relative_call_to_global_array_comparison_functions() { + if ( namespace\in_array( $_GET['action'], $valid_actions, true ) ) { // Bad, but should become ok once the sniff is able to resolve relative namespaces. + check_admin_referer( 'foo' ); + } +} + +function disallow_namespaced_array_comparison_functions() { + if ( MyNamespace\in_array( $_GET['action'], $valid_actions, true ) // Bad. + || \MyNamespace\array_search( array( 'subscribe', 'unsubscribe' ), $_GET['action'], true ) // Bad. + || namespace\Sub\array_keys( $_GET['actions'], 'my_action', true ) // Bad. + ) { + check_admin_referer( 'foo' ); + } +} + +function allow_fully_qualified_unslashing_functions() { + $var = \stripslashes_from_strings_only( $_POST['foo'] ); // OK. + wp_verify_nonce( $var ); + echo $var; +} + +function allow_fully_qualified_unslashing_functions_mixed_case() { + $var = \stripslashes_FROM_strings_ONLY( $_POST['foo'] ); // OK. + wp_verify_nonce( $var ); + echo $var; +} + +function allow_namespace_relative_call_to_global_unslashing_functions() { + $var = namespace\stripslashes_from_strings_only( $_POST['foo'] ); // Bad, but should become ok once the sniff is able to resolve relative namespaces. + wp_verify_nonce( $var ); + echo $var; +} + +function disallow_namespaced_unslashing_functions() { + $var = MyNamespace\stripslashes_from_strings_only( $_POST['foo'] ); // Bad. + $var = \MyNamespace\stripslashes_deep( $_POST['foo'] ); // Bad. + $var = namespace\Sub\wp_unslash( $_POST['foo'] ); // Bad. + wp_verify_nonce( $var ); + echo $var; +} diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.8.inc b/WordPress/Tests/Security/NonceVerificationUnitTest.8.inc index c2c134ddb7..7d3a70d8b6 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.8.inc +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.8.inc @@ -3,4 +3,5 @@ class IgnoreProperties { public $_GET = array( 'key' => 'something' ); // OK. public $_POST; // OK. + public private(set) string $_REQUEST; // Ok. } diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.php b/WordPress/Tests/Security/NonceVerificationUnitTest.php index edb18099c4..ea76b5a4b0 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.php +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.php @@ -74,6 +74,23 @@ public function getErrorList( $testFile = '' ) { 453 => 1, 470 => 1, 478 => 1, + 524 => 2, + 537 => 1, + 545 => 1, + 557 => 1, + 565 => 1, + 593 => 1, + 601 => 1, + 602 => 1, + 603 => 1, + 628 => 1, + 636 => 1, + 637 => 1, + 638 => 1, + 680 => 1, + 686 => 1, + 687 => 1, + 688 => 1, ); case 'NonceVerificationUnitTest.2.inc': @@ -106,6 +123,10 @@ public function getWarningList( $testFile = '' ) { return array( 365 => 1, 379 => 1, + 653 => 1, + 659 => 1, + 660 => 1, + 661 => 1, ); case 'NonceVerificationUnitTest.4.inc': diff --git a/WordPress/Tests/Security/SafeRedirectUnitTest.inc b/WordPress/Tests/Security/SafeRedirectUnitTest.inc index 690fbe70f0..3d8d19c02a 100644 --- a/WordPress/Tests/Security/SafeRedirectUnitTest.inc +++ b/WordPress/Tests/Security/SafeRedirectUnitTest.inc @@ -2,3 +2,11 @@ wp_redirect( $location ); // Warning. wp_safe_redirect( $location ); // OK. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\wp_redirect( $location ); +MyNamespace\wp_redirect( $location ); +\MyNamespace\wp_redirect( $location ); +namespace\wp_redirect( $location ); // The sniff should start flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/Security/SafeRedirectUnitTest.php b/WordPress/Tests/Security/SafeRedirectUnitTest.php index b62e6f86c8..358cdcfdd1 100644 --- a/WordPress/Tests/Security/SafeRedirectUnitTest.php +++ b/WordPress/Tests/Security/SafeRedirectUnitTest.php @@ -37,6 +37,7 @@ public function getErrorList() { public function getWarningList() { return array( 3 => 1, + 9 => 1, ); } } diff --git a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.4.inc b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.4.inc index d33d0a18e0..26f6cdd71f 100644 --- a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.4.inc +++ b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.4.inc @@ -77,11 +77,11 @@ is_textdomain_loaded( 'some-other-plugin' ); * Incorrect text domain, should be replaced. */ load_textdomain( 'text-domain', '/path/to/file.mo' ); -load_plugin_textdomain( 'text-domain', false, '/languages/' ); +\load_plugin_textdomain( 'text-domain', false, '/languages/' ); load_muplugin_textdomain( 'other-text-domain', '/languages/' ); load_theme_textdomain( 'third-text-domain', '/path/to/languages/' ); load_child_theme_textdomain( 'text-domain', '/path/to/languages/' ); -unload_textdomain( 'text-domain' ); +unload_TEXTDOMAIN( 'text-domain' ); __( $text, 'text-domain' ); _e( $text, 'text-domain' ); @@ -281,5 +281,13 @@ __ ( $args ); +/* + * Safeguard correct handling of namespaced function calls (partially qualified is already tested above). + */ +\MyNamespace\load_textdomain( 'text-domain', '/path/to/file.mo' ); +namespace\__( $text, 'text-domain' ); +\ESC_HTML__( $text, 'text-domain' ); +\get_translations_for_domain(); + // phpcs:set WordPress.Utils.I18nTextDomainFixer old_text_domain[] // phpcs:set WordPress.Utils.I18nTextDomainFixer new_text_domain false diff --git a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.4.inc.fixed b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.4.inc.fixed index f7fd2c6f4e..98f6adabfd 100644 --- a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.4.inc.fixed +++ b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.4.inc.fixed @@ -77,11 +77,11 @@ is_textdomain_loaded( 'some-other-plugin' ); * Incorrect text domain, should be replaced. */ load_textdomain( 'something-else', '/path/to/file.mo' ); -load_plugin_textdomain( 'something-else', false, '/languages/' ); +\load_plugin_textdomain( 'something-else', false, '/languages/' ); load_muplugin_textdomain( 'something-else', '/languages/' ); load_theme_textdomain( 'something-else', '/path/to/languages/' ); load_child_theme_textdomain( 'something-else', '/path/to/languages/' ); -unload_textdomain( 'something-else' ); +unload_TEXTDOMAIN( 'something-else' ); __( $text, 'something-else' ); _e( $text, 'something-else' ); @@ -287,5 +287,13 @@ __ ( 'something-else' ); +/* + * Safeguard correct handling of namespaced function calls (partially qualified is already tested above). + */ +\MyNamespace\load_textdomain( 'text-domain', '/path/to/file.mo' ); +namespace\__( $text, 'text-domain' ); +\ESC_HTML__( $text, 'something-else' ); +\get_translations_for_domain( 'something-else' ); + // phpcs:set WordPress.Utils.I18nTextDomainFixer old_text_domain[] // phpcs:set WordPress.Utils.I18nTextDomainFixer new_text_domain false diff --git a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php index a8c7c95e84..872050aa97 100644 --- a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php +++ b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php @@ -154,6 +154,8 @@ public function getErrorList( $testFile = '' ) { 245 => 1, 277 => 1, 278 => 1, + 289 => 1, + 290 => 1, ); default: diff --git a/WordPress/Tests/WP/CronIntervalUnitTest.inc b/WordPress/Tests/WP/CronIntervalUnitTest.inc index e9541f49d2..5ebc161d4c 100644 --- a/WordPress/Tests/WP/CronIntervalUnitTest.inc +++ b/WordPress/Tests/WP/CronIntervalUnitTest.inc @@ -165,9 +165,9 @@ add_filter( 'cron_schedules', function ( $schedules ) { class FQNConstants { public function add_schedules() { add_filter( 'cron_schedules', array( $this, 'add_weekly_schedule' ) ); // Ok: > 15 min. - add_filter( 'cron_schedules', array( $this, 'add_eight_minute_schedule' ) ); // Warning: 8 min. - add_filter( 'cron_schedules', array( $this, 'add_hundred_minute_schedule' ) ); // Warning: time undetermined. - add_filter( 'cron_schedules', array( $this, 'sneaky_fake_wp_constant_schedule' ) ); // Warning: time undetermined. + \add_filter( 'cron_schedules', array( $this, 'add_eight_minute_schedule' ) ); // Warning: 8 min. + ADD_FILTER( 'cron_schedules', array( $this, 'add_hundred_minute_schedule' ) ); // Warning: time undetermined. + \Add_Filter( 'cron_schedules', array( $this, 'sneaky_fake_wp_constant_schedule' ) ); // Warning: time undetermined. } public function add_weekly_schedule( $schedules ) { @@ -316,6 +316,7 @@ function first_class_weekly_schedule( $schedules ) { } add_filter( 'cron_schedules', 'first_class_weekly_schedule'(...)); // Ok: > 15 min. add_filter( 'cron_schedules', first_class_weekly_schedule(...)); // Ok: > 15 min. +add_filter( 'cron_schedules', namespace\first_class_weekly_schedule(...)); // Ok: > 15 min. function first_class_six_min_schedule( $schedules ) { $schedules['every_6_mins'] = array( @@ -326,3 +327,35 @@ function first_class_six_min_schedule( $schedules ) { } add_filter( 'cron_schedules', first_class_six_min_schedule(...)); // Warning: 6 min. add_filter( 'cron_schedules', 'first_class_six_min_schedule'(...)); // Warning: 6 min. +add_filter( 'cron_schedules', \first_class_six_min_schedule(...)); // Warning: 6 min. +add_filter( 'cron_schedules', namespace\first_class_six_min_schedule(...)); // Warning: 6 min. + +/* + * The tests below document the current behavior of the sniff, even though they are false negatives. The sniff treats + * the first-class callable examples below as if referencing the global function first_class_six_min_schedule() + * and not a namespaced function with the same name. + * + * Related to: https://github.com/WordPress/WordPress-Coding-Standards/issues/2644. + */ +add_filter( 'cron_schedules', MyNamespace\first_class_weekly_schedule(...)); // False negative - Ok: > 15 min, but should be marked `ChangeDetected`. +add_filter( 'cron_schedules', \MyNamespace\first_class_weekly_schedule(...)); // False negative - Ok: > 15 min, but should be marked `ChangeDetected`. +add_filter( 'cron_schedules', namespace\Sub\first_class_weekly_schedule(...)); // False negative - Ok: > 15 min, but should be marked `ChangeDetected`. + +/* + * The tests below document the current behavior of the sniff, even though they are false positives. The sniff treats + * the first-class callable examples below as if referencing the global function first_class_six_min_schedule() + * and not a namespaced function with the same name. Fixing this incorrect behavior is not trivial. + * + * Related to: https://github.com/WordPress/WordPress-Coding-Standards/issues/2644. + */ +add_filter( 'cron_schedules', MyNamespace\first_class_six_min_schedule(...)); // False positive - `CronSchedulesInterval` warning (6 min), but should be `ChangeDetected`. +add_filter( 'cron_schedules', \MyNamespace\first_class_six_min_schedule(...)); // False positive - `CronSchedulesInterval` warning (6 min), but should be `ChangeDetected`. +add_filter( 'cron_schedules', namespace\Sub\first_class_six_min_schedule(...)); // False positive - `CronSchedulesInterval` warning (6 min), but should be `ChangeDetected`. + +/* + * Safeguard correct handling of all types of namespaced function calls (except FQN global function call which is + * handled above). + */ +MyNamespace\add_filter( 'cron_schedules', 'unknown_callback' ); // Ok. +\MyNamespace\add_filter( 'cron_schedules', 'unknown_callback' ); // Ok. +namespace\add_filter( 'cron_schedules', 'unknown_callback' ); // Ok. The sniff should start flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/WP/CronIntervalUnitTest.php b/WordPress/Tests/WP/CronIntervalUnitTest.php index 81f02c2768..2417abaf41 100644 --- a/WordPress/Tests/WP/CronIntervalUnitTest.php +++ b/WordPress/Tests/WP/CronIntervalUnitTest.php @@ -65,8 +65,13 @@ public function getWarningList() { 286 => 1, 288 => 1, 290 => 1, - 327 => 1, 328 => 1, + 329 => 1, + 330 => 1, + 331 => 1, + 351 => 1, + 352 => 1, + 353 => 1, ); } } diff --git a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.inc b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.1.inc similarity index 99% rename from WordPress/Tests/WP/DeprecatedFunctionsUnitTest.inc rename to WordPress/Tests/WP/DeprecatedFunctionsUnitTest.1.inc index 5d8281d6cb..57a44658ef 100644 --- a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.inc +++ b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.1.inc @@ -418,14 +418,14 @@ the_block_template_skip_link(); wp_admin_bar_header(); wp_img_tag_add_decoding_attr(); wp_update_https_detection_errors(); - -/* - * Warning. - */ /* ============ WP 6.5 ============ */ block_core_file_ensure_interactivity_dependency(); block_core_image_ensure_interactivity_dependency(); block_core_query_ensure_interactivity_dependency(); + +/* + * Warning. + */ /* ============ WP 6.6 ============ */ wp_interactivity_process_directives_of_interactive_blocks(); wp_render_elements_support(); @@ -441,3 +441,6 @@ wp_targeted_link_rel(); wp_targeted_link_rel_callback(); /* ============ WP 6.8 ============ */ wp_add_editor_classic_theme_styles(); +/* ============ WP 6.9 ============ */ +seems_utf8(); +wp_print_auto_sizes_contain_css_fix(); diff --git a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.2.inc b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.2.inc new file mode 100644 index 0000000000..6c2a7bb8e1 --- /dev/null +++ b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.2.inc @@ -0,0 +1,9 @@ + Key is the line number, value is the number of expected errors. */ - public function getErrorList() { - $start_line = 8; - $end_line = 420; - $errors = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); + public function getErrorList( $testFile = '' ) { + switch ( $testFile ) { + case 'DeprecatedFunctionsUnitTest.1.inc': + $start_line = 8; + $end_line = 424; + $errors = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); + + // Unset the lines related to version comments. + unset( + $errors[10], + $errors[12], + $errors[14], + $errors[16], + $errors[29], + $errors[55], + $errors[57], + $errors[59], + $errors[73], + $errors[76], + $errors[80], + $errors[102], // Undeprecated function. + $errors[118], + $errors[125], + $errors[162], + $errors[175], + $errors[179], + $errors[211], + $errors[234], + $errors[252], + $errors[256], + $errors[263], + $errors[275], + $errors[282], + $errors[286], + $errors[291], + $errors[296], + $errors[304], + $errors[311], + $errors[319], + $errors[323], + $errors[330], + $errors[332], + $errors[337], + $errors[340], + $errors[344], + $errors[346], + $errors[353], + $errors[357], + $errors[359], + $errors[361], + $errors[363], + $errors[369], + $errors[371], + $errors[373], + $errors[383], + $errors[386], + $errors[410], + $errors[421] + ); + + return $errors; - // Unset the lines related to version comments. - unset( - $errors[10], - $errors[12], - $errors[14], - $errors[16], - $errors[29], - $errors[55], - $errors[57], - $errors[59], - $errors[73], - $errors[76], - $errors[80], - $errors[102], // Undeprecated function. - $errors[118], - $errors[125], - $errors[162], - $errors[175], - $errors[179], - $errors[211], - $errors[234], - $errors[252], - $errors[256], - $errors[263], - $errors[275], - $errors[282], - $errors[286], - $errors[291], - $errors[296], - $errors[304], - $errors[311], - $errors[319], - $errors[323], - $errors[330], - $errors[332], - $errors[337], - $errors[340], - $errors[344], - $errors[346], - $errors[353], - $errors[357], - $errors[359], - $errors[361], - $errors[363], - $errors[369], - $errors[371], - $errors[373], - $errors[383], - $errors[386], - $errors[410] - ); + case 'DeprecatedFunctionsUnitTest.2.inc': + return array( + 6 => 1, + ); - return $errors; + default: + return array(); + } } /** * Returns the lines where warnings should occur. * + * @param string $testFile The test file to check for warnings. + * * @return array Key is the line number, value is the number of expected warnings. */ - public function getWarningList() { - $start_line = 426; - $end_line = 443; - $warnings = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); + public function getWarningList( $testFile = '' ) { + switch ( $testFile ) { + case 'DeprecatedFunctionsUnitTest.1.inc': + $start_line = 430; + $end_line = 446; + $warnings = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); + + // Unset the lines related to version comments. + unset( + $warnings[432], + $warnings[442], + $warnings[444] + ); - // Unset the lines related to version comments. - unset( - $warnings[429], - $warnings[432], - $warnings[442] - ); + return $warnings; - return $warnings; + default: + return array(); + } } } diff --git a/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc b/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc index 1fc0c27b04..40d309e71a 100644 --- a/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc +++ b/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc @@ -95,7 +95,8 @@ wp_title_rss( 'deprecated' ); wp_upload_bits( '', 'deprecated' ); xfn_check( '', '', 'deprecated' ); global_terms( $foo, 'deprecated' ); - -// All will give an WARNING as they have been deprecated after WP 6.5. inject_ignored_hooked_blocks_metadata_attributes('', 'deprecated'); + +// All will give an WARNING as they have been deprecated after WP 6.6. wp_render_elements_support_styles('deprecated'); +_wp_can_use_pcre_u('deprecated'); diff --git a/WordPress/Tests/WP/DeprecatedParametersUnitTest.php b/WordPress/Tests/WP/DeprecatedParametersUnitTest.php index 3d516ec983..9f4d2d87d1 100644 --- a/WordPress/Tests/WP/DeprecatedParametersUnitTest.php +++ b/WordPress/Tests/WP/DeprecatedParametersUnitTest.php @@ -28,7 +28,7 @@ final class DeprecatedParametersUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { $start_line = 42; - $end_line = 97; + $end_line = 98; $errors = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); $errors[22] = 1; @@ -52,8 +52,8 @@ public function getErrorList() { */ public function getWarningList() { return array( - 100 => 1, 101 => 1, + 102 => 1, ); } } diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc index ea8ae72524..50302d54fc 100644 --- a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc +++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc @@ -80,10 +80,10 @@ enum BACKGROUND_COLOR: string implements Colorful {} // Safeguard support for PHP 8.0+ named parameters. define( case_insensitive: false, value: 'something' ); // OK. Well, not really as missing a required param, but that's not the concern of this sniff. -define( case_insensitive: false, constant_name: 'STYLESHEETPATH', value: 'something' ); // Bad. +\DEFINE( case_insensitive: false, constant_name: 'STYLESHEETPATH', value: 'something' ); // Bad. // Safeguard that comments in the parameters are ignored. -define( +Define( // Name. 'STYLESHEETPATH', // Value. @@ -102,3 +102,11 @@ enum ContainsConst { } echo HEADER_TEXTCOLOR::$var; // OK. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\define( 'HEADER_IMAGE', 'something' ); // Bad. +\MyNamespace\define( 'TEMPLATEPATH', 'something' ); // Ok. +MyNamespace\define( 'PLUGINDIR', 'something' ); // Ok. +namespace\define( 'MUPLUGINDIR', 'something' ); // Ok. diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php index e27fd838c2..138d0eb4ef 100644 --- a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php +++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php @@ -36,26 +36,27 @@ public function getErrorList() { */ public function getWarningList() { return array( - 50 => 1, - 51 => 1, - 52 => 1, - 53 => 1, - 54 => 1, - 55 => 1, - 56 => 1, - 57 => 1, - 58 => 1, - 59 => 1, - 60 => 1, - 61 => 1, - 63 => 1, - 64 => 1, - 66 => 1, - 67 => 1, - 71 => 1, - 72 => 1, - 83 => 1, - 88 => 1, + 50 => 1, + 51 => 1, + 52 => 1, + 53 => 1, + 54 => 1, + 55 => 1, + 56 => 1, + 57 => 1, + 58 => 1, + 59 => 1, + 60 => 1, + 61 => 1, + 63 => 1, + 64 => 1, + 66 => 1, + 67 => 1, + 71 => 1, + 72 => 1, + 83 => 1, + 88 => 1, + 109 => 1, ); } } diff --git a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.inc b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.inc index 5e952933d2..66a33f4984 100644 --- a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.inc +++ b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.inc @@ -14,8 +14,8 @@ $obj?->query_posts(); // OK, not the global function. // Ensure the sniff doesn't act on namespaced calls. MyNamespace\query_posts(); // OK, not the global function. +\MyNamespace\query_posts(); // OK, not the global function. namespace\query_posts(); // OK, not the global function. - // ... but does act on fully qualified function calls. \query_posts(); // Warning. diff --git a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.inc b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.1.inc similarity index 73% rename from WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.inc rename to WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.1.inc index b9c5d46614..03157513e4 100644 --- a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.inc +++ b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.1.inc @@ -29,7 +29,7 @@ wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), function() { }, true ); // OK. wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), $version, true ); // OK. -wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), '1.1.0' ); // Warning - In Footer is set to a falsy (default) value. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), '1.1.0' ); // Warning - $in_footer is not explicitly set. wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), '1.1.0', false ); // OK. wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), '1.1.0', null ); // OK. wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), '1.1.0', 0 ); // OK. @@ -84,5 +84,27 @@ wp_register_script( 'someScript-js', $url, [], 0_0.0_0, true ); // Error - 0, fa // Safeguard handling of PHP 8.1 explicit octals. wp_register_script( 'someScript-js', $url, [], 0o0, true ); // Error - 0, false or NULL are not allowed. -// Live coding/parse error. -wp_register_style( src: 'https://example.com/someScript.js', ver: /*to do*/, handle: 'someScript-js', ); +// Safeguard against PHP 8.5 deprecation of non-standard cast names. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), (boolean) 1, true ); // OK. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), (boolean) 0, true ); // Error - 0, false or NULL are not allowed. + +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), (integer) 1, true ); // OK. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), (integer) 0, true ); // Error - 0, false or NULL are not allowed. + +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), (double) 1, true ); // OK. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), (double) 0, true ); // Error - 0, false or NULL are not allowed. + +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), (binary) 0, true ); // Error - 0, false or NULL are not allowed. + +// Safeguard handling of non-lowercase `null`. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), NULL, true ); // Warning - 0, false or NULL are not allowed. + +/* + * Safeguard handling of fully qualified \true, \false and \null. + * Also safeguard that adding T_NS_SEPARATOR to $false_tokens doesn't cause false positives due to problems in is_falsy(). + */ +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), \FALSE, \true ); // Error - 0, false or NULL are not allowed. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), \null, \TRUE ); // Warning - 0, false or NULL are not allowed. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), \Null, true ); // Warning - 0, false or NULL are not allowed. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), \true, \False ); // Ok. +wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), \get_version(), \null ); // OK. diff --git a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.2.inc b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.2.inc new file mode 100644 index 0000000000..6a41502a2c --- /dev/null +++ b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.2.inc @@ -0,0 +1,5 @@ + Key is the line number, value is the number of expected errors. */ - public function getErrorList() { - return array( - 6 => 1, - 9 => 1, - 10 => 1, - 12 => 1, - 13 => 1, - 14 => 1, - 22 => 1, - 54 => 1, - 57 => 1, - 61 => 1, - 82 => 1, - 85 => 1, - 88 => 1, - ); + public function getErrorList( $testFile = '' ) { + switch ( $testFile ) { + case 'EnqueuedResourceParametersUnitTest.1.inc': + return array( + 6 => 1, + 9 => 1, + 10 => 1, + 12 => 1, + 13 => 1, + 14 => 1, + 22 => 1, + 54 => 1, + 57 => 1, + 61 => 1, + 82 => 1, + 85 => 1, + 89 => 1, + 92 => 1, + 95 => 1, + 97 => 1, + 106 => 1, + ); + + case 'EnqueuedResourceParametersUnitTest.2.inc': + return array( + 5 => 1, + ); + + default: + return array(); + } } /** * Returns the lines where warnings should occur. * + * @param string $testFile The name of the file being tested. + * * @return array Key is the line number, value is the number of expected warnings. */ - public function getWarningList() { - return array( - 3 => 2, - 11 => 1, - 32 => 1, - 39 => 2, - 42 => 1, - 45 => 1, - 66 => 2, - 77 => 1, - ); + public function getWarningList( $testFile = '' ) { + switch ( $testFile ) { + case 'EnqueuedResourceParametersUnitTest.1.inc': + return array( + 3 => 2, + 11 => 1, + 32 => 1, + 39 => 2, + 42 => 1, + 45 => 1, + 66 => 2, + 77 => 1, + 100 => 1, + 107 => 1, + 108 => 1, + ); + + default: + return array(); + } } } diff --git a/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.1.inc b/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.1.inc index 80603e7e8b..db57dc363b 100644 --- a/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.1.inc +++ b/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.1.inc @@ -311,6 +311,26 @@ list( get($year, $day) => &$not_a_wp_global[$year] ] = $array; -// Live coding/parse error. -// This has to be the last test in the file! -list( $tab, $tabs +/* + * Safeguard that PHP 8.4+ asymmetric visibility properties don't lead to false positives. + * Including those defined using constructor property promotion. + */ +class AsymmetricVisibilityProperties { + public private(set) string $pagenow = 'bar'; // Ok. + + public function __construct(public protected(set) int $page = 0) {} // Ok. +} + +/* + * $GLOBALS with non-string key, unable to determine which variable is being overridden. + */ +function globals_with_non_string_key() { + $GLOBALS[ "some{$name}" ] = 'test'; // Ok. + $GLOBALS[ $obj->global_name ] = 'test'; // Ok. + $GLOBALS[ MyClass::GLOBAL_NAME ] = 'test'; // Ok. + $GLOBALS[ GLOBAL_NAME_CONSTANT ] = 'test'; // Ok. + $GLOBALS[ get_my_global_name() ] = 'test'; // Ok. + $GLOBALS[ MyNamespace\get_my_global_name() ] = 'test'; // Ok. + $GLOBALS[ \MyNamespace\get_my_global_name() ] = 'test'; // Ok. + $GLOBALS[ namespace\get_my_global_name() ] = 'test'; // Ok. +} diff --git a/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.8.inc b/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.8.inc new file mode 100644 index 0000000000..7dba2caad2 --- /dev/null +++ b/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.8.inc @@ -0,0 +1,8 @@ + --> - + + + + diff --git a/composer.json b/composer.json index 37a8c46252..898bf88b73 100644 --- a/composer.json +++ b/composer.json @@ -16,18 +16,18 @@ } ], "require": { - "php": ">=5.4", + "php": ">=7.2", "ext-filter": "*", "ext-libxml": "*", "ext-tokenizer": "*", "ext-xmlreader": "*", - "squizlabs/php_codesniffer": "^3.13.0", + "squizlabs/php_codesniffer": "^3.13.4", "phpcsstandards/phpcsutils": "^1.1.0", - "phpcsstandards/phpcsextra": "^1.4.0" + "phpcsstandards/phpcsextra": "^1.5.0" }, "require-dev": { - "phpcompatibility/php-compatibility": "^9.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "phpunit/phpunit": "^8.0 || ^9.0", "phpcsstandards/phpcsdevtools": "^1.2.0", "php-parallel-lint/php-parallel-lint": "^1.4.0", "php-parallel-lint/php-console-highlighter": "^1.0.0" @@ -36,6 +36,8 @@ "ext-iconv": "For improved results", "ext-mbstring": "For improved results" }, + "minimum-stability": "dev", + "prefer-stable": true, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true diff --git a/phpcs.xml.dist.sample b/phpcs.xml.dist.sample index 64be6228c3..cfb232b862 100644 --- a/phpcs.xml.dist.sample +++ b/phpcs.xml.dist.sample @@ -78,7 +78,7 @@ https://github.com/PHPCompatibility/PHPCompatibility --> - + diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e9edfc43bc..b69c29b925 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - phpVersion: 70100 # Needs to be 70100 or higher... sigh... + phpVersion: 70200 level: 5 paths: - WordPress diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a5f12e7386..a7a2b1401f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@