diff --git a/.gitattributes b/.gitattributes index 3472eccbfa..78c933e3f4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,7 +8,7 @@ /.gitignore export-ignore /.codecov.yml export-ignore /.phpcs.xml.dist export-ignore -/CODE_OF_CONDUCT.md export-ignore +/_typos.toml export-ignore /phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore /.github export-ignore diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 337db8e8fa..80032cba98 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -29,7 +29,7 @@ We welcome contributions from everyone, and want your PR to have the best chance 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). + 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](https://github.com/WordPress/.github/blob/trunk/CODE_OF_CONDUCT.md). * **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. @@ -62,8 +62,8 @@ When you introduce new `public` sniff properties, or your sniff extends a class ### Pre-requisites * WordPress-Coding-Standards -* PHP_CodeSniffer 3.13.4 or higher -* PHPCSUtils 1.1.0 or higher +* PHP_CodeSniffer 3.13.5 or higher +* PHPCSUtils 1.2.2 or higher * PHPCSExtra 1.5.0 or higher * PHPUnit 8.x - 9.x diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2b9891a7f0..b9a37be052 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,8 +8,8 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" - time: "09:00" + interval: "cron" + cronjob: "10 22 5,20 * *" # At 22:10, every 5th and 20th day of the month. open-pull-requests-limit: 5 commit-message: prefix: "GH Actions:" diff --git a/.github/release-checklist.md b/.github/release-checklist.md index 37426e0d86..234999b496 100644 --- a/.github/release-checklist.md +++ b/.github/release-checklist.md @@ -15,6 +15,7 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH - [ ] PHPCSUtils: check if there have been [releases][phpcsutils-releases] since the last WordPressCS release and update WordPressCS code to take advantage of any new utilities - PR #xxx - [ ] PHPCSExtra: check if there have been [releases][phpcsextra-releases] since the last WordPressCS release and check through the changelog to see if there is anything WordPressCS could take advantage of - PR #xxx - [ ] Check if the minimum WP version property needs updating in `MinimumWPVersionTrait::$default_minimum_wp_version` and if so, action it - PR #xxx +- [ ] Check if the `minimum_wp_version` and `testVersion` properties in `phpcs.xml.dist.sample` need updating and if so, action it - PR #xxx - [ ] Check if any of the list based sniffs need updating and if so, action it. :pencil2: Make sure the "last updated" annotation in the docblocks for these lists has also been updated! List based sniffs: @@ -32,7 +33,7 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH - [ ] `$wp_time_constants` in `WordPress.WP.CronInterval` - PR #xxx - [ ] `$known_test_classes` in `IsUnitTestTrait` - PR #xxx - [ ] ...etc... -- [ ] Verify there there has been no vandalism on the wiki (and if so, remove/fix it). +- [ ] Verify there has been no vandalism on the wiki (and if so, remove/fix it). ### Release prep @@ -69,7 +70,7 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH - [ ] 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]. +- [ ] Submit for the ["Monthly Dev Roundup"][dev-roundup]. [phpcs-releases]: https://github.com/PHPCSStandards/PHP_CodeSniffer/releases [phpcsutils-releases]: https://github.com/PHPCSStandards/PHPCSUtils/releases diff --git a/.github/workflows/basic-qa.yml b/.github/workflows/basic-qa.yml index 10a1ec6263..9b489f7a7e 100644 --- a/.github/workflows/basic-qa.yml +++ b/.github/workflows/basic-qa.yml @@ -26,12 +26,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup PHP - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: 'latest' coverage: none @@ -50,7 +50,7 @@ jobs: phpcsstandards/phpcsextra:"${{ env.EXTRA_DEV }}" - name: Install Composer dependencies - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -66,38 +66,38 @@ jobs: # Validate the Ruleset XML files. # @link http://xmlsoft.org/xmllint.html - name: Validate the WordPress rulesets - uses: phpcsstandards/xmllint-validate@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 + uses: phpcsstandards/xmllint-validate@5189514594c8d5f4cf21b7e5af50f54d697973d7 # v2.0.0 with: pattern: "./*/ruleset.xml" xsd-file: "vendor/squizlabs/php_codesniffer/phpcs.xsd" - name: Validate the sample ruleset - uses: phpcsstandards/xmllint-validate@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 + uses: phpcsstandards/xmllint-validate@5189514594c8d5f4cf21b7e5af50f54d697973d7 # v2.0.0 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@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 + uses: phpcsstandards/xmllint-validate@5189514594c8d5f4cf21b7e5af50f54d697973d7 # v2.0.0 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@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 + uses: phpcsstandards/xmllint-validate@5189514594c8d5f4cf21b7e5af50f54d697973d7 # v2.0.0 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@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 + uses: phpcsstandards/xmllint-validate@5189514594c8d5f4cf21b7e5af50f54d697973d7 # v2.0.0 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@0fd9c4a9046055f621fca4bbdccb8eab1fd59fdc # v1.0.1 + uses: phpcsstandards/xmllint-validate@5189514594c8d5f4cf21b7e5af50f54d697973d7 # v2.0.0 with: pattern: "phpunit.xml.dist" xsd-file: "vendor/phpunit/phpunit/schema/9.2.xsd" @@ -117,7 +117,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false @@ -133,7 +133,7 @@ jobs: # Show XML violations inline in the file diff. - name: Enable showing XML issues inline - uses: korelstar/xmllint-problem-matcher@1bd292d642ddf3d369d02aaa8b262834d61198c0 # v1.2.0 + uses: korelstar/xmllint-problem-matcher@dd2ad21bd8a2de0187cb621419537f345e7e509c # v1.3.0 - name: Check the code-style consistency of the xml files run: | @@ -157,12 +157,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Set up PHP - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: ${{ matrix.php }} # Allow for PHP deprecation notices. @@ -182,7 +182,7 @@ jobs: run: composer config --unset lock - name: Install Composer dependencies - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: composer-options: --no-dev # Bust the cache at least once a month - output format: YYYY-MM. @@ -240,12 +240,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Install PHP - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: 'latest' coverage: none @@ -255,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@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1 + uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # 4.0.0 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -270,9 +270,9 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3" # v6.0.0 + uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0 with: persist-credentials: false - name: "Search for misspellings" - uses: "crate-ci/typos@626c4bedb751ce0b7f03262ca97ddda9a076ae1c" # v1.39.2 + uses: "crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14" # v1.48.0 diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index 5ee925db9e..5da0c17a35 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -30,12 +30,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Set up PHP - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: ${{ matrix.php }} # With stable PHPCS dependencies, allow for PHP deprecation notices. @@ -48,7 +48,7 @@ jobs: run: composer config --unset lock - name: Install Composer dependencies - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -75,7 +75,7 @@ jobs: - name: Send coverage report to Codecov if: ${{ success() && github.event.repository.fork == false && github.ref_name == 'develop' }} - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 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 2bcae652cc..3a9514793c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -74,7 +74,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false @@ -90,7 +90,7 @@ jobs: fi - name: Set up PHP - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: ${{ matrix.php }} ini-values: ${{ steps.set_ini.outputs.PHP_INI }} @@ -110,7 +110,7 @@ jobs: run: composer config --unset lock - name: Install Composer dependencies - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -137,7 +137,7 @@ jobs: - name: Send coverage report to Codecov if: ${{ success() && matrix.coverage == true && github.event.repository.fork == false }} - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: ./build/logs/clover.xml fail_ci_if_error: true diff --git a/CHANGELOG.md b/CHANGELOG.md index d08d7fbc7c..9fa867d25a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,94 @@ All notable changes to this project will be documented in this file. -This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/). +This project adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/). ## [Unreleased] _No documentation available about unreleased changes as of yet._ +## [3.4.0] - 2026-07-16 + +We're happy to welcome [@rodrigoprimo] as co-maintainer of WordPressCS as of this release. + +### Added +- `WordPress.Arrays.ArrayDeclarationSpacing`: new `allow_single_item_single_line_explicit_key_arrays` property to replace the, now deprecated, `allow_single_item_single_line_associative_arrays` property. [#2696] +- End-user documentation to the following existing sniffs: `WordPress.Arrays.ArrayDeclarationSpacing` (props [@RafaelFunchal], [@mattgaldino] and [@rodrigoprimo], [#2489], [#2593], [#2682]), `WordPress.CodeAnalysis.AssignmentInTernaryCondition` (props [@Nic-Sevic] and [@rodrigoprimo], [#2488], [#2678]), `WordPress.DB.RestrictedClasses` (props [@paulgibbs] and [@rodrigoprimo], [#2455], [#2689]), `WordPress.DB.DirectDatabaseQuery` (props [@jaymcp] and [@rodrigoprimo], [#2458], [#2697]), `WordPress.DB.RestrictedFunctions` (props [@paulgibbs] and [@rodrigoprimo], [#2453], [#2676]), `WordPress.DB.SlowDBQuery` (props [@petitphp] and [@rodrigoprimo], [#2464], [#2699]), `WordPress.PHP.DevelopmentFunctions` (props [@gogdzl] and [@rodrigoprimo], [#2490], [#2690]), `WordPress.PHP.NoSilencedErrors` (props [@gogdzl] and [@rodrigoprimo], [#2495], [#2694]), `WordPress.PHP.PregQuoteDelimiter` (props [@tikifez] and [@rodrigoprimo], [#2487], [#2677]), `WordPress.PHP.RestrictedPHPFunctions` (props [@gogdzl] and [@rodrigoprimo], [#2491], [#2693]), `WordPress.Security.PluginMenuSlug` (props [@jasonkenison], [#2592]), `WordPress.WP.AlternativeFunctions` (props [@pamprn09], [@bhubbard] and [@rodrigoprimo], [#2496], [#2588], [#2687]), `WordPress.WP.DiscouragedConstants` (props [@RafaelFunchal], [@paulopmt1] and [@rodrigoprimo], [#2493], [#2589], [#2680]), `WordPress.WP.GlobalVariablesOverride` (props [@paulopmt1] and [@rodrigoprimo], [#2586], [#2679]). + This documentation can be exposed via the [`PHP_CodeSniffer` `--generator=...` command-line argument](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Usage). + +### Changed +- The minimum required `PHP_CodeSniffer` version to 3.13.5 (was 3.13.4). [#2761] +- The minimum required `PHPCSUtils` version to 1.2.2 (was 1.1.0). [#2761] +- 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.7`. [#2757] +- `WordPress.NamingConventions.PrefixAllGlobals` has been updated to recognize pluggable functions introduced in WP up to WP 7.0.0. [#2747] +- `WordPress.WP.ClassNameCase` has been updated to recognize classes introduced in WP up to WP 7.0.0. [#2747] +- `WordPress.WP.DeprecatedFunctions` now detects functions deprecated in WordPress up to WP 7.0.0. [#2747] +- The `ConstantsHelper::is_use_of_global_constant()` method will no longer flag a constant alias created via an import `use` statement as it were the use of a global constant. [#2579] +- The `ConstantsHelper::is_in_function_call()` method will now act fully case-agnostic for the function names being checked. [#2706] + Previously, the `$valid_functions` parameter would need to be passed with the function names as keys in lowercase. +- `WordPress.PHP.NoSilencedErrors`: error silencing is no longer accepted for the `parse_url()` function. [#2701] +- Improved the wording of the error message for `WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound`. [#2688] +- Improved the wording of the error message for `WordPress.PHP.RestrictedPHPFunctions`. [#2702] +- Various housekeeping, including documentation and test improvements. Includes a contribution by [@dd32]. + +### Deprecated +- `WordPress.Arrays.ArrayDeclarationSpacing`: the `allow_single_item_single_line_associative_arrays` property has been deprecated in favor of the new `allow_single_item_single_line_explicit_key_arrays` property. [#2696] + This is a name change only. The functionality of these properties is the same. + +### Fixed +- `WordPress.DB.PreparedSQL` and `WordPress.DB.PreparedSQLPlaceholders`: false positive for static method calls to a non-global class named `wpdb`. [#2753] +- `WordPress.Security.EscapeOutput`: false positive for `get_search_query()` when the `$escaped` parameter was passed as fully qualified or non-lowercase `true`. [#2618] +- `WordPress.Security.EscapeOutput`: false negative for `_deprecated_file()` calls when the `basename( __FILE__ )` pattern used non-standard casing for either `basename()` and/or `__FILE__`. [#2729] +- `WordPress.WP.AlternativeFunctions`: false negative when class functions/constants/properties use the same name as select global WP constants/functions. [#2617] +- `WordPress.WP.AlternativeFunctions`: false positive for fully qualified references to the global PHP stream constants \STDIN, \STDOUT, and \STDERR. [#2617] +- `WordPress.WP.CronInterval`: false positive when the callback function reference used a different case than the function declaration, even though they are in the same file. [#2730] + +[#2453]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2453 +[#2455]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2455 +[#2458]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2458 +[#2464]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2464 +[#2487]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2487 +[#2488]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2488 +[#2489]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2489 +[#2490]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2490 +[#2491]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2491 +[#2493]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2493 +[#2495]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2495 +[#2496]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2496 +[#2579]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2579 +[#2586]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2586 +[#2588]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2588 +[#2589]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2589 +[#2592]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2592 +[#2593]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2593 +[#2617]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2617 +[#2618]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2618 +[#2676]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2676 +[#2677]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2677 +[#2678]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2678 +[#2679]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2679 +[#2680]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2680 +[#2682]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2682 +[#2687]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2687 +[#2688]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2688 +[#2689]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2689 +[#2690]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2690 +[#2693]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2693 +[#2694]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2694 +[#2696]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2696 +[#2697]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2697 +[#2699]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2699 +[#2701]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2701 +[#2702]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2702 +[#2706]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2706 +[#2729]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2729 +[#2730]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2730 +[#2747]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2747 +[#2753]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2753 +[#2757]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2757 +[#2761]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2761 + + ## [3.3.0] - 2025-11-25 ### Added @@ -1715,6 +1797,7 @@ Initial tagged release. [PHPCompatibility]: https://github.com/PHPCompatibility/PHPCompatibility [Unreleased]: https://github.com/WordPress/WordPress-Coding-Standards/compare/main...HEAD +[3.4.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.3.0...3.4.0 [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 @@ -1750,17 +1833,21 @@ Initial tagged release. [@anomiex]: https://github.com/anomiex [@aiolachiara]: https://github.com/aiolachiara +[@bhubbard]: https://github.com/bhubbard [@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 +[@dd32]: https://github.com/dd32 [@desrosj]: https://github.com/desrosj [@dingo-d]: https://github.com/dingo-d [@fredden]: https://github.com/fredden [@GaryJones]: https://github.com/GaryJones +[@gogdzl]: https://github.com/gogdzl [@grappler]: https://github.com/grappler [@Ipstenu]: https://github.com/Ipstenu +[@jasonkenison]: https://github.com/jasonkenison [@jaymcp]: https://github.com/jaymcp [@JDGrimes]: https://github.com/JDGrimes [@johnjago]: https://github.com/johnjago @@ -1768,10 +1855,18 @@ Initial tagged release. [@khacoder]: https://github.com/khacoder [@Luc45]: https://github.com/Luc45 [@marconmartins]: https://github.com/marconmartins +[@mattgaldino]: https://github.com/mattgaldino +[@Nic-Sevic]: https://github.com/Nic-Sevic [@NielsdeBlaauw]: https://github.com/NielsdeBlaauw +[@pamprn09]: https://github.com/pamprn09 +[@paulgibbs]: https://github.com/paulgibbs +[@paulopmt1]: https://github.com/paulopmt1 +[@petitphp]: https://github.com/petitphp +[@RafaelFunchal]: https://github.com/RafaelFunchal [@richardkorthuis]: https://github.com/richardkorthuis [@rodrigoprimo]: https://github.com/rodrigoprimo [@slaFFik]: https://github.com/slaFFik [@sandeshjangam]: https://github.com/sandeshjangam [@szepeviktor]: https://github.com/szepeviktor +[@tikifez]: https://github.com/tikifez [@westonruter]: https://github.com/westonruter diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index c0a53dc110..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement in -the [WordPress Slack](https://make.wordpress.org/chat/) in the [#core-coding-standards channel](https://wordpress.slack.com/archives/C5VCTJGH3). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/README.md b/README.md index 5ebe6c660c..c72802d69b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ @@ -252,6 +252,8 @@ At this moment, WordPressCS offer the following tools: See [CONTRIBUTING](.github/CONTRIBUTING.md), including information about [unit testing](.github/CONTRIBUTING.md#unit-testing) the standard. +Anyone contributing to the WordPress Coding Standards is expected to conduct themselves in accordance with the WordPress project's [Code of Conduct](https://github.com/WordPress/.github/blob/trunk/CODE_OF_CONDUCT.md). + ## Funding If you want to sponsor the work on WordPressCS, you can do so by donating to the [PHP_CodeSniffer Open Collective](https://opencollective.com/php_codesniffer). diff --git a/WordPress/AbstractClassRestrictionsSniff.php b/WordPress/AbstractClassRestrictionsSniff.php index e0f1c85802..d380522b3a 100644 --- a/WordPress/AbstractClassRestrictionsSniff.php +++ b/WordPress/AbstractClassRestrictionsSniff.php @@ -134,7 +134,7 @@ public function is_targetted_token( $stackPtr ) { $nameEnd = ( $this->phpcsFile->findNext( array( \T_OPEN_PARENTHESIS, \T_WHITESPACE, \T_SEMICOLON, \T_CLOSE_PARENTHESIS, \T_CLOSE_TAG ), ( $stackPtr + 2 ) ) - 1 ); } else { - $nameEnd = ( $this->phpcsFile->findNext( array( \T_CLOSE_CURLY_BRACKET, \T_WHITESPACE ), ( $stackPtr + 2 ) ) - 1 ); + $nameEnd = ( $this->phpcsFile->findNext( array( \T_OPEN_CURLY_BRACKET, \T_WHITESPACE ), ( $stackPtr + 2 ) ) - 1 ); } if ( isset( $this->tokens[ $stackPtr + 2 ] ) && false !== $nameEnd ) { diff --git a/WordPress/Docs/Arrays/ArrayDeclarationSpacingStandard.xml b/WordPress/Docs/Arrays/ArrayDeclarationSpacingStandard.xml new file mode 100644 index 0000000000..9b61acc2aa --- /dev/null +++ b/WordPress/Docs/Arrays/ArrayDeclarationSpacingStandard.xml @@ -0,0 +1,58 @@ + + + + + + + + 'cat' => 1, + 'paged' => 2, +); + ]]> + + + 'cat' => 1, 'paged' => 2 ); + ]]> + + + + + + + + 'post', + 'page', +); + +$args = array( + 'cat' => 1, + 'paged' => 2, +); + ]]> + + + 'post', 'page', +); + + +$args = array( + 'cat' => 1, 'paged' => 2, +); + ]]> + + + diff --git a/WordPress/Docs/CodeAnalysis/AssignmentInTernaryConditionStandard.xml b/WordPress/Docs/CodeAnalysis/AssignmentInTernaryConditionStandard.xml new file mode 100644 index 0000000000..5cb2579dba --- /dev/null +++ b/WordPress/Docs/CodeAnalysis/AssignmentInTernaryConditionStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + === 'a' ) ? 'b' : 'c'; + ]]> + + + = 'a' ) ? 'b' : 'c'; + ]]> + + + diff --git a/WordPress/Docs/DB/DirectDatabaseQueryStandard.xml b/WordPress/Docs/DB/DirectDatabaseQueryStandard.xml new file mode 100644 index 0000000000..d727c064e8 --- /dev/null +++ b/WordPress/Docs/DB/DirectDatabaseQueryStandard.xml @@ -0,0 +1,124 @@ + + + + + + + + WP_Query( + array( + 'post_type' => 'page', + ) +); + ]]> + + + $wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM %i + WHERE post_type = %s", + $wpdb->posts, + 'page' + ) +); + ]]> + + + + + + + + wp_cache_get( $key ); + + if ( false !== $cached ) { + return $cached; + } + + $results = $wpdb->get_col( + "SELECT ID FROM $wpdb->posts + WHERE post_status = 'draft'" + ); + + wp_cache_set( $key, $results ); + + return $results; +} + ]]> + + + get_col( + "SELECT ID FROM $wpdb->posts + WHERE post_status = 'draft'" + ); + + return $results; +} + ]]> + + + + + update( + $wpdb->posts, + array( 'post_title' => $title ), + array( 'ID' => $post_id ) + ); + + clean_post_cache( $post_id ); +} + ]]> + + + update( + $wpdb->posts, + array( 'post_title' => $title ), + array( 'ID' => $post_id ) + ); +} + ]]> + + + + + + + + + + + query( + "ALTER TABLE {$wpdb->posts} + ADD COLUMN rating int" +); + ]]> + + + diff --git a/WordPress/Docs/DB/RestrictedClassesStandard.xml b/WordPress/Docs/DB/RestrictedClassesStandard.xml new file mode 100644 index 0000000000..36094d6b9c --- /dev/null +++ b/WordPress/Docs/DB/RestrictedClassesStandard.xml @@ -0,0 +1,66 @@ + + + + + + + + get_posts(); + ]]> + + + new mysqli( + 'localhost', + $user, + $pass, + $db +); + +$results = $mysqli->query( + "SELECT * FROM wp_posts LIMIT 5" +); + ]]> + + + + + wp_insert_post( + array( 'post_title' => 'Title' ) +); + +// or... + +global $wpdb; +$wpdb->insert( + $wpdb->posts, + array( 'post_title' => 'Title' ), + array( '%s' ) +); + ]]> + + + new PDO( + $dsn, + $user, + $pass +); + +$stmt = $pdo->prepare( + "INSERT INTO wp_posts (post_title) + VALUES (?)" +); + +$stmt->execute( array( 'Title' ) ); + ]]> + + + diff --git a/WordPress/Docs/DB/RestrictedFunctionsStandard.xml b/WordPress/Docs/DB/RestrictedFunctionsStandard.xml new file mode 100644 index 0000000000..75defe7fba --- /dev/null +++ b/WordPress/Docs/DB/RestrictedFunctionsStandard.xml @@ -0,0 +1,53 @@ + + + + + + + + get_posts(); + ]]> + + + mysqli_query( + $mysql, + "SELECT * FROM wp_posts LIMIT 5" +); + ]]> + + + + + wp_insert_post( + array( 'post_title' => 'Title' ) +); + +// or... + +global $wpdb; +$wpdb->insert( + $wpdb->posts, + array( 'post_title' => 'Title' ), + array( '%s' ) +); + ]]> + + + mysqli_query( + $mysql, + "INSERT INTO wp_posts (post_title) + VALUES ('Title')" +); + ]]> + + + diff --git a/WordPress/Docs/DB/SlowDBQueryStandard.xml b/WordPress/Docs/DB/SlowDBQueryStandard.xml new file mode 100644 index 0000000000..230c7fafb6 --- /dev/null +++ b/WordPress/Docs/DB/SlowDBQueryStandard.xml @@ -0,0 +1,33 @@ + + + + + + + + 'post', +) ); + + +$args = 'post_type=post&orderby=date'; + ]]> + + + meta_key' => 'color', + 'meta_value' => 'blue', +) ); + +$args = 'post_type=post&meta_key=featured'; + ]]> + + + diff --git a/WordPress/Docs/PHP/DevelopmentFunctionsStandard.xml b/WordPress/Docs/PHP/DevelopmentFunctionsStandard.xml new file mode 100644 index 0000000000..625d86c435 --- /dev/null +++ b/WordPress/Docs/PHP/DevelopmentFunctionsStandard.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/WordPress/Docs/PHP/NoSilencedErrorsStandard.xml b/WordPress/Docs/PHP/NoSilencedErrorsStandard.xml new file mode 100644 index 0000000000..9db8f6e942 --- /dev/null +++ b/WordPress/Docs/PHP/NoSilencedErrorsStandard.xml @@ -0,0 +1,29 @@ + + + + + + + + strtr( $str, $replace_pairs ); + ]]> + + + @strtr( $str, $replace_pairs ); + ]]> + + + diff --git a/WordPress/Docs/PHP/PregQuoteDelimiterStandard.xml b/WordPress/Docs/PHP/PregQuoteDelimiterStandard.xml new file mode 100644 index 0000000000..1b91c32fc7 --- /dev/null +++ b/WordPress/Docs/PHP/PregQuoteDelimiterStandard.xml @@ -0,0 +1,35 @@ + + + + + + + + '#' ); +preg_match( + '#^' . $quoted_input . '#i', + $post_content, + $matches +); + ]]> + + + ); +preg_match( + '#^' . $quoted_input . '#i', + $post_content, + $matches +); + ]]> + + + diff --git a/WordPress/Docs/PHP/RestrictedPHPFunctionsStandard.xml b/WordPress/Docs/PHP/RestrictedPHPFunctionsStandard.xml new file mode 100644 index 0000000000..bebbd86b19 --- /dev/null +++ b/WordPress/Docs/PHP/RestrictedPHPFunctionsStandard.xml @@ -0,0 +1,34 @@ + + + + + + + + function () { + return foo( 'bar' ); + } +); + ]]> + + + create_function( + '', + 'return foo( "bar" );' + ) +); + ]]> + + + diff --git a/WordPress/Docs/Security/PluginMenuSlugStandard.xml b/WordPress/Docs/Security/PluginMenuSlugStandard.xml new file mode 100644 index 0000000000..df6b52e375 --- /dev/null +++ b/WordPress/Docs/Security/PluginMenuSlugStandard.xml @@ -0,0 +1,53 @@ + + + + + + + + 'my-plugin-main', + 'my_plugin_main_page' +); + +add_submenu_page( + 'my_plugin_main_page', + 'My Plugin Subpage', + 'Subpage', + 'manage_options', + 'my-plugin-subpage', + 'my_plugin_subpage' +); + ]]> + + + __FILE__, + 'my_plugin_main_page' +); + +add_submenu_page( + __FILE__ . 'my_plugin_main_page', + 'My Plugin Subpage', + 'Subpage', + 'manage_options', + 'my-plugin-subpage', + 'my_plugin_subpage' +); + ]]> + + + diff --git a/WordPress/Docs/WP/AlternativeFunctionsStandard.xml b/WordPress/Docs/WP/AlternativeFunctionsStandard.xml new file mode 100644 index 0000000000..bf2f5f3a95 --- /dev/null +++ b/WordPress/Docs/WP/AlternativeFunctionsStandard.xml @@ -0,0 +1,25 @@ + + + + + + + + wp_rand( 1, 100 ); + ]]> + + + mt_rand( 1, 100 ); + ]]> + + + diff --git a/WordPress/Docs/WP/DiscouragedConstantsStandard.xml b/WordPress/Docs/WP/DiscouragedConstantsStandard.xml new file mode 100644 index 0000000000..583d62d767 --- /dev/null +++ b/WordPress/Docs/WP/DiscouragedConstantsStandard.xml @@ -0,0 +1,46 @@ + + + + + + + + get_stylesheet_directory(); + ]]> + + + STYLESHEETPATH; + ]]> + + + + + + + + add_theme_support( + 'custom-header', + array( 'width' => 200 ) +); + ]]> + + + 'HEADER_IMAGE_WIDTH', + 200 +); + ]]> + + + diff --git a/WordPress/Docs/WP/GlobalVariablesOverrideStandard.xml b/WordPress/Docs/WP/GlobalVariablesOverrideStandard.xml new file mode 100644 index 0000000000..18df55e13a --- /dev/null +++ b/WordPress/Docs/WP/GlobalVariablesOverrideStandard.xml @@ -0,0 +1,41 @@ + + + + + + + + $prefix_query = new WP_Query( $args ); + +$GLOBALS['prefix_data'] = 'some data'; + +foreach ( $selected_posts as $prefix_post ) { + // Do something. +} + ]]> + + + global $wp_query; + $wp_query = new WP_Query( $args ); + + $GLOBALS['post'] = get_post( 1 ); +} + +foreach ( $selected_posts as $post ) { + // Do something. +} + ]]> + + + diff --git a/WordPress/Helpers/ConstantsHelper.php b/WordPress/Helpers/ConstantsHelper.php index 6decbb4010..320c6312f8 100644 --- a/WordPress/Helpers/ConstantsHelper.php +++ b/WordPress/Helpers/ConstantsHelper.php @@ -79,6 +79,7 @@ public static function is_use_of_global_constant( File $phpcsFile, $stackPtr ) { \T_INSTANCEOF => true, \T_INSTEADOF => true, \T_GOTO => true, + \T_AS => true, ); $tokens_to_ignore += Tokens::$ooScopeTokens; $tokens_to_ignore += Collections::objectOperators(); diff --git a/WordPress/Helpers/ContextHelper.php b/WordPress/Helpers/ContextHelper.php index b90006fde0..1da3286715 100644 --- a/WordPress/Helpers/ContextHelper.php +++ b/WordPress/Helpers/ContextHelper.php @@ -199,8 +199,9 @@ public static function is_token_namespaced( File $phpcsFile, $stackPtr ) { * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The index of the token in the stack. * @param array $valid_functions List of valid function names. - * Note: The keys to this array should be the function names - * in lowercase. Values are irrelevant. + * Note: The keys to this array should be the function names. + * Values are irrelevant. The matching of function names found in + * the code against the keys in this array is done case-insensitively. * @param bool $global_function Optional. Whether to make sure that the function call is * to a global function. If `false`, calls to methods, be it static * `Class::method()` or via an object `$obj->method()`, and @@ -217,6 +218,8 @@ public static function is_token_namespaced( File $phpcsFile, $stackPtr ) { * @return int|bool Stack pointer to the function call T_STRING token or false otherwise. */ public static function is_in_function_call( File $phpcsFile, $stackPtr, array $valid_functions, $global_function = true, $allow_nested = false ) { + $valid_functions = array_change_key_case( $valid_functions, \CASE_LOWER ); + $tokens = $phpcsFile->getTokens(); if ( ! isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) { return false; diff --git a/WordPress/Helpers/MinimumWPVersionTrait.php b/WordPress/Helpers/MinimumWPVersionTrait.php index b651e80bb6..2cc6f0b120 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.6'; + private $default_minimum_wp_version = '6.7'; /** * Overrule the minimum supported WordPress version with a command-line/config value. diff --git a/WordPress/Helpers/WPDBTrait.php b/WordPress/Helpers/WPDBTrait.php index afa6ffc54f..5843ea4225 100644 --- a/WordPress/Helpers/WPDBTrait.php +++ b/WordPress/Helpers/WPDBTrait.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\BackCompat\BCFile; use PHPCSUtils\Tokens\Collections; +use WordPressCS\WordPress\Helpers\ContextHelper; /** * Helper utilities for sniffs which examine WPDB method calls. @@ -23,12 +24,23 @@ trait WPDBTrait { /** - * Checks whether this is a call to a $wpdb method that we want to sniff. + * Checks whether this is a call to one of a specific group of $wpdb method(s). * - * If available in the class using this trait, the $methodPtr, $i and $end properties - * are automatically set to correspond to the start and end of the method call. - * The $i property is also set if this is not a method call but rather the - * use of a $wpdb property. + * Supports both instance method calls (e.g., `$wpdb->prepare()`) and static + * method calls (e.g., `wpdb::esc_like()`). + * + * Note: Static calls on non-static wpdb methods are problematic at runtime, but this + * helper still matches them so sniffs can flag them in the code under scan. + * + * If the following properties are explicitly declared in the class using this trait, + * they will be automatically set: + * - `$methodPtr`: Stack pointer to the method name. + * - `$i`: Stack pointer to the opening parenthesis of the method call. + * - `$end`: Stack pointer to the comma after the first parameter, or to the + * token directly after the first parameter if there is no comma. + * + * The `$methodPtr` and `$i` properties may be set even when this method returns `false` + * (e.g., for property access like `$wpdb->show_errors`). * * @since 0.8.0 * @since 0.9.0 The return value is now always boolean. The $end and $i member @@ -41,7 +53,7 @@ trait WPDBTrait { * for properties in the sniff class(es) using it.}} * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The index of the $wpdb variable. + * @param int $stackPtr The index of the $wpdb variable or wpdb (class) name token. * @param array $target_methods Array of methods. Key(s) should be method name * in lowercase. * @@ -68,6 +80,11 @@ final protected function is_wpdb_method_call( File $phpcsFile, $stackPtr, array return false; } + // If calling the method statically, ensure we are calling the global wpdb class. + if ( \T_STRING === $tokens[ $stackPtr ]['code'] && ContextHelper::is_token_namespaced( $phpcsFile, $stackPtr ) ) { + return false; + } + $methodPtr = $phpcsFile->findNext( Tokens::$emptyTokens, ( $is_object_call + 1 ), null, true, null, true ); if ( false === $methodPtr ) { return false; diff --git a/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php b/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php index bd0f75acf1..2e8b794e05 100644 --- a/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php +++ b/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php @@ -19,32 +19,44 @@ /** * Enforces WordPress array spacing format. * - * - Checks that associative arrays are multi-line. + * - Checks that arrays with explicit keys are multi-line. * - Checks that each array item in a multi-line array starts on a new line. * * @link https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/#indentation * * @since 0.11.0 - The WordPress specific additional checks have now been split off * from the `WordPress.Arrays.ArrayDeclaration` sniff into this sniff. - * - Added sniffing & fixing for associative arrays. + * - Added sniffing & fixing for arrays with explicit keys. * @since 0.12.0 Decoupled this sniff from the upstream sniff completely. * This sniff now extends the WordPressCS native `Sniff` class instead. * @since 0.13.0 Added the last remaining checks from the `WordPress.Arrays.ArrayDeclaration` * sniff which were not covered elsewhere. * The `WordPress.Arrays.ArrayDeclaration` sniff has now been deprecated. * @since 0.13.0 Class name changed: this class is now namespaced. - * @since 0.14.0 Single item associative arrays are now by default exempt from the + * @since 0.14.0 Single item arrays with explicit keys are now by default exempt from the * "must be multi-line" rule. This behavior can be changed using the * `allow_single_item_single_line_associative_arrays` property. * @since 3.0.0 Removed various whitespace related checks and fixers in favor of the PHPCSExtra * `NormalizedArrays.Arrays.ArrayBraceSpacing` sniff. + * @since 3.4.0 The `allow_single_item_single_line_associative_arrays` property has been + * deprecated in favor of the new `allow_single_item_single_line_explicit_key_arrays` property. */ final class ArrayDeclarationSpacingSniff extends Sniff { /** - * Whether or not to allow single item associative arrays to be single line. + * Whether to allow single item arrays with explicit keys to be single line. + * + * @since 3.4.0 + * + * @var bool Defaults to true. + */ + public $allow_single_item_single_line_explicit_key_arrays = true; + + /** + * Whether or not to allow single item arrays with explicit keys to be single line. * * @since 0.14.0 + * @deprecated 3.4.0 Use $allow_single_item_single_line_explicit_key_arrays instead. * * @var bool Defaults to true. */ @@ -102,7 +114,7 @@ public function process_token( $stackPtr ) { } /** - * Check that associative arrays are always multi-line. + * Check that arrays with explicit keys are always multi-line. * * @since 0.13.0 The actual checks contained in this method used to * be in the `process()` method. @@ -114,10 +126,19 @@ public function process_token( $stackPtr ) { * @return void */ protected function process_single_line_array( $stackPtr, $opener, $closer ) { + // For now, if the new property has not been changed from its default value and the deprecated property has, use + // the deprecated property's value. + $allow_single_item = $this->allow_single_item_single_line_explicit_key_arrays; + if ( true === $allow_single_item + && false === $this->allow_single_item_single_line_associative_arrays + ) { + $allow_single_item = false; + } + $array_items = PassedParameters::getParameters( $this->phpcsFile, $stackPtr ); - if ( ( false === $this->allow_single_item_single_line_associative_arrays + if ( ( false === $allow_single_item && empty( $array_items ) ) - || ( true === $this->allow_single_item_single_line_associative_arrays + || ( true === $allow_single_item && \count( $array_items ) === 1 ) ) { return; @@ -137,9 +158,9 @@ protected function process_single_line_array( $stackPtr, $opener, $closer ) { if ( false === $array_has_keys ) { return; } - $error = 'When an array uses associative keys, each value should start on %s.'; - if ( true === $this->allow_single_item_single_line_associative_arrays ) { - $error = 'When a multi-item array uses associative keys, each value should start on %s.'; + $error = 'When an array is declared with explicit keys, each value should start on %s.'; + if ( true === $allow_single_item ) { + $error = 'When a multi-item array is declared with explicit keys, each value should start on %s.'; } /* diff --git a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php index fb5b1bdb02..85c79f2c09 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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0. * @@ -337,6 +337,11 @@ final class PrefixAllGlobalsSniff extends AbstractFunctionParameterSniff { 'twentytwentytwo_styles' => true, 'twentytwentytwo_support' => true, 'wp_authenticate' => true, + + /* + * The wp_cache_* functions below are conditionally (re)declared as pluggable in wp-includes/cache-compat.php, + * allowing a persistent object cache drop-in to override them. Their primary declarations are in wp-includes/cache.php. + */ 'wp_cache_add_multiple' => true, 'wp_cache_delete_multiple' => true, 'wp_cache_flush_group' => true, @@ -348,6 +353,8 @@ final class PrefixAllGlobalsSniff extends AbstractFunctionParameterSniff { 'wp_cache_set_multiple_salted' => true, 'wp_cache_set_salted' => true, 'wp_cache_supports' => true, + 'wp_cache_switch_to_blog' => true, + 'wp_check_password' => true, 'wp_clear_auth_cookie' => true, 'wp_clearcookie' => true, // Deprecated. @@ -396,7 +403,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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0. * diff --git a/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php b/WordPress/Sniffs/NamingConventions/ValidPostTypeSlugSniff.php index f5cb955b03..384b74e74e 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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 2.2.0 * diff --git a/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php b/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php index 400da4a6dd..06cc8879be 100644 --- a/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php +++ b/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php @@ -142,7 +142,6 @@ final class NoSilencedErrorsSniff extends Sniff { // Miscellaneous other functions. 'imagecreatefromstring' => true, 'imagecreatefromwebp' => true, - 'parse_url' => true, // Pre-PHP 5.3.3 an E_WARNING was thrown when URL parsing failed. 'unserialize' => true, ); diff --git a/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php b/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php index f3102e33d5..f4c47c85e7 100644 --- a/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php +++ b/WordPress/Sniffs/PHP/RestrictedPHPFunctionsSniff.php @@ -35,7 +35,7 @@ public function getGroups() { return array( 'create_function' => array( 'type' => 'error', - 'message' => '%s() is deprecated as of PHP 7.2 and removed in PHP 8.0. Please use declared named or anonymous functions instead.', + 'message' => '%s() internally performs an eval(), which makes this a very dangerous function. For this reason, it was deprecated as of PHP 7.2 and removed in PHP 8.0. Please use anonymous functions, or declare a named function instead.', 'functions' => array( 'create_function', ), diff --git a/WordPress/Sniffs/Security/EscapeOutputSniff.php b/WordPress/Sniffs/Security/EscapeOutputSniff.php index e0714e36c4..b05732e823 100644 --- a/WordPress/Sniffs/Security/EscapeOutputSniff.php +++ b/WordPress/Sniffs/Security/EscapeOutputSniff.php @@ -438,7 +438,7 @@ public function process_matched_token( $stackPtr, $group_name, $matched_content if ( false !== $file_param ) { // Check for a particular code pattern which can safely be ignored. - if ( preg_match( '`^[\\\\]?basename\s*\(\s*__FILE__\s*\)$`', $file_param['clean'] ) === 1 ) { + if ( preg_match( '`^[\\\\]?basename\s*\(\s*__FILE__\s*\)$`i', $file_param['clean'] ) === 1 ) { unset( $params[1], $params['file'] ); // Remove the param, whether passed positionally or named. } } @@ -741,7 +741,7 @@ protected function check_code_is_escaped( $start, $end, $code = 'OutputNotEscape // Special case get_search_query() which is unsafe if $escaped = false. if ( 'get_search_query' === strtolower( $functionName ) ) { $escaped_param = PassedParameters::getParameter( $this->phpcsFile, $ptr, 1, 'escaped' ); - if ( false !== $escaped_param && 'true' !== $escaped_param['clean'] ) { + if ( false !== $escaped_param && 'true' !== strtolower( ltrim( $escaped_param['clean'], '\\' ) ) ) { $this->phpcsFile->addError( 'Output from get_search_query() is unsafe due to $escaped parameter being set to "false".', $ptr, diff --git a/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php b/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php index 40fb0c61c2..9a0a2a9d03 100644 --- a/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php +++ b/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php @@ -283,7 +283,7 @@ public function process_matched_token( $stackPtr, $group_name, $matched_content } $contains_wp_path_constant = preg_match( - '`\b(?:ABSPATH|WP_(?:CONTENT|PLUGIN)_DIR|WPMU_PLUGIN_DIR|TEMPLATEPATH|STYLESHEETPATH|(?:MU)?PLUGINDIR)\b`', + '`(?|::)\b(?:ABSPATH|WP_(?:CONTENT|PLUGIN)_DIR|WPMU_PLUGIN_DIR|TEMPLATEPATH|STYLESHEETPATH|(?:MU)?PLUGINDIR)\b`', $filename_param['clean'] ); if ( 1 === $contains_wp_path_constant ) { @@ -292,7 +292,7 @@ public function process_matched_token( $stackPtr, $group_name, $matched_content } $contains_wp_path_function_call = preg_match( - '`(?:get_home_path|plugin_dir_path|get_(?:stylesheet|template)_directory|wp_upload_dir)\s*\(`i', + '`(?|::)(?:get_home_path|plugin_dir_path|get_(?:stylesheet|template)_directory|wp_upload_dir)\s*\(`i', $filename_param['clean'] ); if ( 1 === $contains_wp_path_function_call ) { @@ -353,7 +353,9 @@ public function process_matched_token( $stackPtr, $group_name, $matched_content */ protected function is_local_data_stream( $clean_param_value ) { - $stripped = TextStrings::stripQuotes( $clean_param_value ); + $stripped = TextStrings::stripQuotes( $clean_param_value ); + $clean_param_value = ltrim( $clean_param_value, '\\' ); + if ( isset( $this->allowed_local_streams[ $stripped ] ) || isset( $this->allowed_local_stream_constants[ $clean_param_value ] ) ) { diff --git a/WordPress/Sniffs/WP/CapabilitiesSniff.php b/WordPress/Sniffs/WP/CapabilitiesSniff.php index 758359f9c6..6e9cb8fad9 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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0 * diff --git a/WordPress/Sniffs/WP/ClassNameCaseSniff.php b/WordPress/Sniffs/WP/ClassNameCaseSniff.php index ef8170e275..d6056fb20c 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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0 * @@ -115,6 +115,12 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'Walker_Page', 'Walker_PageDropdown', 'WP', + 'WP_AI_Client_Ability_Function_Resolver', + 'WP_AI_Client_Cache', + 'WP_AI_Client_Discovery_Strategy', + 'WP_AI_Client_Event_Dispatcher', + 'WP_AI_Client_HTTP_Client', + 'WP_AI_Client_Prompt_Builder', 'WP_Abilities_Registry', 'WP_Ability', 'WP_Ability_Categories_Registry', @@ -149,6 +155,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'WP_Comment_Query', 'WP_Comments_List_Table', 'WP_Community_Events', + 'WP_Connector_Registry', 'WP_Customize_Background_Image_Control', 'WP_Customize_Background_Image_Setting', 'WP_Customize_Background_Position_Control', @@ -234,6 +241,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'WP_Http_Curl', 'WP_Http_Encoding', 'WP_Http_Streams', + 'WP_Icons_Registry', 'WP_Image_Editor', 'WP_Image_Editor_GD', 'WP_Image_Editor_Imagick', @@ -296,6 +304,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'WP_REST_Font_Families_Controller', 'WP_REST_Global_Styles_Controller', 'WP_REST_Global_Styles_Revisions_Controller', + 'WP_REST_Icons_Controller', 'WP_REST_Menu_Items_Controller', 'WP_REST_Menu_Locations_Controller', 'WP_REST_Menus_Controller', @@ -432,7 +441,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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0 * @@ -459,12 +468,179 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { 'Twenty_Twenty_One_SVG_Icons', ); + /** + * List of all AI Client classes included in WP Core. + * + * Includes both the WP AI Client classes and the bundled dependencies. + * + * Note: this list will be enhanced in the class constructor. + * + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} + * + * @since 3.4.0 + * + * @var string[] The class names in their "proper" case. + * The constructor will add the lowercased class name as a key to each entry. + */ + private $aiclient_classes = array( + // Classes. + 'WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery', + 'WordPress\AiClientDependencies\Http\Discovery\Exception\ClassInstantiationFailedException', + 'WordPress\AiClientDependencies\Http\Discovery\Exception\DiscoveryFailedException', + 'WordPress\AiClientDependencies\Http\Discovery\Exception\NoCandidateFoundException', + 'WordPress\AiClientDependencies\Http\Discovery\Exception\NotFoundException', + 'WordPress\AiClientDependencies\Http\Discovery\Exception\PuliUnavailableException', + 'WordPress\AiClientDependencies\Http\Discovery\Exception\StrategyUnavailableException', + 'WordPress\AiClientDependencies\Http\Discovery\Psr17FactoryDiscovery', + 'WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery', + 'WordPress\AiClientDependencies\Http\Discovery\Strategy\CommonClassesStrategy', + 'WordPress\AiClientDependencies\Http\Discovery\Strategy\CommonPsr17ClassesStrategy', + 'WordPress\AiClientDependencies\Http\Discovery\Strategy\PuliBetaStrategy', + 'WordPress\AiClientDependencies\Nyholm\Psr7\Factory\HttplugFactory', + 'WordPress\AiClientDependencies\Nyholm\Psr7\Factory\Psr17Factory', + 'WordPress\AiClientDependencies\Nyholm\Psr7\Request', + 'WordPress\AiClientDependencies\Nyholm\Psr7\Response', + 'WordPress\AiClientDependencies\Nyholm\Psr7\ServerRequest', + 'WordPress\AiClientDependencies\Nyholm\Psr7\Stream', + 'WordPress\AiClientDependencies\Nyholm\Psr7\UploadedFile', + 'WordPress\AiClientDependencies\Nyholm\Psr7\Uri', + 'WordPress\AiClient\AiClient', + 'WordPress\AiClient\Builders\MessageBuilder', + 'WordPress\AiClient\Builders\PromptBuilder', + 'WordPress\AiClient\Common\AbstractDataTransferObject', + 'WordPress\AiClient\Common\AbstractEnum', + 'WordPress\AiClient\Common\Exception\InvalidArgumentException', + 'WordPress\AiClient\Common\Exception\RuntimeException', + 'WordPress\AiClient\Common\Exception\TokenLimitReachedException', + 'WordPress\AiClient\Events\AfterGenerateResultEvent', + 'WordPress\AiClient\Events\BeforeGenerateResultEvent', + 'WordPress\AiClient\Files\DTO\File', + 'WordPress\AiClient\Files\Enums\FileTypeEnum', + 'WordPress\AiClient\Files\Enums\MediaOrientationEnum', + 'WordPress\AiClient\Files\ValueObjects\MimeType', + 'WordPress\AiClient\Messages\DTO\Message', + 'WordPress\AiClient\Messages\DTO\MessagePart', + 'WordPress\AiClient\Messages\DTO\ModelMessage', + 'WordPress\AiClient\Messages\DTO\UserMessage', + 'WordPress\AiClient\Messages\Enums\MessagePartChannelEnum', + 'WordPress\AiClient\Messages\Enums\MessagePartTypeEnum', + 'WordPress\AiClient\Messages\Enums\MessageRoleEnum', + 'WordPress\AiClient\Messages\Enums\ModalityEnum', + 'WordPress\AiClient\Operations\DTO\GenerativeAiOperation', + 'WordPress\AiClient\Operations\Enums\OperationStateEnum', + 'WordPress\AiClient\Providers\AbstractProvider', + 'WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel', + 'WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory', + 'WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiProvider', + 'WordPress\AiClient\Providers\ApiBasedImplementation\GenerateTextApiBasedProviderAvailability', + 'WordPress\AiClient\Providers\ApiBasedImplementation\ListModelsApiBasedProviderAvailability', + 'WordPress\AiClient\Providers\DTO\ProviderMetadata', + 'WordPress\AiClient\Providers\DTO\ProviderModelsMetadata', + 'WordPress\AiClient\Providers\Enums\ProviderTypeEnum', + 'WordPress\AiClient\Providers\Enums\ToolTypeEnum', + 'WordPress\AiClient\Providers\Http\Abstracts\AbstractClientDiscoveryStrategy', + 'WordPress\AiClient\Providers\Http\Collections\HeadersCollection', + 'WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication', + 'WordPress\AiClient\Providers\Http\DTO\Request', + 'WordPress\AiClient\Providers\Http\DTO\RequestOptions', + 'WordPress\AiClient\Providers\Http\DTO\Response', + 'WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum', + 'WordPress\AiClient\Providers\Http\Enums\RequestAuthenticationMethod', + 'WordPress\AiClient\Providers\Http\Exception\ClientException', + 'WordPress\AiClient\Providers\Http\Exception\NetworkException', + 'WordPress\AiClient\Providers\Http\Exception\RedirectException', + 'WordPress\AiClient\Providers\Http\Exception\ResponseException', + 'WordPress\AiClient\Providers\Http\Exception\ServerException', + 'WordPress\AiClient\Providers\Http\HttpTransporter', + 'WordPress\AiClient\Providers\Http\HttpTransporterFactory', + 'WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor', + 'WordPress\AiClient\Providers\Http\Util\ResponseUtil', + 'WordPress\AiClient\Providers\Models\DTO\ModelConfig', + 'WordPress\AiClient\Providers\Models\DTO\ModelMetadata', + 'WordPress\AiClient\Providers\Models\DTO\ModelRequirements', + 'WordPress\AiClient\Providers\Models\DTO\RequiredOption', + 'WordPress\AiClient\Providers\Models\DTO\SupportedOption', + 'WordPress\AiClient\Providers\Models\Enums\CapabilityEnum', + 'WordPress\AiClient\Providers\Models\Enums\OptionEnum', + 'WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleImageGenerationModel', + 'WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleModelMetadataDirectory', + 'WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleTextGenerationModel', + 'WordPress\AiClient\Providers\ProviderRegistry', + 'WordPress\AiClient\Results\DTO\Candidate', + 'WordPress\AiClient\Results\DTO\GenerativeAiResult', + 'WordPress\AiClient\Results\DTO\TokenUsage', + 'WordPress\AiClient\Results\Enums\FinishReasonEnum', + 'WordPress\AiClient\Tools\DTO\FunctionCall', + 'WordPress\AiClient\Tools\DTO\FunctionDeclaration', + 'WordPress\AiClient\Tools\DTO\FunctionResponse', + 'WordPress\AiClient\Tools\DTO\WebSearch', + + // Interfaces. + 'WordPress\AiClientDependencies\Http\Discovery\Exception', + 'WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy', + 'WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface', + 'WordPress\AiClientDependencies\Psr\Http\Client\ClientExceptionInterface', + 'WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface', + 'WordPress\AiClientDependencies\Psr\Http\Client\NetworkExceptionInterface', + 'WordPress\AiClientDependencies\Psr\Http\Client\RequestExceptionInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\MessageInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestFactoryInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileFactoryInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\UriFactoryInterface', + 'WordPress\AiClientDependencies\Psr\Http\Message\UriInterface', + 'WordPress\AiClientDependencies\Psr\SimpleCache\CacheInterface', + 'WordPress\AiClient\Common\Contracts\AiClientExceptionInterface', + 'WordPress\AiClient\Common\Contracts\CachesDataInterface', + 'WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface', + 'WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface', + 'WordPress\AiClient\Operations\Contracts\OperationInterface', + 'WordPress\AiClient\Providers\ApiBasedImplementation\Contracts\ApiBasedModelInterface', + 'WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface', + 'WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface', + 'WordPress\AiClient\Providers\Contracts\ProviderInterface', + 'WordPress\AiClient\Providers\Contracts\ProviderOperationsHandlerInterface', + 'WordPress\AiClient\Providers\Contracts\ProviderWithOperationsHandlerInterface', + 'WordPress\AiClient\Providers\Http\Contracts\ClientWithOptionsInterface', + 'WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface', + 'WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface', + 'WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface', + 'WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface', + 'WordPress\AiClient\Providers\Models\Contracts\ModelInterface', + 'WordPress\AiClient\Providers\Models\ImageGeneration\Contracts\ImageGenerationModelInterface', + 'WordPress\AiClient\Providers\Models\ImageGeneration\Contracts\ImageGenerationOperationModelInterface', + 'WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts\SpeechGenerationModelInterface', + 'WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts\SpeechGenerationOperationModelInterface', + 'WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface', + 'WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationOperationModelInterface', + 'WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts\TextToSpeechConversionModelInterface', + 'WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts\TextToSpeechConversionOperationModelInterface', + 'WordPress\AiClient\Providers\Models\VideoGeneration\Contracts\VideoGenerationModelInterface', + 'WordPress\AiClient\Providers\Models\VideoGeneration\Contracts\VideoGenerationOperationModelInterface', + 'WordPress\AiClient\Results\Contracts\ResultInterface', + + // Traits. + 'WordPress\AiClientDependencies\Nyholm\Psr7\MessageTrait', + 'WordPress\AiClientDependencies\Nyholm\Psr7\RequestTrait', + 'WordPress\AiClientDependencies\Nyholm\Psr7\StreamTrait', + 'WordPress\AiClient\Common\Traits\WithDataCachingTrait', + 'WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait', + 'WordPress\AiClient\Providers\Http\Traits\WithRequestAuthenticationTrait', + ); + /** * List of all AVIF classes included in WP Core. * * Note: this list will be enhanced in the class constructor. * - * {@internal To be updated after every major release. Last updated for WordPress 6.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.1.0 * @@ -486,7 +662,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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0 * @@ -522,7 +698,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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0 * @@ -547,7 +723,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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0 * @@ -691,7 +867,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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 3.0.0 * @@ -812,6 +988,17 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { */ private $wp_themes_classes_lc = array(); + /** + * List of all AI Client classes in lowercase. + * + * This array is automatically generated in the class constructor based on the $aiclient_classes property. + * + * @since 3.4.0 + * + * @var string[] The class names in lowercase. + */ + private $aiclient_classes_lc = array(); + /** * List of all AVIF classes in lowercase. * @@ -826,7 +1013,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { /** * List of all GetID3 classes in lowercase. * - * This array is automatically generated in the class constructor based on the $phpmailer_classes property. + * This array is automatically generated in the class constructor based on the $getid3_classes property. * * @since 3.0.0 * @@ -879,6 +1066,7 @@ final class ClassNameCaseSniff extends AbstractClassRestrictionsSniff { private $class_groups = array( 'wp_classes', 'wp_themes_classes', + 'aiclient_classes', 'avif_classes', 'getid3_classes', 'phpmailer_classes', diff --git a/WordPress/Sniffs/WP/CronIntervalSniff.php b/WordPress/Sniffs/WP/CronIntervalSniff.php index a93315daa9..886eee073f 100644 --- a/WordPress/Sniffs/WP/CronIntervalSniff.php +++ b/WordPress/Sniffs/WP/CronIntervalSniff.php @@ -291,7 +291,7 @@ private function find_function_by_name( $functionName ) { for ( $ptr = 0; $ptr < $this->phpcsFile->numTokens; $ptr++ ) { if ( \T_FUNCTION === $this->tokens[ $ptr ]['code'] ) { $foundName = FunctionDeclarations::getName( $this->phpcsFile, $ptr ); - if ( $foundName === $functionName ) { + if ( strcasecmp( $foundName, $functionName ) === 0 ) { $functionPtr = $ptr; break; } elseif ( isset( $this->tokens[ $ptr ]['scope_closer'] ) ) { diff --git a/WordPress/Sniffs/WP/DeprecatedClassesSniff.php b/WordPress/Sniffs/WP/DeprecatedClassesSniff.php index eec0d8f7cc..38232b5e3c 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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @var array */ diff --git a/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php b/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php index 6b74d6294b..76647d9aff 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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @var array */ @@ -1720,6 +1720,20 @@ final class DeprecatedFunctionsSniff extends AbstractFunctionRestrictionsSniff { 'alt' => 'wp_enqueue_img_auto_sizes_contain_css_fix()', 'version' => '6.9.0', ), + + // WP 7.0.0. + 'addslashes_gpc' => array( + 'alt' => 'wp_slash()', + 'version' => '7.0.0', + ), + 'block_core_navigation_block_contains_core_navigation' => array( + 'alt' => 'block_core_navigation_block_tree_has_block_type()', + 'version' => '7.0.0', + ), + 'wp_sanitize_script_attributes' => array( + 'alt' => 'wp_get_script_tag() or wp_get_inline_script_tag()', + 'version' => '7.0.0', + ), ); /** diff --git a/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php b/WordPress/Sniffs/WP/DeprecatedParameterValuesSniff.php index 1eefb6596c..b88826d990 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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @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 265035f249..2133e3903d 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.9.0-RC2.} + * {@internal To be updated after every major release. Last updated for WordPress 7.0.0.} * * @since 0.12.0 * diff --git a/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php b/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php index 7db5a37854..bd3486205a 100644 --- a/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php +++ b/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php @@ -17,7 +17,7 @@ use WordPressCS\WordPress\Helpers\ConstantsHelper; /** - * Warns against usage of discouraged WP CONSTANTS and recommends alternatives. + * Warns against usage and (re-)declaration of discouraged WP constants and recommends alternatives. * * @since 0.14.0 */ diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc index fd47c0a3a9..5bd00f8acf 100644 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc @@ -82,6 +82,15 @@ $bad = array( // Don't confuse list arrows with array arrows. $okay = array( $item1, list( 'key1' => $a, 'key2' => $b ) = $array, $item3 ); -// Live coding/parse error. -// This must be the last test in the file! -$ignore = array( $item1, 'key' => 'value', +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_explicit_key_arrays false + +$bad = array( 'key' => 'value' ); // Bad. +$bad = array( 'key1' => 'value1', 'key2' => 'value2' ); // Bad. + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_explicit_key_arrays true + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays null + +$ok = array( 'key' => 'value' ); // OK, invalid value for deprecated property should be ignored. + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays true diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc.fixed b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc.fixed index c0bddcc551..267427ae3b 100644 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc.fixed +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.1.inc.fixed @@ -135,6 +135,20 @@ $bad = array( // Don't confuse list arrows with array arrows. $okay = array( $item1, list( 'key1' => $a, 'key2' => $b ) = $array, $item3 ); -// Live coding/parse error. -// This must be the last test in the file! -$ignore = array( $item1, 'key' => 'value', +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_explicit_key_arrays false + +$bad = array( +'key' => 'value' +); // Bad. +$bad = array( +'key1' => 'value1', +'key2' => 'value2' +); // Bad. + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_explicit_key_arrays true + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays null + +$ok = array( 'key' => 'value' ); // OK, invalid value for deprecated property should be ignored. + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_associative_arrays true diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc index 34ea62060f..da24a06279 100644 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc @@ -107,3 +107,10 @@ $bad = [ // Don't confuse list arrows with array arrows. $okay = [ $item1, [ 'key1' => $a, 'key2' => $b ] = $array, $item3 ]; + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_explicit_key_arrays false + +$bad = [ 'key' => 'value' ]; // Bad. +$bad = [ 'key1' => 'value1', 'key2' => 'value2' ]; // Bad. + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_explicit_key_arrays true diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc.fixed b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc.fixed index c4b03f73cc..3480020aa9 100644 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc.fixed +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.2.inc.fixed @@ -163,3 +163,15 @@ $bad = [ // Don't confuse list arrows with array arrows. $okay = [ $item1, [ 'key1' => $a, 'key2' => $b ] = $array, $item3 ]; + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_explicit_key_arrays false + +$bad = [ +'key' => 'value' +]; // Bad. +$bad = [ +'key1' => 'value1', +'key2' => 'value2' +]; // Bad. + +// phpcs:set WordPress.Arrays.ArrayDeclarationSpacing allow_single_item_single_line_explicit_key_arrays true diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.3.inc b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.3.inc new file mode 100644 index 0000000000..184b1da687 --- /dev/null +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.3.inc @@ -0,0 +1,8 @@ + 'value', diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php index 9a289959e6..6a46292970 100644 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php @@ -51,29 +51,33 @@ public function getErrorList( $testFile = '' ) { 62 => 1, 63 => 1, 75 => 1, + 87 => 1, + 88 => 1, ); // Short arrays. case 'ArrayDeclarationSpacingUnitTest.2.inc': return array( - 9 => 4, - 13 => 2, - 15 => 1, - 19 => 1, - 22 => 1, - 25 => 1, - 44 => 1, - 45 => 1, - 48 => 1, - 49 => 1, - 52 => 2, - 54 => 1, - 57 => 1, - 58 => 1, - 62 => 1, - 63 => 1, - 75 => 1, - 97 => 1, + 9 => 4, + 13 => 2, + 15 => 1, + 19 => 1, + 22 => 1, + 25 => 1, + 44 => 1, + 45 => 1, + 48 => 1, + 49 => 1, + 52 => 2, + 54 => 1, + 57 => 1, + 58 => 1, + 62 => 1, + 63 => 1, + 75 => 1, + 97 => 1, + 113 => 1, + 114 => 1, ); default: diff --git a/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.inc b/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.inc index c451ccc91b..63b8d03ccd 100644 --- a/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.inc +++ b/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.inc @@ -4,8 +4,17 @@ esc_html( 'text' ); esc_html( $var ); esc_html( 'text', 'domain' ); // Warning. -esc_html( $foo, $bar ); // Warning. -esc_attr( +\ESC_HTML( $foo, $bar ); // Warning. +esc_ATTR( 'text', // Some comment. MY_DOMAIN // More comment. ); // Warning. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\esc_attr( 'text', 'domain' ); // Warning. +MyNamespace\esc_html( 'text', 'domain' ); +\MyNamespace\esc_attr( 'text', 'domain' ); +namespace\esc_html( 'text', 'domain' ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\esc_attr( 'text', 'domain' ); diff --git a/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.php b/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.php index 4218e90dc9..eee562f2d6 100644 --- a/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.php +++ b/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.php @@ -36,9 +36,10 @@ public function getErrorList() { */ public function getWarningList() { return array( - 6 => 1, - 7 => 1, - 8 => 1, + 6 => 1, + 7 => 1, + 8 => 1, + 16 => 1, ); } } diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc index 601877bfa4..791df5a509 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc @@ -518,3 +518,17 @@ $where = $wpdb->prepare( */ $callback = $wpdb->prepare(...); // OK. +/* + * Safeguard correct handling of all types of namespaced calls to the wpdb::prepare() method. + * + * Note that calling wpdb::prepare() statically will result in an error. Still, the tests are included here since the + * sniff handles those calls. + * + * Related to: https://github.com/WordPress/WordPress-Coding-Standards/issues/2710. + */ +$sql = \wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // Error. +$sql = \WPDB::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // Error. +$sql = MyNamespace\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // OK. +$sql = \MyNamespace\WPDB::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // OK. +$sql = namespace\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // Ok. The sniff should start flagging this once it can resolve relative namespaces as this test file is not namespaced. +$sql = namespace\Sub\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // OK. diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php index df0bc753bf..916567cf3e 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php @@ -106,6 +106,10 @@ public function getErrorList() { // Named parameter support. 418 => 1, + + // Fully qualified calls to the global class wpdb. + 529 => 1, + 530 => 1, ); } diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc index 1f1a49076c..ac97d71ce8 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc @@ -143,5 +143,17 @@ echo $wpdb::CONSTANT_NAME; // Not an identifiable method call. $wpdb->{$methodName}('query'); -// Don't throw an error during live coding. -wpdb::prepare( "SELECT * FROM $wpdb->posts +/* + * Safeguard correct handling of all types of namespaced calls to the wpdb::prepare() method. + * + * Note that calling wpdb::prepare() statically will result in an error. Still, the tests are included here since the + * sniff handles those calls. + * + * Related to: https://github.com/WordPress/WordPress-Coding-Standards/issues/2710. + */ +\wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Bad. +\WPDB::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Bad. +MyNamespace\wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Ok. +\MyNamespace\WPDB::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Ok. +namespace\wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Ok. The sniff should start flagging this once it can resolve relative namespaces as this test file is not namespaced. +namespace\Sub\wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Ok. diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.3.inc b/WordPress/Tests/DB/PreparedSQLUnitTest.3.inc new file mode 100644 index 0000000000..4047216b82 --- /dev/null +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.3.inc @@ -0,0 +1,8 @@ +posts diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.php b/WordPress/Tests/DB/PreparedSQLUnitTest.php index 0a296ff984..8d887dd044 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.php @@ -66,6 +66,8 @@ public function getErrorList( $testFile = '' ) { 124 => 1, 128 => 1, 132 => 2, + 154 => 1, + 155 => 1, ); case 'PreparedSQLUnitTest.2.inc': diff --git a/WordPress/Tests/DB/RestrictedClassesUnitTest.1.inc b/WordPress/Tests/DB/RestrictedClassesUnitTest.1.inc index 4449a0395b..d41ff86379 100644 --- a/WordPress/Tests/DB/RestrictedClassesUnitTest.1.inc +++ b/WordPress/Tests/DB/RestrictedClassesUnitTest.1.inc @@ -30,7 +30,7 @@ class MyMysqli extends mysqli {} class YourMysqli extends \mysqli {} class OurMysqli implements mysqli {} -class TheirMysqli implements \mysqli {} +class TheirMysqli implements \MYSQLI {} $db5 = new PDO(); $db6 = ( new PDO() )->exec(); @@ -115,3 +115,24 @@ $anon = new readonly class { $anon = new readonly class() extends PDOStatement {}; // Error. $anon = new #[MyAttribute] readonly class {}; + +/* + * Safeguard correct handling of namespaced class references (the namespace types not handled below are already handled + * in other tests above). + */ +$obj = new MyNamespace\PDO(); +$obj = new \MyNamespace\PDOStatement(); +$obj = new namespace\Sub\mysqli(); +$obj = new namespace\PDO(); // Error. +class MyClass1 extends MyNamespace\mysqli {} +class MyClass2 extends \MyNamespace\PDO {} +class MyClass3 extends namespace\Sub\PDOStatement {} +class MyClass4 extends namespace\mysqli {} // Error. +class MyClass5 implements MyNamespace\mysqli {} +class MyClass6 implements \MyNamespace\PDO {} +class MyClass7 implements namespace\Sub\PDOStatement {} +class MyClass8 implements namespace\mysqli {} // Error. +MyNamespace\mysqli::do_something(); +\MyNamespace\PDO::do_something(); +namespace\Sub\PDOStatement::do_something(); +namespace\MYSQLI::do_something(); // Error. diff --git a/WordPress/Tests/DB/RestrictedClassesUnitTest.php b/WordPress/Tests/DB/RestrictedClassesUnitTest.php index a07ace04d8..a9bb96546e 100644 --- a/WordPress/Tests/DB/RestrictedClassesUnitTest.php +++ b/WordPress/Tests/DB/RestrictedClassesUnitTest.php @@ -102,6 +102,10 @@ public function getErrorList( $testFile = '' ) { 103 => 1, 106 => 1, 115 => 1, + 126 => 1, + 130 => 1, + 134 => 1, + 138 => 1, ); case 'RestrictedClassesUnitTest.2.inc': diff --git a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.inc b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.inc index 3bebe1fdc2..d570897176 100644 --- a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.inc +++ b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.inc @@ -23,8 +23,8 @@ prefix_mysql_info(); // Ok. // MYSQL Extension. mysql_affected_rows(); -mysql_connect(); -mysql_close(); +Mysql_CONNECT(); +\MYSQL_close(); mysql_fetch_row(); mysql_info(); mysql_numrows(); @@ -99,6 +99,7 @@ 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. +MyNamespace\mysqli_init(); +\MyNamespace\mysqlnd_qc_clear_cache(); +namespace\maxdb_close(); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\mysqli_fetch(); diff --git a/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.inc b/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.inc index 07634e769a..95e92f2bff 100644 --- a/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.inc +++ b/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.inc @@ -20,7 +20,7 @@ current_time( // Error. ); current_time( 'timestamp', $gmt ); // Warning. -current_time( 'timestamp', false ); // Warning. +\Current_Time( 'timestamp', false ); // Warning. current_time( 'U', 0 ); // Warning. current_time( 'U' ); // Warning. @@ -30,3 +30,12 @@ current_time( gmt: true, type: 'mysql', ); // OK. current_time( type: 'Y-m-d' ); // OK. current_time( gmt: true, type: 'timestamp' ); // Error. current_time( gmt: 0, type : 'U' ); // Warning. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\current_time( 'timestamp', true ); // Error. +MyNamespace\current_time( 'timestamp', true ); +\MyNamespace\current_time( 'timestamp', true ); +namespace\current_time( 'timestamp', true ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\current_time( 'timestamp', true ); diff --git a/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.inc.fixed b/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.inc.fixed index 5b2fa7e28a..080c332946 100644 --- a/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.inc.fixed +++ b/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.inc.fixed @@ -17,7 +17,7 @@ current_time( // Error. ); current_time( 'timestamp', $gmt ); // Warning. -current_time( 'timestamp', false ); // Warning. +\Current_Time( 'timestamp', false ); // Warning. current_time( 'U', 0 ); // Warning. current_time( 'U' ); // Warning. @@ -27,3 +27,12 @@ current_time( gmt: true, type: 'mysql', ); // OK. current_time( type: 'Y-m-d' ); // OK. time(); // Error. current_time( gmt: 0, type : 'U' ); // Warning. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\time(); // Error. +MyNamespace\current_time( 'timestamp', true ); +\MyNamespace\current_time( 'timestamp', true ); +namespace\current_time( 'timestamp', true ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\current_time( 'timestamp', true ); diff --git a/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.php b/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.php index c667af8234..4525e58d79 100644 --- a/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.php +++ b/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.php @@ -31,6 +31,7 @@ public function getErrorList() { 11 => 1, 17 => 1, 31 => 1, + 37 => 1, ); } diff --git a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.inc b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.inc index f96842c9e7..e9c010e8a8 100644 --- a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.inc +++ b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.inc @@ -5,13 +5,15 @@ date_default_timezone_set( 'Foo/Bar' ); // Bad. $date = new DateTime(); $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' ), 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' ); +\DATE_default_timezone_SET( 'Foo/Bar' ); +MyNamespace\date( 'Y-m-d' ); \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. +namespace\date( 'Y-m-d' ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\date_default_timezone_set( 'Foo/Bar' ); diff --git a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php index a2f29c2364..8a37da1c3a 100644 --- a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php +++ b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php @@ -30,6 +30,7 @@ public function getErrorList() { 3 => 1, 8 => 2, 14 => 1, + 15 => 1, ); } diff --git a/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.inc b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.inc new file mode 100644 index 0000000000..731fa04732 --- /dev/null +++ b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.inc @@ -0,0 +1,21 @@ +assertFalse( ArrayWalkingFunctionsHelper::get_callback_parameter( self::$phpcsFile, -1 ) ); + } + + /** + * Test get_callback_parameter() returns the callback parameter info or false. + * + * @dataProvider dataGetCallbackParameter + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param string|false $expectedContent The expected 'clean' content of the callback parameter, + * or false if the method should return false. + * + * @return void + */ + public function testGetCallbackParameter( $testMarker, $expectedContent ) { + $stackPtr = $this->getTargetToken( $testMarker, array( \T_STRING, \T_NAME_FULLY_QUALIFIED ) ); + $result = ArrayWalkingFunctionsHelper::get_callback_parameter( self::$phpcsFile, $stackPtr ); + + if ( false === $expectedContent ) { + $this->assertFalse( $result ); + } else { + $this->assertSame( $expectedContent, $result['clean'] ); + } + } + + /** + * Data provider. + * + * @see testGetCallbackParameter() + * + * @return array> + */ + public static function dataGetCallbackParameter() { + return array( + // Cases where false should be returned. + 'not_array_walking_function' => array( + 'testMarker' => '/* testNotArrayWalkingFunction */', + 'expectedContent' => false, + ), + 'callback_param_missing' => array( + 'testMarker' => '/* testCallbackParamMissing */', + 'expectedContent' => false, + ), + + // Cases where the callback parameter should be returned. + 'array_map_callback' => array( + 'testMarker' => '/* testArrayMapCallback */', + 'expectedContent' => "'sanitize_text_field'", + ), + 'map_deep_mixed_case' => array( + 'testMarker' => '/* testMapDeepMixedCase */', + 'expectedContent' => "'esc_html'", + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/IsArrayWalkingFunctionUnitTest.php b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/IsArrayWalkingFunctionUnitTest.php new file mode 100644 index 0000000000..16f3e0ac35 --- /dev/null +++ b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/IsArrayWalkingFunctionUnitTest.php @@ -0,0 +1,64 @@ +assertSame( + $expectedResult, + ArrayWalkingFunctionsHelper::is_array_walking_function( $functionName ) + ); + } + + /** + * Data provider. + * + * @see testIsArrayWalkingFunction() + * + * @return array> + */ + public static function dataIsArrayWalkingFunction() { + return array( + 'lowercase_name' => array( + 'functionName' => 'array_map', + 'expectedResult' => true, + ), + 'mixedcase_name' => array( + 'functionName' => 'mAp_DeEp', + 'expectedResult' => true, + ), + 'not_an_array_walking_function' => array( + 'functionName' => 'array_filter', + 'expectedResult' => false, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInArrayComparisonUnitTest.inc b/WordPress/Tests/Helpers/ContextHelper/IsInArrayComparisonUnitTest.inc new file mode 100644 index 0000000000..09385129a0 --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsInArrayComparisonUnitTest.inc @@ -0,0 +1,30 @@ +array_search( /* testObjectMethod */ $value, $haystack ); +$obj?->array_keys( $array, /* testNullsafeObjectMethod */ $value ); +ArrayHelper::in_array( /* testStaticMethod */ $value, $haystack ); +array_keys( /* testArrayKeysFirstParamOnly */ $array ); +array_keys( array: /* testArrayKeysWrongNamedParam */ $array, search_value: 'value', strict: true ); + +/* + * The below should be recognized as being inside an array comparison function call. + */ + +in_array( /* testInArray */ $value, $haystack ); +array_search( /* testArraySearch */ $value, $haystack ); +array_keys( $array, /* testArrayKeysWithFilterValue */ $value ); +array_keys( filter_value: /* testArrayKeysNamedParam */ $value, array: $array ); +IN_array( /* testMixedCaseFunction */ $value, $haystack ); +\array_search( /* testFullyQualifiedFunction */ $value, $haystack ); +\ARRAY_KEYS( $array, /* testFullyQualifiedUpperCaseFunction */ $value ); +in_array( my_function( /* testNestedFunctionCall */ $value ), $haystack ); diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInArrayComparisonUnitTest.php b/WordPress/Tests/Helpers/ContextHelper/IsInArrayComparisonUnitTest.php new file mode 100644 index 0000000000..e92a450d14 --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsInArrayComparisonUnitTest.php @@ -0,0 +1,131 @@ +getTargetToken( $testMarker, \T_VARIABLE ); + $result = ContextHelper::is_in_array_comparison( self::$phpcsFile, $stackPtr ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsInArrayComparison() + * + * @return array> + */ + public static function dataIsInArrayComparison() { + return array( + // Cases that should return false. + 'bare_variable' => array( + 'testMarker' => '/* testBareVariable */', + 'expectedResult' => false, + ), + 'other_function_call' => array( + 'testMarker' => '/* testOtherFunctionCall */', + 'expectedResult' => false, + ), + 'partially_qualified_function' => array( + 'testMarker' => '/* testPartiallyQualifiedFunction */', + 'expectedResult' => false, + ), + 'fully_qualified_namespaced_function' => array( + 'testMarker' => '/* testFullyQualifiedNamespacedFunction */', + 'expectedResult' => false, + ), + 'namespace_relative_function' => array( + 'testMarker' => '/* testNamespaceRelativeFunction */', + 'expectedResult' => false, + ), + 'namespace_relative_sub_function' => array( + 'testMarker' => '/* testNamespaceRelativeSubFunction */', + 'expectedResult' => false, + ), + 'object_method' => array( + 'testMarker' => '/* testObjectMethod */', + 'expectedResult' => false, + ), + 'nullsafe_object_method' => array( + 'testMarker' => '/* testNullsafeObjectMethod */', + 'expectedResult' => false, + ), + 'static_method' => array( + 'testMarker' => '/* testStaticMethod */', + 'expectedResult' => false, + ), + 'array_keys_first_param_only' => array( + 'testMarker' => '/* testArrayKeysFirstParamOnly */', + 'expectedResult' => false, + ), + 'array_keys_wrong_named_param' => array( + 'testMarker' => '/* testArrayKeysWrongNamedParam */', + 'expectedResult' => false, + ), + + // Cases that should return true. + 'in_array' => array( + 'testMarker' => '/* testInArray */', + 'expectedResult' => true, + ), + 'array_search' => array( + 'testMarker' => '/* testArraySearch */', + 'expectedResult' => true, + ), + 'array_keys_with_filter_value' => array( + 'testMarker' => '/* testArrayKeysWithFilterValue */', + 'expectedResult' => true, + ), + 'array_keys_named_param' => array( + 'testMarker' => '/* testArrayKeysNamedParam */', + 'expectedResult' => true, + ), + 'mixed_case_function' => array( + 'testMarker' => '/* testMixedCaseFunction */', + 'expectedResult' => true, + ), + 'fully_qualified_function' => array( + 'testMarker' => '/* testFullyQualifiedFunction */', + 'expectedResult' => true, + ), + 'fully_qualified_upper_case_function' => array( + 'testMarker' => '/* testFullyQualifiedUpperCaseFunction */', + 'expectedResult' => true, + ), + 'nested_function_call' => array( + 'testMarker' => '/* testNestedFunctionCall */', + 'expectedResult' => true, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc new file mode 100644 index 0000000000..8a20dade8a --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc @@ -0,0 +1,78 @@ +valid_function1( /* testObjectMethodInsideCall */ array() ); +/* testNullsafeObjectMethod */ +$obj?->valid_function1( /* testNullsafeObjectMethodInsideCall */ [ 1, 2, 3 ] ); + +/* + * The below should only be recognized as inside a function call to one of the valid functions when `$allow_nested` + * is `true`. + */ + +/* testNestedOuter */ +valid_function1( another_function( /* testNestedOuterInsideCall */ 'param' ) ); +/* testNestedMultipleLevels */ +valid_function1( middle_function( inner_function( /* testNestedMultipleLevelsInsideCall */ 999 ) ) ); + +/* + * The below should only be recognized as inside a function call to one of the valid functions when both + * `$global_function` is `false` and `$allow_nested` is `true`. + */ + +MyNamespace\/* testNestedBothNamespacedOuter */ valid_function1( + MyNamespace\other_function( + /* testNestedBothNamespacedOuterInsideCall */ 'value' . $var + ) +); + +/* + * Safeguard to ensure parentheses in other parameters within the same function call don't confuse the method. + */ +/* testOtherParamsWithParentheses */ +valid_function1( + array( 'key1' => 'value1' ), + /* testOtherParamsWithParenthesesInsideCall */ $var, + function() { return 'closure value'; } +); diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php new file mode 100644 index 0000000000..c80188631b --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php @@ -0,0 +1,462 @@ +> + */ + private const PARAMETER_MAP = array( + self::GLOBAL_ONLY => array( + 'global_function' => true, + 'allow_nested' => false, + ), + self::GLOBAL_NESTED => array( + 'global_function' => true, + 'allow_nested' => true, + ), + self::NON_GLOBAL_ONLY => array( + 'global_function' => false, + 'allow_nested' => false, + ), + self::NON_GLOBAL_NESTED => array( + 'global_function' => false, + 'allow_nested' => true, + ), + ); + + /** + * Expected results: when a test case uses this constant, `is_in_function_call()` should return `false` regardless + * of the value of the parameters `$global_function` and `$allow_nested`. + * + * @var array + */ + private const EXPECT_NO_MATCH = array( + self::GLOBAL_ONLY => false, + self::GLOBAL_NESTED => false, + self::NON_GLOBAL_ONLY => false, + self::NON_GLOBAL_NESTED => false, + ); + + /** + * Expected results: when a test case uses this constant, `is_in_function_call()` should return the function name + * pointer regardless of the value of the parameters `$global_function` and `$allow_nested`. + * + * @var array + */ + private const EXPECT_ALWAYS_MATCH = array( + self::GLOBAL_ONLY => true, + self::GLOBAL_NESTED => true, + self::NON_GLOBAL_ONLY => true, + self::NON_GLOBAL_NESTED => true, + ); + + /** + * Expected results: when a test case uses this constant, `is_in_function_call()` should return the function name + * pointer when `$global_function` is `false`, and `false` when `$global_function` is `true`. + * + * @var array + */ + private const EXPECT_NON_GLOBAL_ONLY = array( + self::GLOBAL_ONLY => false, + self::GLOBAL_NESTED => false, + self::NON_GLOBAL_ONLY => true, + self::NON_GLOBAL_NESTED => true, + ); + + /** + * Expected results: when a test case uses this constant, `is_in_function_call()` should return the function name + * pointer when `$allow_nested` is `true`, and `false` when `$allow_nested` is `false`. + * + * @var array + */ + private const EXPECT_NESTED_ONLY = array( + self::GLOBAL_ONLY => false, + self::GLOBAL_NESTED => true, + self::NON_GLOBAL_ONLY => false, + self::NON_GLOBAL_NESTED => true, + ); + + /** + * Expected results: when a test case uses this constant, `is_in_function_call()` should return the function name + * pointer when both `$global_function` is `false` and `$allow_nested` is `true`, and `false` otherwise. + * + * @var array + */ + private const EXPECT_NON_GLOBAL_NESTED_ONLY = array( + self::GLOBAL_ONLY => false, + self::GLOBAL_NESTED => false, + self::NON_GLOBAL_ONLY => false, + self::NON_GLOBAL_NESTED => true, + ); + + /** + * Test is_in_function_call() when $valid_functions is an empty array. + * + * @return void + */ + public function testIsInFunctionCallShouldReturnFalseWhenEmptyValidFunctions() { + $insideFunctionPtr = $this->getTargetToken( '/* testLowercaseNameInsideCall */', \T_VARIABLE ); + $result = ContextHelper::is_in_function_call( + self::$phpcsFile, + $insideFunctionPtr, + array() + ); + + $this->assertFalse( $result ); + } + + /** + * Test that is_in_function_call() matches regardless of the case of $valid_functions keys. + * + * @dataProvider dataIsInFunctionCallShouldMatchRegardlessOfValidFunctionsKeyCase + * + * @param string $functionName The function name to use as a key in $valid_functions. + * + * @return void + */ + public function testIsInFunctionCallShouldMatchRegardlessOfValidFunctionsKeyCase( $functionName ) { + $insideFunctionPtr = $this->getTargetToken( '/* testLowercaseNameInsideCall */', \T_VARIABLE ); + $expected = $this->getTargetToken( '/* testLowercaseName */', \T_STRING ); + $result = ContextHelper::is_in_function_call( + self::$phpcsFile, + $insideFunctionPtr, + array( + $functionName => true, + ) + ); + + $this->assertSame( $expected, $result ); + } + + /** + * Data provider. + * + * @see testIsInFunctionCallShouldMatchRegardlessOfValidFunctionsKeyCase() + * + * @return array> + */ + public static function dataIsInFunctionCallShouldMatchRegardlessOfValidFunctionsKeyCase() { + return array( + 'lowercase key' => array( + 'functionName' => 'valid_function1', + ), + 'uppercase key' => array( + 'functionName' => 'VALID_FUNCTION1', + ), + 'mixed case key' => array( + 'functionName' => 'Valid_Function1', + ), + ); + } + + /** + * Test is_in_function_call() with specific parameters. + * + * @dataProvider dataIsInFunctionCallWithDefaultParams + * @dataProvider dataIsInFunctionCallWithGlobalFalse + * @dataProvider dataIsInFunctionCallWithNestedTrue + * @dataProvider dataIsInFunctionCallWithGlobalFalseNestedTrue + * + * @param string $marker The comment which prefaces the target token. + * @param int|string $tokenType The token type to search for. + * @param bool $shouldMatch Whether `is_in_function_call()` should find a match. + * @param string|null $expectedMarker The comment which prefaces the expected function name + * in the test file (if a match is expected). + * @param array $params The is_in_function_call() parameter values. + * + * @return void + */ + public function testIsInFunctionCall( $marker, $tokenType, $shouldMatch, $expectedMarker, $params ) { + $insideFunctionPtr = $this->getTargetToken( $marker, $tokenType ); + $result = ContextHelper::is_in_function_call( + self::$phpcsFile, + $insideFunctionPtr, + array( + 'valid_function1' => true, + 'valid_function2' => true, + ), + $params['global_function'], + $params['allow_nested'] + ); + + $expected = false; + if ( true === $shouldMatch ) { + $expected = $this->getTargetToken( $expectedMarker, \T_STRING ); + } + + $this->assertSame( $expected, $result ); + } + + /** + * Data provider. + * + * @see testIsInFunctionCall() + * + * @return array|null>> + */ + public static function dataIsInFunctionCallWithDefaultParams() { + $data = self::dataIsInFunctionCall(); + $newData = array(); + + // Update 'shouldMatch' and 'params' to only contain the values relevant for this test. + foreach ( $data as $key => $dataset ) { + $key = self::GLOBAL_ONLY . ' | ' . $key; + $newData[ $key ] = $dataset; + $newData[ $key ]['shouldMatch'] = $dataset['shouldMatch'][ self::GLOBAL_ONLY ]; + $newData[ $key ]['params'] = self::PARAMETER_MAP[ self::GLOBAL_ONLY ]; + } + + return $newData; + } + + /** + * Data provider. + * + * @see testIsInFunctionCall() + * + * @return array|null>> + */ + public static function dataIsInFunctionCallWithGlobalFalse() { + $data = self::dataIsInFunctionCall(); + $newData = array(); + + // Update 'shouldMatch' and 'params' to only contain the values relevant for this test. + foreach ( $data as $key => $dataset ) { + $key = self::NON_GLOBAL_ONLY . ' | ' . $key; + $newData[ $key ] = $dataset; + $newData[ $key ]['shouldMatch'] = $dataset['shouldMatch'][ self::NON_GLOBAL_ONLY ]; + $newData[ $key ]['params'] = self::PARAMETER_MAP[ self::NON_GLOBAL_ONLY ]; + } + + return $newData; + } + + /** + * Data provider. + * + * @see testIsInFunctionCall() + * + * @return array|null>> + */ + public static function dataIsInFunctionCallWithNestedTrue() { + $data = self::dataIsInFunctionCall(); + $newData = array(); + + // Update 'shouldMatch' and 'params' to only contain the values relevant for this test. + foreach ( $data as $key => $dataset ) { + $key = self::GLOBAL_NESTED . ' | ' . $key; + $newData[ $key ] = $dataset; + $newData[ $key ]['shouldMatch'] = $dataset['shouldMatch'][ self::GLOBAL_NESTED ]; + $newData[ $key ]['params'] = self::PARAMETER_MAP[ self::GLOBAL_NESTED ]; + } + + return $newData; + } + + /** + * Data provider. + * + * @see testIsInFunctionCall() + * + * @return array|null>> + */ + public static function dataIsInFunctionCallWithGlobalFalseNestedTrue() { + $data = self::dataIsInFunctionCall(); + $newData = array(); + + // Update 'shouldMatch' and 'params' to only contain the values relevant for this test. + foreach ( $data as $key => $dataset ) { + $key = self::NON_GLOBAL_NESTED . ' | ' . $key; + $newData[ $key ] = $dataset; + $newData[ $key ]['shouldMatch'] = $dataset['shouldMatch'][ self::NON_GLOBAL_NESTED ]; + $newData[ $key ]['params'] = self::PARAMETER_MAP[ self::NON_GLOBAL_NESTED ]; + } + + return $newData; + } + + /** + * Base data provider. Wrapper data providers adapt this data for their specific parameter combination. + * + * @return array>> + */ + public static function dataIsInFunctionCall() { + $data = array( + // Cases that should never match (regardless of parameters). + 'plain_assignment' => array( + 'marker' => '/* testPlainAssignment */', + 'tokenType' => \T_CONSTANT_ENCAPSED_STRING, + 'shouldMatch' => self::EXPECT_NO_MATCH, + ), + 'different_function' => array( + 'marker' => '/* testDifferentFunction */', + 'tokenType' => \T_LNUMBER, + 'shouldMatch' => self::EXPECT_NO_MATCH, + ), + 'inside_closure' => array( + 'marker' => '/* testInsideClosure */', + 'tokenType' => \T_VARIABLE, + 'shouldMatch' => self::EXPECT_NO_MATCH, + ), + 'variable_function' => array( + 'marker' => '/* testVariableFunction */', + 'tokenType' => \T_VARIABLE, + 'shouldMatch' => self::EXPECT_NO_MATCH, + ), + 'if_condition' => array( + 'marker' => '/* testIfCondition */', + 'tokenType' => \T_TRUE, + 'shouldMatch' => self::EXPECT_NO_MATCH, + ), + + // Cases that should always match (regardless of parameters). + 'lowercase_name' => array( + 'marker' => '/* testLowercaseNameInsideCall */', + 'tokenType' => \T_VARIABLE, + 'shouldMatch' => self::EXPECT_ALWAYS_MATCH, + 'expectedMarker' => '/* testLowercaseName */', + ), + 'uppercase_name' => array( + 'marker' => '/* testUppercaseNameInsideCall */', + 'tokenType' => \T_CONSTANT_ENCAPSED_STRING, + 'shouldMatch' => self::EXPECT_ALWAYS_MATCH, + 'expectedMarker' => '/* testUppercaseName */', + ), + 'fully_qualified' => array( + 'marker' => '/* testFullyQualifiedInsideCall */', + 'tokenType' => \T_LNUMBER, + 'shouldMatch' => self::EXPECT_ALWAYS_MATCH, + 'expectedMarker' => '/* testFullyQualified */', + ), + 'nested_inner' => array( + 'marker' => '/* testNestedInnerInsideCall */', + 'tokenType' => \T_TRUE, + 'shouldMatch' => self::EXPECT_ALWAYS_MATCH, + 'expectedMarker' => '/* testNestedInner */', + ), + + // Cases that match only when `$global_function` is `false`. + 'namespaced_function' => array( + 'marker' => '/* testNamespacedFunctionInsideCall */', + 'tokenType' => \T_STRING, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testNamespacedFunction */', + ), + 'fully_qualified_namespaced_function' => array( + 'marker' => '/* testFullyQualifiedNamespacedFunctionInsideCall */', + 'tokenType' => \T_NULL, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testFullyQualifiedNamespacedFunction */', + ), + 'namespace_relative_function' => array( + 'marker' => '/* testNamespaceRelativeFunctionInsideCall */', + 'tokenType' => \T_DNUMBER, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testNamespaceRelativeFunction */', + ), + 'static_method' => array( + 'marker' => '/* testStaticMethodInsideCall */', + 'tokenType' => \T_CONSTANT_ENCAPSED_STRING, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testStaticMethod */', + ), + 'object_method' => array( + 'marker' => '/* testObjectMethodInsideCall */', + 'tokenType' => \T_ARRAY, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testObjectMethod */', + ), + 'nullsafe_object_method' => array( + 'marker' => '/* testNullsafeObjectMethodInsideCall */', + 'tokenType' => \T_OPEN_SHORT_ARRAY, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testNullsafeObjectMethod */', + ), + + // Cases that match only when `$allow_nested` is `true`. + 'nested_outer' => array( + 'marker' => '/* testNestedOuterInsideCall */', + 'tokenType' => \T_CONSTANT_ENCAPSED_STRING, + 'shouldMatch' => self::EXPECT_NESTED_ONLY, + 'expectedMarker' => '/* testNestedOuter */', + ), + 'nested_multiple_levels' => array( + 'marker' => '/* testNestedMultipleLevelsInsideCall */', + 'tokenType' => \T_LNUMBER, + 'shouldMatch' => self::EXPECT_NESTED_ONLY, + 'expectedMarker' => '/* testNestedMultipleLevels */', + ), + 'nested_both_namespaced_outer' => array( + 'marker' => '/* testNestedBothNamespacedOuterInsideCall */', + 'tokenType' => \T_STRING_CONCAT, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_NESTED_ONLY, + 'expectedMarker' => '/* testNestedBothNamespacedOuter */', + ), + + // Safeguard: parentheses in other parameters should not confuse the method. + 'other_params_with_parentheses' => array( + 'marker' => '/* testOtherParamsWithParenthesesInsideCall */', + 'tokenType' => \T_VARIABLE, + 'shouldMatch' => self::EXPECT_ALWAYS_MATCH, + 'expectedMarker' => '/* testOtherParamsWithParentheses */', + ), + ); + + foreach ( $data as $key => $dataset ) { + if ( isset( $dataset['expectedMarker'] ) === false ) { + $data[ $key ]['expectedMarker'] = null; + } + } + + return $data; + } +} diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInIssetOrEmptyUnitTest.inc b/WordPress/Tests/Helpers/ContextHelper/IsInIssetOrEmptyUnitTest.inc new file mode 100644 index 0000000000..b3567121f8 --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsInIssetOrEmptyUnitTest.inc @@ -0,0 +1,34 @@ +array_key_exists( 'key', /* testObjectMethod */ $array ); +$obj?->key_exists( 'key', /* testNullsafeObjectMethod */ $array ); +MyClass::array_key_exists( 'key', /* testStaticMethod */ $array ); +key_exists( 'key', my_function( /* testNestedNonTargetFunctionCall */ $array ) ); +$obj->isset( /* testIssetObjectMethod */ $value ); +Foo::empty( /* testEmptyStaticMethod */ $value ); +MyNamespace\isset( /* testIssetNamespacedFunction */ $value ); + +/* + * The below should be recognized as being inside an isset/empty check. + */ + +isset( /* testIsset */ $value ); +empty( /* testEmpty */ $value ); +array_key_exists( 'key', /* testUnqualifiedFunction */ $array ); +Key_Exists( 'key', /* testMixedCaseFunction */ $array ); +\array_key_exists( 'key', /* testFullyQualifiedFunction */ $array ); +\KEY_EXISTS( 'key', /* testFullyQualifiedUpperCaseFunction */ $array ); +array_key_exists( array: /* testNamedParamReversedOrder */ $array, key: 'foo' ); +array_key_exists( 'key', \key_exists( 'key', /* testNestedValidFunctionCall */ $array ) ); diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInIssetOrEmptyUnitTest.php b/WordPress/Tests/Helpers/ContextHelper/IsInIssetOrEmptyUnitTest.php new file mode 100644 index 0000000000..83bb739adb --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsInIssetOrEmptyUnitTest.php @@ -0,0 +1,147 @@ +getTargetToken( $testMarker, \T_VARIABLE ); + $result = ContextHelper::is_in_isset_or_empty( self::$phpcsFile, $stackPtr ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsInIssetOrEmpty() + * + * @return array> + */ + public static function dataIsInIssetOrEmpty() { + return array( + // Cases that should return false. + 'bare_variable' => array( + 'testMarker' => '/* testBareVariable */', + 'expectedResult' => false, + ), + 'other_function_call' => array( + 'testMarker' => '/* testOtherFunctionCall */', + 'expectedResult' => false, + ), + 'missing_array_param' => array( + 'testMarker' => '/* testMissingArrayParam */', + 'expectedResult' => false, + ), + 'key_param_not_array_param' => array( + 'testMarker' => '/* testKeyParamNotArrayParam */', + 'expectedResult' => false, + ), + 'partially_qualified_function' => array( + 'testMarker' => '/* testPartiallyQualifiedFunction */', + 'expectedResult' => false, + ), + 'fully_qualified_namespaced_function' => array( + 'testMarker' => '/* testFullyQualifiedNamespacedFunction */', + 'expectedResult' => false, + ), + 'namespace_relative_function' => array( + 'testMarker' => '/* testNamespaceRelativeFunction */', + 'expectedResult' => false, + ), + 'namespace_relative_sub_function' => array( + 'testMarker' => '/* testNamespaceRelativeSubFunction */', + 'expectedResult' => false, + ), + 'object_method' => array( + 'testMarker' => '/* testObjectMethod */', + 'expectedResult' => false, + ), + 'nullsafe_object_method' => array( + 'testMarker' => '/* testNullsafeObjectMethod */', + 'expectedResult' => false, + ), + 'static_method' => array( + 'testMarker' => '/* testStaticMethod */', + 'expectedResult' => false, + ), + 'nested_non_target_function_call' => array( + 'testMarker' => '/* testNestedNonTargetFunctionCall */', + 'expectedResult' => false, + ), + 'isset_object_method' => array( + 'testMarker' => '/* testIssetObjectMethod */', + 'expectedResult' => false, + ), + 'empty_static_method' => array( + 'testMarker' => '/* testEmptyStaticMethod */', + 'expectedResult' => false, + ), + 'isset_namespaced_function' => array( + 'testMarker' => '/* testIssetNamespacedFunction */', + 'expectedResult' => false, + ), + + // Cases that should return true. + 'isset' => array( + 'testMarker' => '/* testIsset */', + 'expectedResult' => true, + ), + 'empty' => array( + 'testMarker' => '/* testEmpty */', + 'expectedResult' => true, + ), + 'unqualified_function' => array( + 'testMarker' => '/* testUnqualifiedFunction */', + 'expectedResult' => true, + ), + 'mixed_case_function' => array( + 'testMarker' => '/* testMixedCaseFunction */', + 'expectedResult' => true, + ), + 'fully_qualified_function' => array( + 'testMarker' => '/* testFullyQualifiedFunction */', + 'expectedResult' => true, + ), + 'fully_qualified_upper_case_function' => array( + 'testMarker' => '/* testFullyQualifiedUpperCaseFunction */', + 'expectedResult' => true, + ), + 'named_param_reversed_order' => array( + 'testMarker' => '/* testNamedParamReversedOrder */', + 'expectedResult' => true, + ), + 'nested_valid_function_call' => array( + 'testMarker' => '/* testNestedValidFunctionCall */', + 'expectedResult' => true, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInTypeTestUnitTest.inc b/WordPress/Tests/Helpers/ContextHelper/IsInTypeTestUnitTest.inc new file mode 100644 index 0000000000..b3b2015bf2 --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsInTypeTestUnitTest.inc @@ -0,0 +1,26 @@ +is_string( /* testObjectMethod */ $value ); +$obj?->is_object( /* testNullsafeObjectMethod */ $value ); +TypeChecker::is_int( /* testStaticMethod */ $value ); +is_scalar( my_function( /* testNestedNonTargetFunctionCall */ $value ) ); + +/* + * The below should be recognized as being inside a type test function call. + */ + +is_array( /* testUnqualifiedFunction */ $value ); +Is_Bool( /* testMixedCaseFunction */ $value ); +\is_string( /* testFullyQualifiedFunction */ $value ); +\IS_NUMERIC( /* testFullyQualifiedUpperCaseFunction */ $value ); +is_int( is_float( /* testNestedTypeTestCall */ $value ) ); diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInTypeTestUnitTest.php b/WordPress/Tests/Helpers/ContextHelper/IsInTypeTestUnitTest.php new file mode 100644 index 0000000000..cc731c6713 --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsInTypeTestUnitTest.php @@ -0,0 +1,115 @@ +getTargetToken( $testMarker, \T_VARIABLE ); + $result = ContextHelper::is_in_type_test( self::$phpcsFile, $stackPtr ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsInTypeTest() + * + * @return array> + */ + public static function dataIsInTypeTest() { + return array( + // Cases that should return false. + 'bare_variable' => array( + 'testMarker' => '/* testBareVariable */', + 'expectedResult' => false, + ), + 'other_function_call' => array( + 'testMarker' => '/* testOtherFunctionCall */', + 'expectedResult' => false, + ), + 'partially_qualified_function' => array( + 'testMarker' => '/* testPartiallyQualifiedFunction */', + 'expectedResult' => false, + ), + 'fully_qualified_namespaced_function' => array( + 'testMarker' => '/* testFullyQualifiedNamespacedFunction */', + 'expectedResult' => false, + ), + 'namespace_relative_function' => array( + 'testMarker' => '/* testNamespaceRelativeFunction */', + 'expectedResult' => false, + ), + 'namespace_relative_sub_function' => array( + 'testMarker' => '/* testNamespaceRelativeSubFunction */', + 'expectedResult' => false, + ), + 'object_method' => array( + 'testMarker' => '/* testObjectMethod */', + 'expectedResult' => false, + ), + 'nullsafe_object_method' => array( + 'testMarker' => '/* testNullsafeObjectMethod */', + 'expectedResult' => false, + ), + 'static_method' => array( + 'testMarker' => '/* testStaticMethod */', + 'expectedResult' => false, + ), + 'nested_non_target_function_call' => array( + 'testMarker' => '/* testNestedNonTargetFunctionCall */', + 'expectedResult' => false, + ), + + // Cases that should return true. + 'unqualified_function' => array( + 'testMarker' => '/* testUnqualifiedFunction */', + 'expectedResult' => true, + ), + 'mixed_case_function' => array( + 'testMarker' => '/* testMixedCaseFunction */', + 'expectedResult' => true, + ), + 'fully_qualified_function' => array( + 'testMarker' => '/* testFullyQualifiedFunction */', + 'expectedResult' => true, + ), + 'fully_qualified_upper_case_function' => array( + 'testMarker' => '/* testFullyQualifiedUpperCaseFunction */', + 'expectedResult' => true, + ), + 'nested_type_test_call' => array( + 'testMarker' => '/* testNestedTypeTestCall */', + 'expectedResult' => true, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/FormattingFunctionsHelper/IsFormattingFunctionUnitTest.php b/WordPress/Tests/Helpers/FormattingFunctionsHelper/IsFormattingFunctionUnitTest.php new file mode 100644 index 0000000000..e567d0f7c3 --- /dev/null +++ b/WordPress/Tests/Helpers/FormattingFunctionsHelper/IsFormattingFunctionUnitTest.php @@ -0,0 +1,64 @@ +assertSame( + $expectedResult, + FormattingFunctionsHelper::is_formatting_function( $functionName ) + ); + } + + /** + * Data provider. + * + * @see testIsFormattingFunction() + * + * @return array> + */ + public static function dataIsFormattingFunction() { + return array( + 'lowercase_name' => array( + 'functionName' => 'sprintf', + 'expectedResult' => true, + ), + 'mixedcase_name' => array( + 'functionName' => 'iMpLoDe', + 'expectedResult' => true, + ), + 'not_a_formatting_function' => array( + 'functionName' => 'printf', + 'expectedResult' => false, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/UnslashingFunctionsHelper/IsUnslashingFunctionUnitTest.php b/WordPress/Tests/Helpers/UnslashingFunctionsHelper/IsUnslashingFunctionUnitTest.php new file mode 100644 index 0000000000..c797cb2443 --- /dev/null +++ b/WordPress/Tests/Helpers/UnslashingFunctionsHelper/IsUnslashingFunctionUnitTest.php @@ -0,0 +1,64 @@ +assertSame( + $expectedResult, + UnslashingFunctionsHelper::is_unslashing_function( $functionName ) + ); + } + + /** + * Data provider. + * + * @see testIsUnslashingFunction() + * + * @return array> + */ + public static function dataIsUnslashingFunction() { + return array( + 'lowercase_name' => array( + 'functionName' => 'wp_unslash', + 'expectedResult' => true, + ), + 'mixedcase_name' => array( + 'functionName' => 'sTrIpSlAsHeS_DeEp', + 'expectedResult' => true, + ), + 'not_an_unslashing_function' => array( + 'functionName' => 'stripslashes', + 'expectedResult' => false, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.inc b/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.inc new file mode 100644 index 0000000000..e1992e34b8 --- /dev/null +++ b/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.inc @@ -0,0 +1,24 @@ +getTargetToken( $testMarker, \T_STRING ); + $functionName = self::$phpcsFile->getTokens()[ $stackPtr ]['content']; + $parameters = PassedParameters::getParameters( self::$phpcsFile, $stackPtr ); + + $result = WPHookHelper::get_hook_name_param( $functionName, $parameters ); + + if ( is_array( $result ) ) { + // The details of the parameter are populated by PassedParameters::getParameters(). + // Here we only verify which parameter was selected. + $result = $result['clean']; + } + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testGetHookNameParam() + * + * @return array> + */ + public static function dataGetHookNameParam() { + return array( + 'not_a_hook_function' => array( + 'testMarker' => '/* testNotAHookFunction */', + 'expectedResult' => false, + ), + 'hook_name_param_missing' => array( + 'testMarker' => '/* testHookNameParamMissing */', + 'expectedResult' => false, + ), + 'lowercase_name' => array( + 'testMarker' => '/* testLowercaseName */', + 'expectedResult' => "'my_action'", + ), + 'mixedcase_name' => array( + 'testMarker' => '/* testMixedCaseName */', + 'expectedResult' => "'my_filter'", + ), + 'named_parameter' => array( + 'testMarker' => '/* testNamedParameter */', + 'expectedResult' => "'my_action'", + ), + ); + } +} diff --git a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php index 98649fb620..763760f1c3 100644 --- a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php @@ -17,7 +17,7 @@ * @since 0.10.0 * @since 0.13.0 Class name changed: this class is now namespaced. * - * @covers \WordPressCS\WordPress\Helpers\WPHookHelper + * @covers \WordPressCS\WordPress\Helpers\WPHookHelper::get_functions * @covers \WordPressCS\WordPress\Sniffs\NamingConventions\ValidHookNameSniff */ final class ValidHookNameUnitTest extends AbstractSniffUnitTest { diff --git a/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.1.inc b/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.1.inc index 5c0ed5729a..bf6450c790 100644 --- a/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.1.inc +++ b/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.1.inc @@ -5,7 +5,7 @@ register_post_type( 'my_own_post_type', array() ); // OK. register_post_type( 'my-own-post-type-too-long', array() ); // Bad. register_post_type( 'author', array() ); // Bad. Reserved slug name. register_post_type( 'My-Own-Post-Type', array() ); // Bad. Invalid chars: uppercase. -register_post_type( 'my/own/post/type', array() ); // Bad. Invalid chars: "/". +register_POST_TYPE( 'my/own/post/type', array() ); // Bad. Invalid chars: "/". register_post_type( <<get_post_type_id() ); // Non string literal. Warning register_post_type( null, array() ); // Non string literal. Warning with severity: 3 register_post_type( 1000, array() ); // Non string literal. Warning with severity: 3 -register_post_type( 'wp_', array() ); // Bad. Reserved prefix. +\REGISTER_post_TYPE( 'wp_', array() ); // Bad. Reserved prefix. register_post_type( 'wp_post_type', array() ); // Bad. Reserved prefix. register_post_type( '', array() ); // Bad. Empty post type slug. @@ -68,3 +68,12 @@ register_post_type( // Post type name. $name,// Non string literal. Warning with severity: 3 ); + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\register_post_type( 'my-own-post-type-too-long', array() ); // Bad. +MyNamespace\register_post_type( 'my-own-post-type-too-long', array() ); // Ok. +\MyNamespace\register_post_type( 'my-own-post-type-too-long', array() ); // Ok. +namespace\register_post_type( 'my-own-post-type-too-long', array() ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\register_post_type( 'my-own-post-type-too-long', array() ); // Ok. diff --git a/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.php b/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.php index 821b73ef24..cb2d41c998 100644 --- a/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.php @@ -57,6 +57,7 @@ public function getErrorList( $testFile = '' ) { 52 => 1, 62 => 1, 64 => 1, + 75 => 1, ); case 'ValidPostTypeSlugUnitTest.2.inc': diff --git a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc index ada6fa9b4a..963e06888a 100644 --- a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc @@ -30,7 +30,7 @@ phpinfo(); // Ok - within excluded group. // Reset group exclusions. // phpcs:set WordPress.PHP.DevelopmentFunctions exclude[] -trigger_error(); // Error. +\TRIGGER_ERROR(); // Error. phpinfo(); // Error. Wrapper_Class::var_dump(); // OK, not the native PHP function. @@ -40,6 +40,7 @@ $wrapper ->var_dump(); // OK, not the native PHP 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. +MyNamespace\phpinfo(); +\MyNamespace\print_r( $value ); +namespace\error_reporting(); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\debug_backtrace(); diff --git a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc index 84b7378b65..d79d88abd6 100644 --- a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.inc @@ -1,13 +1,13 @@ 1 ) ); // Bad. +exTRAct( array( 'a' => 1 ) ); // Bad. // Similarly named functions or methods however are fine. my_extract(); // Ok. @@ -12,6 +13,8 @@ $my_object->extract(); // Ok. * Safeguard correct handling of all types of namespaced function calls. */ \extract( array( 'a' => 1 ) ); +\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. +namespace\Sub\extract( array( 'a' => 1 ) ); diff --git a/WordPress/Tests/PHP/DontExtractUnitTest.php b/WordPress/Tests/PHP/DontExtractUnitTest.php index f9a1df7494..52d6fdeb65 100644 --- a/WordPress/Tests/PHP/DontExtractUnitTest.php +++ b/WordPress/Tests/PHP/DontExtractUnitTest.php @@ -30,7 +30,9 @@ final class DontExtractUnitTest extends AbstractSniffUnitTest { public function getErrorList() { return array( 3 => 1, - 14 => 1, + 4 => 1, + 15 => 1, + 16 => 1, ); } diff --git a/WordPress/Tests/PHP/IniSetUnitTest.inc b/WordPress/Tests/PHP/IniSetUnitTest.inc index c5ef7295d1..96a310cb19 100644 --- a/WordPress/Tests/PHP/IniSetUnitTest.inc +++ b/WordPress/Tests/PHP/IniSetUnitTest.inc @@ -14,8 +14,8 @@ ini_set('short_open_tag', 'On'); // Ok. ini_set('short_open_tag', 'on'); // Ok. ini_set('bcmath.scale', 0); // Error. -ini_set( 'bcmath.scale' ,0 ); // Error. -ini_set('display_errors', 0); // Error. +\ini_SET( 'bcmath.scale' ,0 ); // Error. +INI_set('display_errors', 0); // Error. ini_set('error_reporting', 0); // Error. ini_set('filter.default', 'full_special_chars'); // Error. ini_set('filter.default_flags', 0); // Error. @@ -58,3 +58,12 @@ ini_set( // Set the number of decimals. 0 ); // Error. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\ini_set( 'bcmath.scale', 0 ); // Error. +MyNamespace\ini_alter( 'bcmath.scale', 0 ); // Ok. +\MyNamespace\ini_set( 'bcmath.scale', 0 ); // Ok. +namespace\ini_alter( 'bcmath.scale', 0 ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\ini_set( 'bcmath.scale', 0 ); // Ok. diff --git a/WordPress/Tests/PHP/IniSetUnitTest.php b/WordPress/Tests/PHP/IniSetUnitTest.php index fede4955b2..1dd5ca4ab7 100644 --- a/WordPress/Tests/PHP/IniSetUnitTest.php +++ b/WordPress/Tests/PHP/IniSetUnitTest.php @@ -49,6 +49,7 @@ public function getErrorList() { 42 => 1, 51 => 1, 55 => 1, + 65 => 1, ); } diff --git a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc index b3bb11a71c..7d34f92fe9 100644 --- a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.inc @@ -13,9 +13,9 @@ $title = preg_split( 'cool', get_the_title() ); // Good. if ( ereg( '[A-Za-z]+', $title, $regs ) ) // Bad, ereg deprecated. Use preg_match instead. die( $regs ); -if ( eregi( '[a-z]+', $title, $regs ) ) {} // Bad, eregi deprecated. Use preg_match instead. +if ( \EREGI( '[a-z]+', $title, $regs ) ) {} // Bad, eregi deprecated. Use preg_match instead. -$title = ereg_replace( 'cool', 'not cool', get_the_title() ); // Bad, ereg_replace has been deprecated. Use preg_replace instead. +$title = ereg_REPLACE( 'cool', 'not cool', get_the_title() ); // Bad, ereg_replace has been deprecated. Use preg_replace instead. $title = eregi_replace( 'cool', 'not cool', get_the_title() ); // Bad, eregi_replace also deprecated. Use preg_replace instead. @@ -29,6 +29,7 @@ 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. +MyNamespace\ereg( 'pattern', $string ); +\MyNamespace\ereg_replace( 'pattern', 'replacement', $string ); +namespace\spliti( ':', $date ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\sql_regcase( 'string' ); diff --git a/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.inc b/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.inc index 1942b2b07f..06611613af 100644 --- a/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.inc +++ b/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.inc @@ -4,12 +4,21 @@ preg_quote($keywords, '/'); // OK. preg_quote( $keywords, '`' ); // OK. preg_quote($keywords); // Warning. -$textbody = preg_replace ( "/" . preg_quote($word) . "/", // Warning +$textbody = preg_replace ( "/" . \PREG_quote($word) . "/", // Warning "" . $word . "", $textbody ); // Safeguard support for PHP 8.0+ named parameters. preg_quote(delimiter: '#', str: $keywords); // OK. preg_quote(str: $keywords); // Warning. -preg_quote(str: $keywords, delimitter: '#'); // Warning (typo in param name). +Preg_QUOTE(str: $keywords, delimitter: '#'); // Warning (typo in param name). preg_quote(delimiter: '#'); // OK. Invalid function call, but that's not the concern of this sniff. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\preg_quote($keywords); // Warning. +MyNamespace\preg_quote($keywords); // Ok. +\MyNamespace\preg_quote($keywords); // Ok. +namespace\preg_quote($keywords); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\preg_quote($keywords); // Ok. diff --git a/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.php b/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.php index 63d4e0eb9f..4c6a254fae 100644 --- a/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.php +++ b/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.php @@ -40,6 +40,7 @@ public function getWarningList() { 7 => 1, 13 => 1, 14 => 1, + 20 => 1, ); } } diff --git a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc index 09465fa6c5..bada4800a3 100644 --- a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc +++ b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.inc @@ -3,11 +3,14 @@ add_action( 'widgets_init', create_function( '', // Error. 'return register_widget( "time_more_on_time_widget" );' ) ); +CREATE_function( '', '' ); // Error. /* * Safeguard correct handling of all types of namespaced function calls. */ \create_function('', 'return;'); +\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. +namespace\Sub\create_function('', 'return;'); diff --git a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php index 5cfa75ae51..a596706030 100644 --- a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php @@ -28,7 +28,9 @@ final class RestrictedPHPFunctionsUnitTest extends AbstractSniffUnitTest { public function getErrorList() { return array( 3 => 1, - 10 => 1, + 6 => 1, + 11 => 1, + 12 => 1, ); } diff --git a/WordPress/Tests/PHP/StrictInArrayUnitTest.inc b/WordPress/Tests/PHP/StrictInArrayUnitTest.inc index fd1e194225..15ce708653 100644 --- a/WordPress/Tests/PHP/StrictInArrayUnitTest.inc +++ b/WordPress/Tests/PHP/StrictInArrayUnitTest.inc @@ -3,7 +3,7 @@ in_array( 1, array( '1', 1, true ), true ); // Ok. in_array( 1, array( '1', 1, true ) ); // Warning. -in_array( 1, array( '1', 1, true ), false ); // Warning. +\In_Array( 1, array( '1', 1, true ), false ); // Warning. IN_ARRAY( 1, array( '1', 1, true ), false ); // Warning. Foo::in_array( 1, array( '1', 1, true ) ); // Ok. @@ -55,3 +55,12 @@ array_search( $haystack, true // Use strict typing. ); // Ok. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\in_array( 1, array( '1', 2 ) ); // Bad. +MyNamespace\array_search( 1, array( '1', 2 ) ); // Ok. +\MyNamespace\array_keys( array( '1', 2 ) ); // Ok. +namespace\in_array( 1, array( '1', 2 ) ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\array_search( 1, array( '1', 2 ) ); // Ok. diff --git a/WordPress/Tests/PHP/StrictInArrayUnitTest.php b/WordPress/Tests/PHP/StrictInArrayUnitTest.php index 41aa9d37c4..d7104d7d9b 100644 --- a/WordPress/Tests/PHP/StrictInArrayUnitTest.php +++ b/WordPress/Tests/PHP/StrictInArrayUnitTest.php @@ -52,6 +52,7 @@ public function getWarningList() { 44 => 1, 48 => 1, 49 => 1, + 62 => 1, ); } } diff --git a/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc b/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc index 52c3a59976..f744f385da 100644 --- a/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc +++ b/WordPress/Tests/Security/EscapeOutputUnitTest.1.inc @@ -258,7 +258,7 @@ echo esc_html_x( $some_nasty_var, 'context' ); // Ok. basename( __FILE__ ), '1.3.0' ); // Bad. +_deprecated_file( $obj?->basename( __FILE__ ), '1.3.0' ); // Bad. +_deprecated_file( MyClass::basename( __FILE__ ), '1.3.0' ); // Bad. +_deprecated_file( BASENAME, __FILE__ ); // Bad. +_deprecated_file( MyNamespace\basename( __FILE__ ), '1.3.0' ); // Bad. +_deprecated_file( \MyNamespace\basename( __FILE__ ), '1.3.0' ); // Bad. +_deprecated_file( namespace\basename( __FILE__ ), '1.3.0' ); // Bad. We might want to update the regex so that the sniff stops flagging this once it can resolve relative namespaces. +_deprecated_file( namespace\Sub\basename( __FILE__ ), '1.3.0' ); // Bad. +_deprecated_file( basename(...), '1.3.0' ); // Bad. + +/* + * Safeguard correct handling of FQN true/false/null constants. + */ +echo \true, \False, \NULL; // Ok. + +/* + * Safeguard correct handling of namespaced constants that mirror the name of "safe" global PHP constants. + */ +echo MyNamespace\PHP_EOL; // Bad. +echo \MyNamespace\PHP_VERSION_ID; // Bad. +echo namespace\PHP_EXTRA_VERSION; // Bad. The sniff should stop flagging this once it can resolve relative namespaces. +echo namespace\Sub\PHP_VERSION; // Bad. + +/* + * Safeguard correct handling of FQN functions with multiple PHP short echo tags. + */ +?> + + + +', \array_map( 'esc_html', $items ) ); // Ok. +echo implode( '
', MyNamespace\array_map( 'esc_html', $items ) ); // Bad x 2. +echo implode( '
', \MyNamespace\map_deep( $items, 'esc_html' ) ); // Bad. +echo implode( '
', namespace\array_map( 'esc_html', $items ) ); // Bad x 2. The sniff should stop flagging this once it can resolve relative namespaces. +echo implode( '
', namespace\Sub\map_deep( $items, 'esc_html' ) ); // Bad. + +/* + * Safeguard correct handling of get_search_query() with FQN and non-standard case booleans for the $escaped parameter. + */ +echo \get_search_query( TRUE ); // Ok. +echo \get_search_query( \true ); // Ok. +echo \get_search_query( \True ); // Ok. +echo \get_search_query( FaLsE ); // Bad. +echo \get_search_query( \false ); // Bad. +echo \get_search_query( \FALSE ); // Bad. diff --git a/WordPress/Tests/Security/EscapeOutputUnitTest.php b/WordPress/Tests/Security/EscapeOutputUnitTest.php index aea521720d..7111a1ff13 100644 --- a/WordPress/Tests/Security/EscapeOutputUnitTest.php +++ b/WordPress/Tests/Security/EscapeOutputUnitTest.php @@ -164,6 +164,50 @@ public function getErrorList( $testFile = '' ) { 672 => 1, 673 => 1, 678 => 1, + 694 => 1, + 700 => 1, + 701 => 1, + 702 => 1, + 703 => 1, + 709 => 1, + 710 => 1, + 711 => 1, + 712 => 1, + 717 => 1, + 726 => 1, + 728 => 1, + 729 => 1, + 730 => 1, + 731 => 1, + 737 => 1, + 738 => 1, + 739 => 1, + 740 => 1, + 741 => 1, + 747 => 1, + 751 => 1, + 760 => 1, + 761 => 1, + 762 => 1, + 763 => 1, + 764 => 1, + 765 => 1, + 766 => 1, + 767 => 1, + 768 => 1, + 778 => 1, + 779 => 1, + 780 => 1, + 781 => 1, + 787 => 1, + 788 => 1, + 800 => 2, + 801 => 1, + 802 => 2, + 803 => 1, + 811 => 1, + 812 => 1, + 813 => 1, ); case 'EscapeOutputUnitTest.6.inc': diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc index fc688e09de..605caeb8c0 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc @@ -291,7 +291,7 @@ function function_containing_nested_closure() { }; } -// Tests specifically for the ContextHelper::is_in_function_call(). + function disallow_custom_unslash_before_noncecheck_via_method() { $var = MyClass::stripslashes_from_strings_only( $_POST['foo'] ); // Bad. wp_verify_nonce( $var ); diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.php b/WordPress/Tests/Security/NonceVerificationUnitTest.php index ea76b5a4b0..d5ee162048 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.php +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.php @@ -18,10 +18,6 @@ * @since 0.13.0 Class name changed: this class is now namespaced. * @since 1.0.0 This sniff has been moved from the `CSRF` category to the `Security` category. * - * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_in_function_call - * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_in_type_test - * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_in_isset_or_empty - * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_in_array_comparison * @covers \WordPressCS\WordPress\Sniffs\Security\NonceVerificationSniff */ final class NonceVerificationUnitTest extends AbstractSniffUnitTest { diff --git a/WordPress/Tests/Security/PluginMenuSlugUnitTest.inc b/WordPress/Tests/Security/PluginMenuSlugUnitTest.inc index 2c77ee621e..3f4e34f948 100644 --- a/WordPress/Tests/Security/PluginMenuSlugUnitTest.inc +++ b/WordPress/Tests/Security/PluginMenuSlugUnitTest.inc @@ -2,11 +2,11 @@ add_menu_page( $page_title, $menu_title, $capability, __FILE__, $function, $icon_url, $position ); // Bad. -add_dashboard_page( $page_title, $menu_title, $capability, __file__, $function); // Bad. +\ADD_DASHBOARD_PAGE( $page_title, $menu_title, $capability, __file__, $function); // Bad. add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, 'awesome-submenu-page', $function ); // Ok. -add_submenu_page( __FILE__ . 'parent', $page_title, $menu_title, $capability, __FILE__, $function ); // Bad x 2. +Add_Submenu_Page( __FILE__ . 'parent', $page_title, $menu_title, $capability, __FILE__, $function ); // Bad x 2. // These are all ok: not calling the WP core function. $my_class->add_dashboard_page( $page_title, $menu_title, $capability, __FILE__, $function); // Ok. @@ -20,3 +20,12 @@ add_submenu_page( parent_slug: __FILE__, // Bad. capability: $capability, ); + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\add_menu_page( $page_title, $menu_title, $capability, __FILE__, $function, $icon_url, $position ); // Bad. +MyNamespace\add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, __FILE__, $function ); // Ok. +\MyNamespace\add_dashboard_page( $page_title, $menu_title, $capability, __FILE__, $function ); // Ok. +namespace\add_menu_page( $page_title, $menu_title, $capability, __FILE__, $function, $icon_url, $position ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, __FILE__, $function ); // Ok. diff --git a/WordPress/Tests/Security/PluginMenuSlugUnitTest.php b/WordPress/Tests/Security/PluginMenuSlugUnitTest.php index 6741d048a1..cee8c0e1b6 100644 --- a/WordPress/Tests/Security/PluginMenuSlugUnitTest.php +++ b/WordPress/Tests/Security/PluginMenuSlugUnitTest.php @@ -42,6 +42,7 @@ public function getWarningList() { 5 => 1, 9 => 2, 20 => 1, + 27 => 1, ); } } diff --git a/WordPress/Tests/Security/SafeRedirectUnitTest.inc b/WordPress/Tests/Security/SafeRedirectUnitTest.inc index 3d8d19c02a..28d8a7b0a8 100644 --- a/WordPress/Tests/Security/SafeRedirectUnitTest.inc +++ b/WordPress/Tests/Security/SafeRedirectUnitTest.inc @@ -1,12 +1,15 @@ 1, - 9 => 1, + 3 => 1, + 4 => 1, + 10 => 1, + 11 => 1, ); } } diff --git a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.1.inc b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.1.inc index cc4edc147a..e19f439248 100644 --- a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.1.inc +++ b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.1.inc @@ -500,3 +500,127 @@ function test_in_match_condition_is_regarded_as_comparison() { }; } } + +/* + * Safeguard correct handling of qualified and relative namespaced calls to array key exists functions. + * Non-namespaced and fully qualified calls are already covered above. + */ +function test_namespaced_array_key_exists() { + if ( MyNamespace\array_key_exists( 'key_exists1', $_POST ) ) { + $id = (int) $_POST['key_exists1']; // Bad. + } + if ( namespace\key_exists( 'key_exists2', $_POST ) ) { + $id = (int) $_POST['key_exists2']; // Bad. Note: This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. + } + if ( namespace\Sub\array_key_exists( 'key_exists3', $_POST ) ) { + $id = (int) $_POST['key_exists3']; // Bad. + } +} + +/* + * Safeguard correct handling of all types of namespaced calls to type test functions. + */ +function test_namespaced_type_test_functions() { + if ( isset( $_POST['type_test1'] ) && \is_int( $_POST['type_test1'] ) ) {} // OK. + if ( isset( $_POST['type_test2'] ) && MyNamespace\is_string( $_POST['type_test2'] ) ) {} // Bad. + if ( isset( $_POST['type_test3'] ) && \MyNamespace\is_array( $_POST['type_test3'] ) ) {} // Bad. + if ( isset( $_POST['type_test4'] ) && namespace\is_numeric( $_POST['type_test4'] ) ) {} // Bad. Note: This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. + if ( isset( $_POST['type_test5'] ) && namespace\Sub\is_bool( $_POST['type_test5'] ) ) {} // Bad. +} + +/* + * Safeguard correct handling of all types of namespaced calls to array comparison functions. + */ +function test_namespaced_array_comparison_functions() { + if ( isset( $_POST['array_cmp1'] ) && \in_array( $_POST['array_cmp1'], $my_array, true ) ) {} // OK. + if ( isset( $_POST['array_cmp2'] ) && MyNamespace\array_search( $_POST['array_cmp2'], $my_array, true ) ) {} // Bad. + if ( isset( $_POST['array_cmp3'] ) && \MyNamespace\array_keys( $my_array, $_POST['array_cmp3'] ) ) {} // Bad. + if ( isset( $_POST['array_cmp4'] ) && namespace\in_array( $_POST['array_cmp4'], $my_array, true ) ) {} // Bad. Note: This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. + if ( isset( $_POST['array_cmp5'] ) && namespace\Sub\array_search( $_POST['array_cmp5'], $my_array, true ) ) {} // Bad. +} + +/* + * Safeguard correct handling of all types of namespaced calls to unslashing functions. + * + * Note: The "Bad" test cases below are false negatives. They should trigger 2 errors (MissingUnslash + + * InputNotSanitized), not 1 (MissingUnslash). This problem only affects PHPCS 3.x and does not happen in PHPCS 4.x. It + * will be addressed in https://github.com/WordPress/WordPress-Coding-Standards/issues/2665. + */ +function test_namespaced_unslashing_functions() { + if ( isset( $_POST['unslash1'] ) ) { + $text = sanitize_text_field( \wp_unslash( $_POST['unslash1'] ) ); // OK. + } + if ( isset( $_POST['unslash2'] ) ) { + $text = sanitize_text_field( MyNamespace\stripslashes_deep( $_POST['unslash2'] ) ); // Bad. + } + if ( isset( $_POST['unslash3'] ) ) { + $text = sanitize_text_field( \MyNamespace\stripslashes_from_strings_only( $_POST['unslash3'] ) ); // Bad. + } + if ( isset( $_POST['unslash4'] ) ) { + $text = sanitize_text_field( namespace\wp_unslash( $_POST['unslash4'] ) ); // Bad. Note: This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. + } + if ( isset( $_POST['unslash5'] ) ) { + $text = sanitize_text_field( namespace\Sub\stripslashes_deep( $_POST['unslash5'] ) ); // Bad. + } +} + +/* + * Safeguard correct handling of all types of namespaced calls to array walking functions. + */ +function test_namespaced_array_walking_functions() { + if ( isset( $_POST['array_walk1'] ) ) { + $data = \array_map( 'sanitize_text_field', \wp_unslash( $_POST['array_walk1'] ) ); // OK. + } + if ( isset( $_POST['array_walk2'] ) ) { + $data = MyNamespace\map_deep( wp_unslash( $_POST['array_walk2'] ), 'sanitize_text_field' ); // Bad. + } + if ( isset( $_POST['array_walk3'] ) ) { + $data = \MyNamespace\array_map( 'sanitize_text_field', \wp_unslash( $_POST['array_walk3'] ) ); // Bad. + } + if ( isset( $_POST['array_walk4'] ) ) { + $data = namespace\map_deep( wp_unslash( $_POST['array_walk4'] ), 'sanitize_text_field' ); // Bad. Note: This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. + } + if ( isset( $_POST['array_walk5'] ) ) { + $data = namespace\Sub\array_map( 'sanitize_text_field', \wp_unslash( $_POST['array_walk5'] ) ); // Bad. + } +} + +/* + * Safeguard correct handling of fully qualified and relative namespaced calls to sanitizing functions. + * Qualified calls are already covered above. + */ +function test_namespaced_sanitizing_functions() { + if ( isset( $_POST['sanitize1'] ) ) { + $text = \sanitize_text_field( wp_unslash( $_POST['sanitize1'] ) ); // OK. + } + if ( isset( $_POST['sanitize2'] ) ) { + $email = \MyNamespace\sanitize_email( wp_unslash( $_POST['sanitize2'] ) ); // Bad. + } + if ( isset( $_POST['sanitize3'] ) ) { + $url = namespace\sanitize_url( wp_unslash( $_POST['sanitize3'] ) ); // Bad. Note: This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. + } + if ( isset( $_POST['sanitize4'] ) ) { + $title = namespace\Sub\sanitize_title( wp_unslash( $_POST['sanitize4'] ) ); // Bad. + } +} + +/* + * Safeguard correct handling of all types of namespaced calls to unslashing + sanitizing functions. + */ +function test_namespaced_unslashing_sanitizing_functions() { + if ( isset( $_POST['unslash_sanitize1'] ) ) { + $id = \absint( $_POST['unslash_sanitize1'] ); // OK. + } + if ( isset( $_POST['unslash_sanitize2'] ) ) { + $is_active = MyNamespace\boolval( $_POST['unslash_sanitize2'] ); // Bad. + } + if ( isset( $_POST['unslash_sanitize3'] ) ) { + $id = \MyNamespace\intval( $_POST['unslash_sanitize3'] ); // Bad. + } + if ( isset( $_POST['unslash_sanitize4'] ) ) { + $price = namespace\floatval( $_POST['unslash_sanitize4'] ); // Bad. Note: This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. + } + if ( isset( $_POST['unslash_sanitize5'] ) ) { + $key = namespace\Sub\sanitize_key( $_POST['unslash_sanitize5'] ); // Bad. + } +} diff --git a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php index 065162c2a8..a5428b1560 100644 --- a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php +++ b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php @@ -114,6 +114,35 @@ public function getErrorList( $testFile = '' ) { 497 => 1, 498 => 1, 499 => 3, + 510 => 1, + 513 => 1, + 516 => 1, + 525 => 2, + 526 => 2, + 527 => 2, + 528 => 2, + 536 => 2, + 537 => 2, + 538 => 2, + 539 => 2, + + // The error counts below differ depending on whether running PHPCS 3.x or PHPCS 4.x. See the comment in the test case file. + 554 => 1, + 557 => 1, + 560 => 1, + 563 => 1, + + 575 => 1, + 578 => 1, + 581 => 1, + 584 => 1, + 597 => 1, + 600 => 1, + 603 => 1, + 615 => 2, + 618 => 2, + 621 => 2, + 624 => 2, ); case 'ValidatedSanitizedInputUnitTest.2.inc': diff --git a/WordPress/Tests/WP/AlternativeFunctionsUnitTest.inc b/WordPress/Tests/WP/AlternativeFunctionsUnitTest.inc index a88e5638a9..70b67b1071 100644 --- a/WordPress/Tests/WP/AlternativeFunctionsUnitTest.inc +++ b/WordPress/Tests/WP/AlternativeFunctionsUnitTest.inc @@ -32,7 +32,7 @@ file_get_contents( $local_file, true ); // OK. file_get_contents( $url, false ); // Warning. file_get_contents(); // OK - no params, so nothing to do. file_get_contents( 'http://remoteurl.com/file/?w=1' ); // Warning. -file_get_contents( 'https://wordpress.org' ); // Warning. +\file_GET_contents( 'https://wordpress.org' ); // Warning. file_get_contents(ABSPATH . 'wp-admin/css/some-file.css'); // OK. file_get_contents(MYABSPATH . 'plugin-file.json'); // Warning. file_get_contents( MUPLUGINDIR . 'some-file.xml' ); // OK. @@ -147,3 +147,36 @@ file_get_contents( // Not using plugin_dir_path() for reasons. $url ); // Warning. + +/* + * Safeguard correct handling of all types of namespaced function calls + */ +\curl_init(); +MyNamespace\parse_url( 'http://example.com/' ); +\MyNamespace\json_encode( $data ); +namespace\unlink(); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\strip_tags( $string ); + +/* + * Safeguard that the sniff does not incorrectly ignore class methods/constants with the same + * name as WordPress global functions/constants when used in file_get_contents(). + */ +file_get_contents( MyClass::wp_upload_dir() . 'subdir/file.inc' ); +file_get_contents( $this->GET_HOME_PATH() . 'subdir/file.inc' ); +file_get_contents( $this?->plugin_dir_path() . 'subdir/file.inc' ); +file_get_contents( MyClass::ABSPATH . 'subdir/file.inc' ); +file_get_contents( $this->WPMU_PLUGIN_DIR . 'subdir/file.inc' ); +file_get_contents( $this?->TEMPLATEPATH . 'subdir/file.inc' ); + +/* + * Safeguard correct handling of namespaced variants of STDIN/STDOUT/STDERR constants. + * + * Note: passing stream resources to these functions is not valid PHP and will be addressed in + * https://github.com/WordPress/WordPress-Coding-Standards/issues/2602. These tests document the current behavior of the + * sniff. + */ +fopen( \STDIN, 'r' ); +file_put_contents( MyNamespace\STDOUT, $data ); +file_get_contents( \MyNamespace\STDIN ); +file_put_contents( namespace\STDERR, $data ); // The sniff should not flag this once it can resolve relative namespaces. +readfile( namespace\Sub\STDIN ); diff --git a/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php b/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php index b0f1bf96aa..58f4083729 100644 --- a/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php +++ b/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php @@ -89,6 +89,17 @@ public function getWarningList() { 131 => 1, 142 => 1, 146 => 1, + 154 => 1, + 164 => 1, + 165 => 1, + 166 => 1, + 167 => 1, + 168 => 1, + 169 => 1, + 179 => 1, + 180 => 1, + 181 => 1, + 182 => 1, ); } } diff --git a/WordPress/Tests/WP/CapabilitiesUnitTest.1.inc b/WordPress/Tests/WP/CapabilitiesUnitTest.1.inc index ab1fdb9c41..3cd45f9c9c 100644 --- a/WordPress/Tests/WP/CapabilitiesUnitTest.1.inc +++ b/WordPress/Tests/WP/CapabilitiesUnitTest.1.inc @@ -63,8 +63,8 @@ add_menu_page( $pagetitle, 'menu_title', 'foo_bar', 'handle', 'function', 'icon_ * Roles found instead of capabilities. */ add_posts_page( 'page_title', 'menu_title', 'administrator', 'menu_slug', 'function' ); // Error. -add_media_page( 'page_title', 'menu_title', 'editor', 'menu_slug', 'function' ); // Error. -add_pages_page( 'page_title', 'menu_title', 'author', 'menu_slug', 'function' ); // Error. +\aDd_MeDiA_pAgE( 'page_title', 'menu_title', 'editor', 'menu_slug', 'function' ); // Error. +ADD_PAGES_PAGE( 'page_title', 'menu_title', 'author', 'menu_slug', 'function' ); // Error. add_comments_page( 'page_title', 'menu_title', 'contributor', 'menu_slug', 'function' ); // Error. add_theme_page( 'page_title', $menu_title, 'subscriber', 'menu_slug', 'function' ); // Error. add_plugins_page( 'page_title', 'menu_title', 'super_admin', 'menu_slug', 'function' ); // Error. @@ -111,8 +111,12 @@ add_menu_page( $p, $t, /* deliberately empty */, $slug, ); add_menu_page( [] ); // Should bow out because the parameter is not found. $obj->current_user_can( 'foo_bar' ); // Ok. We're not checking for method calls. -My\NamespaceS\add_posts_page( 'page_title', 'menu_title', 'administrator', 'menu_slug', 'function' ); // Ok. We're not checking namespaced functions. -// Parse error, should be handled correctly by bowing out. -// This must be the last test in the file! -add_posts_page( 'page_title', +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\add_posts_page( 'page_title', 'menu_title', 'administrator', 'menu_slug', 'function' ); // Bad. +My\NamespaceS\current_user_can( 'administrator' ); // Ok. +\MyNamespace\add_comments_page( 'page_title', 'menu_title', 'administrator', 'menu_slug', 'function' ); // Ok. +namespace\author_can( $post, 'administrator' ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\add_posts_page( 'page_title', 'menu_title', 'administrator', 'menu_slug', 'function' ); // Ok. diff --git a/WordPress/Tests/WP/CapabilitiesUnitTest.5.inc b/WordPress/Tests/WP/CapabilitiesUnitTest.5.inc new file mode 100644 index 0000000000..d5f8d0d393 --- /dev/null +++ b/WordPress/Tests/WP/CapabilitiesUnitTest.5.inc @@ -0,0 +1,8 @@ + 1, 85 => 1, 106 => 1, + 118 => 1, ); case 'CapabilitiesUnitTest.3.inc': diff --git a/WordPress/Tests/WP/CapitalPDangitUnitTest.1.inc b/WordPress/Tests/WP/CapitalPDangitUnitTest.1.inc index 391148fb56..fd95c66be2 100644 --- a/WordPress/Tests/WP/CapitalPDangitUnitTest.1.inc +++ b/WordPress/Tests/WP/CapitalPDangitUnitTest.1.inc @@ -244,3 +244,12 @@ class TypeClassConstants { // Ensures no false positives on incorrect casing in a class constant type name. public const (\Fully\Qualified\MyClass&wordPRESS)|string ANOTHER_WORDPRESS = 'wordpress'; } + +/* + * Safeguard correct handling of all types of namespaced calls to the define() function. + */ +\DEFINE( 'WORDPRESS_SOMETHING', 'wordpress' ); // OK. +MyNamespace\define( 'WORDPRESS_SOMETHING', 'wordpress' ); // Bad. +\MyNamespace\Define( 'WORDPRESS_SOMETHING', 'wordpress' ); // Bad. +namespace\Sub\define( 'WORDPRESS_SOMETHING', 'wordpress' ); // Bad. +namespace\define( 'WORDPRESS_SOMETHING', 'wordpress' ); // The sniff should stop flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/WP/CapitalPDangitUnitTest.1.inc.fixed b/WordPress/Tests/WP/CapitalPDangitUnitTest.1.inc.fixed index 5630f0d415..d43a7af60b 100644 --- a/WordPress/Tests/WP/CapitalPDangitUnitTest.1.inc.fixed +++ b/WordPress/Tests/WP/CapitalPDangitUnitTest.1.inc.fixed @@ -244,3 +244,12 @@ class TypeClassConstants { // Ensures no false positives on incorrect casing in a class constant type name. public const (\Fully\Qualified\MyClass&wordPRESS)|string ANOTHER_WORDPRESS = 'wordpress'; } + +/* + * Safeguard correct handling of all types of namespaced calls to the define() function. + */ +\DEFINE( 'WORDPRESS_SOMETHING', 'wordpress' ); // OK. +MyNamespace\define( 'WORDPRESS_SOMETHING', 'WordPress' ); // Bad. +\MyNamespace\Define( 'WORDPRESS_SOMETHING', 'WordPress' ); // Bad. +namespace\Sub\define( 'WORDPRESS_SOMETHING', 'WordPress' ); // Bad. +namespace\define( 'WORDPRESS_SOMETHING', 'WordPress' ); // The sniff should stop flagging this once it can resolve relative namespaces. diff --git a/WordPress/Tests/WP/CapitalPDangitUnitTest.php b/WordPress/Tests/WP/CapitalPDangitUnitTest.php index ea1cfcf65e..ff545fcc65 100644 --- a/WordPress/Tests/WP/CapitalPDangitUnitTest.php +++ b/WordPress/Tests/WP/CapitalPDangitUnitTest.php @@ -69,6 +69,10 @@ public function getWarningList( $testFile = '' ) { 204 => 1, 205 => 1, 224 => 1, + 252 => 1, + 253 => 1, + 254 => 1, + 255 => 1, ); case 'CapitalPDangitUnitTest.2.inc': diff --git a/WordPress/Tests/WP/ClassNameCaseUnitTest.inc b/WordPress/Tests/WP/ClassNameCaseUnitTest.inc index 1c540dc656..4ddaed0bea 100644 --- a/WordPress/Tests/WP/ClassNameCaseUnitTest.inc +++ b/WordPress/Tests/WP/ClassNameCaseUnitTest.inc @@ -65,3 +65,37 @@ class NotYetDetected { public function paramTypeDeclaration( wp_role $role ) {} public function returnTypeDeclaration() : WP_TERM {} } + +class ImplementsInterfaceCorrectCase implements SimplePie_Cache_Base {} +class ImplementsInterfaceIncorrectCase implements simplepie_cache_base {} + +/* + * Safeguard correct handling of namespaced class references (the namespace types not handled below are already handled + * in other tests above). + */ +$obj = new MyNamespace\atomEntry(); +$obj = new \MyNamespace\core_upgrader(); +$obj = new namespace\Sub\file_upload_upgrader(); +$obj = new namespace\pop3(); // Warning. +class MyClass1 extends MyNamespace\twentytwenty_walker_page {} +class MyClass2 extends \MyNamespace\TWENTY_ELEVEN_EPHEMERA_WIDGET {} +class MyClass3 extends namespace\Sub\twenty_Twenty_One_SVG_icons {} +class MyClass4 extends namespace\twentynineteen_SVG_icons {} // Warning. +class MyClass5 implements \WPORG\REQUESTS\AUTH {} // Warning. +class MyClass6 implements \MyNamespace\SIMPLEPIE\Cache\namefilter {} +class MyClass7 implements MyNamespace\requests_auth {} +class MyClass8 implements namespace\Sub\WpOrg\REQUESTS\proxy {} +class MyClass9 implements namespace\simplepie\CACHE\base {} // Warning. +\avifinfo\Box::prepare_query(); // Warning. +MyNamespace\Avifinfo\CHAN_PROP::prepare_query(); +\MyNamespace\Avifinfo\features::prepare_query(); +namespace\Sub\AVIFINFO\parser::prepare_query(); +namespace\AVIFINFO\TILE::prepare_query(); // Warning. + +/* + * AI Client classes. + */ +$obj = new WP_AI_Client_Cache(); +$obj = new \WP_ai_client_cache(); // Warning. +$obj = new \WordPress\AiClient\AiClient(); +$obj = new WordPress\AiClient\aiclient(); // Warning. diff --git a/WordPress/Tests/WP/ClassNameCaseUnitTest.php b/WordPress/Tests/WP/ClassNameCaseUnitTest.php index 7869ea7f6f..f34d832c2d 100644 --- a/WordPress/Tests/WP/ClassNameCaseUnitTest.php +++ b/WordPress/Tests/WP/ClassNameCaseUnitTest.php @@ -36,15 +36,24 @@ public function getErrorList() { */ public function getWarningList() { return array( - 37 => 1, - 38 => 1, - 40 => 1, - 42 => 1, - 43 => 1, - 46 => 1, - 47 => 1, - 48 => 1, - 49 => 1, + 37 => 1, + 38 => 1, + 40 => 1, + 42 => 1, + 43 => 1, + 46 => 1, + 47 => 1, + 48 => 1, + 49 => 1, + 70 => 1, + 79 => 1, + 83 => 1, + 84 => 1, + 88 => 1, + 89 => 1, + 93 => 1, + 99 => 1, + 101 => 1, ); } } diff --git a/WordPress/Tests/WP/CronIntervalUnitTest.inc b/WordPress/Tests/WP/CronIntervalUnitTest.inc index 5ebc161d4c..b4b81fc714 100644 --- a/WordPress/Tests/WP/CronIntervalUnitTest.inc +++ b/WordPress/Tests/WP/CronIntervalUnitTest.inc @@ -166,7 +166,7 @@ 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, 'ADD_HUNDRED_MINUTE_SCHEDULE' ) ); // Warning: time undetermined. \Add_Filter( 'cron_schedules', array( $this, 'sneaky_fake_wp_constant_schedule' ) ); // Warning: time undetermined. } @@ -284,7 +284,7 @@ class FirstClassCallables { public function add_schedules() { add_filter( 'cron_schedules', $this->cron_weekly_schedule(...) ); // Ok: > 15 min. add_filter( 'cron_schedules', $this->cron_eight_minute_schedule(...) ); // Warning: 8 min. - add_filter( 'cron_schedules', self::cron_weekly_schedule(...) ); // Ok: > 15 min. + add_filter( 'cron_schedules', self::Cron_Weekly_Schedule(...) ); // Ok: > 15 min. add_filter( 'cron_schedules', static::cron_eight_minute_schedule(...) ); // Warning: 8 min. add_filter( 'cron_schedules', [$this, 'cron_weekly_schedule'](...) ); // Ok: > 15 min. add_filter( 'cron_schedules', array($this, 'cron_eight_minute_schedule')(...) ); // Warning: 8 min. @@ -328,6 +328,7 @@ 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', \FIRST_CLASS_SIX_MIN_SCHEDULE(...)); // Warning: 6 min. add_filter( 'cron_schedules', namespace\first_class_six_min_schedule(...)); // Warning: 6 min. /* diff --git a/WordPress/Tests/WP/CronIntervalUnitTest.php b/WordPress/Tests/WP/CronIntervalUnitTest.php index 2417abaf41..4bd6add5b3 100644 --- a/WordPress/Tests/WP/CronIntervalUnitTest.php +++ b/WordPress/Tests/WP/CronIntervalUnitTest.php @@ -69,9 +69,10 @@ public function getWarningList() { 329 => 1, 330 => 1, 331 => 1, - 351 => 1, + 332 => 1, 352 => 1, 353 => 1, + 354 => 1, ); } } diff --git a/WordPress/Tests/WP/DeprecatedClassesUnitTest.inc b/WordPress/Tests/WP/DeprecatedClassesUnitTest.1.inc similarity index 100% rename from WordPress/Tests/WP/DeprecatedClassesUnitTest.inc rename to WordPress/Tests/WP/DeprecatedClassesUnitTest.1.inc diff --git a/WordPress/Tests/WP/DeprecatedClassesUnitTest.2.inc b/WordPress/Tests/WP/DeprecatedClassesUnitTest.2.inc new file mode 100644 index 0000000000..6b710f9d2f --- /dev/null +++ b/WordPress/Tests/WP/DeprecatedClassesUnitTest.2.inc @@ -0,0 +1,23 @@ + Key is the line number, value is the number of expected errors. */ - public function getErrorList() { - $start_line = 9; - $end_line = 28; - $errors = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); + public function getErrorList( $testFile = '' ) { + switch ( $testFile ) { + case 'DeprecatedClassesUnitTest.1.inc': + $start_line = 9; + $end_line = 28; + $errors = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); + + // Unset the lines related to version comments. + unset( $errors[16], $errors[18], $errors[21], $errors[26] ); + + return $errors; - // Unset the lines related to version comments. - unset( $errors[16], $errors[18], $errors[21], $errors[26] ); + case 'DeprecatedClassesUnitTest.2.inc': + return array( + 9 => 1, + 13 => 1, + 14 => 1, + 18 => 1, + 19 => 1, + 23 => 1, + ); - return $errors; + default: + return array(); + } } /** diff --git a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.1.inc b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.1.inc index 57a44658ef..34d92a3b62 100644 --- a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.1.inc +++ b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.1.inc @@ -422,13 +422,13 @@ wp_update_https_detection_errors(); block_core_file_ensure_interactivity_dependency(); block_core_image_ensure_interactivity_dependency(); block_core_query_ensure_interactivity_dependency(); +/* ============ WP 6.6 ============ */ +wp_interactivity_process_directives_of_interactive_blocks(); +wp_render_elements_support(); /* * Warning. */ -/* ============ WP 6.6 ============ */ -wp_interactivity_process_directives_of_interactive_blocks(); -wp_render_elements_support(); /* ============ WP 6.7 ============ */ current_user_can_for_blog(); wp_create_block_style_variation_instance_name(); @@ -444,3 +444,7 @@ wp_add_editor_classic_theme_styles(); /* ============ WP 6.9 ============ */ seems_utf8(); wp_print_auto_sizes_contain_css_fix(); +/* ============ WP 7.0 ============ */ +addslashes_gpc(); +block_core_navigation_block_contains_core_navigation(); +wp_sanitize_script_attributes(); diff --git a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.2.inc b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.2.inc index 6c2a7bb8e1..654c7d4ff4 100644 --- a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.2.inc +++ b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.2.inc @@ -4,6 +4,13 @@ * Safeguard correct handling of all types of namespaced function calls. */ \the_category_ID(); -MyNamespace\the_category_ID(); -\MyNamespace\the_category_ID(); -namespace\the_category_ID(); // The sniff should start flagging this once it can resolve relative namespaces. +\THE_category_id(); +MyNamespace\permalink_link(); +\MyNamespace\get_postdata(); +namespace\create_user(); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\user_can_edit_post(); + +/* + * Safeguard correct handling of non-standard case function call. + */ +wp_ADMIN_bar_HEADER(); diff --git a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php index 6931a37a9b..451d635219 100644 --- a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php +++ b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php @@ -32,7 +32,7 @@ public function getErrorList( $testFile = '' ) { switch ( $testFile ) { case 'DeprecatedFunctionsUnitTest.1.inc': $start_line = 8; - $end_line = 424; + $end_line = 427; $errors = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); // Unset the lines related to version comments. @@ -85,14 +85,17 @@ public function getErrorList( $testFile = '' ) { $errors[383], $errors[386], $errors[410], - $errors[421] + $errors[421], + $errors[425] ); return $errors; case 'DeprecatedFunctionsUnitTest.2.inc': return array( - 6 => 1, + 6 => 1, + 7 => 1, + 16 => 1, ); default: @@ -110,15 +113,15 @@ public function getErrorList( $testFile = '' ) { public function getWarningList( $testFile = '' ) { switch ( $testFile ) { case 'DeprecatedFunctionsUnitTest.1.inc': - $start_line = 430; - $end_line = 446; + $start_line = 433; + $end_line = 450; $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] + $warnings[444], + $warnings[447] ); return $warnings; diff --git a/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.inc b/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.1.inc similarity index 83% rename from WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.inc rename to WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.1.inc index c2f75c8747..18a26a2b93 100644 --- a/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.inc +++ b/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.1.inc @@ -3,8 +3,8 @@ // All will give an ERROR. get_bloginfo( 'home' ); -get_bloginfo( 'siteurl' ); -get_bloginfo( "text_direction" ); +\GeT_bLoGiNfO( 'siteurl' ); +Get_Bloginfo( "text_direction" ); echo bloginfo( 'home' ); echo bloginfo( "siteurl" ); echo bloginfo( 'text_direction' ); @@ -55,5 +55,11 @@ wp_get_typography_font_size_value( $preset, array() ); // OK. wp_get_typography_font_size_value( $preset, true ); // Error. wp_get_typography_font_size_value( $preset, false ); // Error. -// Live coding/parse error. -get_bloginfo( show: /*to do*/, ); +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\get_bloginfo( 'home' ); // Bad. +MyNamespace\register_setting( 'privacy' ); // Ok. +\MyNamespace\unregister_setting( 'misc' ); // Ok. +namespace\get_option('blacklist_keys'); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\add_option('comment_whitelist', $value); // Ok. diff --git a/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.2.inc b/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.2.inc new file mode 100644 index 0000000000..b5923d40e4 --- /dev/null +++ b/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.2.inc @@ -0,0 +1,8 @@ + Key is the line number, value is the number of expected errors. */ - public function getErrorList() { - return array( - 5 => 1, - 6 => 1, - 7 => 1, - 8 => 1, - 9 => 1, - 10 => 1, - 11 => 1, - 12 => 1, - 13 => 1, - 14 => 1, - 15 => 1, - 16 => 1, - 17 => 1, - 18 => 1, - 35 => 1, - 40 => 1, - 43 => 1, - 44 => 1, - 45 => 1, - 46 => 1, - 47 => 1, - 48 => 1, - 49 => 1, - 50 => 1, - 51 => 1, - ); + public function getErrorList( $testFile = '' ) { + switch ( $testFile ) { + case 'DeprecatedParameterValuesUnitTest.1.inc': + return array( + 5 => 1, + 6 => 1, + 7 => 1, + 8 => 1, + 9 => 1, + 10 => 1, + 11 => 1, + 12 => 1, + 13 => 1, + 14 => 1, + 15 => 1, + 16 => 1, + 17 => 1, + 18 => 1, + 35 => 1, + 40 => 1, + 43 => 1, + 44 => 1, + 45 => 1, + 46 => 1, + 47 => 1, + 48 => 1, + 49 => 1, + 50 => 1, + 51 => 1, + 55 => 1, + 56 => 1, + 61 => 1, + ); + + default: + return array(); + } } /** @@ -61,9 +72,6 @@ public function getErrorList() { * @return array Key is the line number, value is the number of expected warnings. */ public function getWarningList() { - return array( - 55 => 1, - 56 => 1, - ); + return array(); } } diff --git a/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc b/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc index 40d309e71a..80d5026d86 100644 --- a/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc +++ b/WordPress/Tests/WP/DeprecatedParametersUnitTest.inc @@ -37,14 +37,23 @@ wp_install( user_name: '', deprecated: '', user_email: '', blog_title: '', is_pu // Error: Parameter is passed with incorrect default, unconventional order. wp_install( is_public: '', user_name: '', user_email: '', deprecated: 'should be empty', blog_title: '' ); +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\add_option( '', '', [] ); // Bad. +MyNamespace\get_blog_list( $foo, $bar, 'deprecated' ); // Ok. +\MyNamespace\get_wp_title_rss( 'deprecated' ); // Ok. +namespace\the_author( 'deprecated', 'deprecated' ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\wp_title_rss( 'deprecated' ); // Ok. + // All will give an ERROR. The functions are ordered alphabetically. _future_post_hook( 10, $post ); _load_remote_block_patterns( $value ); _wp_post_revision_fields( $foo, 'deprecated' ); add_option( '', '', [] ); -add_option( '', '', 1.23 ); -add_option( '', '', 10 ); +\Add_Option( '', '', 1.23 ); +ADD_OPTION( '', '', 10 ); add_option( '', '', false ); add_option( '', '', 'deprecated' ); comments_link( 'deprecated', 'deprecated' ); @@ -96,7 +105,7 @@ wp_upload_bits( '', 'deprecated' ); xfn_check( '', '', 'deprecated' ); global_terms( $foo, 'deprecated' ); 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'); + +// All will give an WARNING as they have been deprecated after WP 6.7. _wp_can_use_pcre_u('deprecated'); diff --git a/WordPress/Tests/WP/DeprecatedParametersUnitTest.php b/WordPress/Tests/WP/DeprecatedParametersUnitTest.php index 9f4d2d87d1..51c9e11751 100644 --- a/WordPress/Tests/WP/DeprecatedParametersUnitTest.php +++ b/WordPress/Tests/WP/DeprecatedParametersUnitTest.php @@ -27,8 +27,8 @@ final class DeprecatedParametersUnitTest extends AbstractSniffUnitTest { * @return array Key is the line number, value is the number of expected errors. */ public function getErrorList() { - $start_line = 42; - $end_line = 98; + $start_line = 51; + $end_line = 108; $errors = array_fill( $start_line, ( ( $end_line - $start_line ) + 1 ), 1 ); $errors[22] = 1; @@ -38,9 +38,12 @@ public function getErrorList() { // Named param. $errors[38] = 1; + // Fully qualified function call. + $errors[43] = 1; + // Override number of errors. - $errors[50] = 2; - $errors[76] = 2; + $errors[59] = 2; + $errors[85] = 2; return $errors; } @@ -52,8 +55,7 @@ public function getErrorList() { */ public function getWarningList() { return array( - 101 => 1, - 102 => 1, + 111 => 1, ); } } diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc index 50302d54fc..f7a4a1d48f 100644 --- a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc +++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc @@ -61,7 +61,7 @@ echo BACKGROUND_COLOR; echo BACKGROUND_IMAGE; use const STYLESHEETPATH as SSP; -use const ABC as STYLESHEETPATH; +use const ABC as STYLESHEETPATH; // This is ok, as `STYLESHEETPATH` is not a global constant here. switch( STYLESHEETPATH ) { case STYLESHEETPATH: diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php index 138d0eb4ef..9e820865ca 100644 --- a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php +++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php @@ -49,7 +49,6 @@ public function getWarningList() { 60 => 1, 61 => 1, 63 => 1, - 64 => 1, 66 => 1, 67 => 1, 71 => 1, diff --git a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.inc b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.1.inc similarity index 83% rename from WordPress/Tests/WP/DiscouragedFunctionsUnitTest.inc rename to WordPress/Tests/WP/DiscouragedFunctionsUnitTest.1.inc index 66a33f4984..067a7907a5 100644 --- a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.inc +++ b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.1.inc @@ -1,7 +1,7 @@ query_posts(); // OK, not the global function. MyClass::wp_reset_query(); // OK, not the global function. $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. + + + + + + // Ensure the sniff doesn't act on functions not listed in the target functions array. query_post(); // OK, not one of the target functions. @@ -64,6 +64,11 @@ wp_reset_query(); // OK, excluded group. // Safeguard that a function used as a PHP 8.1+ first class callable is also flagged. call_user_func( query_posts(...), $param ); // Warning. -// Live coding/parse error. -// This has to be the last test in the file!!! -\query_posts +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\query_POSTS(); // Warning. +MyNamespace\wp_reset_query(); // OK, not the global function. +\MyNamespace\query_posts(); // OK, not the global function. +namespace\wp_reset_query(); // OK, not the global function. +namespace\Sub\query_posts(); // OK, not the global function. diff --git a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.2.inc b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.2.inc new file mode 100644 index 0000000000..0476780120 --- /dev/null +++ b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.2.inc @@ -0,0 +1,8 @@ + Key is the line number, value is the number of expected warnings. */ - public function getWarningList() { - return array( - 3 => 1, - 4 => 1, - 20 => 1, - 33 => 1, - 34 => 1, - 53 => 1, - 62 => 1, - 65 => 1, - ); + public function getWarningList( $testFile = '' ) { + switch ( $testFile ) { + case 'DiscouragedFunctionsUnitTest.1.inc': + return array( + 3 => 1, + 4 => 1, + 33 => 1, + 34 => 1, + 53 => 1, + 62 => 1, + 65 => 1, + 70 => 1, + ); + + default: + return array(); + } } } diff --git a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.1.inc b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.1.inc index 03157513e4..8592a34121 100644 --- a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.1.inc +++ b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.1.inc @@ -42,7 +42,7 @@ wp_register_style( 'script-name', 'https://example.com/someScript.js', false, '1 wp_register_style( 'script-name', 'https://example.com/someScript.js' ); // Warning - missing $ver. wp_enqueue_style( 'script-name', 'https://example.com/someScript.js', false, '1.0.0'); // OK. -wp_enqueue_style( 'script-name', 'https://example.com/someScript.js' ); // Warning - missing $ver. +WP_ENQUEUE_style( 'script-name', 'https://example.com/someScript.js' ); // Warning - missing $ver. wp_register_script( 'someScript-js' ); // OK. wp_enqueue_script( 'someScript-js' ); // OK. @@ -108,3 +108,13 @@ wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array 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. + +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\wp_enqueue_script( 'script-name', 'https://example.com/someScript.js', false, '1.1.0' ); // Warning - missing $in_footer. +\wp_REGISTER_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), '1.1.0' ); // Warning - missing $in_footer. +MyNamespace\wp_register_style( 'style-name', 'https://example.com/style.css' ); // Ok. +\MyNamespace\wp_enqueue_style( 'style-name', 'https://example.com/style.css' ); // Ok. +namespace\wp_register_script( 'someScript-js', 'https://example.com/someScript.js' , array( 'jquery' ), '1.1.0' ); // The sniff should start flagging this once it can resolve relative namespaces (once it does, it should be "Warning - missing $in_footer"). +namespace\Sub\wp_enqueue_style( 'style-name', 'https://example.com/style.css' ); // Ok. diff --git a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.php b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.php index eaa719c19b..a1dd90b4f5 100644 --- a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.php +++ b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.php @@ -82,6 +82,8 @@ public function getWarningList( $testFile = '' ) { 100 => 1, 107 => 1, 108 => 1, + 115 => 1, + 116 => 1, ); default: diff --git a/WordPress/Tests/WP/GetMetaSingleUnitTest.inc b/WordPress/Tests/WP/GetMetaSingleUnitTest.inc index 54ea2fecc0..33aed6468d 100644 --- a/WordPress/Tests/WP/GetMetaSingleUnitTest.inc +++ b/WordPress/Tests/WP/GetMetaSingleUnitTest.inc @@ -31,7 +31,7 @@ $incorrect_but_ok = get_metadata( 'post' ); * These should all be flagged with a warning. */ $warning = \get_post_meta( $post_id, $meta_key ); -implode(', ', get_post_meta( $post_id, $meta_key )); +implode(', ', \GET_POST_META( $post_id, $meta_key )); if (get_post_meta( $post_id, key: $meta_key )) {} $warning = get_post_meta( $post_id, key: $meta_key, sinngle: true ); // Typo in parameter name. echo get_comment_meta( $comment_id, $meta_key ); @@ -46,3 +46,11 @@ $warning = get_metadata( ); $warning = get_metadata_raw( 'post', $post_id, $meta_key ); $warning = get_metadata_default( 'post', $post_id, $meta_key ); + +/* + * Safeguard correct handling of fully qualified and relative namespaced function calls (fully qualified global function + * call and partially qualified namespaced function call are already handled above). + */ +\MyNamespace\get_user_meta( $user_id, $meta_key ); +namespace\get_metadata( 'post', $post_id, $meta_key ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\get_comment_meta( $comment_id, $meta_key ); diff --git a/WordPress/Tests/WP/I18nUnitTest.1.inc b/WordPress/Tests/WP/I18nUnitTest.1.inc index 6256e6d84a..3abf8ec3ba 100644 --- a/WordPress/Tests/WP/I18nUnitTest.1.inc +++ b/WordPress/Tests/WP/I18nUnitTest.1.inc @@ -47,7 +47,7 @@ _n( 'I have %d cat.', 'I have %d cats.', $number, 'my-slug' ); // OK. _n( 'I have %d cat.', 'I have %d cats.', $number, "illegal $string" ); // Bad. _n( 'I have %d cat.', 'I have %d cats.', $number, SOMETHING ); // Bad. -_n_noop( 'I have %d cat.', 'I have %d cats.' ); // Bad, no text domain. +_N_NOOP( 'I have %d cat.', 'I have %d cats.' ); // Bad, no text domain. _n_noop( 'I have %d cat.', 'I have %d cats.', 'my-slug' ); // OK. _n_noop( 'I have %d cat.', 'I have %d cats.', "illegal $string" ); // Bad. _n_noop( 'I have %d cat.', 'I have %d cats.', SOMETHING ); // Bad. @@ -75,7 +75,7 @@ __( 'foo', 'my-slug', 'too-many-args' ); // Bad. _x( 'string', 'context', 'my-slug', 'too-many-args' ); // Bad. _n( 'I have %d cat.', 'I have %d cats.', $number, 'my-slug', 'too-many-args' ); // Bad. _n_noop( 'I have %d cat.', 'I have %d cats.', 'my-slug', 'too-many-args' ); // Bad. -_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug', 'too-many-args' ); // Bad. +\_Nx_Noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug', 'too-many-args' ); // Bad. // Make sure that multi-line string literals are accepted. _nx( 'I have @@ -317,4 +317,15 @@ esc_html_e( 'foo', '' ); // Bad: text-domain can not be empty. // PHP 8.0+: safeguard handling of newly introduced placeholders. __( 'There are %1$h monkeys in the %H', 'my-slug' ); // Bad: multiple arguments should be numbered. +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\_( 'foo', 'my-slug' ); // Bad. +\translate( 'foo', 'my-slug' ); // Bad. +\translate_with_gettext_context( 'foo', 'bar', 'my-slug' ); // Bad. +MyNamespace\__( 'foo', 'my-slug' ); // Ok. +\MyNamespace\_e( 'foo', 'my-slug' ); // Ok. +namespace\esc_html_e( 'foo', '' ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\translate( 'foo', 'my-slug' ); // Ok. + // phpcs:enable WordPress.WP.I18n.MissingTranslatorsComment diff --git a/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed b/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed index b23e9b6192..ae6358abbf 100644 --- a/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed +++ b/WordPress/Tests/WP/I18nUnitTest.1.inc.fixed @@ -47,7 +47,7 @@ _n( 'I have %d cat.', 'I have %d cats.', $number, 'my-slug' ); // OK. _n( 'I have %d cat.', 'I have %d cats.', $number, "illegal $string" ); // Bad. _n( 'I have %d cat.', 'I have %d cats.', $number, SOMETHING ); // Bad. -_n_noop( 'I have %d cat.', 'I have %d cats.' ); // Bad, no text domain. +_N_NOOP( 'I have %d cat.', 'I have %d cats.' ); // Bad, no text domain. _n_noop( 'I have %d cat.', 'I have %d cats.', 'my-slug' ); // OK. _n_noop( 'I have %d cat.', 'I have %d cats.', "illegal $string" ); // Bad. _n_noop( 'I have %d cat.', 'I have %d cats.', SOMETHING ); // Bad. @@ -75,7 +75,7 @@ __( 'foo', 'my-slug', 'too-many-args' ); // Bad. _x( 'string', 'context', 'my-slug', 'too-many-args' ); // Bad. _n( 'I have %d cat.', 'I have %d cats.', $number, 'my-slug', 'too-many-args' ); // Bad. _n_noop( 'I have %d cat.', 'I have %d cats.', 'my-slug', 'too-many-args' ); // Bad. -_nx_noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug', 'too-many-args' ); // Bad. +\_Nx_Noop( 'I have %d cat.', 'I have %d cats.', 'Not really.', 'my-slug', 'too-many-args' ); // Bad. // Make sure that multi-line string literals are accepted. _nx( 'I have @@ -317,4 +317,15 @@ esc_html_e( 'foo', '' ); // Bad: text-domain can not be empty. // PHP 8.0+: safeguard handling of newly introduced placeholders. __( 'There are %1$h monkeys in the %H', 'my-slug' ); // Bad: multiple arguments should be numbered. +/* + * Safeguard correct handling of all types of namespaced function calls. + */ +\_( 'foo', 'my-slug' ); // Bad. +\translate( 'foo', 'my-slug' ); // Bad. +\translate_with_gettext_context( 'foo', 'bar', 'my-slug' ); // Bad. +MyNamespace\__( 'foo', 'my-slug' ); // Ok. +\MyNamespace\_e( 'foo', 'my-slug' ); // Ok. +namespace\esc_html_e( 'foo', '' ); // The sniff should start flagging this once it can resolve relative namespaces. +namespace\Sub\translate( 'foo', 'my-slug' ); // Ok. + // phpcs:enable WordPress.WP.I18n.MissingTranslatorsComment diff --git a/WordPress/Tests/WP/I18nUnitTest.php b/WordPress/Tests/WP/I18nUnitTest.php index 3834eb189f..20e7accaab 100644 --- a/WordPress/Tests/WP/I18nUnitTest.php +++ b/WordPress/Tests/WP/I18nUnitTest.php @@ -148,6 +148,7 @@ public function getErrorList( $testFile = '' ) { 311 => 1, 315 => 1, 318 => 1, + 323 => 1, ); case 'I18nUnitTest.2.inc': @@ -217,6 +218,8 @@ public function getWarningList( $testFile = '' ) { 300 => 1, 301 => 1, 302 => 1, + 324 => 1, + 325 => 1, ); case 'I18nUnitTest.2.inc': diff --git a/_typos.toml b/_typos.toml index 5cc842866c..c632b5ff33 100644 --- a/_typos.toml +++ b/_typos.toml @@ -19,6 +19,7 @@ extend-ignore-identifiers-re = [ # These are search targets for sniffs, can't be helped. 'avail_post_stati', 'url_is_accessable_via_ssl', + 'is_writeable', ] [default.extend-words] diff --git a/composer.json b/composer.json index 898bf88b73..4961107d8d 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,8 @@ "ext-libxml": "*", "ext-tokenizer": "*", "ext-xmlreader": "*", - "squizlabs/php_codesniffer": "^3.13.4", - "phpcsstandards/phpcsutils": "^1.1.0", + "squizlabs/php_codesniffer": "^3.13.5", + "phpcsstandards/phpcsutils": "^1.2.2", "phpcsstandards/phpcsextra": "^1.5.0" }, "require-dev": { diff --git a/phpcs.xml.dist.sample b/phpcs.xml.dist.sample index cfb232b862..9bed296db1 100644 --- a/phpcs.xml.dist.sample +++ b/phpcs.xml.dist.sample @@ -78,7 +78,7 @@ https://github.com/PHPCompatibility/PHPCompatibility --> - +