From 279eaefefcd523761eb6b3b08df8ac6058d7a9f0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 9 Mar 2023 11:19:51 +0100 Subject: [PATCH 001/212] Changelog: add forgotten link Follow up on 295 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf6d903..9a5ff6a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -477,6 +477,7 @@ Initial public release as a stand-alone package. [PHP Parallel Lint]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/releases [PHP Console Highlighter]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/releases +[2.3.1]: https://github.com/Yoast/yoastcs/compare/2.3.0...2.3.1 [2.3.0]: https://github.com/Yoast/yoastcs/compare/2.2.1...2.3.0 [2.2.1]: https://github.com/Yoast/yoastcs/compare/2.2.0...2.2.1 [2.2.0]: https://github.com/Yoast/yoastcs/compare/2.1.0...2.2.0 From b51c4b0746a3c8bca7b5628f96b76ac63523de7b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 29 Mar 2023 17:15:40 +0200 Subject: [PATCH 002/212] README: add documentation about the "Threshold" report ... as introduced in PR 245. --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index b6ea3245..da98bdaf 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,33 @@ Refer to [Using PHP Code Sniffer Tool](https://www.jetbrains.com/phpstorm/help/u After installation, the `Yoast` standard will be available as a choice in PHP Code Sniffer Validation inspection. +### The YoastCS "Threshold" report + +The YoastCS package includes a custom `YoastCS\Yoast\Reports\Threshold` report for PHP_CodeSniffer to compare the current PHPCS run results with predefined "threshold" settings. + +The report will look in the runtime environment for the following two environment variables and will take the values of those as the thresholds to compare the PHPCS run results against: +* `YOASTCS_THRESHOLD_ERRORS` +* `YOASTCS_THRESHOLD_WARNINGS` + +If the environment variables are not set, they will default to 0 for both, i.e. no errors or warnings allowed. + +The report will not print any details about the issues found, it just shows a summary based on the thresholds: +``` +PHP CODE SNIFFER THRESHOLD COMPARISON +------------------------------------------------------------------------------------------------------------------------ +Coding standards ERRORS: 148/130. +Coding standards WARNINGS: 539/539. + +Please fix any errors introduced in your code and run PHPCS again to verify. +Please fix any warnings introduced in your code and run PHPCS again to verify. +``` + +After the report has run, a global `YOASTCS_ABOVE_THRESHOLD` constant (boolean) will be available which can be used in calling scripts. + +To use this report, run PHPCS with the following command-line argument: `--report=YoastCS\Yoast\Reports\Threshold`. +_Note: depending on the OS the command is run on, the backslashes in the report name may need to be escaped (doubled)._ + + ## Changelog The changelog for this package can be found in the [CHANGELOG.md](https://github.com/Yoast/yoastcs/blob/develop/CHANGELOG.md) file. From 404460b3a28645564200ab464294849b058dbd06 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 30 Apr 2023 14:07:44 +0200 Subject: [PATCH 003/212] Composer: update PHPCSDevTools to v 1.2.1 Ref: https://github.com/PHPCSStandards/PHPCSDevTools/releases/tag/1.2.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5e8fe43f..eacdc498 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-master", "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0", - "phpcsstandards/phpcsdevtools": "^1.2.0" + "phpcsstandards/phpcsdevtools": "^1.2.1" }, "config": { "allow-plugins": { From dd49ac9ba7dd271275f23dd88c574f9aa7446cb9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 30 Apr 2023 14:30:20 +0200 Subject: [PATCH 004/212] Dependabot: add reviewer config The Dependabot PRs can have a tendency to stay open for a long time, while for the GH Actions and Composer dependency related ones I keep an eye on all used action runner releases anyway. However, I don't "watch" all Yoast repos, so adding this configuration will ensure that I get notified about Dependabot PRs for this repo (and can merge it if appropriate or close it otherwise). --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 72db5c9b..5af18898 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,6 +15,8 @@ updates: prefix: "GH Actions:" labels: - "yoast cs/qa" + reviewers: + - "jrfnl" # Maintain dependencies for Composer. - package-ecosystem: "composer" @@ -31,3 +33,5 @@ updates: prefix: "Composer:" labels: - "yoast cs/qa" + reviewers: + - "jrfnl" From 81b20b59704db6e96db13f561a47b7accff52742 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 30 Apr 2023 15:05:04 +0200 Subject: [PATCH 005/212] GH Actions: tweak the way the PHPCS versions are set As things were, whenever the minimum PHPCS or WPCS version would be changed, the branch protection settings for both the `main` and the `develop` branch would need to be updated and all "required builds" referencing the old PHPCS/WPCS version would need to be removed, while new "required builds" would need to be added referencing the new minimum PHPCS/WPCS version. This was a fiddly process and time-consuming. The change proposed in this commit takes advantage of the Composer `--prefer-lowest` setting to achieve the same without a hard-coded PHPCS/WPCS version in the build name, which means that once the branch protection settings have been updated for this PR, they shouldn't need updating anymore for future PHPCS/WPCS version bumps. Notes: * Includes a new debug step ("Verify installed versions"). * The `--prefer-lowest` command is not run via the `ramsey/composer-install` action runner, but in a separate step. Running it via `ramsey/composer-install` would also impact the PHPUnit version, which would break the builds. * I'm only making this change in the `test.yml` workflow, not in the `quicktest.yml` workflow. - The builds in `quicktest` are not _required_, so do not impact the branch protection settings. - This also allows to still test mixed high/low variants (i.e. low PHPCS, high WPCS and visa versa) via the `quicktest` workflow. --- .github/workflows/test.yml | 51 +++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a8567ba..31c7fe5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,13 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Note: while WPCS 3.0.0 is under development, the "highest" release for WPCS should use `dev-master`. +# Once WPCS 3.0.0 has been released and YoastCS has been made compatible, the matrix should switch (back) +# WPCS `dev-master` to `dev-develop`. +env: + PHPCS_HIGHEST: 'dev-master' + WPCS_HIGHEST: 'dev-master' + jobs: #### TEST STAGE #### test: @@ -30,13 +37,8 @@ jobs: # You can define jobs here and then augment them with extra variables in `include`, # as well as add extra jobs in `include`. # @link https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix - # - # Note: while WPCS 3.0.0 is under development, the matrix will use `dev-master`. - # Once it has been released and YoastCS has been made compatible, the matrix should switch (back) - # WPCS `dev-master` to `dev-develop`. php_version: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] - phpcs_version: ['3.7.2', 'dev-master'] - wpcs_version: ['2.3.0', 'dev-master'] + cs_dependencies: ['lowest', 'highest'] experimental: [false] include: @@ -44,22 +46,20 @@ jobs: # PHP nightly - php_version: '8.3' - phpcs_version: 'dev-master' - wpcs_version: 'dev-master' + cs_dependencies: 'highest' experimental: true # Test against WPCS unstable. Re-enable when WPCS is not in dev for the next major. #- php_version: '8.0' - # phpcs_version: 'dev-master' - # wpcs_version: 'dev-develop' + # cs_dependencies: 'highest' # experimental: true # Test against the next major of PHPCS. Temporarily disabled due to upstream bugs. #- php_version: '7.4' + # cs_dependencies: 'highest' # phpcs_version: '4.0.x-dev' - # wpcs_version: 'dev-develop' # experimental: true - name: "Test${{ matrix.phpcs_version == 'dev-master' && matrix.wpcs_version == 'dev-master' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - PHPCS ${{ matrix.phpcs_version }} - WPCS ${{ matrix.wpcs_version }}" + name: "Test${{ matrix.cs_dependencies == 'highest' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" continue-on-error: ${{ matrix.experimental }} @@ -72,7 +72,7 @@ jobs: - name: Setup ini config id: set_ini run: | - if [[ "${{ matrix.phpcs_version }}" != "dev-master" && "${{ matrix.phpcs_version }}" != "4.0.x-dev" ]]; then + if [[ "${{ matrix.cs_dependencies }}" != "highest" && "${{ matrix.phpcs_version }}" != "4.0.x-dev" ]]; then echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT else echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT @@ -92,18 +92,22 @@ jobs: # Set Composer up to download only PHPCS from source for PHPCS 4.x. # The source is needed to get the base testcase from PHPCS. # All other jobs can use `auto`, which is Composer's default value. - - name: 'Composer: conditionally prefer source for PHPCS' + - name: 'Composer: conditionally prefer source for PHPCS 4.x' if: ${{ startsWith( matrix.phpcs_version, '4' ) }} run: composer config preferred-install.squizlabs/php_codesniffer source - - name: 'Composer: adjust dependencies' + - name: 'Composer: adjust CS dependencies (high)' + if: ${{ matrix.cs_dependencies == 'highest' }} + run: | + composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ env.PHPCS_HIGHEST }}" --no-interaction + composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ env.WPCS_HIGHEST }}" --no-interaction + + - name: 'Composer: adjust CS dependencies (4.x)' + if: ${{ matrix.phpcs_version }} run: | - # Set the PHPCS version to test against. composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-interaction - # Set the WPCS version to test against. - composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ matrix.wpcs_version }}" --no-interaction - - name: 'Composer: conditionally remove PHPCSDevtools' + - name: 'Composer: conditionally remove PHPCSDevtools for PHPCS 4.x' if: ${{ startsWith( matrix.phpcs_version, '4' ) }} # Remove devtools as it will not (yet) install on PHPCS 4.x. run: composer remove --no-update --dev phpcsstandards/phpcsdevtools --no-interaction @@ -125,13 +129,20 @@ jobs: composer-options: --ignore-platform-req=php+ custom-cache-suffix: $(date -u "+%Y-%m") + - name: "Composer: set PHPCS version for tests (low)" + if: ${{ matrix.cs_dependencies == 'lowest' }} + run: composer update squizlabs/php_codesniffer wp-coding-standards/wpcs --with-dependencies --prefer-lowest --ignore-platform-req=php+ --no-scripts --no-interaction + + - name: Verify installed versions + run: composer info --no-dev + - name: Verify installed standards run: vendor/bin/phpcs -i # The results of the linting will be shown inline in the PR via the CS2PR tool. # @link https://github.com/staabm/annotate-pull-request-from-checkstyle/ - name: Lint against parse errors - if: ${{ matrix.phpcs_version == 'dev-master' && matrix.wpcs_version == 'dev-master' }} + if: ${{ matrix.cs_dependencies == 'highest' }} run: composer lint -- --checkstyle | cs2pr - name: Run the unit tests - PHP 5.4 - 8.0 From 73f610bbcaea631c1d3fdf883e47e7eb1237e69a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 30 Apr 2023 17:29:24 +0200 Subject: [PATCH 006/212] PHPUnit: expand code coverage configuration * Add missing directory for which coverage should be recorded. * Add logging configuration. Includes: * Ignoring the potentially generated code coverage files in the `build/*` directory to prevent it being committed. * Adding a script to the `composer.json` file to run code coverage. --- .gitignore | 1 + composer.json | 3 +++ phpunit.xml.dist | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 5d83e0c3..069bf8be 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.lock .cache/phpcs.cache phpcs.xml phpunit.xml +build/ diff --git a/composer.json b/composer.json index eacdc498..9b625291 100644 --- a/composer.json +++ b/composer.json @@ -59,6 +59,9 @@ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" ], "test": [ + "@php ./vendor/phpunit/phpunit/phpunit --filter Yoast --bootstrap=\"./vendor/squizlabs/php_codesniffer/tests/bootstrap.php\" ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage" + ], + "coverage": [ "@php ./vendor/phpunit/phpunit/phpunit --filter Yoast --bootstrap=\"./vendor/squizlabs/php_codesniffer/tests/bootstrap.php\" ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" ], "check-complete": [ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0aa18327..01528aef 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,9 +21,15 @@ Yoast/Sniffs/ + Yoast/Utils/ + + + + + From 84a41d78633420d53ebda1211401c8353eb4ce28 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 30 Apr 2023 18:07:28 +0200 Subject: [PATCH 007/212] GH Actions: start recording code coverage This commit updates the GH Actions `test` workflow to record and upload code coverage to Coveralls. Notes: * The `test` workflow will now also run for the `develop` branch, but in that case, will _only_ run the coverage jobs. This is intentional as otherwise we would not be able to see the code coverage progression over time. * A new `job` has been added with the steps for the coverage builds. These steps largely duplicate the steps in the `test` workflow, but keeping everything in one workflow would make that workflow a lot more complicated with various conditions, so having a separate workflow seemed prudent. * Coverage will be recorded using `Xdebug` as the code coverage driver. * Running code coverage is slow, so with a large number of builds, like this repo had, it is prudent to only run code coverage for select builds. With that in mind, coverage is only recorded for four builds and only run when the `test` builds have passed - or have been skipped when running against `develop` - to prevent wasting resources. The coverage builds are: - Low PHP (`5.4`) with high/low PHPCS/WPCS dependencies. - High PHP (`7.4`) with high/low PHPCS/WPCS dependencies. Note: the high PHP builds _should_ by rights be run against the latest PHP version, but that is a little difficult for this repo as the highest supported PHPUnit version at this time is PHPUnit 7.x and PHPUnit 7.x does not support running code coverage on PHP 8.x. Once the PHPUnit version requirements can be widened, the `high` build for code coverage should be adjusted to run on a PHP 8.x version. * The `php-coveralls/php-coveralls` package is used to convert the coverage information from a format as generated by PHPUnit to a format as can be consumed by Coveralls. - The `php-coveralls/php-coveralls` package is not 100% compatible with PHP 8.x (yet). As coverage is not recorded for PHP 8.x for this repo, this is not an issue. - As this package is only needed for the upload to Coveralls, the package has **not** been added it to the `composer.json` file, but will be (global) installed in the GH Actions workflow only. - For uploading code coverage with PHP < 5.5, `php-coveralls/php-coveralls` `1.x` is needed, but that version of the package doesn't play nice with GH Actions. To get round that, the jobs which run on PHP 5.4, will switch to PHP 7.4 for uploading the code coverage reports. * Coveralls requires a token to identify the repo and prevent unauthorized uploads. This token has been added to the repository secrets. People with admin access to the GH repo automatically also have access to the admin settings in Coveralls. If ever needed, the Coveralls token can be found (and regenerated) in the Coveralls admin for a repo. After regeneration, the token as stored in the GH repo Settings -> Secrets and Variables -> Actions -> Repository secrets should be updated. * As the workflow send multiple code coverage reports to Coveralls, we need to tell it to process those reports in parallel and give each report a unique name. That's what the `COVERALLS_PARALLEL` and COVERALLS_FLAG_NAME` settings are about. * The `coveralls-finish` job will signal to Coveralls that all reports have been uploaded. This basically gives the "okay" to Coveralls to report on the changes in code coverage via a comment on a GitHub PR as well as via a status check. The pertinent Coveralls settings used for this repo are: * "Only send aggregate Coverage updates to SCM": enabled * "Leave comments": enabled * "(Comment) Format": compact * "Use Status API": enabled * "Coverage threshold for failure": 95% * "Coverage decrease threshold for failure": 0.5% --- .github/workflows/test.yml | 116 ++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31c7fe5a..f582fbe1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - develop paths-ignore: - '**.md' pull_request: @@ -27,6 +28,7 @@ env: jobs: #### TEST STAGE #### test: + if: ${{ github.ref != 'refs/heads/develop' }} runs-on: ubuntu-latest strategy: @@ -37,7 +39,9 @@ jobs: # You can define jobs here and then augment them with extra variables in `include`, # as well as add extra jobs in `include`. # @link https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix - php_version: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + # + # The matrix is set up so as not to duplicate the builds which are run for code coverage. + php_version: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '8.0', '8.1', '8.2'] cs_dependencies: ['lowest', 'highest'] experimental: [false] @@ -154,3 +158,113 @@ jobs: run: composer test -- --no-configuration --dont-report-useless-tests env: PHPCS_IGNORE_TESTS: 'PHPCompatibility,WordPress' + + #### CODE COVERAGE STAGE #### + # N.B.: Coverage is only checked on the lowest and highest stable PHP versions + # and a low/high of each major for PHPCS. + # These builds are left out off the "test" stage so as not to duplicate test runs. + coverage: + # No use running the coverage builds if there are failing test builds. + needs: test + # The default condition is success(), but this is false when one of the previous jobs is skipped + if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped') + + runs-on: ubuntu-latest + + strategy: + matrix: + # 7.4 should be updated to 8.2 when higher PHPUnit versions can be supported. + php_version: ['5.4', '7.4'] + cs_dependencies: ['lowest', 'highest'] + + name: "Coverage${{ matrix.cs_dependencies == 'highest' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + # On stable PHPCS versions, allow for PHP deprecation notices. + # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. + - name: Setup ini config + id: set_ini + run: | + if [[ "${{ matrix.cs_dependencies }}" != "highest" && "${{ matrix.phpcs_version }}" != "4.0.x-dev" ]]; then + echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT + else + echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT + fi + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php_version }} + ini-values: ${{ steps.set_ini.outputs.PHP_INI }} + coverage: xdebug + tools: cs2pr + + - name: 'Composer: adjust CS dependencies (high)' + if: ${{ matrix.cs_dependencies == 'highest' }} + run: | + composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ env.PHPCS_HIGHEST }}" --no-interaction + composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ env.WPCS_HIGHEST }}" --no-interaction + + # Install dependencies and handle caching in one go. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies + uses: "ramsey/composer-install@v2" + with: + # Bust the cache at least once a month - output format: YYYY-MM. + custom-cache-suffix: $(date -u "+%Y-%m") + + - name: "Composer: set PHPCS version for tests (low)" + if: ${{ matrix.cs_dependencies == 'lowest' }} + run: composer update squizlabs/php_codesniffer wp-coding-standards/wpcs --with-dependencies --prefer-lowest --ignore-platform-req=php+ --no-scripts --no-interaction + + - name: Verify installed versions + run: composer info --no-dev + + - name: Verify installed standards + run: vendor/bin/phpcs -i + + # The results of the linting will be shown inline in the PR via the CS2PR tool. + # @link https://github.com/staabm/annotate-pull-request-from-checkstyle/ + - name: Lint against parse errors + if: ${{ matrix.cs_dependencies == 'highest' }} + run: composer lint -- --checkstyle | cs2pr + + - name: Run the unit tests with code coverage + run: composer coverage + + # Uploading the results with PHP Coveralls v1 won't work from GH Actions, so switch the PHP version. + - name: Switch to PHP 7.4 + if: ${{ success() && matrix.php_version != '7.4' }} + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + + # Global install is used to prevent a conflict with the local composer.lock in PHP 8.0+. + - name: Install Coveralls + if: ${{ success() }} + run: composer global require php-coveralls/php-coveralls:"^2.5.3" --no-interaction + + - name: Upload coverage results to Coveralls + if: ${{ success() }} + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: php-${{ matrix.php_version }}-cs-${{ matrix.cs_dependencies }} + run: php-coveralls -v -x build/logs/clover.xml + + coveralls-finish: + needs: coverage + if: always() && needs.coverage.result == 'success' + + runs-on: ubuntu-latest + + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.COVERALLS_TOKEN }} + parallel-finished: true From 262e7a201634863d4926bcd2f4275ea07709d85a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 30 Apr 2023 18:15:31 +0200 Subject: [PATCH 008/212] README: show off code coverage --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index da98bdaf..a9992f40 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Yoast Coding Standards +[![Coverage Status](https://coveralls.io/repos/github/Yoast/yoastcs/badge.svg?branch=develop)](https://coveralls.io/github/Yoast/yoastcs?branch=develop) + Yoast Coding Standards (YoastCS) is a project with rulesets for code style and quality tools to be used in Yoast projects. ## Installation From 4f5d4d62c1ca73141eb8491d99975862fe8304c4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 1 May 2023 19:44:18 +0200 Subject: [PATCH 009/212] Tests: show text summary ... instead of a detailed breakdown. The breakdown will be in Coveralls anyway. --- phpunit.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 01528aef..b8e34b92 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -26,7 +26,7 @@ - + From c62a5804e943d7d69cb910c56ca997f44e7de4ed Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 00:57:08 +0200 Subject: [PATCH 010/212] GH Actions: fix the builds WPCS 3.0.0 has been released and the WordPressCS `master` branch has been renamed to `main` and now contains WPCS 3.0.0. As YoastCS is not (yet) compatible with WPCS 3.0.0, the CI builds are failing. This commit makes the minimal changes needed to get the CI builds passing again, by: 1. Not updating WPCS to `master` for the CS check in the `basics` workflow. 2. Running the tests against the highest currently supported WPCS release `2.3.0` instead of against `dev-master`. --- .github/workflows/basics.yml | 4 ++-- .github/workflows/quicktest.yml | 6 +++--- .github/workflows/test.yml | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 5e0d5f81..be03a0cc 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -46,8 +46,8 @@ jobs: composer remove --no-update --dev phpunit/phpunit --no-scripts --no-interaction # Using PHPCS `master` as an early detection system for bugs upstream. composer require --no-update --no-scripts squizlabs/php_codesniffer:"dev-master" --no-interaction - # Using WPCS `master` (=stable). This can be changed back to `dev-develop` after the WPCS 3.0.0 release. - composer require --no-update --no-scripts wp-coding-standards/wpcs:"dev-master" --no-interaction + # Using WPCS `main` (=stable). This can be changed back to `dev-develop` after the WPCS 3.0.0 release. + # composer require --no-update --no-scripts wp-coding-standards/wpcs:"dev-main" --no-interaction # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index f1fcc14e..3c316d32 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -28,7 +28,7 @@ jobs: include: - php_version: 'latest' phpcs_version: 'dev-master' - wpcs_version: 'dev-master' + wpcs_version: '2.3.0' - php_version: 'latest' phpcs_version: '3.7.2' wpcs_version: '2.3.0' @@ -37,7 +37,7 @@ jobs: wpcs_version: '2.3.0' - php_version: '5.4' phpcs_version: '3.7.2' - wpcs_version: 'dev-master' + wpcs_version: '2.3.0' name: "QTest${{ matrix.phpcs_version == 'dev-master' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - PHPCS ${{ matrix.phpcs_version }} - WPCS ${{ matrix.wpcs_version }}" @@ -79,7 +79,7 @@ jobs: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") - # For the PHP 8.0 and higher, we need to install with ignore platform reqs as not all dependencies allow it. + # For PHP 8.0 and higher, we need to install with ignore platform reqs as not all dependencies allow it. - name: Install Composer dependencies - with ignore platform if: ${{ startsWith( matrix.php_version, '8' ) || matrix.php_version == 'latest' }} uses: ramsey/composer-install@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f582fbe1..e1f7a315 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,12 +18,12 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -# Note: while WPCS 3.0.0 is under development, the "highest" release for WPCS should use `dev-master`. -# Once WPCS 3.0.0 has been released and YoastCS has been made compatible, the matrix should switch (back) -# WPCS `dev-master` to `dev-develop`. +# Note: while WPCS 3.0.0 is not (yet) supported by YoastCS, the "highest" release for WPCS should use +# the last stable WPCS release supported by YoastCS, i.e. `2.3.0`. +# Once support for WPCS 3.0.0 has been added, the matrix should switch (back) using `dev-develop`. env: PHPCS_HIGHEST: 'dev-master' - WPCS_HIGHEST: 'dev-master' + WPCS_HIGHEST: '2.3.0' jobs: #### TEST STAGE #### @@ -125,7 +125,7 @@ jobs: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") - # For the PHP 8/"nightly", we need to install with ignore platform reqs as we're still using PHPUnit 7. + # For PHP 8/"nightly", we need to install with ignore platform reqs as we're still using PHPUnit 7. - name: Install Composer dependencies - with ignore platform if: ${{ startsWith( matrix.php_version, '8' ) }} uses: ramsey/composer-install@v2 From 9d14dbd2b1931f85bb775e8e4cf7f0efcadef8be Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Sep 2023 04:11:13 +0200 Subject: [PATCH 011/212] .gitattributes: minor clean up * Alignment for readability. * Sorted the list - folders first, then files, alphabetic order. * Remove files which don't exist in this repo from the list. --- .gitattributes | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/.gitattributes b/.gitattributes index 0a102227..978d51ea 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,17 +5,13 @@ # https://www.reddit.com/r/PHP/comments/2jzp6k/i_dont_need_your_tests_in_my_production # https://blog.madewithlove.be/post/gitattributes/ # -/.gitattributes export-ignore -/.gitignore export-ignore -/.phpcs.xml export-ignore -/.phpcs.xml.dist export-ignore -/phpcs.xml export-ignore -/phpcs.xml.dist export-ignore -/phpunit.xml export-ignore -/phpunit.xml.dist export-ignore -/.cache export-ignore -/.github export-ignore -/Yoast/Tests export-ignore +/.cache export-ignore +/.github export-ignore +/Yoast/Tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.phpcs.xml.dist export-ignore +phpunit.xml.dist export-ignore # # Auto detect text files and perform LF normalization From 0d6853e8539a9e3b7b7ab17785be56d391090e0f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Sep 2023 04:11:48 +0200 Subject: [PATCH 012/212] .gitignore: minor tweak (group like files together) --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 069bf8be..cdd94a4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ vendor/ composer.lock -.phpcs.xml .cache/phpcs.cache +.phpcs.xml phpcs.xml phpunit.xml build/ From 03a6c57f9c5451c370f5711266b2932114cc593f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Sep 2023 04:13:18 +0200 Subject: [PATCH 013/212] Composer: remove redundant script This script hasn't been used (in practice) since the Composer PHPCS Installer plugin became a non-dev dependency, so we may as well remove it. --- composer.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/composer.json b/composer.json index 9b625291..04223d11 100644 --- a/composer.json +++ b/composer.json @@ -45,10 +45,6 @@ "minimum-stability": "dev", "prefer-stable": true, "scripts": { - "config-yoastcs" : [ - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run", - "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --config-set default_standard Yoast" - ], "lint": [ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --show-deprecated --exclude vendor --exclude .git" ], From b2eadca1829e5bfe63de89f900519ba56d5d8bd7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 22 Sep 2023 03:29:28 +0200 Subject: [PATCH 014/212] Composer: normalize the file Well, mostly (scripts are not alphabetized, but still grouped by task). Note: this is done as a one-time only action. The normalize script will **_not_** be run in CI to enforce normalization. Style has been standardized to `--indent-style=tab --indent-size=1`. Ref: https://github.com/ergebnis/composer-normalize --- composer.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 04223d11..1388e2a2 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,8 @@ { "name": "yoast/yoastcs", "description": "PHP_CodeSniffer rules for Yoast projects", + "license": "MIT", + "type": "phpcodesniffer-standard", "keywords": [ "phpcs", "standards", @@ -8,8 +10,6 @@ "wordpress", "yoast" ], - "homepage": "https://github.com/Yoast/yoastcs", - "license": "MIT", "authors": [ { "name": "Team Yoast", @@ -17,33 +17,33 @@ "homepage": "https://yoast.com" } ], - "type" : "phpcodesniffer-standard", + "homepage": "https://github.com/Yoast/yoastcs", "support": { "issues": "https://github.com/Yoast/yoastcs/issues", "source": "https://github.com/Yoast/yoastcs" }, "require": { "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.7.2", - "wp-coding-standards/wpcs": "^2.3.0", - "phpcompatibility/phpcompatibility-wp": "^2.1.4", "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", + "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", - "php-parallel-lint/php-console-highlighter": "^1.0.0" + "phpcompatibility/phpcompatibility-wp": "^2.1.4", + "squizlabs/php_codesniffer": "^3.7.2", + "wp-coding-standards/wpcs": "^2.3.0" }, "require-dev": { "phpcompatibility/php-compatibility": "^9.3.5", - "roave/security-advisories": "dev-master", + "phpcsstandards/phpcsdevtools": "^1.2.1", "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0", - "phpcsstandards/phpcsdevtools": "^1.2.1" + "roave/security-advisories": "dev-master" }, + "minimum-stability": "dev", + "prefer-stable": true, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } }, - "minimum-stability": "dev", - "prefer-stable": true, "scripts": { "lint": [ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --show-deprecated --exclude vendor --exclude .git" From 07a7fb7e81dda201c3918c780eb3c96a574a6fd7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 22 Sep 2023 03:31:14 +0200 Subject: [PATCH 015/212] Composer: add script descriptions These descriptions will be used when a list of the available scripts is requested on the command-line using the `composer list` or `composer run -l` commands. These descriptions also help document the different scripts for the maintainers of the `composer.json` file. Ref: https://getcomposer.org/doc/articles/scripts.md#custom-descriptions- --- composer.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/composer.json b/composer.json index 1388e2a2..6b017e6b 100644 --- a/composer.json +++ b/composer.json @@ -63,5 +63,13 @@ "check-complete": [ "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness ./Yoast" ] + }, + "scripts-descriptions": { + "lint": "Check the PHP files for parse errors.", + "check-cs": "Check the PHP files for code style violations and best practices.", + "fix-cs": "Auto-fix code style violations in the PHP files.", + "test": "Run the unit tests without code coverage.", + "coverage": "Run the unit tests with code coverage.", + "check-complete": "Check if all the sniffs have tests and XML documentation." } } From 885354e8f368d37dcc1c6bd88075258f0ef7953e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 4 Apr 2020 16:52:17 +0200 Subject: [PATCH 016/212] Tests: add bootstrap file for PHPUnit As, as of YoastCS 3.0, more external standards will be used, manually maintaining the `PHPCS_IGNORE_TESTS` lists becomes cumbersome. This commit adds a bootstrap file which will: * Load the PHPCS autoloader (was previously done from the command-line); * Will automatically set the `PHPCS_IGNORE_TESTS` environment variable to the correct value to prevent running tests from other standards. --- .github/workflows/quicktest.yml | 4 +- .github/workflows/test.yml | 4 +- composer.json | 4 +- phpunit-bootstrap.php | 83 +++++++++++++++++++++++++++++++++ phpunit.xml.dist | 5 +- 5 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 phpunit-bootstrap.php diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index 3c316d32..7ba036fd 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -100,6 +100,4 @@ jobs: - name: Run the unit tests - PHP 8.1+ if: ${{ matrix.php_version >= '8.1' || matrix.php_version == 'latest'}} - run: composer test -- --no-configuration --dont-report-useless-tests - env: - PHPCS_IGNORE_TESTS: 'PHPCompatibility,WordPress' + run: composer test -- --no-configuration --bootstrap=./phpunit-bootstrap.php --dont-report-useless-tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1f7a315..700d0788 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,9 +155,7 @@ jobs: - name: Run the unit tests - PHP 8.1+ if: ${{ matrix.php_version >= '8.1' }} - run: composer test -- --no-configuration --dont-report-useless-tests - env: - PHPCS_IGNORE_TESTS: 'PHPCompatibility,WordPress' + run: composer test -- --no-configuration --bootstrap=./phpunit-bootstrap.php --dont-report-useless-tests #### CODE COVERAGE STAGE #### # N.B.: Coverage is only checked on the lowest and highest stable PHP versions diff --git a/composer.json b/composer.json index 6b017e6b..829fff5f 100644 --- a/composer.json +++ b/composer.json @@ -55,10 +55,10 @@ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" ], "test": [ - "@php ./vendor/phpunit/phpunit/phpunit --filter Yoast --bootstrap=\"./vendor/squizlabs/php_codesniffer/tests/bootstrap.php\" ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage" + "@php ./vendor/phpunit/phpunit/phpunit --filter Yoast ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage" ], "coverage": [ - "@php ./vendor/phpunit/phpunit/phpunit --filter Yoast --bootstrap=\"./vendor/squizlabs/php_codesniffer/tests/bootstrap.php\" ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" + "@php ./vendor/phpunit/phpunit/phpunit --filter Yoast ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" ], "check-complete": [ "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness ./Yoast" diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php new file mode 100644 index 00000000..e6a4ecd0 --- /dev/null +++ b/phpunit-bootstrap.php @@ -0,0 +1,83 @@ + true, +]; + +$all_standards = Standards::getInstalledStandards(); +$all_standards[] = 'Generic'; + +$standards_to_ignore = []; +foreach ( $all_standards as $standard ) { + if ( isset( $yoast_standards[ $standard ] ) === true ) { + continue; + } + + $standards_to_ignore[] = $standard; +} + +$standards_to_ignore_string = \implode( ',', $standards_to_ignore ); + +// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- This is not production, but test code. +\putenv( "PHPCS_IGNORE_TESTS={$standards_to_ignore_string}" ); + +// Clean up. +unset( $phpcs_dir, $composer_phpcs_path, $all_standards, $standards_to_ignore, $standard, $standards_to_ignore_string ); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b8e34b92..a1962b29 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,6 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd" backupGlobals="true" + bootstrap="./phpunit-bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="false" colors="true" convertErrorsToExceptions="true" @@ -29,8 +30,4 @@ - - - - From cd7d155666b8221df08f807a9a23d5c9121e0ab3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 22 Sep 2023 12:09:33 +0200 Subject: [PATCH 017/212] GH Actions: automate some label management This is a quite straight-forward workflow to just remove some labels which should only be on open issues/open PRs and which should be removed once an issue or PR has been closed/merged. Just attempting to reduce some manual labour. --- .github/workflows/manage-labels.yml | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/manage-labels.yml diff --git a/.github/workflows/manage-labels.yml b/.github/workflows/manage-labels.yml new file mode 100644 index 00000000..faa0f7f9 --- /dev/null +++ b/.github/workflows/manage-labels.yml @@ -0,0 +1,68 @@ +name: Remove outdated labels + +on: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target + pull_request_target: + types: + - closed + issues: + types: + - closed + +jobs: + on-pr-merge: + runs-on: ubuntu-latest + if: github.repository_owner == 'Yoast' && github.event.pull_request.merged == true + + name: Clean up labels on PR merge + + steps: + - uses: mondeja/remove-labels-gh-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + labels: | + Status: awaiting feedback + Status: blocked + Status: needs investigation + Status: wait for upstream/PHPCSExtra + Status: wait for upstream/Slevomat + Status: wait for upstream/VIPCS + Status: wait for upstream/WordPressCS + + on-pr-close: + runs-on: ubuntu-latest + if: github.repository_owner == 'Yoast' && github.event_name == 'pull_request_target' && github.event.pull_request.merged == false + + name: Clean up labels on PR close + + steps: + - uses: mondeja/remove-labels-gh-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + labels: | + Status: awaiting feedback + Status: blocked + Status: needs investigation + Status: wait for upstream/PHPCSExtra + Status: wait for upstream/Slevomat + Status: wait for upstream/VIPCS + Status: wait for upstream/WordPressCS + + on-issue-close: + runs-on: ubuntu-latest + if: github.repository_owner == 'Yoast' && github.event.issue.state == 'closed' + + name: Clean up labels on issue close + + steps: + - uses: mondeja/remove-labels-gh-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + labels: | + Status: awaiting feedback + Status: blocked + Status: needs investigation + Status: wait for upstream/PHPCSExtra + Status: wait for upstream/Slevomat + Status: wait for upstream/VIPCS + Status: wait for upstream/WordPressCS From bc74840478163fa62816c2e724de711c6a0877a3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 1 May 2019 01:38:56 +0200 Subject: [PATCH 018/212] CI/QA: update phpunit.xml.dist file --- phpunit.xml.dist | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a1962b29..65ec8d33 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,16 +1,16 @@ From 13d9d177b5ced8f38d057d0451ab3fd10fec79e0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 16 Sep 2023 09:38:32 +0200 Subject: [PATCH 019/212] PHPCS: update minimum supported WP version ... to be in line with the minimum supported version in the plugins. --- Yoast/ruleset.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 084e6b76..e7db917e 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -31,7 +31,7 @@ Ref: https://github.com/WordPress/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#minimum-wp-version-to-check-for-usage-of-deprecated-functions-classes-and-function-parameters --> - + From 0ec6c3415f00f5e010ee87200b4e1699d3aba831 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 22 Sep 2023 03:41:02 +0200 Subject: [PATCH 020/212] Composer: update requried PHP extensions Discovered using the ComposerRequireChecker tool and manually verified. Ref: https://github.com/maglnet/ComposerRequireChecker --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 829fff5f..66b237ba 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ }, "require": { "php": ">=5.4", + "ext-tokenizer": "*", "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", From cf886cb172e89ff8bd56a9726f4c50131b738a05 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 19 Oct 2023 08:22:56 +0200 Subject: [PATCH 021/212] Files/TestDoubles: bug fix - not all tests were being run There was a bug in the `glob` expression used to collect all the test case files, which meant that not all test case files were found and checked during the test runs. Fixed now. Includes one minor, non-consequential fix to the test expectations. --- Yoast/Tests/Files/TestDoublesUnitTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Yoast/Tests/Files/TestDoublesUnitTest.php b/Yoast/Tests/Files/TestDoublesUnitTest.php index 9401a866..9e1ca262 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTest.php +++ b/Yoast/Tests/Files/TestDoublesUnitTest.php @@ -41,7 +41,7 @@ public function setCliValues( $testFile, $config ) { */ protected function getTestFiles( $testFileBase ) { $sep = \DIRECTORY_SEPARATOR; - $test_files = \glob( \dirname( $testFileBase ) . $sep . 'TestDoublesUnitTests{' . $sep . ',' . $sep . '*' . $sep . '}*.inc', \GLOB_BRACE ); + $test_files = \glob( \dirname( $testFileBase ) . $sep . 'TestDoublesUnitTests' . $sep . 'tests{' . $sep . ',' . $sep . '*' . $sep . '}*.inc', \GLOB_BRACE ); if ( ! empty( $test_files ) ) { return $test_files; @@ -117,7 +117,7 @@ public function getErrorList( $testFile = '' ) { // In tests/mocks. case 'correct-custom-dir-not-mock.inc': return [ - 3 => 1, + 4 => 1, ]; case 'not-double-or-mock.inc': // In tests. From 85eb9281abb0743e10aa6ef36154416ee3159360 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 19 Oct 2023 08:36:36 +0200 Subject: [PATCH 022/212] NamingConventions/NamespaceName: bug fix - not all tests were being run There was a bug in the `glob` expression used to collect all the test case files, which meant that not all test case files were found and checked during the test runs. Fixed now. --- .../Tests/NamingConventions/NamespaceNameUnitTest.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index 19fadee3..6dee51a2 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -42,7 +42,15 @@ public function setCliValues( $testFile, $config ) { */ protected function getTestFiles( $testFileBase ) { $sep = \DIRECTORY_SEPARATOR; - $test_files = \glob( \dirname( $testFileBase ) . $sep . 'NamespaceNameUnitTests{' . $sep . ',' . $sep . '*' . $sep . '}*.inc', \GLOB_BRACE ); + $test_files = \glob( + \dirname( $testFileBase ) . $sep . 'NamespaceNameUnitTests{' + . $sep . ',' // Files in the "NamespaceNameUnitTests" directory. + . $sep . '*' . $sep . ',' // Files in first-level subdirectories. + . $sep . '*' . $sep . '*' . $sep . ',' // Files in second-level subdirectories. + . $sep . '*' . $sep . '*' . $sep . '*' . $sep // Files in third-level subdirectories. + . '}*.inc', + \GLOB_BRACE + ); if ( ! empty( $test_files ) ) { return $test_files; From 86efafbb8fe6096816ac412bf78492673c3cd4c5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 19 Oct 2023 09:33:40 +0200 Subject: [PATCH 023/212] YoastCS/test bootstrap: minor tweaks No need for FQN function calls when the file is not namespaced. --- phpunit-bootstrap.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php index e6a4ecd0..c75a99e7 100644 --- a/phpunit-bootstrap.php +++ b/phpunit-bootstrap.php @@ -13,18 +13,18 @@ use PHP_CodeSniffer\Util\Standards; -if ( \defined( 'PHP_CODESNIFFER_IN_TESTS' ) === false ) { - \define( 'PHP_CODESNIFFER_IN_TESTS', true ); +if ( defined( 'PHP_CODESNIFFER_IN_TESTS' ) === false ) { + define( 'PHP_CODESNIFFER_IN_TESTS', true ); } /* * Load the necessary PHPCS files. */ // Get the PHPCS dir from an environment variable. -$phpcs_dir = \getenv( 'PHPCS_DIR' ); +$phpcs_dir = getenv( 'PHPCS_DIR' ); $composer_phpcs_path = __DIR__ . '/vendor/squizlabs/php_codesniffer'; -if ( $phpcs_dir === false && \is_dir( $composer_phpcs_path ) ) { +if ( $phpcs_dir === false && is_dir( $composer_phpcs_path ) ) { // PHPCS installed via Composer. $phpcs_dir = $composer_phpcs_path; } @@ -33,13 +33,13 @@ * PHPCS in a custom directory. * For this to work, the `PHPCS_DIR` needs to be set in a custom `phpunit.xml` file. */ - $phpcs_dir = \realpath( $phpcs_dir ); + $phpcs_dir = realpath( $phpcs_dir ); } // Try and load the PHPCS autoloader. if ( $phpcs_dir !== false - && \file_exists( $phpcs_dir . '/autoload.php' ) - && \file_exists( $phpcs_dir . '/tests/bootstrap.php' ) + && file_exists( $phpcs_dir . '/autoload.php' ) + && file_exists( $phpcs_dir . '/tests/bootstrap.php' ) ) { require_once $phpcs_dir . '/autoload.php'; require_once $phpcs_dir . '/tests/bootstrap.php'; // PHPUnit 6.x+ support. @@ -74,10 +74,10 @@ $standards_to_ignore[] = $standard; } -$standards_to_ignore_string = \implode( ',', $standards_to_ignore ); +$standards_to_ignore_string = implode( ',', $standards_to_ignore ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- This is not production, but test code. -\putenv( "PHPCS_IGNORE_TESTS={$standards_to_ignore_string}" ); +putenv( "PHPCS_IGNORE_TESTS={$standards_to_ignore_string}" ); // Clean up. unset( $phpcs_dir, $composer_phpcs_path, $all_standards, $standards_to_ignore, $standard, $standards_to_ignore_string ); From edbd32ddb160f8eb97cdc359c85deb7d28c606d0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 4 Nov 2021 15:26:06 +0100 Subject: [PATCH 024/212] GH Actions: don't run CS checks on `main` ... as the can't be fixed on `main` anyway. This would need a PR to `develop`. (consistency with other workflows for the Yoast repos) --- .github/workflows/basics.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index be03a0cc..8a91062c 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -6,6 +6,8 @@ on: push: paths-ignore: - '**.md' + branches-ignore: + - 'main' pull_request: # Allow manually triggering the workflow. workflow_dispatch: From 69ac2a24ae824b5d1727561153d6153cc4a1786b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 17 Sep 2023 23:52:59 +0200 Subject: [PATCH 025/212] GH Actions/basics: tweak the workflow * Split step which was running multiple commands into two steps and combine a couple of those commands into a single command for improved error management. * Ensure all steps have a name. * Remove redundant comment. --- .github/workflows/basics.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 8a91062c..966155b8 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -42,14 +42,18 @@ jobs: - name: Validate Composer installation run: composer validate --no-check-all --strict - - name: 'Composer: adjust dependencies' - run: | - # The sniff stage doesn't run the unit tests, so no need for PHPUnit. - composer remove --no-update --dev phpunit/phpunit --no-scripts --no-interaction - # Using PHPCS `master` as an early detection system for bugs upstream. - composer require --no-update --no-scripts squizlabs/php_codesniffer:"dev-master" --no-interaction - # Using WPCS `main` (=stable). This can be changed back to `dev-develop` after the WPCS 3.0.0 release. - # composer require --no-update --no-scripts wp-coding-standards/wpcs:"dev-main" --no-interaction + # The sniff stage doesn't run the unit tests, so no need for PHPUnit. + - name: 'Composer: adjust dependencies - remove PHPUnit' + run: composer remove --no-update --dev phpunit/phpunit --no-scripts --no-interaction + + # Use the WIP/develop branches of all CS dependencies as an early detection system for bugs upstream. + - name: 'Composer: adjust dependencies - use dev versions of CS dependencies' + # Note: while WPCS 3.0.0 is not yet supported, WPCS will not be updated. + # Once YoastCS has been updated for WPCS 3.0.0, WPCS dev-develop should be added (back) to this command. + # wp-coding-standards/wpcs:"dev-develop" + run: > + composer require --no-update --no-scripts --no-interaction + squizlabs/php_codesniffer:"dev-master" # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies @@ -66,7 +70,8 @@ jobs: # Show XML violations inline in the file diff. # @link https://github.com/marketplace/actions/xmllint-problem-matcher - - uses: korelstar/xmllint-problem-matcher@v1 + - name: Show XML issues inline + uses: korelstar/xmllint-problem-matcher@v1 # Validate the ruleset XML file. # @link http://xmlsoft.org/xmllint.html @@ -75,7 +80,6 @@ jobs: # Validate the Docs XML files. # @link http://xmlsoft.org/xmllint.html - # For the build to properly error when validating against a scheme, these each have to be in their own condition. - name: Validate the XML sniff docs against schema run: xmllint --noout --schema vendor/phpcsstandards/phpcsdevtools/DocsXsd/phpcsdocs.xsd ./Yoast/Docs/*/*Standard.xml From 4609d7e0957d4246a28bb32ebca10a6fa35f262b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 17 Sep 2023 23:55:40 +0200 Subject: [PATCH 026/212] GH Actions/test: remove the `experimental` key from the matrix ... as, at this time, it isn't really needed. --- .github/workflows/test.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 700d0788..75e905d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,8 +32,6 @@ jobs: runs-on: ubuntu-latest strategy: - # Keys: - # - experimental: Whether the build is "allowed to fail". matrix: # The GHA matrix works different from Travis. # You can define jobs here and then augment them with extra variables in `include`, @@ -43,7 +41,6 @@ jobs: # The matrix is set up so as not to duplicate the builds which are run for code coverage. php_version: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '8.0', '8.1', '8.2'] cs_dependencies: ['lowest', 'highest'] - experimental: [false] include: # Experimental builds. These are allowed to fail. @@ -51,21 +48,18 @@ jobs: # PHP nightly - php_version: '8.3' cs_dependencies: 'highest' - experimental: true # Test against WPCS unstable. Re-enable when WPCS is not in dev for the next major. #- php_version: '8.0' # cs_dependencies: 'highest' - # experimental: true # Test against the next major of PHPCS. Temporarily disabled due to upstream bugs. #- php_version: '7.4' # cs_dependencies: 'highest' # phpcs_version: '4.0.x-dev' - # experimental: true name: "Test${{ matrix.cs_dependencies == 'highest' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" - continue-on-error: ${{ matrix.experimental }} + continue-on-error: ${{ matrix.php == '8.3' }} steps: - name: Checkout code From 53b3008356f0ccac796d5b28f2187f1f653adc26 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 00:03:25 +0200 Subject: [PATCH 027/212] GH Actions/test: remove unused steps for PHPCS 4.x Testing against PHPCS 4.x-dev has not been possible for a while and development of that version looks to be stagnated anyway, so testing against it is not really relevant at this time. This commit removes all references to testing against PHPCS 4.x from the workflow. If/when testing against the next PHPCS major becomes relevant again, this can brought back and adjusted based on the reality at that time. --- .github/workflows/test.yml | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 75e905d4..d1884e81 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,11 +52,6 @@ jobs: #- php_version: '8.0' # cs_dependencies: 'highest' - # Test against the next major of PHPCS. Temporarily disabled due to upstream bugs. - #- php_version: '7.4' - # cs_dependencies: 'highest' - # phpcs_version: '4.0.x-dev' - name: "Test${{ matrix.cs_dependencies == 'highest' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" continue-on-error: ${{ matrix.php == '8.3' }} @@ -70,7 +65,7 @@ jobs: - name: Setup ini config id: set_ini run: | - if [[ "${{ matrix.cs_dependencies }}" != "highest" && "${{ matrix.phpcs_version }}" != "4.0.x-dev" ]]; then + if [[ "${{ matrix.cs_dependencies }}" != "highest" ]]; then echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT else echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT @@ -83,16 +78,6 @@ jobs: ini-values: ${{ steps.set_ini.outputs.PHP_INI }} coverage: none tools: cs2pr - env: - # Token is needed for the PHPCS 4.x run against source. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Set Composer up to download only PHPCS from source for PHPCS 4.x. - # The source is needed to get the base testcase from PHPCS. - # All other jobs can use `auto`, which is Composer's default value. - - name: 'Composer: conditionally prefer source for PHPCS 4.x' - if: ${{ startsWith( matrix.phpcs_version, '4' ) }} - run: composer config preferred-install.squizlabs/php_codesniffer source - name: 'Composer: adjust CS dependencies (high)' if: ${{ matrix.cs_dependencies == 'highest' }} @@ -100,16 +85,6 @@ jobs: composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ env.PHPCS_HIGHEST }}" --no-interaction composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ env.WPCS_HIGHEST }}" --no-interaction - - name: 'Composer: adjust CS dependencies (4.x)' - if: ${{ matrix.phpcs_version }} - run: | - composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-interaction - - - name: 'Composer: conditionally remove PHPCSDevtools for PHPCS 4.x' - if: ${{ startsWith( matrix.phpcs_version, '4' ) }} - # Remove devtools as it will not (yet) install on PHPCS 4.x. - run: composer remove --no-update --dev phpcsstandards/phpcsdevtools --no-interaction - # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies - name: Install Composer dependencies - normal @@ -180,7 +155,7 @@ jobs: - name: Setup ini config id: set_ini run: | - if [[ "${{ matrix.cs_dependencies }}" != "highest" && "${{ matrix.phpcs_version }}" != "4.0.x-dev" ]]; then + if [[ "${{ matrix.cs_dependencies }}" != "highest" ]]; then echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT else echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT From 6131d4132a6e1127050b4aa469da3e5ff6894209 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 02:06:23 +0200 Subject: [PATCH 028/212] GH Actions: tweak the way the PHPCS/WPCS versions are set Next iteration after PR 300. YoastCS 3.0 will not just have PHPCS and WPCS as dependencies for the sniff tests, but also PHPCSUtils. PR 300 already changed the logic of the workflow to take advantage of the Composer `--prefer-lowest` setting. This commit takes this yet another step further. **Stable vs Dev** As things were, the sniff tests were being run against the _lowest_ supported CS dependencies and the _development_ version of supported CS dependencies (with the exception of WPCS dev as WPCS 3.0.0 is not yet supported). While testing against the _dev_ versions of CS dependencies is relevant, it is more important that the tests safeguard that the sniffs work correctly on user-installable combinations of the CS dependencies. Now, it could be argued that every single combination of the different versions of each of these dependencies should be tested, but that would make the matrix _huge_ to little added benefit, especially as most of the time, the `lowest` and `stable` supported versions of CS dependencies will be the same for YoastCS (though not always, which still makes the distinction and running of tests against both `lowest` and `stable` relevant as it will allow for new releases of the CS dependencies automatically). To that end, I'm changing the test matrices to test by default against the `lowest` and `stable` supported versions of dependencies with some select extra builds testing against the `dev` versions of supported CS dependencies. PHP parse error linting will be run against all builds with the `stable` key now. Note: `stable` is also the name used for the Composer sister-argument to `--prefer-lowest`: `--prefer-stable`, so should be clear-cut for anyone reading the workflow. **Consequences** Quicktest workflow: * This will no longer contain exact versions of the CS dependencies in the test matrix, which should simplify workflow maintenance. * The "Composer update" step for the `dev` dependencies has been adjusted. Instead of multiple commands, it now does all the updates in one combined command, which should make debugging of the workflow easier. * One extra build has been added to test against `dev` dependencies. Test workflow: * The standard builds will now run against `lowest` and `stable` versions of dependencies and a number of extra builds have been added to test against `dev` versions of dependencies. I considered marking those as "allowed to fail" (`continue-on-error`), but decided against that as the sniffs should always be ready for the next minor/patch release of the CS dependencies. * The commented out build against WPCS dev can now be removed as that will be handled via the "all CS dependencies" `dev` builds (once WPCS 3.0 is supported and re-enabled for update for the `dev` builds). * The "Composer update" step for the `dev` dependencies has been adjusted. Instead of multiple commands, it now does all the updates in one combined command, which should make debugging of the workflow easier. * The "Composer update" step for the `lowest` depencies has been adjusted to be more readable. * Note that the direct Composer commands do not include `--update-with-dependencies` as our and our child dependencies overlap anyway. * The code coverage builds will now run against `lowest`, and `dev`, which is basically the same as it was before. Also note that the direct Composer command for updating the CS dependencies currently contain the `--ignore-platform-req=php+` argument. This is needed for PHP 8.x due to the max supported PHPUnit version being PHPUnit 7.x. This CLI argument can be removed once upstream PR squizlabs/PHP_CodeSniffer 3803 has been merged. --- .github/workflows/quicktest.yml | 45 +++++++++--------- .github/workflows/test.yml | 82 ++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index 7ba036fd..47e25929 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -19,38 +19,31 @@ concurrency: jobs: #### QUICK TEST STAGE #### # This is a much quicker test which only runs the unit tests and linting against low/high - # supported PHP/PHPCS/WPCS combinations. + # of the supported PHP/CS dependencies combinations. quicktest: runs-on: ubuntu-latest strategy: matrix: + php_version: ['5.4', 'latest'] + cs_dependencies: ['lowest', 'stable'] + include: - php_version: 'latest' - phpcs_version: 'dev-master' - wpcs_version: '2.3.0' - - php_version: 'latest' - phpcs_version: '3.7.2' - wpcs_version: '2.3.0' - - php_version: '5.4' - phpcs_version: 'dev-master' - wpcs_version: '2.3.0' - - php_version: '5.4' - phpcs_version: '3.7.2' - wpcs_version: '2.3.0' + cs_dependencies: 'dev' - name: "QTest${{ matrix.phpcs_version == 'dev-master' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - PHPCS ${{ matrix.phpcs_version }} - WPCS ${{ matrix.wpcs_version }}" + name: "QTest${{ matrix.cs_dependencies == 'stable' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS deps ${{ matrix.cs_dependencies }}" steps: - name: Checkout code uses: actions/checkout@v3 - # On stable PHPCS versions, allow for PHP deprecation notices. + # With stable PHPCS dependencies, allow for PHP deprecation notices. # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. - name: Setup ini config id: set_ini run: | - if [ "${{ matrix.phpcs_version }}" != "dev-master" ]; then + if [ "${{ matrix.cs_dependencies }}" != "dev" ]; then echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT else echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT @@ -63,12 +56,13 @@ jobs: ini-values: ${{ steps.set_ini.outputs.PHP_INI }} coverage: none - - name: 'Composer: adjust dependencies' - run: | - # Set the PHPCS version to test against. - composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-interaction - # Set the WPCS version to test against. - composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ matrix.wpcs_version }}" --no-interaction + - name: "Composer: set PHPCS dependencies for tests (dev)" + if: ${{ matrix.cs_dependencies == 'dev' }} + # Note: while WPCS 3.0.0 is not yet supported, WPCS will not be updated for the `dev` builds. + # Once YoastCS has been updated for WPCS 3.0.0, WPCS dev-develop should be added to this command. + run: > + composer require --no-update --no-scripts --no-interaction --ignore-platform-req=php+ + squizlabs/php_codesniffer:"dev-master" # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies @@ -87,11 +81,18 @@ jobs: composer-options: --ignore-platform-req=php+ custom-cache-suffix: $(date -u "+%Y-%m") + - name: "Composer: downgrade PHPCS dependencies for tests (lowest) (with ignore platform)" + if: ${{ matrix.cs_dependencies == 'lowest' }} + run: > + composer update --prefer-lowest --no-scripts --no-interaction --ignore-platform-req=php+ + squizlabs/php_codesniffer + wp-coding-standards/wpcs + - name: Verify installed standards run: vendor/bin/phpcs -i - name: Lint against parse errors - if: matrix.phpcs_version == 'dev-master' + if: matrix.cs_dependencies == 'stable' run: composer lint - name: Run the unit tests - PHP 5.4 - 8.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d1884e81..05715330 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,19 +40,29 @@ jobs: # # The matrix is set up so as not to duplicate the builds which are run for code coverage. php_version: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '8.0', '8.1', '8.2'] - cs_dependencies: ['lowest', 'highest'] + cs_dependencies: ['lowest', 'stable'] include: - # Experimental builds. These are allowed to fail. - + # Make the matrix complete (when combined with the code coverage builds). + - php_version: '5.4' + cs_dependencies: 'stable' + - php_version: '7.4' + cs_dependencies: 'stable' + + # Test against dev versions of all CS dependencies with select PHP versions for early detection of issues. + - php_version: '7.0' + cs_dependencies: 'dev' + - php_version: '8.0' + cs_dependencies: 'dev' + - php_version: '8.2' + cs_dependencies: 'dev' + + # Experimental build(s). These are allowed to fail. # PHP nightly - php_version: '8.3' - cs_dependencies: 'highest' - # Test against WPCS unstable. Re-enable when WPCS is not in dev for the next major. - #- php_version: '8.0' - # cs_dependencies: 'highest' + cs_dependencies: 'dev' - name: "Test${{ matrix.cs_dependencies == 'highest' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" + name: "Test${{ matrix.cs_dependencies == 'stable' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" continue-on-error: ${{ matrix.php == '8.3' }} @@ -60,12 +70,12 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - # On stable PHPCS versions, allow for PHP deprecation notices. + # With stable PHPCS dependencies, allow for PHP deprecation notices. # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. - name: Setup ini config id: set_ini run: | - if [[ "${{ matrix.cs_dependencies }}" != "highest" ]]; then + if [ "${{ matrix.cs_dependencies }}" != "dev" ]; then echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT else echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT @@ -79,11 +89,13 @@ jobs: coverage: none tools: cs2pr - - name: 'Composer: adjust CS dependencies (high)' - if: ${{ matrix.cs_dependencies == 'highest' }} - run: | - composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ env.PHPCS_HIGHEST }}" --no-interaction - composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ env.WPCS_HIGHEST }}" --no-interaction + - name: "Composer: set PHPCS dependencies for tests (dev)" + if: ${{ matrix.cs_dependencies == 'dev' }} + # Note: while WPCS 3.0.0 is not yet supported, WPCS will not be updated for the `dev` builds. + # Once YoastCS has been updated for WPCS 3.0.0, WPCS dev-develop should be added to this command. + run: > + composer require --no-update --no-scripts --no-interaction --ignore-platform-req=php+ + squizlabs/php_codesniffer:"${{ env.PHPCS_HIGHEST }}" # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies @@ -102,9 +114,12 @@ jobs: composer-options: --ignore-platform-req=php+ custom-cache-suffix: $(date -u "+%Y-%m") - - name: "Composer: set PHPCS version for tests (low)" + - name: "Composer: downgrade PHPCS dependencies for tests (lowest) (with ignore platform)" if: ${{ matrix.cs_dependencies == 'lowest' }} - run: composer update squizlabs/php_codesniffer wp-coding-standards/wpcs --with-dependencies --prefer-lowest --ignore-platform-req=php+ --no-scripts --no-interaction + run: > + composer update --prefer-lowest --no-scripts --no-interaction --ignore-platform-req=php+ + squizlabs/php_codesniffer + wp-coding-standards/wpcs - name: Verify installed versions run: composer info --no-dev @@ -115,7 +130,7 @@ jobs: # The results of the linting will be shown inline in the PR via the CS2PR tool. # @link https://github.com/staabm/annotate-pull-request-from-checkstyle/ - name: Lint against parse errors - if: ${{ matrix.cs_dependencies == 'highest' }} + if: ${{ matrix.cs_dependencies == 'stable' }} run: composer lint -- --checkstyle | cs2pr - name: Run the unit tests - PHP 5.4 - 8.0 @@ -140,22 +155,22 @@ jobs: strategy: matrix: - # 7.4 should be updated to 8.2 when higher PHPUnit versions can be supported. + # 7.4 should be updated to 8.x when higher PHPUnit versions can be supported. php_version: ['5.4', '7.4'] - cs_dependencies: ['lowest', 'highest'] + cs_dependencies: ['lowest', 'dev'] - name: "Coverage${{ matrix.cs_dependencies == 'highest' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" + name: "Coverage${{ matrix.cs_dependencies == 'stable' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" steps: - name: Checkout code uses: actions/checkout@v3 - # On stable PHPCS versions, allow for PHP deprecation notices. + # With stable PHPCS dependencies, allow for PHP deprecation notices. # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. - name: Setup ini config id: set_ini run: | - if [[ "${{ matrix.cs_dependencies }}" != "highest" ]]; then + if [ "${{ matrix.cs_dependencies }}" != "dev" ]; then echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT else echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT @@ -169,11 +184,13 @@ jobs: coverage: xdebug tools: cs2pr - - name: 'Composer: adjust CS dependencies (high)' - if: ${{ matrix.cs_dependencies == 'highest' }} - run: | - composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ env.PHPCS_HIGHEST }}" --no-interaction - composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ env.WPCS_HIGHEST }}" --no-interaction + - name: "Composer: set PHPCS dependencies for tests (dev)" + if: ${{ matrix.cs_dependencies == 'dev' }} + # Note: while WPCS 3.0.0 is not yet supported, WPCS will not be updated for the `dev` builds. + # Once YoastCS has been updated for WPCS 3.0.0, WPCS dev-develop should be added to this command. + run: > + composer require --no-update --no-scripts --no-interaction + squizlabs/php_codesniffer:"${{ env.PHPCS_HIGHEST }}" # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies @@ -183,9 +200,12 @@ jobs: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") - - name: "Composer: set PHPCS version for tests (low)" + - name: "Composer: downgrade PHPCS dependencies for tests (lowest)" if: ${{ matrix.cs_dependencies == 'lowest' }} - run: composer update squizlabs/php_codesniffer wp-coding-standards/wpcs --with-dependencies --prefer-lowest --ignore-platform-req=php+ --no-scripts --no-interaction + run: > + composer update --prefer-lowest --no-scripts --no-interaction + squizlabs/php_codesniffer + wp-coding-standards/wpcs - name: Verify installed versions run: composer info --no-dev @@ -196,7 +216,7 @@ jobs: # The results of the linting will be shown inline in the PR via the CS2PR tool. # @link https://github.com/staabm/annotate-pull-request-from-checkstyle/ - name: Lint against parse errors - if: ${{ matrix.cs_dependencies == 'highest' }} + if: ${{ matrix.cs_dependencies == 'dev' }} run: composer lint -- --checkstyle | cs2pr - name: Run the unit tests with code coverage From dbf537a2334843f120d7799424e5170bacb1a2a0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Sep 2023 04:12:11 +0200 Subject: [PATCH 029/212] Composer: add PHPCSExtra Add PHPCSExtra as a new dependency. PHPCSExtra is an external standard for PHP_CodeSniffer and is also a requirement for WPCS 3.0.0. The reason to add it as a direct requirement for YoastCS is that we'll not only be using sniffs from PHPCSExtra via WPCS, but also want to use additional sniffs which PHPCSExtra has to offer, which makes it not just a requirement of WPCS, but also a requirement of YoastCS. Ref: https://github.com/PHPCSStandards/PHPCSExtra --- .github/workflows/basics.yml | 1 + README.md | 1 + composer.json | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 966155b8..2ef7146a 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -54,6 +54,7 @@ jobs: run: > composer require --no-update --no-scripts --no-interaction squizlabs/php_codesniffer:"dev-master" + phpcsstandards/phpcsextra:"dev-develop" # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies diff --git a/README.md b/README.md index a9992f40..cacf2234 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ The `Yoast` standard for PHP_CodeSniffer is comprised of the following: * The `WordPress` ruleset from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) implementing the official [WordPress PHP Coding Standards](https://make.wordpress.org/core/handbook/coding-standards/php/), with some [select exclusions](https://github.com/Yoast/yoastcs/blob/develop/Yoast/ruleset.xml#L29-L75). * The [`PHPCompatibilityWP`](https://github.com/PHPCompatibility/PHPCompatibilityWP) ruleset which checks code for PHP cross-version compatibility while preventing false positives for functionality polyfilled within WordPress. * Select additional sniffs taken from [`PHP_CodeSniffer`](https://github.com/squizlabs/PHP_CodeSniffer). +* Select additional sniffs taken from [`PHPCSExtra`](https://github.com/PHPCSStandards/PHPCSExtra). * A number of custom Yoast specific sniffs. Files within version management and dependency related directories, such as the Composer `vendor` directory, are excluded from the scans by default. diff --git a/composer.json b/composer.json index 66b237ba..3874d100 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/phpcompatibility-wp": "^2.1.4", + "phpcsstandards/phpcsextra": "^1.1.2", "squizlabs/php_codesniffer": "^3.7.2", "wp-coding-standards/wpcs": "^2.3.0" }, From b2f1773e43ce95a32a87f48b03b6677e6826edad Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Sep 2023 04:41:07 +0200 Subject: [PATCH 030/212] YoastCS: remove `IfElseDeclaration` sniff in favour of similar from PHPCSExtra This commit removes the YoastCS native `Yoast.ControlStructures.IfElseDeclaration` sniff in favour of a similar sniff from PHPCSExtra. The PHPCSExtra `Universal.ControlStructures.IfElseDeclaration` sniff includes a fixer, making it more powerful than the YoastCS sniff. --- .../IfElseDeclarationStandard.xml | 59 ------------ .../IfElseDeclarationSniff.php | 89 ------------------- .../IfElseDeclarationUnitTest.inc | 86 ------------------ .../IfElseDeclarationUnitTest.php | 46 ---------- Yoast/ruleset.xml | 4 + 5 files changed, 4 insertions(+), 280 deletions(-) delete mode 100644 Yoast/Docs/ControlStructures/IfElseDeclarationStandard.xml delete mode 100644 Yoast/Sniffs/ControlStructures/IfElseDeclarationSniff.php delete mode 100644 Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.inc delete mode 100644 Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.php diff --git a/Yoast/Docs/ControlStructures/IfElseDeclarationStandard.xml b/Yoast/Docs/ControlStructures/IfElseDeclarationStandard.xml deleted file mode 100644 index c2f9b211..00000000 --- a/Yoast/Docs/ControlStructures/IfElseDeclarationStandard.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - -elseif ($bar) { - $var = 2; -} - ]]> - - - elseif ($bar) { - $var = 2; -} - ]]> - - - - - - - - elseif ($bar) { - $var = 2; -} - ]]> - - - else { - $var = 2; - } - ]]> - - - diff --git a/Yoast/Sniffs/ControlStructures/IfElseDeclarationSniff.php b/Yoast/Sniffs/ControlStructures/IfElseDeclarationSniff.php deleted file mode 100644 index 78476b86..00000000 --- a/Yoast/Sniffs/ControlStructures/IfElseDeclarationSniff.php +++ /dev/null @@ -1,89 +0,0 @@ -getTokens(); - - if ( isset( $tokens[ $stackPtr ]['scope_opener'] ) ) { - $scope_open = $tokens[ $stackPtr ]['scope_opener']; - } - else { - // Deal with "else if". - $next = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); - if ( $tokens[ $next ]['code'] === \T_IF && isset( $tokens[ $next ]['scope_opener'] ) ) { - $scope_open = $tokens[ $next ]['scope_opener']; - } - } - - if ( ! isset( $scope_open ) || $tokens[ $scope_open ]['code'] === \T_COLON ) { - // No scope opener found or alternative syntax (not our concern). - return; - } - - $previous_scope_closer = $phpcsFile->findPrevious( \T_CLOSE_CURLY_BRACKET, ( $stackPtr - 1 ) ); - - if ( $tokens[ $previous_scope_closer ]['line'] === $tokens[ $stackPtr ]['line'] ) { - $phpcsFile->addError( - '%s statement must be on a new line', - $stackPtr, - 'NewLine', - [ \ucfirst( $tokens[ $stackPtr ]['content'] ) ] - ); - } - elseif ( $tokens[ $previous_scope_closer ]['column'] !== $tokens[ $stackPtr ]['column'] ) { - $phpcsFile->addError( - '%s statement not aligned with previous part of the control structure', - $stackPtr, - 'Alignment', - [ \ucfirst( $tokens[ $stackPtr ]['content'] ) ] - ); - } - - $previous_non_empty = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); - - if ( $previous_scope_closer !== $previous_non_empty ) { - $error = 'Nothing but whitespace and comments allowed between closing bracket and %s statement, found "%s"'; - $data = [ - $tokens[ $stackPtr ]['content'], - \trim( $phpcsFile->getTokensAsString( ( $previous_scope_closer + 1 ), ( $stackPtr - ( $previous_scope_closer + 1 ) ) ) ), - ]; - $phpcsFile->addError( $error, $stackPtr, 'StatementFound', $data ); - } - } -} diff --git a/Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.inc b/Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.inc deleted file mode 100644 index fa42db12..00000000 --- a/Yoast/Tests/ControlStructures/IfElseDeclarationUnitTest.inc +++ /dev/null @@ -1,86 +0,0 @@ - => - */ - public function getErrorList() { - return [ - 22 => 1, - 28 => 1, - 30 => 1, - 34 => 1, - 37 => 1, - 44 => 1, - 51 => 1, - 76 => 1, - 84 => 2, - ]; - } - - /** - * Returns the lines where warnings should occur. - * - * @return array => - */ - public function getWarningList() { - return []; - } -} diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index e7db917e..72acbfb9 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -138,6 +138,10 @@ These may make it into WPCS at some point. If so, they can be removed here. ############################################################################# --> + + + + From 9ab69a90dcff6cee61b748f076590777dddf6d04 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Sep 2023 04:46:51 +0200 Subject: [PATCH 031/212] YoastCS: remove `NamespaceDeclaration` sniff in favour of similar from PHPCSExtra This commit removes the `Yoast.Namespaces.NamespaceDeclaration` sniff in favour of three sniffs from PHPCSExtra, which combined do the same thing. --- .../NamespaceDeclarationStandard.xml | 65 ----------- .../Namespaces/NamespaceDeclarationSniff.php | 110 ------------------ .../NamespaceDeclarationUnitTest.1.inc | 8 -- .../NamespaceDeclarationUnitTest.2.inc | 11 -- .../NamespaceDeclarationUnitTest.php | 49 -------- Yoast/ruleset.xml | 9 ++ 6 files changed, 9 insertions(+), 243 deletions(-) delete mode 100644 Yoast/Docs/Namespaces/NamespaceDeclarationStandard.xml delete mode 100644 Yoast/Sniffs/Namespaces/NamespaceDeclarationSniff.php delete mode 100644 Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.1.inc delete mode 100644 Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.2.inc delete mode 100644 Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.php diff --git a/Yoast/Docs/Namespaces/NamespaceDeclarationStandard.xml b/Yoast/Docs/Namespaces/NamespaceDeclarationStandard.xml deleted file mode 100644 index 2bd53679..00000000 --- a/Yoast/Docs/Namespaces/NamespaceDeclarationStandard.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - ; - ]]> - - - { - // Code. -} - ]]> - - - - - - - - - Yoast\Sub; - ]]> - - - ; - ]]> - - - - - - - - - - - - namespace Yoast\Sub\B { -} - ]]> - - - diff --git a/Yoast/Sniffs/Namespaces/NamespaceDeclarationSniff.php b/Yoast/Sniffs/Namespaces/NamespaceDeclarationSniff.php deleted file mode 100644 index 1d9179a3..00000000 --- a/Yoast/Sniffs/Namespaces/NamespaceDeclarationSniff.php +++ /dev/null @@ -1,110 +0,0 @@ -getTokens(); - - $statements = []; - - while ( ( $stackPtr = $phpcsFile->findNext( \T_NAMESPACE, ( $stackPtr + 1 ) ) ) !== false ) { - - $next_non_empty = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); - if ( $next_non_empty === false ) { - // Live coding or parse error. - break; - } - - if ( $tokens[ $next_non_empty ]['code'] === \T_NS_SEPARATOR ) { - // Not a namespace declaration, but the use of the namespace keyword as operator. - continue; - } - - // OK, found a namespace declaration. - $statements[] = $stackPtr; - - if ( isset( $tokens[ $stackPtr ]['scope_condition'] ) - && $tokens[ $stackPtr ]['scope_condition'] === $stackPtr - ) { - // Scoped namespace declaration. - $phpcsFile->addError( - 'Scoped namespace declarations are not allowed.', - $stackPtr, - 'ScopedForbidden' - ); - } - - if ( $tokens[ $next_non_empty ]['code'] === \T_SEMICOLON - || $tokens[ $next_non_empty ]['code'] === \T_OPEN_CURLY_BRACKET - ) { - // Namespace declaration without namespace name (= global namespace). - $phpcsFile->addError( - 'Namespace declarations without a namespace name are not allowed.', - $stackPtr, - 'NoNameForbidden' - ); - } - } - - $count = \count( $statements ); - if ( $count > 1 ) { - $data = [ - $count, - $tokens[ $statements[0] ]['line'], - ]; - - for ( $i = 1; $i < $count; $i++ ) { - $phpcsFile->addError( - 'There should be only one namespace declaration per file. Found %d namespace declarations. The first declaration was found on line %d', - $statements[ $i ], - 'MultipleFound', - $data - ); - } - } - - return ( $phpcsFile->numTokens + 1 ); - } -} diff --git a/Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.1.inc b/Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.1.inc deleted file mode 100644 index 0127313d..00000000 --- a/Yoast/Tests/Namespaces/NamespaceDeclarationUnitTest.1.inc +++ /dev/null @@ -1,8 +0,0 @@ - => - */ - public function getErrorList( $testFile = '' ) { - switch ( $testFile ) { - case 'NamespaceDeclarationUnitTest.2.inc': - return [ - 3 => 1, - 5 => 3, - 7 => 2, - 9 => 3, - 11 => 2, - ]; - - default: - return []; - } - } - - /** - * Returns the lines where warnings should occur. - * - * @return array => - */ - public function getWarningList() { - return []; - } -} diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 72acbfb9..82a53fc3 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -142,6 +142,15 @@ + + + + + + + + + From 820adfb9e188b0378d456aada0e788743c39775e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 16 Sep 2023 01:21:09 +0200 Subject: [PATCH 032/212] Docs: remove redundant `@package` tags `@package` tags are an arcane manner to group related files as belonging to one project. For projects using namespaces, the current recommendation is to only have `@package` tags when they have supplemental information to the namespace. As that isn't the case in YoastCS (with the exception of the non-namespaced test bootstrap file), I'm proposing to remove the `@package` tags from the YoastCS class docblocks. Along the same lines, the `@author` tag is also an outdated practice as `git blame` will work just fine, so I'm removing those too. Includes cleaning up (normalizing) the tag description alignments in the class docblocks. :point_right: reviewing with whitespace changes ignored should make it easier to see that the only real change is the removal of the package/author tags. Refs: * https://docs.phpdoc.org/3.0/guide/references/phpdoc/tags/package.html#package * https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc-tags.md#59-package --- .../Commenting/CodeCoverageIgnoreDeprecatedSniff.php | 5 +---- Yoast/Sniffs/Commenting/CoversTagSniff.php | 5 +---- Yoast/Sniffs/Commenting/FileCommentSniff.php | 5 +---- Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php | 5 +---- Yoast/Sniffs/Files/FileNameSniff.php | 4 +--- Yoast/Sniffs/Files/TestDoublesSniff.php | 4 +--- Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php | 5 +---- Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php | 5 +---- Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php | 5 +---- Yoast/Sniffs/Tools/BrainMonkeyRaceConditionSniff.php | 3 --- Yoast/Sniffs/WhiteSpace/FunctionSpacingSniff.php | 5 +---- Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php | 5 +---- .../Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php | 6 ++---- Yoast/Tests/Commenting/CoversTagUnitTest.php | 6 ++---- Yoast/Tests/Commenting/FileCommentUnitTest.php | 6 ++---- Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php | 6 ++---- Yoast/Tests/Files/FileNameUnitTest.php | 6 ++---- Yoast/Tests/Files/TestDoublesUnitTest.php | 6 ++---- Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php | 8 +++----- Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php | 6 ++---- Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php | 8 +++----- Yoast/Tests/Tools/BrainMonkeyRaceConditionUnitTest.php | 4 ++-- Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php | 6 ++---- Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php | 6 ++---- Yoast/Utils/CustomPrefixesTrait.php | 5 +---- 25 files changed, 38 insertions(+), 97 deletions(-) diff --git a/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php b/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php index 026045a2..e2bd6a03 100644 --- a/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php +++ b/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php @@ -10,10 +10,7 @@ * Verifies functions which are marked as `deprecated` have a `codeCoverageIgnore` tag * in their docblock. * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 1.1.0 + * @since 1.1.0 */ class CodeCoverageIgnoreDeprecatedSniff implements Sniff { diff --git a/Yoast/Sniffs/Commenting/CoversTagSniff.php b/Yoast/Sniffs/Commenting/CoversTagSniff.php index dfdff6c0..d41a88e1 100644 --- a/Yoast/Sniffs/Commenting/CoversTagSniff.php +++ b/Yoast/Sniffs/Commenting/CoversTagSniff.php @@ -14,10 +14,7 @@ * - there are no duplicate @coversNothing tags; * - a method does not have both a @covers as well as a @coversNothing tag. * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 1.3.0 + * @since 1.3.0 */ class CoversTagSniff implements Sniff { diff --git a/Yoast/Sniffs/Commenting/FileCommentSniff.php b/Yoast/Sniffs/Commenting/FileCommentSniff.php index fc0607d0..7e184a91 100644 --- a/Yoast/Sniffs/Commenting/FileCommentSniff.php +++ b/Yoast/Sniffs/Commenting/FileCommentSniff.php @@ -22,10 +22,7 @@ * see if it's still relevant to have this sniff and if so, if the sniff needs * adjustments.}} * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 1.2.0 + * @since 1.2.0 */ class FileCommentSniff extends Squiz_FileCommentSniff { diff --git a/Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php b/Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php index b3b102e8..5279ac7a 100644 --- a/Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php +++ b/Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php @@ -9,10 +9,7 @@ /** * Verifies that all test functions have at least one @covers tag. * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 1.3.0 + * @since 1.3.0 */ class TestsHaveCoversTagSniff implements Sniff { diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index 8fa26823..180ff23c 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -19,9 +19,7 @@ * - Files which don't contain an object structure, but do contain function declarations should * have a "-functions" suffix. * - * @package Yoast\YoastCS - * - * @since 0.5 + * @since 0.5 */ class FileNameSniff implements Sniff { diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index 7b923cd5..18584135 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -11,9 +11,7 @@ * Additionally, checks that all classes in the `doubles` directory/directories * have `Double` or `Mock` in the class name. * - * @package Yoast\YoastCS - * - * @since 1.0.0 + * @since 1.0.0 */ class TestDoublesSniff implements Sniff { diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index cd21b3d8..1badb285 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -19,10 +19,7 @@ * as well as that the levels directly translate to the (sub-)directory a file is * placed in. * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 2.0.0 + * @since 2.0.0 */ class NamespaceNameSniff implements Sniff { diff --git a/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php b/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php index 872ff12b..3b3adbd8 100644 --- a/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php +++ b/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php @@ -7,10 +7,7 @@ /** * Check the number of words in object names declared within a namespace. * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 2.0.0 + * @since 2.0.0 */ class ObjectNameDepthSniff extends WPCS_Sniff { diff --git a/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php b/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php index 7f1f5e77..7046d684 100644 --- a/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php @@ -27,10 +27,7 @@ * namespace-like prefix for hooks, the `WrongPrefix` warning should be * changed to an error and only namespace-like prefixes should be allowed.} * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 2.0.0 + * @since 2.0.0 */ class ValidHookNameSniff extends WPCS_ValidHookNameSniff { diff --git a/Yoast/Sniffs/Tools/BrainMonkeyRaceConditionSniff.php b/Yoast/Sniffs/Tools/BrainMonkeyRaceConditionSniff.php index 75cc8ff5..cbd3f98a 100644 --- a/Yoast/Sniffs/Tools/BrainMonkeyRaceConditionSniff.php +++ b/Yoast/Sniffs/Tools/BrainMonkeyRaceConditionSniff.php @@ -11,9 +11,6 @@ * @link https://github.com/Yoast/yoastcs/issues/264 * * @since 2.3.0 - * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer */ class BrainMonkeyRaceConditionSniff extends Sniff { diff --git a/Yoast/Sniffs/WhiteSpace/FunctionSpacingSniff.php b/Yoast/Sniffs/WhiteSpace/FunctionSpacingSniff.php index ecae05cc..a18ea52a 100644 --- a/Yoast/Sniffs/WhiteSpace/FunctionSpacingSniff.php +++ b/Yoast/Sniffs/WhiteSpace/FunctionSpacingSniff.php @@ -13,10 +13,7 @@ * in the global namespace as those are often wrapped in an if clause which causes * a fixer conflict. * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 1.0.0 + * @since 1.0.0 */ class FunctionSpacingSniff extends Squiz_FunctionSpacingSniff { diff --git a/Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php b/Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php index ae9c198b..13fcda4b 100644 --- a/Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php +++ b/Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php @@ -7,10 +7,7 @@ /** * Discourages the use of various functions and suggests (Yoast) alternatives. * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 1.3.0 + * @since 1.3.0 */ class AlternativeFunctionsSniff extends AbstractFunctionRestrictionsSniff { diff --git a/Yoast/Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php b/Yoast/Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php index 225d1cae..b15da4ed 100644 --- a/Yoast/Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php +++ b/Yoast/Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php @@ -7,11 +7,9 @@ /** * Unit test class for the CodeCoverageIgnoreDeprecated sniff. * - * @package Yoast\YoastCS + * @since 1.1.0 * - * @since 1.1.0 - * - * @covers YoastCS\Yoast\Sniffs\Commenting\CodeCoverageIgnoreDeprecatedSniff + * @covers YoastCS\Yoast\Sniffs\Commenting\CodeCoverageIgnoreDeprecatedSniff */ class CodeCoverageIgnoreDeprecatedUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/Commenting/CoversTagUnitTest.php b/Yoast/Tests/Commenting/CoversTagUnitTest.php index 08be552c..0c8b7eb2 100644 --- a/Yoast/Tests/Commenting/CoversTagUnitTest.php +++ b/Yoast/Tests/Commenting/CoversTagUnitTest.php @@ -7,11 +7,9 @@ /** * Unit test class for the CoversTag sniff. * - * @package Yoast\YoastCS + * @since 1.3.0 * - * @since 1.3.0 - * - * @covers YoastCS\Yoast\Sniffs\Commenting\CoversTagSniff + * @covers YoastCS\Yoast\Sniffs\Commenting\CoversTagSniff */ class CoversTagUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/Commenting/FileCommentUnitTest.php b/Yoast/Tests/Commenting/FileCommentUnitTest.php index 2b5c80cf..a852e49e 100644 --- a/Yoast/Tests/Commenting/FileCommentUnitTest.php +++ b/Yoast/Tests/Commenting/FileCommentUnitTest.php @@ -7,11 +7,9 @@ /** * Unit test class for the FileComment sniff. * - * @package Yoast\YoastCS + * @since 1.2.0 * - * @since 1.2.0 - * - * @covers YoastCS\Yoast\Sniffs\Commenting\FileCommentSniff + * @covers YoastCS\Yoast\Sniffs\Commenting\FileCommentSniff */ class FileCommentUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php b/Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php index 123a5d30..08799fd6 100644 --- a/Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php +++ b/Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php @@ -7,11 +7,9 @@ /** * Unit test class for the TestsHaveCoversTag sniff. * - * @package Yoast\YoastCS + * @since 1.3.0 * - * @since 1.3.0 - * - * @covers YoastCS\Yoast\Sniffs\Commenting\TestsHaveCoversTagSniff + * @covers YoastCS\Yoast\Sniffs\Commenting\TestsHaveCoversTagSniff */ class TestsHaveCoversTagUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/Files/FileNameUnitTest.php b/Yoast/Tests/Files/FileNameUnitTest.php index f885e01a..e86a6aef 100644 --- a/Yoast/Tests/Files/FileNameUnitTest.php +++ b/Yoast/Tests/Files/FileNameUnitTest.php @@ -8,11 +8,9 @@ /** * Unit test class for the FileName sniff. * - * @package Yoast\YoastCS + * @since 0.5 * - * @since 0.5 - * - * @covers YoastCS\Yoast\Sniffs\Files\FileNameSniff + * @covers YoastCS\Yoast\Sniffs\Files\FileNameSniff */ class FileNameUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/Files/TestDoublesUnitTest.php b/Yoast/Tests/Files/TestDoublesUnitTest.php index 9e1ca262..8db17a5e 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTest.php +++ b/Yoast/Tests/Files/TestDoublesUnitTest.php @@ -8,11 +8,9 @@ /** * Unit test class for the TestDoubles sniff. * - * @package Yoast\YoastCS + * @since 1.0.0 * - * @since 1.0.0 - * - * @covers YoastCS\Yoast\Sniffs\Files\TestDoublesSniff + * @covers YoastCS\Yoast\Sniffs\Files\TestDoublesSniff */ class TestDoublesUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index 6dee51a2..cc93efb0 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -8,12 +8,10 @@ /** * Unit test class for the NamespaceName sniff. * - * @package Yoast\YoastCS + * @since 2.0.0 * - * @since 2.0.0 - * - * @covers YoastCS\Yoast\Sniffs\NamingConventions\NamespaceNameSniff - * @covers YoastCS\Yoast\Utils\CustomPrefixesTrait + * @covers YoastCS\Yoast\Sniffs\NamingConventions\NamespaceNameSniff + * @covers YoastCS\Yoast\Utils\CustomPrefixesTrait */ class NamespaceNameUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php b/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php index b12f9bfb..cdcda9d3 100644 --- a/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php +++ b/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php @@ -7,11 +7,9 @@ /** * Unit test class for the ObjectNameDepth sniff. * - * @package Yoast\YoastCS + * @since 2.0.0 * - * @since 2.0.0 - * - * @covers YoastCS\Yoast\Sniffs\NamingConventions\ObjectNameDepthSniff + * @covers YoastCS\Yoast\Sniffs\NamingConventions\ObjectNameDepthSniff */ class ObjectNameDepthUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php b/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php index b49dd619..e0b2a6ea 100644 --- a/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php @@ -8,12 +8,10 @@ /** * Unit test class for the ValidHookName sniff. * - * @package Yoast\YoastCS + * @since 2.0.0 * - * @since 2.0.0 - * - * @covers YoastCS\Yoast\Sniffs\NamingConventions\ValidHookNameSniff - * @covers YoastCS\Yoast\Utils\CustomPrefixesTrait + * @covers YoastCS\Yoast\Sniffs\NamingConventions\ValidHookNameSniff + * @covers YoastCS\Yoast\Utils\CustomPrefixesTrait */ class ValidHookNameUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/Tools/BrainMonkeyRaceConditionUnitTest.php b/Yoast/Tests/Tools/BrainMonkeyRaceConditionUnitTest.php index 979f8969..f63631ae 100644 --- a/Yoast/Tests/Tools/BrainMonkeyRaceConditionUnitTest.php +++ b/Yoast/Tests/Tools/BrainMonkeyRaceConditionUnitTest.php @@ -7,9 +7,9 @@ /** * Unit test class for the BrainMonkeyRaceCondition sniff. * - * @package Yoast\YoastCS + * @since 2.3.0 * - * @covers YoastCS\Yoast\Sniffs\Tools\BrainMonkeyRaceConditionSniff + * @covers YoastCS\Yoast\Sniffs\Tools\BrainMonkeyRaceConditionSniff */ class BrainMonkeyRaceConditionUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php b/Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php index 228018bf..4d137635 100644 --- a/Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php +++ b/Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php @@ -7,11 +7,9 @@ /** * Unit test class for the FunctionSpacing sniff. * - * @package Yoast\YoastCS + * @since 1.0.0 * - * @since 1.0.0 - * - * @covers YoastCS\Yoast\Sniffs\WhiteSpace\FunctionSpacingSniff + * @covers YoastCS\Yoast\Sniffs\WhiteSpace\FunctionSpacingSniff */ class FunctionSpacingUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php b/Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php index 702eade0..b308657e 100644 --- a/Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php +++ b/Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php @@ -7,11 +7,9 @@ /** * Unit test class for the AlternativeFunctions sniff. * - * @package Yoast\YoastCS + * @since 1.3.0 * - * @since 1.3.0 - * - * @covers YoastCS\Yoast\Sniffs\Yoast\AlternativeFunctionsSniff + * @covers YoastCS\Yoast\Sniffs\Yoast\AlternativeFunctionsSniff */ class AlternativeFunctionsUnitTest extends AbstractSniffUnitTest { diff --git a/Yoast/Utils/CustomPrefixesTrait.php b/Yoast/Utils/CustomPrefixesTrait.php index 6b54fa07..a4b4f79e 100644 --- a/Yoast/Utils/CustomPrefixesTrait.php +++ b/Yoast/Utils/CustomPrefixesTrait.php @@ -6,10 +6,7 @@ * Trait to add a custom `$prefixes` property to sniffs and utility functions * to validate the property value. * - * @package Yoast\YoastCS - * @author Juliette Reinders Folmer - * - * @since 2.0.0 + * @since 2.0.0 */ trait CustomPrefixesTrait { From fd7d1e8488ee8eca4176272f3179291757e2b761 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 8 Jun 2022 08:21:17 +0200 Subject: [PATCH 033/212] CS/QA: make all classes final The PHPCS native autoloader which YoastCS uses, doesn't always play nice with sniffs extending other sniffs, which can lead to fatal "Class already declared" errors. With this in mind, all YoastCS classes will now be made `final`. This will now also be enforced via a sniff available from PHPCSExtra. Note: this is a breaking change! --- .phpcs.xml.dist | 3 +++ Yoast/Reports/Threshold.php | 2 +- Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php | 2 +- Yoast/Sniffs/Commenting/CoversTagSniff.php | 2 +- Yoast/Sniffs/Commenting/FileCommentSniff.php | 2 +- Yoast/Sniffs/Commenting/TestsHaveCoversTagSniff.php | 2 +- Yoast/Sniffs/Files/FileNameSniff.php | 2 +- Yoast/Sniffs/Files/TestDoublesSniff.php | 2 +- Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php | 2 +- Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php | 2 +- Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php | 2 +- Yoast/Sniffs/Tools/BrainMonkeyRaceConditionSniff.php | 2 +- Yoast/Sniffs/WhiteSpace/FunctionSpacingSniff.php | 2 +- Yoast/Sniffs/Yoast/AlternativeFunctionsSniff.php | 2 +- .../Tests/Commenting/CodeCoverageIgnoreDeprecatedUnitTest.php | 2 +- Yoast/Tests/Commenting/CoversTagUnitTest.php | 2 +- Yoast/Tests/Commenting/FileCommentUnitTest.php | 2 +- Yoast/Tests/Commenting/TestsHaveCoversTagUnitTest.php | 2 +- Yoast/Tests/Files/FileNameUnitTest.php | 2 +- Yoast/Tests/Files/TestDoublesUnitTest.php | 2 +- Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php | 2 +- Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php | 2 +- Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php | 2 +- Yoast/Tests/Tools/BrainMonkeyRaceConditionUnitTest.php | 2 +- Yoast/Tests/WhiteSpace/FunctionSpacingUnitTest.php | 2 +- Yoast/Tests/Yoast/AlternativeFunctionsUnitTest.php | 2 +- 26 files changed, 28 insertions(+), 25 deletions(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 7f70a828..d6d78813 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -70,6 +70,9 @@ + + + + + + + + + + - + @@ -76,13 +76,13 @@ - + - + - - - - - - - - - - - - @@ -192,31 +180,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -231,4 +194,16 @@ */index\.php + + + + + + 5 + + diff --git a/composer.json b/composer.json index 13539cdf..55fbc671 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "phpcsstandards/phpcsextra": "^1.1.2", "phpcsstandards/phpcsutils": "^1.0.8", "squizlabs/php_codesniffer": "^3.7.2", - "wp-coding-standards/wpcs": "^2.3.0" + "wp-coding-standards/wpcs": "^3.0.1" }, "require-dev": { "phpcompatibility/php-compatibility": "^9.3.5", From f2acb4990c8b19d5f58d3b942ed15ba947311346 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 24 Feb 2021 14:14:24 +0100 Subject: [PATCH 040/212] NamingConventions/ObjectNameDepth: account for upstream WPCS changes Update some function calls using WPCS native utilities, which still exist, but have been moved around or have a changed signature. Ref: * https://github.com/WordPress/WordPress-Coding-Standards/wiki/Upgrade-Guide-to-WordPressCS-3.0.0-for-Developers-of-external-standards --- .../Sniffs/NamingConventions/ObjectNameDepthSniff.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php b/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php index c986a17a..eb395d11 100644 --- a/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php +++ b/Yoast/Sniffs/NamingConventions/ObjectNameDepthSniff.php @@ -4,15 +4,21 @@ use PHPCSUtils\Utils\Namespaces; use PHPCSUtils\Utils\ObjectDeclarations; +use WordPressCS\WordPress\Helpers\IsUnitTestTrait; +use WordPressCS\WordPress\Helpers\SnakeCaseHelper; use WordPressCS\WordPress\Sniff as WPCS_Sniff; /** * Check the number of words in object names declared within a namespace. * * @since 2.0.0 + * + * @uses \WordPressCS\WordPress\Helpers\IsUnitTestTrait::$custom_test_classes */ final class ObjectNameDepthSniff extends WPCS_Sniff { + use IsUnitTestTrait; + /** * Maximum number of words. * @@ -88,7 +94,7 @@ public function process_token( $stackPtr ) { // Handle names which are potentially in CamelCaps. if ( \strpos( $snakecase_object_name, '_' ) === false ) { - $snakecase_object_name = self::get_snake_case_name_suggestion( $snakecase_object_name ); + $snakecase_object_name = SnakeCaseHelper::get_suggestion( $snakecase_object_name ); } $parts = \explode( '_', $snakecase_object_name ); @@ -99,7 +105,7 @@ public function process_token( $stackPtr ) { */ $last = \array_pop( $parts ); if ( isset( $this->test_suffixes[ $last ] ) ) { - if ( $this->test_suffixes[ $last ] === true && $this->is_test_class( $stackPtr ) ) { + if ( $this->test_suffixes[ $last ] === true && $this->is_test_class( $this->phpcsFile, $stackPtr ) ) { --$part_count; } else { From 54c86de5ca443b948980b37ca13ed0c8bdf1ee06 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 16 Sep 2023 01:26:31 +0200 Subject: [PATCH 041/212] NamingConventions/ValidHookName::transform(): sync parameter names The parameter name of the upstream method was updated in WPCS 3.0.0 to not overlap with a reserved keyword. And for potential future support of function calls to sniff methods using named parameters, the signature of a method in a child class overloading a method in a parent class should be kept in sync. This makes it so for the `NamingConventions/ValidHookName::transform()` method. --- .../NamingConventions/ValidHookNameSniff.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php b/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php index ea6149c5..94e9e4a6 100644 --- a/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php @@ -167,33 +167,33 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p * plugin prefix for hook names and remembers whether a prefix was found to allow * checking whether it was the correct one. * - * @param string $string The target string. + * @param string $text_string The target string. * @param string $regex The punctuation regular expression to use. * @param string $transform_type Whether to do a partial or complete transform. * Valid values are: 'full', 'case', 'punctuation'. * @return string */ - protected function transform( $string, $regex, $transform_type = 'full' ) { + protected function transform( $text_string, $regex, $transform_type = 'full' ) { if ( empty( $this->validated_prefixes ) ) { - return parent::transform( $string, $regex, $transform_type ); + return parent::transform( $text_string, $regex, $transform_type ); } if ( $this->first_string === '' ) { - $this->first_string = $string; + $this->first_string = $text_string; } // Not the first text string. - if ( $string !== $this->first_string ) { - return parent::transform( $string, $regex, $transform_type ); + if ( $text_string !== $this->first_string ) { + return parent::transform( $text_string, $regex, $transform_type ); } // Repeated call for the first text string. if ( $this->found_prefix !== '' ) { - $string = \substr( $string, \strlen( $this->found_prefix ) ); + $text_string = \substr( $text_string, \strlen( $this->found_prefix ) ); } - return $this->found_prefix . parent::transform( $string, $regex, $transform_type ); + return $this->found_prefix . parent::transform( $text_string, $regex, $transform_type ); } /** From 042b77021814286c2d3e60ef260ffb2cce2ca419 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 16 Sep 2023 01:24:22 +0200 Subject: [PATCH 042/212] CS: minor tweaks to comply with WordPressCS 3.0 --- Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php | 1 - Yoast/Sniffs/Files/TestDoublesSniff.php | 1 - Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php | 1 - Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php | 1 - 4 files changed, 4 deletions(-) diff --git a/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php b/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php index 02ac32af..3b63360f 100644 --- a/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php +++ b/Yoast/Sniffs/Commenting/CodeCoverageIgnoreDeprecatedSniff.php @@ -55,7 +55,6 @@ public function process( File $phpcsFile, $stackPtr ) { break; } - if ( $tokens[ $commentEnd ]['code'] !== \T_DOC_COMMENT_CLOSE_TAG ) { // Function without (proper) docblock. Not our concern. return; diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index 2171de2a..93ddd394 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -134,7 +134,6 @@ public function process( File $phpcsFile, $stackPtr ) { $name_contains_double_or_mock = true; } - if ( empty( $this->target_paths ) === true ) { if ( $name_contains_double_or_mock === true ) { // No valid target paths found. diff --git a/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php b/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php index c883fe63..9b1150b8 100644 --- a/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php +++ b/Yoast/Tests/NamingConventions/ObjectNameDepthUnitTest.php @@ -68,4 +68,3 @@ public function getWarningList( $testFile = '' ) { } } } - diff --git a/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php b/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php index b5e01d0c..18d6d8fa 100644 --- a/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/ValidHookNameUnitTest.php @@ -82,4 +82,3 @@ public function getWarningList() { ]; } } - From 757c33a1a7022b3c7abc618e5c379b907ea3e18e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Sep 2023 06:30:43 +0200 Subject: [PATCH 043/212] Drop support for PHP < 7.2 The majority of the Yoast codebases now have a minimum of PHP 7.2. Additionally, the YoastCS checks should give the same results independently of the PHP version on which the CS check is being run, which means that generally speaking, the CS check is run on a high PHP version, both locally as well as in CI. With this in mind, there is no reason to keep the YoastCS minimum supported PHP version at PHP 5.4, so let's bring it in line with the other Yoast codebases and let it have a PHP 7.2 minimum. Note: PHP_CodeSniffer itself still has a PHP 5.4 minimum. PHPCS 4.x is expected to have a PHP 7.2 minimum and this change is in line with that. --- .github/workflows/quicktest.yml | 4 ++-- .github/workflows/test.yml | 10 ++++------ .phpcs.xml.dist | 6 ------ composer.json | 6 +++--- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index 4e84aaee..cc479486 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -25,7 +25,7 @@ jobs: strategy: matrix: - php_version: ['5.4', 'latest'] + php_version: ['7.2', 'latest'] cs_dependencies: ['lowest', 'stable'] include: @@ -96,7 +96,7 @@ jobs: if: matrix.cs_dependencies == 'stable' run: composer lint - - name: Run the unit tests - PHP 5.4 - 8.0 + - name: Run the unit tests - PHP 7.2 - 8.0 if: ${{ matrix.php_version < '8.1' && matrix.php_version != 'latest' }} run: composer test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ab3116a..d4bda001 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,19 +37,17 @@ jobs: # @link https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix # # The matrix is set up so as not to duplicate the builds which are run for code coverage. - php_version: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '8.0', '8.1', '8.2'] + php_version: ['7.3', '8.0', '8.1', '8.2'] cs_dependencies: ['lowest', 'stable'] include: # Make the matrix complete (when combined with the code coverage builds). - - php_version: '5.4' + - php_version: '7.2' cs_dependencies: 'stable' - php_version: '7.4' cs_dependencies: 'stable' # Test against dev versions of all CS dependencies with select PHP versions for early detection of issues. - - php_version: '7.0' - cs_dependencies: 'dev' - php_version: '8.0' cs_dependencies: 'dev' - php_version: '8.2' @@ -132,7 +130,7 @@ jobs: if: ${{ matrix.cs_dependencies == 'stable' }} run: composer lint -- --checkstyle | cs2pr - - name: Run the unit tests - PHP 5.4 - 8.0 + - name: Run the unit tests - PHP 7.2 - 8.0 if: ${{ matrix.php_version < '8.1' }} run: composer test @@ -155,7 +153,7 @@ jobs: strategy: matrix: # 7.4 should be updated to 8.x when higher PHPUnit versions can be supported. - php_version: ['5.4', '7.4'] + php_version: ['7.2', '7.4'] cs_dependencies: ['lowest', 'dev'] name: "Coverage${{ matrix.cs_dependencies == 'stable' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index a21da6ec..5e48d56e 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -37,9 +37,6 @@ ############################################################################# --> - - - @@ -57,9 +54,6 @@ - - - +phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast + + +phpcs:set Yoast.Files.FileName excluded_files_strict_check[] enums/excluded-enum.inc + + +phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast + + +phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast + + +phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast + + +phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast + + Date: Sun, 22 Oct 2023 08:30:31 +0200 Subject: [PATCH 121/212] Files/FileName: bug fix - excluded paths were not always normalized correctly The `is_file_excluded()` method would first call the `clean_custom_array_property()` method with the `$flip` parameter set to `true`, resulting in an array containing the excluded files as the keys. After that it would call the `normalize_directory_separators()` method on all _values_ via an `array_map()`. In effect, this means that the actual values, which are stored in the array _keys_ would still not be normalized, meaning the "is file excluded" check could return the wrong result for ruleset provided paths containing a backslash directory separator. Even though, in practice, this didn't lead to problems as the paths in rulesets are set using forward slashes, it is still a bug. Fixed now by no longer "flipping" the array. And as the `$flip` parameter of the `clean_custom_array_property()` method is now no longer used, the parameter and its handling has been removed from the function. Includes test. --- Yoast/Sniffs/Files/FileNameSniff.php | 15 ++++----------- Yoast/Tests/Files/FileNameUnitTest.php | 1 + .../classes/excluded-backslash-file.inc | 8 ++++++++ 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 Yoast/Tests/Files/FileNameUnitTests/classes/excluded-backslash-file.inc diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index 4e1f7ec2..d6c5b9b1 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -234,7 +234,7 @@ public function process( File $phpcsFile, $stackPtr ) { * @return bool */ private function is_file_excluded( File $phpcsFile, $path_to_file ) { - $exclude = $this->clean_custom_array_property( $this->excluded_files_strict_check, true, true ); + $exclude = $this->clean_custom_array_property( $this->excluded_files_strict_check, true ); if ( empty( $exclude ) ) { return false; @@ -258,7 +258,7 @@ private function is_file_excluded( File $phpcsFile, $path_to_file ) { // Lowercase the filename to not interfere with the lowercase/dashes rule. $path_to_file = \strtolower( \ltrim( $path_to_file, '/' ) ); - return isset( $exclude[ $path_to_file ] ); + return \in_array( $path_to_file, $exclude, true ); } /** @@ -268,25 +268,18 @@ private function is_file_excluded( File $phpcsFile, $path_to_file ) { * - Remove whitespace surrounding values. * - Remove empty array entries. * - * Optionally flips the array to allow for using `isset` instead of `in_array`. - * * @param array $property The current property value. - * @param bool $flip Whether to flip the array values to keys. * @param bool $to_lower Whether to lowercase the array values. * - * @return array|array + * @return array */ - private function clean_custom_array_property( $property, $flip = false, $to_lower = false ) { + private function clean_custom_array_property( $property, $to_lower = false ) { $property = \array_filter( \array_map( 'trim', $property ) ); if ( $to_lower === true ) { $property = \array_map( 'strtolower', $property ); } - if ( $flip === true ) { - $property = \array_fill_keys( $property, false ); - } - return $property; } diff --git a/Yoast/Tests/Files/FileNameUnitTest.php b/Yoast/Tests/Files/FileNameUnitTest.php index bda16a37..277190ae 100644 --- a/Yoast/Tests/Files/FileNameUnitTest.php +++ b/Yoast/Tests/Files/FileNameUnitTest.php @@ -54,6 +54,7 @@ final class FileNameUnitTest extends AbstractSniffUnitTest { 'yoast.inc' => 0, // Class name = prefix, so there would be nothing left otherwise. 'class-wpseo-some-class.inc' => 1, // Prefixes 'class' and 'wpseo' not necessary. 'excluded-CLASS-file.inc' => 1, // Lowercase expected. + 'excluded-backslash-file.inc' => 0, // Interface file names. 'outline-something-interface.inc' => 0, diff --git a/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-backslash-file.inc b/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-backslash-file.inc new file mode 100644 index 00000000..71c95b11 --- /dev/null +++ b/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-backslash-file.inc @@ -0,0 +1,8 @@ + +phpcs:set Yoast.Files.FileName excluded_files_strict_check[] classes\excluded-backslash-file.inc + + Date: Sun, 22 Oct 2023 09:36:15 +0200 Subject: [PATCH 122/212] Files/FileName: simplification in excluded file comparison There is no need to lowercase the "excluded files" + the file name for the `is_file_excluded()` check. Checking these in their original, proper case should be just fine. And as the `$to_lower` parameter of the `clean_custom_array_property()` method is now no longer used, the parameter and its handling has been removed from the function. Includes additional test safeguarding that the comparison is done case-sensitively. --- Yoast/Sniffs/Files/FileNameSniff.php | 16 ++++------------ Yoast/Tests/Files/FileNameUnitTest.php | 1 + .../classes/excluded-class-wrong-case.inc | 8 ++++++++ 3 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 Yoast/Tests/Files/FileNameUnitTests/classes/excluded-class-wrong-case.inc diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index d6c5b9b1..a8337a03 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -234,7 +234,7 @@ public function process( File $phpcsFile, $stackPtr ) { * @return bool */ private function is_file_excluded( File $phpcsFile, $path_to_file ) { - $exclude = $this->clean_custom_array_property( $this->excluded_files_strict_check, true ); + $exclude = $this->clean_custom_array_property( $this->excluded_files_strict_check ); if ( empty( $exclude ) ) { return false; @@ -255,8 +255,7 @@ private function is_file_excluded( File $phpcsFile, $path_to_file ) { $path_to_file = Common::stripBasepath( $path_to_file, $base_path ); } - // Lowercase the filename to not interfere with the lowercase/dashes rule. - $path_to_file = \strtolower( \ltrim( $path_to_file, '/' ) ); + $path_to_file = \ltrim( $path_to_file, '/' ); return \in_array( $path_to_file, $exclude, true ); } @@ -269,18 +268,11 @@ private function is_file_excluded( File $phpcsFile, $path_to_file ) { * - Remove empty array entries. * * @param array $property The current property value. - * @param bool $to_lower Whether to lowercase the array values. * * @return array */ - private function clean_custom_array_property( $property, $to_lower = false ) { - $property = \array_filter( \array_map( 'trim', $property ) ); - - if ( $to_lower === true ) { - $property = \array_map( 'strtolower', $property ); - } - - return $property; + private function clean_custom_array_property( $property ) { + return \array_filter( \array_map( 'trim', $property ) ); } /** diff --git a/Yoast/Tests/Files/FileNameUnitTest.php b/Yoast/Tests/Files/FileNameUnitTest.php index 277190ae..69408edd 100644 --- a/Yoast/Tests/Files/FileNameUnitTest.php +++ b/Yoast/Tests/Files/FileNameUnitTest.php @@ -55,6 +55,7 @@ final class FileNameUnitTest extends AbstractSniffUnitTest { 'class-wpseo-some-class.inc' => 1, // Prefixes 'class' and 'wpseo' not necessary. 'excluded-CLASS-file.inc' => 1, // Lowercase expected. 'excluded-backslash-file.inc' => 0, + 'excluded-class-wrong-case.inc' => 1, // Filename not in line with class name. File not excluded due to wrong case used. // Interface file names. 'outline-something-interface.inc' => 0, diff --git a/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-class-wrong-case.inc b/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-class-wrong-case.inc new file mode 100644 index 00000000..433fd45e --- /dev/null +++ b/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-class-wrong-case.inc @@ -0,0 +1,8 @@ + +phpcs:set Yoast.Files.FileName excluded_files_strict_check[] classes/excluded-Class-wrong-Case.inc + + Date: Sun, 22 Oct 2023 08:09:12 +0200 Subject: [PATCH 123/212] Files/FileName: only clean up the OO prefixes when needed Efficiency tweak. Cleaning the ruleset passed OO prefixes doesn't need to be done time and again every single time this sniff is called. For a normal PHPCS run, where the property is set in a ruleset, doing it once and reusing the cleaned up version in subsequent calls to the sniff will be sufficient. For test runs, this may need to be done more often, but that can be handled by storing the previous value of `$oo_prefixes` property and checking if it has changed before doing the validation. This commit implements this. --- Yoast/Sniffs/Files/FileNameSniff.php | 48 +++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index a8337a03..63a84f41 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -73,6 +73,22 @@ final class FileNameSniff implements Sniff { */ public $excluded_files_strict_check = []; + /** + * Cache of previously set OO prefixes. + * + * Prevents having to do the same prefix validation over and over again. + * + * @var array + */ + private $previous_oo_prefixes = []; + + /** + * Validated & cleaned up OO set prefixes. + * + * @var array + */ + private $clean_oo_prefixes = []; + /** * Returns an array of tokens this test wants to listen for. * @@ -153,11 +169,9 @@ public function process( File $phpcsFile, $stackPtr ) { $oo_name = ObjectDeclarations::getName( $phpcsFile, $oo_structure ); if ( ! empty( $oo_name ) ) { - $prefixes = $this->clean_custom_array_property( $this->oo_prefixes ); - if ( ! empty( $prefixes ) ) { - // Use reverse natural sorting to get the longest of overlapping prefixes first. - \rsort( $prefixes, ( \SORT_NATURAL | \SORT_FLAG_CASE ) ); - foreach ( $prefixes as $prefix ) { + $this->validate_oo_prefixes(); + if ( ! empty( $this->clean_oo_prefixes ) ) { + foreach ( $this->clean_oo_prefixes as $prefix ) { if ( $oo_name !== $prefix && \stripos( $oo_name, $prefix ) === 0 ) { $oo_name = \substr( $oo_name, \strlen( $prefix ) ); $oo_name = \ltrim( $oo_name, '_-' ); @@ -285,4 +299,28 @@ private function clean_custom_array_property( $property ) { private function normalize_directory_separators( $path ) { return \ltrim( \strtr( $path, '\\', '/' ), '/' ); } + + /** + * Validate and sort the OO prefixes passed from a custom ruleset. + * + * This will only need to be done once in a normal PHPCS run, though for + * tests the function may be called multiple times. + * + * @return void + */ + private function validate_oo_prefixes() { + if ( $this->previous_oo_prefixes === $this->oo_prefixes ) { + return; + } + + // Set the cache *before* validation so as to not break the above compare. + $this->previous_oo_prefixes = $this->oo_prefixes; + + $this->clean_oo_prefixes = $this->clean_custom_array_property( $this->oo_prefixes ); + + if ( ! empty( $this->clean_oo_prefixes ) ) { + // Use reverse natural sorting to get the longest of overlapping prefixes first. + \rsort( $this->clean_oo_prefixes, ( \SORT_NATURAL | \SORT_FLAG_CASE ) ); + } + } } From a0e1125647d95a86327a0112241a2fda6334c908 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 22 Oct 2023 10:04:10 +0200 Subject: [PATCH 124/212] Files/FileName: only clean up the excluded files when needed Efficiency tweak. Cleaning the ruleset passed "excluded files" doesn't need to be done time and again every single time this sniff is called. For a normal PHPCS run, where the property is set in a ruleset, doing it once and reusing the cleaned up version in subsequent calls to the sniff will be sufficient. For test runs, this may need to be done more often, but that can be handled by storing the previous value of `$excluded_files_strict_check` property and checking if it has changed before doing the validation. This commit implements this. Additionally it adds some extra safeguards for incorrectly passed "excluded files" paths. Includes removing the `$phpcsFile` parameter from the `is_file_excluded()` method as it is no longer needed by that method. Includes additional unit tests. Includes updating a pre-existing test to pass duplicate excluded files in different ways. --- Yoast/Sniffs/Files/FileNameSniff.php | 104 ++++++++++++++---- Yoast/Tests/Files/FileNameUnitTest.php | 3 + .../classes/excluded-dot-prefixed.inc | 8 ++ .../classes/excluded-illegal.inc | 8 ++ .../classes/excluded-multiple.inc | 8 ++ .../Files/FileNameUnitTests/excluded-file.inc | 2 +- 6 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 Yoast/Tests/Files/FileNameUnitTests/classes/excluded-dot-prefixed.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/classes/excluded-illegal.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/classes/excluded-multiple.inc diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index 63a84f41..e7f5c66d 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -4,7 +4,6 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Common; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\ObjectDeclarations; use PHPCSUtils\Utils\TextStrings; @@ -89,6 +88,22 @@ final class FileNameSniff implements Sniff { */ private $clean_oo_prefixes = []; + /** + * Cache of previously set list of excluded files. + * + * Prevents having to do the same file validation over and over again. + * + * @var array + */ + private $previous_excluded_files = []; + + /** + * Validated & cleaned up list of absolute paths to the excluded files. + * + * @var array Key is the path, value irrelevant. + */ + private $validated_excluded_files = []; + /** * Returns an array of tokens this test wants to listen for. * @@ -248,30 +263,15 @@ public function process( File $phpcsFile, $stackPtr ) { * @return bool */ private function is_file_excluded( File $phpcsFile, $path_to_file ) { - $exclude = $this->clean_custom_array_property( $this->excluded_files_strict_check ); - - if ( empty( $exclude ) ) { + $this->validate_excluded_files( $phpcsFile ); + if ( empty( $this->validated_excluded_files ) ) { return false; } - $exclude = \array_map( [ $this, 'normalize_directory_separators' ], $exclude ); $path_to_file = $this->normalize_directory_separators( $path_to_file ); - - if ( ! isset( $phpcsFile->config->basepath ) ) { - $phpcsFile->addWarning( - 'For the exclude property to work with relative file path files, the --basepath needs to be set.', - 0, - 'MissingBasePath' - ); - } - else { - $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); - $path_to_file = Common::stripBasepath( $path_to_file, $base_path ); - } - $path_to_file = \ltrim( $path_to_file, '/' ); - return \in_array( $path_to_file, $exclude, true ); + return isset( $this->validated_excluded_files[ $path_to_file ] ); } /** @@ -323,4 +323,70 @@ private function validate_oo_prefixes() { \rsort( $this->clean_oo_prefixes, ( \SORT_NATURAL | \SORT_FLAG_CASE ) ); } } + + /** + * Validate the list of excluded files passed from a custom ruleset. + * + * This will only need to be done once in a normal PHPCS run, though for + * tests the function may be called multiple times. + * + * @param File $phpcsFile The file being scanned. + * + * @return void + */ + private function validate_excluded_files( $phpcsFile ) { + // The basepath check needs to be done first as otherwise the previous/current comparison would be broken. + if ( ! isset( $phpcsFile->config->basepath ) ) { + $phpcsFile->addWarning( + 'For the exclude property to work with relative file path files, the --basepath needs to be set.', + 0, + 'MissingBasePath' + ); + + // Only relevant for the tests: make sure previously set validated paths are cleared out. + $this->validated_excluded_files = []; + + // No use continuing as we can't turn relative paths into absolute paths. + return; + } + + if ( $this->previous_excluded_files === $this->excluded_files_strict_check ) { + return; + } + + // Set the cache *before* validation so as to not break the above compare. + $this->previous_excluded_files = $this->excluded_files_strict_check; + + // Reset a potentially previous set validated value. + $this->validated_excluded_files = []; + + $exclude = $this->clean_custom_array_property( $this->excluded_files_strict_check ); + if ( empty( $exclude ) ) { + return; + } + + $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); + $exclude = \array_map( [ $this, 'normalize_directory_separators' ], $exclude ); + + foreach ( $exclude as $relative ) { + if ( \strpos( $relative, '..' ) !== false ) { + // Ignore paths containing path walking. + continue; + } + + if ( \strpos( $relative, './' ) === 0 ) { + $relative = \substr( $relative, 2 ); + } + + /* + * Note: no need to check if the file really exists. We'll be doing a literal absolute path comparison, + * so if the file doesn't exist, it will never match. + */ + $this->validated_excluded_files[] = $base_path . '/' . $relative; + } + + if ( ! empty( $this->validated_excluded_files ) ) { + $this->validated_excluded_files = \array_flip( $this->validated_excluded_files ); + } + } } diff --git a/Yoast/Tests/Files/FileNameUnitTest.php b/Yoast/Tests/Files/FileNameUnitTest.php index 69408edd..4711af0e 100644 --- a/Yoast/Tests/Files/FileNameUnitTest.php +++ b/Yoast/Tests/Files/FileNameUnitTest.php @@ -56,6 +56,9 @@ final class FileNameUnitTest extends AbstractSniffUnitTest { 'excluded-CLASS-file.inc' => 1, // Lowercase expected. 'excluded-backslash-file.inc' => 0, 'excluded-class-wrong-case.inc' => 1, // Filename not in line with class name. File not excluded due to wrong case used. + 'excluded-illegal.inc' => 1, // Filename not in line with class name. File not excluded due to illegal path setting. + 'excluded-multiple.inc' => 0, + 'excluded-dot-prefixed.inc' => 0, // Interface file names. 'outline-something-interface.inc' => 0, diff --git a/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-dot-prefixed.inc b/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-dot-prefixed.inc new file mode 100644 index 00000000..4282178b --- /dev/null +++ b/Yoast/Tests/Files/FileNameUnitTests/classes/excluded-dot-prefixed.inc @@ -0,0 +1,8 @@ + +phpcs:set Yoast.Files.FileName excluded_files_strict_check[] ./classes/excluded-dot-prefixed.inc + + +phpcs:set Yoast.Files.FileName excluded_files_strict_check[] classes/../classes/excluded-illegal.inc + + +phpcs:set Yoast.Files.FileName excluded_files_strict_check[] classes/excluded-CLASS-file.inc,classes/excluded-multiple.inc + + -phpcs:set Yoast.Files.FileName excluded_files_strict_check[] excluded-file.inc +phpcs:set Yoast.Files.FileName excluded_files_strict_check[] excluded-file.inc,./excluded-file.inc,/excluded-file.inc Date: Mon, 23 Oct 2023 06:04:22 +0200 Subject: [PATCH 125/212] Files/FileName: only throw the "missing basepath" warning once No need to throw this for every single file. Throwing it once should be sufficient. Includes moving the check for the missing basepath to earlier in the process flow. --- Yoast/Sniffs/Files/FileNameSniff.php | 40 +++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index e7f5c66d..e139f2b4 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -104,6 +104,15 @@ final class FileNameSniff implements Sniff { */ private $validated_excluded_files = []; + /** + * Track if the "missing basepath" warning has been thrown. + * + * This prevents this warning potentially being thrown for every single file in a PHPCS run. + * + * @var bool + */ + private $basepath_warning_thrown = false; + /** * Returns an array of tokens this test wants to listen for. * @@ -177,6 +186,10 @@ public function process( File $phpcsFile, $stackPtr ) { $error_code = 'NotHyphenatedLowercase'; $expected = \strtolower( \preg_replace( '`[[:punct:]]`', '-', $file_name ) ); + if ( ! isset( $phpcsFile->config->basepath ) ) { + $this->add_missing_basepath_warning( $phpcsFile ); + } + if ( $this->is_file_excluded( $phpcsFile, $file ) === false ) { $oo_structure = $phpcsFile->findNext( self::NAMED_OO_TOKENS, $stackPtr ); if ( $oo_structure !== false ) { @@ -337,12 +350,6 @@ private function validate_oo_prefixes() { private function validate_excluded_files( $phpcsFile ) { // The basepath check needs to be done first as otherwise the previous/current comparison would be broken. if ( ! isset( $phpcsFile->config->basepath ) ) { - $phpcsFile->addWarning( - 'For the exclude property to work with relative file path files, the --basepath needs to be set.', - 0, - 'MissingBasePath' - ); - // Only relevant for the tests: make sure previously set validated paths are cleared out. $this->validated_excluded_files = []; @@ -389,4 +396,25 @@ private function validate_excluded_files( $phpcsFile ) { $this->validated_excluded_files = \array_flip( $this->validated_excluded_files ); } } + + /** + * Throw a warning if the basepath is missing (and this warning hasn't been thrown before). + * + * @param File $phpcsFile The file being scanned. + * + * @return void + */ + private function add_missing_basepath_warning( File $phpcsFile ) { + if ( $this->basepath_warning_thrown === true ) { + return; + } + + $phpcsFile->addWarning( + 'For the exclude property to work with relative file path files, the --basepath needs to be set.', + 0, + 'MissingBasePath' + ); + + $this->basepath_warning_thrown = true; + } } From b94cf92e91ea5453429fd5c7b381f3cc5ded3c44 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 22 Oct 2023 10:08:50 +0200 Subject: [PATCH 126/212] Files/FileName: implement use of new PathHelper and PathValidationHelper classes --- Yoast/Sniffs/Files/FileNameSniff.php | 52 +++++----------------------- 1 file changed, 8 insertions(+), 44 deletions(-) diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index e139f2b4..c08e1ac3 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -7,6 +7,8 @@ use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\ObjectDeclarations; use PHPCSUtils\Utils\TextStrings; +use YoastCS\Yoast\Utils\PathHelper; +use YoastCS\Yoast\Utils\PathValidationHelper; /** * Ensures files comply with the Yoast file name rules. @@ -100,7 +102,7 @@ final class FileNameSniff implements Sniff { /** * Validated & cleaned up list of absolute paths to the excluded files. * - * @var array Key is the path, value irrelevant. + * @var array Both the key and the value will be the same absolute path. */ private $validated_excluded_files = []; @@ -281,8 +283,7 @@ private function is_file_excluded( File $phpcsFile, $path_to_file ) { return false; } - $path_to_file = $this->normalize_directory_separators( $path_to_file ); - $path_to_file = \ltrim( $path_to_file, '/' ); + $path_to_file = PathHelper::normalize_path( $path_to_file ); return isset( $this->validated_excluded_files[ $path_to_file ] ); } @@ -302,17 +303,6 @@ private function clean_custom_array_property( $property ) { return \array_filter( \array_map( 'trim', $property ) ); } - /** - * Normalize all directory separators to be a forward slash and remove prefixed slash. - * - * @param string $path Path to normalize. - * - * @return string - */ - private function normalize_directory_separators( $path ) { - return \ltrim( \strtr( $path, '\\', '/' ), '/' ); - } - /** * Validate and sort the OO prefixes passed from a custom ruleset. * @@ -364,37 +354,11 @@ private function validate_excluded_files( $phpcsFile ) { // Set the cache *before* validation so as to not break the above compare. $this->previous_excluded_files = $this->excluded_files_strict_check; - // Reset a potentially previous set validated value. - $this->validated_excluded_files = []; - - $exclude = $this->clean_custom_array_property( $this->excluded_files_strict_check ); - if ( empty( $exclude ) ) { - return; - } + $absolute_paths = PathValidationHelper::relative_to_absolute( $phpcsFile, $this->excluded_files_strict_check ); + $absolute_paths = \array_unique( $absolute_paths ); + $absolute_paths = \array_values( $absolute_paths ); - $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); - $exclude = \array_map( [ $this, 'normalize_directory_separators' ], $exclude ); - - foreach ( $exclude as $relative ) { - if ( \strpos( $relative, '..' ) !== false ) { - // Ignore paths containing path walking. - continue; - } - - if ( \strpos( $relative, './' ) === 0 ) { - $relative = \substr( $relative, 2 ); - } - - /* - * Note: no need to check if the file really exists. We'll be doing a literal absolute path comparison, - * so if the file doesn't exist, it will never match. - */ - $this->validated_excluded_files[] = $base_path . '/' . $relative; - } - - if ( ! empty( $this->validated_excluded_files ) ) { - $this->validated_excluded_files = \array_flip( $this->validated_excluded_files ); - } + $this->validated_excluded_files = \array_combine( $absolute_paths, $absolute_paths ); } /** From 7690f3c5ec554cd52c5e1f09bda344fbf96140ec Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 20 Nov 2023 01:05:12 +0100 Subject: [PATCH 127/212] Utils/PathHelper: split `normalize_path()` method ... into a `normalize_absolute_path()` and a `normalize_relative_path()` method. Linux paths are expected to start with a leading slash and functions like `file_exists()` will not work correctly without it. So... leading slashes should not be removed from absolute paths, but should be removed from relative paths (to allow for easy concatenation). Splitting the method and using the appropriate version in all the right places should fix this. Includes updated the tests. Includes updating function calls to the method. --- Yoast/Sniffs/Files/FileNameSniff.php | 2 +- Yoast/Tests/Utils/PathHelperTest.php | 76 +++++++++++++------ .../Tests/Utils/PathValidationHelperTest.php | 2 +- Yoast/Utils/PathHelper.php | 23 ++++-- Yoast/Utils/PathValidationHelper.php | 4 +- 5 files changed, 71 insertions(+), 36 deletions(-) diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index c08e1ac3..2946d390 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -283,7 +283,7 @@ private function is_file_excluded( File $phpcsFile, $path_to_file ) { return false; } - $path_to_file = PathHelper::normalize_path( $path_to_file ); + $path_to_file = PathHelper::normalize_absolute_path( $path_to_file ); return isset( $this->validated_excluded_files[ $path_to_file ] ); } diff --git a/Yoast/Tests/Utils/PathHelperTest.php b/Yoast/Tests/Utils/PathHelperTest.php index 93c448dd..70aec34e 100644 --- a/Yoast/Tests/Utils/PathHelperTest.php +++ b/Yoast/Tests/Utils/PathHelperTest.php @@ -15,64 +15,90 @@ final class PathHelperTest extends TestCase { /** - * Test normalizing a directory path. + * Test normalizing an absolute directory path. * * @dataProvider data_normalize_path - * @covers ::normalize_path + * @covers ::normalize_absolute_path * - * @param string $input The input string. - * @param string $expected The expected function output. + * @param string $input The input string. + * @param string $exp_absolute The expected function output. * * @return void */ - public function test_normalize_path( $input, $expected ) { - $this->assertSame( $expected, PathHelper::normalize_path( $input ) ); + public function test_normalize_absolute_path( $input, $exp_absolute ) { + $this->assertSame( $exp_absolute, PathHelper::normalize_absolute_path( $input ) ); + } + + /** + * Test normalizing a relative directory path. + * + * @dataProvider data_normalize_path + * @covers ::normalize_relative_path + * + * @param string $input The input string. + * @param string $unused Unused param. + * @param string $exp_relative The expected function output. + * + * @return void + */ + public function test_normalize_relative_path( $input, $unused, $exp_relative ) { + $this->assertSame( $exp_relative, PathHelper::normalize_relative_path( $input ) ); } /** * Data provider. * - * @see test_normalize_path() For the array format. + * @see test_normalize_absolute_path() For the array format. + * @see test_normalize_relative_path() For the array format. * * @return array> */ public static function data_normalize_path() { return [ 'path is dot' => [ - 'input' => '.', - 'expected' => './', + 'input' => '.', + 'exp_absolute' => './', + 'exp_relative' => './', ], 'path containing forward slashes only with trailing slash' => [ - 'input' => 'my/path/to/', - 'expected' => 'my/path/to/', + 'input' => 'my/path/to/', + 'exp_absolute' => 'my/path/to/', + 'exp_relative' => 'my/path/to/', ], 'path containing forward slashes only without trailing slash' => [ - 'input' => 'my/path/to', - 'expected' => 'my/path/to/', + 'input' => 'my/path/to', + 'exp_absolute' => 'my/path/to/', + 'exp_relative' => 'my/path/to/', ], 'path containing forward slashes only with leading and trailing slash' => [ - 'input' => '/my/path/to/', - 'expected' => 'my/path/to/', + 'input' => '/my/path/to/', + 'exp_absolute' => '/my/path/to/', + 'exp_relative' => 'my/path/to/', ], 'path containing back-slashes only with trailing slash' => [ - 'input' => 'my\path\to\\', - 'expected' => 'my/path/to/', + 'input' => 'my\path\to\\', + 'exp_absolute' => 'my/path/to/', + 'exp_relative' => 'my/path/to/', ], 'path containing back-slashes only without trailing slash' => [ - 'input' => 'my\path\to', - 'expected' => 'my/path/to/', + 'input' => 'my\path\to', + 'exp_absolute' => 'my/path/to/', + 'exp_relative' => 'my/path/to/', ], 'path containing back-slashes only with leading, no trailing slash' => [ - 'input' => '\my\path\to', - 'expected' => 'my/path/to/', + 'input' => '\my\path\to', + 'exp_absolute' => '/my/path/to/', + 'exp_relative' => 'my/path/to/', ], 'path containing a mix of forward and backslashes with leading and trailing slash' => [ - 'input' => '/my\path/to\\', - 'expected' => 'my/path/to/', + 'input' => '/my\path/to\\', + 'exp_absolute' => '/my/path/to/', + 'exp_relative' => 'my/path/to/', ], 'path containing a mix of forward and backslashes without trailing slash' => [ - 'input' => 'my\path/to', - 'expected' => 'my/path/to/', + 'input' => 'my\path/to', + 'exp_absolute' => 'my/path/to/', + 'exp_relative' => 'my/path/to/', ], ]; } diff --git a/Yoast/Tests/Utils/PathValidationHelperTest.php b/Yoast/Tests/Utils/PathValidationHelperTest.php index c5044992..b464305a 100644 --- a/Yoast/Tests/Utils/PathValidationHelperTest.php +++ b/Yoast/Tests/Utils/PathValidationHelperTest.php @@ -26,7 +26,7 @@ final class PathValidationHelperTest extends NonSniffTestCase { * * @var string */ - private const CLEAN_BASEPATH = 'base/path/'; + private const CLEAN_BASEPATH = '/base/path/'; /** * Test converting a set of relative paths to absolute paths when no basepath is present. diff --git a/Yoast/Utils/PathHelper.php b/Yoast/Utils/PathHelper.php index 4372aac8..866d41d0 100644 --- a/Yoast/Utils/PathHelper.php +++ b/Yoast/Utils/PathHelper.php @@ -20,17 +20,26 @@ final class PathHelper { /** - * Normalize a path to forward slashes and normalize the leading/trailing slashes. + * Normalize an absolute path to forward slashes and to include a trailing slash. * - * @param string $path File or directory path. - * Both absolute as well as relative paths are accepted. + * @param string $path Absolute file or directory path. * * @return string */ - public static function normalize_path( $path ) { - $path = self::normalize_directory_separators( $path ); - $path = self::remove_leading_slash( $path ); - return self::trailingslashit( $path ); + public static function normalize_absolute_path( $path ) { + return self::trailingslashit( self::normalize_directory_separators( $path ) ); + } + + /** + * Normalize a relative path to forward slashes and normalize the leading/trailing + * slashes (no leading, yes trailing). + * + * @param string $path Relative file or directory path. + * + * @return string + */ + public static function normalize_relative_path( $path ) { + return self::remove_leading_slash( self::normalize_absolute_path( $path ) ); } /** diff --git a/Yoast/Utils/PathValidationHelper.php b/Yoast/Utils/PathValidationHelper.php index 9852c1be..20a049b7 100644 --- a/Yoast/Utils/PathValidationHelper.php +++ b/Yoast/Utils/PathValidationHelper.php @@ -41,11 +41,11 @@ public static function relative_to_absolute( File $phpcsFile, array $relative_pa return $absolute; } - $base_path = PathHelper::normalize_path( $phpcsFile->config->basepath ); + $base_path = PathHelper::normalize_absolute_path( $phpcsFile->config->basepath ); foreach ( $relative_paths as $path ) { $result_path = \trim( $path ); - $result_path = PathHelper::normalize_path( $result_path ); + $result_path = PathHelper::normalize_relative_path( $result_path ); if ( $result_path === '' ) { continue; From 0d6de1605bb14e4d9860b60246369a592b77c59e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 05:29:19 +0200 Subject: [PATCH 128/212] Files/TestDoubles: remove `$doubles_path` BC-layer The `public` `$doubles_path` property originally expected a string value. This was changed to an array in YoastCS 1.1.0, though string values were still handled via this BC-layer. Enough time has passed by now, so this BC-layer should now be removed. --- Yoast/Sniffs/Files/TestDoublesSniff.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index d3d7156c..255ba97c 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -98,15 +98,6 @@ public function process( File $phpcsFile, $stackPtr ) { return ( $phpcsFile->numTokens + 1 ); } - /* - * BC-compatibility for when the property was still a string. - * - * {@internal This should be removed in YoastCS 2.0.0.}} - */ - if ( \is_string( $this->doubles_path ) ) { - $this->doubles_path = (array) $this->doubles_path; - } - $tokens = $phpcsFile->getTokens(); $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); $base_path = \rtrim( $base_path, '/' ) . '/'; // Make sure the base_path ends in a single slash. From cee90fcfdd08ead93d48fd5621653dca63d8d535 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 20 Oct 2023 05:56:23 +0200 Subject: [PATCH 129/212] Files/TestDoubles: remove "one object per file" check This is already checked via the `Generic.Files.OneObjectStructurePerFile` sniff, which is included in the WordPress Coding Standards. No need for doubling this check here. Includes removing the associated unit tests. Includes removing the code sample related to mocks/doubles being in the same file as the test class from the XML docs. --- Yoast/Docs/Files/TestDoublesStandard.xml | 30 ------------------- Yoast/Sniffs/Files/TestDoublesSniff.php | 26 ++-------------- Yoast/Tests/Files/TestDoublesUnitTest.php | 16 ---------- .../tests/doubles/multiple-mocks-in-file.inc | 5 ---- .../multiple-objects-in-file-reverse.inc | 7 ----- .../tests/multiple-objects-in-file.inc | 7 ----- 6 files changed, 2 insertions(+), 89 deletions(-) delete mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/multiple-mocks-in-file.inc delete mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/multiple-objects-in-file-reverse.inc delete mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/multiple-objects-in-file.inc diff --git a/Yoast/Docs/Files/TestDoublesStandard.xml b/Yoast/Docs/Files/TestDoublesStandard.xml index 9dc05853..36db7a2e 100644 --- a/Yoast/Docs/Files/TestDoublesStandard.xml +++ b/Yoast/Docs/Files/TestDoublesStandard.xml @@ -9,36 +9,6 @@ The name of the dedicated sub-directory is configurable. ]]> - - - - - - - - - - - findNext( $this->register(), ( $stackPtr + 1 ) ); - if ( $more_objects_in_file === false ) { - $more_objects_in_file = $phpcsFile->findPrevious( $this->register(), ( $stackPtr - 1 ) ); - } - - if ( $more_objects_in_file !== false ) { - $data = [ - $tokens[ $stackPtr ]['content'], - $object_name, - $tokens[ $more_objects_in_file ]['content'], - ObjectDeclarations::getName( $phpcsFile, $more_objects_in_file ), - ]; - - $phpcsFile->addError( - 'Double/Mock test helper classes should be in their own file. Found %1$s: %2$s and %3$s: %4$s', - $stackPtr, - 'OneObjectPerFile', - $data - ); - } - } } /** diff --git a/Yoast/Tests/Files/TestDoublesUnitTest.php b/Yoast/Tests/Files/TestDoublesUnitTest.php index 9d9aa151..ea9bc7de 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTest.php +++ b/Yoast/Tests/Files/TestDoublesUnitTest.php @@ -64,16 +64,6 @@ public function getErrorList( string $testFile = '' ): array { 3 => 1, ]; - case 'multiple-objects-in-file.inc': - return [ - 5 => 2, - ]; - - case 'multiple-objects-in-file-reverse.inc': - return [ - 7 => 2, - ]; - case 'non-existant-doubles-dir.inc': return [ 4 => 1, @@ -100,12 +90,6 @@ public function getErrorList( string $testFile = '' ): array { 3 => 1, ]; - case 'multiple-mocks-in-file.inc': - return [ - 3 => 1, - 5 => 1, - ]; - // In tests/doubles-not-correct. case 'not-in-correct-subdir.inc': return [ diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/multiple-mocks-in-file.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/multiple-mocks-in-file.inc deleted file mode 100644 index a06a57ce..00000000 --- a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/multiple-mocks-in-file.inc +++ /dev/null @@ -1,5 +0,0 @@ - Date: Fri, 20 Oct 2023 08:34:30 +0200 Subject: [PATCH 130/212] Files/TestDoubles: add extra tests ... for a few edge cases previously not covered by tests. --- Yoast/Sniffs/Files/TestDoublesSniff.php | 2 +- Yoast/Tests/Files/TestDoublesUnitTest.php | 11 +++++++---- .../tests/doubles/correct-dir-interface-double.inc | 3 +++ .../tests/doubles/correct-dir-trait-double.inc | 3 +++ .../Files/TestDoublesUnitTests/tests/live-coding.inc | 4 ++++ .../tests/non-existant-doubles-dir-not-double.inc | 6 ++++++ .../tests/non-existant-doubles-dir.inc | 2 +- .../tests/non-existant-doubles-dirs.inc | 6 ++++++ .../tests/not-in-correct-dir-interface-double.inc | 3 +++ .../tests/not-in-correct-dir-trait-double.inc | 3 +++ 10 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-interface-double.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-trait-double.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/live-coding.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/non-existant-doubles-dir-not-double.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/non-existant-doubles-dirs.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/not-in-correct-dir-interface-double.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/not-in-correct-dir-trait-double.inc diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index c32d2481..6178d36c 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -75,7 +75,7 @@ public function process( File $phpcsFile, $stackPtr ) { $file = \preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $phpcsFile->getFileName() ); if ( $file === 'STDIN' ) { - return; + return; // @codeCoverageIgnore } if ( ! isset( $phpcsFile->config->basepath ) ) { diff --git a/Yoast/Tests/Files/TestDoublesUnitTest.php b/Yoast/Tests/Files/TestDoublesUnitTest.php index ea9bc7de..e7759c7d 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTest.php +++ b/Yoast/Tests/Files/TestDoublesUnitTest.php @@ -65,6 +65,7 @@ public function getErrorList( string $testFile = '' ): array { ]; case 'non-existant-doubles-dir.inc': + case 'non-existant-doubles-dirs.inc': return [ 4 => 1, ]; @@ -75,11 +76,9 @@ public function getErrorList( string $testFile = '' ): array { ]; case 'not-in-correct-dir-double.inc': - return [ - 3 => 1, - ]; - case 'not-in-correct-dir-mock.inc': + case 'not-in-correct-dir-interface-double.inc': + case 'not-in-correct-dir-trait-double.inc': return [ 3 => 1, ]; @@ -102,9 +101,13 @@ public function getErrorList( string $testFile = '' ): array { 4 => 1, ]; + case 'live-coding.inc': // In tests. case 'not-double-or-mock.inc': // In tests. + case 'non-existant-doubles-dir-not-double.inc': // In tests. case 'correct-dir-double.inc': // In tests/doubles. case 'correct-dir-mock.inc': // In tests/doubles. + case 'correct-dir-interface.inc': // In tests/doubles. + case 'correct-dir-trait-double.inc': // In tests/doubles. case 'correct-custom-dir.inc': // In tests/mocks. default: return []; diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-interface-double.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-interface-double.inc new file mode 100644 index 00000000..0893831b --- /dev/null +++ b/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-interface-double.inc @@ -0,0 +1,3 @@ + Date: Tue, 31 Oct 2023 03:42:57 +0100 Subject: [PATCH 131/212] Files/TestDoubles: make property `private` As the sniff class is now `final` (since PR 319), there is no need for any `protected` properties, so let's make this `private`. --- Yoast/Sniffs/Files/TestDoublesSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index 6178d36c..628bf53c 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -46,7 +46,7 @@ final class TestDoublesSniff implements Sniff { * * @var string[] */ - protected $target_paths; + private $target_paths; /** * Returns an array of tokens this test wants to listen for. From 46549f072686d79789803a6a89932aff40e52005 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 19 Oct 2023 05:47:21 +0200 Subject: [PATCH 132/212] Files/TestDoubles: minor documentation tweaks --- Yoast/Sniffs/Files/TestDoublesSniff.php | 6 +++--- Yoast/Tests/Files/TestDoublesUnitTest.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index 628bf53c..a557913e 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -34,17 +34,17 @@ final class TestDoublesSniff implements Sniff { * @since 1.1.0 The property type has changed from string to array. * Use of this property with a string value has been deprecated. * - * @var string[] + * @var array */ public $doubles_path = [ '/tests/doubles', ]; /** - * Validated absolute target paths for test double/mock classes or an empty array + * Validated absolute target paths for test fixture directories or an empty array * if the intended target directory/directories don't exist. * - * @var string[] + * @var array */ private $target_paths; diff --git a/Yoast/Tests/Files/TestDoublesUnitTest.php b/Yoast/Tests/Files/TestDoublesUnitTest.php index e7759c7d..09baf7d8 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTest.php +++ b/Yoast/Tests/Files/TestDoublesUnitTest.php @@ -35,7 +35,7 @@ public function setCliValues( $filename, $config ): void { * * @param string $testFileBase The base path that the unit tests files will have. * - * @return string[] + * @return array */ protected function getTestFiles( $testFileBase ): array { $sep = \DIRECTORY_SEPARATOR; From ddd65b44ea3a3f80b2b600db5ed8f973d537454a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 20 Oct 2023 06:01:37 +0200 Subject: [PATCH 133/212] Files/TestDoubles: minor code tweaks [1] Lower nesting levels and remove `elseif` when not needed. :point_right: This commit will be easiest to review while ignoring whitespace changes. --- Yoast/Sniffs/Files/TestDoublesSniff.php | 107 +++++++++++++----------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index a557913e..1adcfcd6 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -127,64 +127,69 @@ public function process( File $phpcsFile, $stackPtr ) { } if ( empty( $this->target_paths ) === true ) { - if ( $name_contains_double_or_mock === true ) { - // No valid target paths found. - $data = [ - $phpcsFile->config->basepath, - ]; - - if ( \count( $this->doubles_path ) === 1 ) { - $data[] = 'directory'; - $data[] = \implode( '', $this->doubles_path ); - } - else { - $all_paths = \implode( '", "', $this->doubles_path ); - $all_paths = \substr_replace( $all_paths, ' and', \strrpos( $all_paths, ',' ), 1 ); - - $data[] = 'directories'; - $data[] = $all_paths; - } - - $phpcsFile->addError( - 'Double/Mock test helper class detected, but no test doubles sub-%2$s found in "%1$s". Expected: "%3$s". Please create the sub-%2$s.', - $stackPtr, - 'NoDoublesDirectory', - $data - ); - } - } - else { - $path_to_file = $this->normalize_directory_separators( $file ); - $is_double_dir = false; - - foreach ( $this->target_paths as $target_path ) { - if ( \stripos( $path_to_file, $target_path ) !== false ) { - $is_double_dir = true; - break; - } + if ( $name_contains_double_or_mock === false ) { + return; } + // Mock/Double class found, but no valid target paths found. $data = [ - $tokens[ $stackPtr ]['content'], - $object_name, + $phpcsFile->config->basepath, ]; - if ( $name_contains_double_or_mock === true && $is_double_dir === false ) { - $phpcsFile->addError( - 'Double/Mock test helper classes should be placed in a dedicated test doubles sub-directory. Found %s: %s', - $stackPtr, - 'WrongDirectory', - $data - ); + if ( \count( $this->doubles_path ) === 1 ) { + $data[] = 'directory'; + $data[] = \implode( '', $this->doubles_path ); } - elseif ( $name_contains_double_or_mock === false && $is_double_dir === true ) { - $phpcsFile->addError( - 'Double/Mock test helper classes should contain "Double" or "Mock" in the class name. Found %s: %s', - $stackPtr, - 'InvalidClassName', - $data - ); + else { + $all_paths = \implode( '", "', $this->doubles_path ); + $all_paths = \substr_replace( $all_paths, ' and', \strrpos( $all_paths, ',' ), 1 ); + + $data[] = 'directories'; + $data[] = $all_paths; } + + $phpcsFile->addError( + 'Double/Mock test helper class detected, but no test doubles sub-%2$s found in "%1$s". Expected: "%3$s". Please create the sub-%2$s.', + $stackPtr, + 'NoDoublesDirectory', + $data + ); + + return; + } + + $path_to_file = $this->normalize_directory_separators( $file ); + $is_double_dir = false; + + foreach ( $this->target_paths as $target_path ) { + if ( \stripos( $path_to_file, $target_path ) !== false ) { + $is_double_dir = true; + break; + } + } + + $data = [ + $tokens[ $stackPtr ]['content'], + $object_name, + ]; + + if ( $name_contains_double_or_mock === true && $is_double_dir === false ) { + $phpcsFile->addError( + 'Double/Mock test helper classes should be placed in a dedicated test doubles sub-directory. Found %s: %s', + $stackPtr, + 'WrongDirectory', + $data + ); + return; + } + + if ( $name_contains_double_or_mock === false && $is_double_dir === true ) { + $phpcsFile->addError( + 'Double/Mock test helper classes should contain "Double" or "Mock" in the class name. Found %s: %s', + $stackPtr, + 'InvalidClassName', + $data + ); } } From 34c2b8e295b6b1a457b516d8ed9e197c7c1a7a43 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 24 Oct 2023 10:42:36 +0200 Subject: [PATCH 134/212] Files/TestDoubles: minor code tweaks [2] Move variable definitions to the point in the code flow where they will be used. No need to define them earlier. --- Yoast/Sniffs/Files/TestDoublesSniff.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index 1adcfcd6..ad8ef0f5 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -99,13 +99,12 @@ public function process( File $phpcsFile, $stackPtr ) { return ( $phpcsFile->numTokens + 1 ); } - $tokens = $phpcsFile->getTokens(); - $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); - $base_path = \rtrim( $base_path, '/' ) . '/'; // Make sure the base_path ends in a single slash. - if ( ! isset( $this->target_paths ) || \defined( 'PHP_CODESNIFFER_IN_TESTS' ) ) { $this->target_paths = []; + $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); + $base_path = \rtrim( $base_path, '/' ) . '/'; // Make sure the base_path ends in a single slash. + foreach ( $this->doubles_path as $doubles_path ) { $target_path = $base_path; $target_path .= \trim( $this->normalize_directory_separators( $doubles_path ), '/' ) . '/'; @@ -168,7 +167,8 @@ public function process( File $phpcsFile, $stackPtr ) { } } - $data = [ + $tokens = $phpcsFile->getTokens(); + $data = [ $tokens[ $stackPtr ]['content'], $object_name, ]; From 3749a69ce2196a5620199c05e379e4f268352342 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 20 Oct 2023 07:58:26 +0200 Subject: [PATCH 135/212] Files/TestDoubles: minor test tweaks Ensure all test files end on a new line. --- .../tests/mocks/correct-custom-dir-not-mock.inc | 2 +- .../TestDoublesUnitTests/tests/mocks/correct-custom-dir.inc | 2 +- .../TestDoublesUnitTests/tests/not-in-correct-custom-dir.inc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir-not-mock.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir-not-mock.inc index 89f388c3..1e02aee6 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir-not-mock.inc +++ b/Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir-not-mock.inc @@ -3,4 +3,4 @@ phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/mocks,/tests/assets class Prefix_ClassName {} -// phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/doubles \ No newline at end of file +// phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/doubles diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir.inc index 26933840..2c7da38f 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir.inc +++ b/Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir.inc @@ -3,4 +3,4 @@ phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/mocks,/tests/assets class Prefix_ClassName_Double {} -// phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/doubles \ No newline at end of file +// phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/doubles diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/not-in-correct-custom-dir.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/not-in-correct-custom-dir.inc index 26933840..2c7da38f 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTests/tests/not-in-correct-custom-dir.inc +++ b/Yoast/Tests/Files/TestDoublesUnitTests/tests/not-in-correct-custom-dir.inc @@ -3,4 +3,4 @@ phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/mocks,/tests/assets class Prefix_ClassName_Double {} -// phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/doubles \ No newline at end of file +// phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/doubles From a65b8541f4a23438ef3eb9bd121743bba55e4e9a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 18 Oct 2023 02:50:09 +0200 Subject: [PATCH 136/212] Files/TestDoubles: use PHPCSUtils in more places --- Yoast/Sniffs/Files/TestDoublesSniff.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index ad8ef0f5..9e17923e 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -5,6 +5,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHPCSUtils\Utils\ObjectDeclarations; +use PHPCSUtils\Utils\TextStrings; /** * Check that all mock/doubles classes are in a `doubles` directory. @@ -72,7 +73,7 @@ public function register() { */ public function process( File $phpcsFile, $stackPtr ) { // Stripping potential quotes to ensure `stdin_path` passed by IDEs does not include quotes. - $file = \preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $phpcsFile->getFileName() ); + $file = TextStrings::stripQuotes( $phpcsFile->getFileName() ); if ( $file === 'STDIN' ) { return; // @codeCoverageIgnore From 282d86e6df2daeb82d7e28e24cb333aa1b96a987 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 19 Oct 2023 05:43:01 +0200 Subject: [PATCH 137/212] Files/TestDoubles: implement use of new PathHelper and PathValidationHelper classes and check case-sensitively Aside from starting to use the new `PathHelper` and `PathValidationHelper` utility function classes, this commit also changes the path comparison from case-INsensitive to case-sensitive, which, what with the change to strict PSR-4 compliance for all test files, including double/mock files, is a change which needs to be made anyway. The tests sub-directory names have been updated to proper case to be in line with this change, the inline property settings in test case files have been updated too. On top of that, a couple of dedicated new test cases have been added to verify the case-sensitive handling of the `double_path` property. Includes updating two pre-existing tests to pass duplicate excluded files in different ways. --- Yoast/Sniffs/Files/TestDoublesSniff.php | 37 +++++-------------- Yoast/Tests/Files/TestDoublesUnitTest.php | 30 +++++++++++---- .../correct-dir-double.inc | 0 .../correct-dir-interface-double.inc | 0 .../{doubles => Doubles}/correct-dir-mock.inc | 0 .../correct-dir-not-double-or-mock.inc | 0 .../correct-dir-trait-double.inc | 0 .../not-in-correct-subdir.inc | 0 .../Mocks/correct-custom-dir-not-mock.inc | 6 +++ .../tests/Mocks/correct-custom-dir.inc | 6 +++ ...correct-custom-dir-not-mock-wrong-case.inc | 6 +++ .../correct-custom-dir-wrong-case.inc | 6 +++ .../correct-custom-lowercase-dir-not-mock.inc | 6 +++ .../correct-custom-lowercase-dir.inc | 6 +++ .../mocks/correct-custom-dir-not-mock.inc | 6 --- .../tests/mocks/correct-custom-dir.inc | 6 --- .../tests/no-doubles-path-property.inc | 2 +- .../non-existant-doubles-dir-not-double.inc | 2 +- .../tests/non-existant-doubles-dir.inc | 4 +- .../tests/non-existant-doubles-dirs.inc | 2 +- .../tests/not-in-correct-custom-dir.inc | 4 +- Yoast/ruleset.xml | 2 +- 22 files changed, 75 insertions(+), 56 deletions(-) rename Yoast/Tests/Files/TestDoublesUnitTests/tests/{doubles => Doubles}/correct-dir-double.inc (100%) rename Yoast/Tests/Files/TestDoublesUnitTests/tests/{doubles => Doubles}/correct-dir-interface-double.inc (100%) rename Yoast/Tests/Files/TestDoublesUnitTests/tests/{doubles => Doubles}/correct-dir-mock.inc (100%) rename Yoast/Tests/Files/TestDoublesUnitTests/tests/{doubles => Doubles}/correct-dir-not-double-or-mock.inc (100%) rename Yoast/Tests/Files/TestDoublesUnitTests/tests/{doubles => Doubles}/correct-dir-trait-double.inc (100%) rename Yoast/Tests/Files/TestDoublesUnitTests/tests/{doubles-not-correct => DoublesNotCorrect}/not-in-correct-subdir.inc (100%) create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/Mocks/correct-custom-dir-not-mock.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/Mocks/correct-custom-dir.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/lowercase/correct-custom-dir-not-mock-wrong-case.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/lowercase/correct-custom-dir-wrong-case.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/lowercase/correct-custom-lowercase-dir-not-mock.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/lowercase/correct-custom-lowercase-dir.inc delete mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir-not-mock.inc delete mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/mocks/correct-custom-dir.inc diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index 9e17923e..e8b35089 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -6,6 +6,8 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHPCSUtils\Utils\ObjectDeclarations; use PHPCSUtils\Utils\TextStrings; +use YoastCS\Yoast\Utils\PathHelper; +use YoastCS\Yoast\Utils\PathValidationHelper; /** * Check that all mock/doubles classes are in a `doubles` directory. @@ -38,14 +40,14 @@ final class TestDoublesSniff implements Sniff { * @var array */ public $doubles_path = [ - '/tests/doubles', + '/tests/Doubles', ]; /** * Validated absolute target paths for test fixture directories or an empty array * if the intended target directory/directories don't exist. * - * @var array + * @var array */ private $target_paths; @@ -101,19 +103,9 @@ public function process( File $phpcsFile, $stackPtr ) { } if ( ! isset( $this->target_paths ) || \defined( 'PHP_CODESNIFFER_IN_TESTS' ) ) { - $this->target_paths = []; - - $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); - $base_path = \rtrim( $base_path, '/' ) . '/'; // Make sure the base_path ends in a single slash. - - foreach ( $this->doubles_path as $doubles_path ) { - $target_path = $base_path; - $target_path .= \trim( $this->normalize_directory_separators( $doubles_path ), '/' ) . '/'; - - if ( \file_exists( $target_path ) && \is_dir( $target_path ) ) { - $this->target_paths[] = \strtolower( $target_path ); - } - } + $this->target_paths = PathValidationHelper::relative_to_absolute( $phpcsFile, $this->doubles_path ); + $this->target_paths = \array_filter( $this->target_paths, 'file_exists' ); + $this->target_paths = \array_filter( $this->target_paths, 'is_dir' ); } $object_name = ObjectDeclarations::getName( $phpcsFile, $stackPtr ); @@ -158,11 +150,11 @@ public function process( File $phpcsFile, $stackPtr ) { return; } - $path_to_file = $this->normalize_directory_separators( $file ); + $path_to_file = PathHelper::normalize_absolute_path( $file ); $is_double_dir = false; foreach ( $this->target_paths as $target_path ) { - if ( \stripos( $path_to_file, $target_path ) !== false ) { + if ( PathHelper::path_starts_with( $path_to_file, $target_path ) === true ) { $is_double_dir = true; break; } @@ -193,15 +185,4 @@ public function process( File $phpcsFile, $stackPtr ) { ); } } - - /** - * Normalize all directory separators to be a forward slash. - * - * @param string $path Path to normalize. - * - * @return string - */ - private function normalize_directory_separators( $path ) { - return \strtr( $path, '\\', '/' ); - } } diff --git a/Yoast/Tests/Files/TestDoublesUnitTest.php b/Yoast/Tests/Files/TestDoublesUnitTest.php index 09baf7d8..615e2071 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTest.php +++ b/Yoast/Tests/Files/TestDoublesUnitTest.php @@ -83,32 +83,46 @@ public function getErrorList( string $testFile = '' ): array { 3 => 1, ]; - // In tests/doubles. + // In tests/Doubles. case 'correct-dir-not-double-or-mock.inc': return [ 3 => 1, ]; - // In tests/doubles-not-correct. + // In tests/DoublesNotCorrect. case 'not-in-correct-subdir.inc': return [ 3 => 1, ]; - // In tests/mocks. + // In tests/Mocks. case 'correct-custom-dir-not-mock.inc': return [ 4 => 1, ]; + // In tests/lowercase. + case 'correct-custom-dir-wrong-case.inc': + return [ + 4 => 1, + ]; + + // In tests/lowercase. + case 'correct-custom-lowercase-dir-not-mock.inc': + return [ + 4 => 1, + ]; + case 'live-coding.inc': // In tests. case 'not-double-or-mock.inc': // In tests. case 'non-existant-doubles-dir-not-double.inc': // In tests. - case 'correct-dir-double.inc': // In tests/doubles. - case 'correct-dir-mock.inc': // In tests/doubles. - case 'correct-dir-interface.inc': // In tests/doubles. - case 'correct-dir-trait-double.inc': // In tests/doubles. - case 'correct-custom-dir.inc': // In tests/mocks. + case 'correct-dir-double.inc': // In tests/Doubles. + case 'correct-dir-mock.inc': // In tests/Doubles. + case 'correct-dir-interface.inc': // In tests/Doubles. + case 'correct-dir-trait-double.inc': // In tests/Doubles. + case 'correct-custom-dir.inc': // In tests/Mocks. + case 'correct-custom-lowercase-dir.inc': // In tests/lowercase. + case 'correct-custom-dir-not-mock-wrong-case.inc': // In tests/lowercase. default: return []; } diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-double.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-double.inc similarity index 100% rename from Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-double.inc rename to Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-double.inc diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-interface-double.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-interface-double.inc similarity index 100% rename from Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-interface-double.inc rename to Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-interface-double.inc diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-mock.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-mock.inc similarity index 100% rename from Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-mock.inc rename to Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-mock.inc diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-not-double-or-mock.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-not-double-or-mock.inc similarity index 100% rename from Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-not-double-or-mock.inc rename to Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-not-double-or-mock.inc diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-trait-double.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-trait-double.inc similarity index 100% rename from Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles/correct-dir-trait-double.inc rename to Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-trait-double.inc diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles-not-correct/not-in-correct-subdir.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/DoublesNotCorrect/not-in-correct-subdir.inc similarity index 100% rename from Yoast/Tests/Files/TestDoublesUnitTests/tests/doubles-not-correct/not-in-correct-subdir.inc rename to Yoast/Tests/Files/TestDoublesUnitTests/tests/DoublesNotCorrect/not-in-correct-subdir.inc diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/Mocks/correct-custom-dir-not-mock.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Mocks/correct-custom-dir-not-mock.inc new file mode 100644 index 00000000..e0bc3cf6 --- /dev/null +++ b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Mocks/correct-custom-dir-not-mock.inc @@ -0,0 +1,6 @@ +phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/Mocks/,/tests/Assets,tests/Mocks,./tests/Mocks + - + From 80aa65ea8337ad8cd0e9fd2be2663c69c02094bd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 20 Oct 2023 06:55:41 +0200 Subject: [PATCH 138/212] Files/TestDoubles: make the sniff codebase agnostic The `public $doubles_path` contained an arbitrary default value. While this default value is applicable for some of the Yoast codebases, it still needs to be duplicated in the ruleset due to a bug/missing feature in PHPCS in how array properties containing default values are handled. Having this arbitrary default value in the sniff also codifies some Yoast-specific repo layout logic in the sniff, while sniffs, generally speaking should be code-base agnostic. With that in mind, I'm removing the default value. The `Yoast` ruleset contains this value anyhow (due to the bug/missing feature) and that is the more appropriate place for code-base specific/organisation specific property values. Includes updating the test case files to allow for this change. Refs: * squizlabs/PHP_CodeSniffer 2154 * squizlabs/PHP_CodeSniffer 2228 --- Yoast/Sniffs/Files/TestDoublesSniff.php | 17 +++++------------ Yoast/Tests/Files/TestDoublesUnitTest.php | 8 ++++---- .../tests/Doubles/correct-dir-double.inc | 3 +++ .../Doubles/correct-dir-interface-double.inc | 3 +++ .../tests/Doubles/correct-dir-mock.inc | 3 +++ .../Doubles/correct-dir-not-double-or-mock.inc | 3 +++ .../tests/Doubles/correct-dir-trait-double.inc | 3 +++ .../DoublesNotCorrect/not-in-correct-subdir.inc | 3 +++ .../tests/Mocks/correct-custom-dir-not-mock.inc | 2 +- .../tests/Mocks/correct-custom-dir.inc | 2 +- .../TestDoublesUnitTests/tests/live-coding.inc | 3 +++ .../correct-custom-dir-not-mock-wrong-case.inc | 2 +- .../lowercase/correct-custom-dir-wrong-case.inc | 2 +- .../correct-custom-lowercase-dir-not-mock.inc | 2 +- .../lowercase/correct-custom-lowercase-dir.inc | 2 +- .../tests/mock-not-in-correct-dir.inc | 3 +++ .../tests/no-doubles-path-property.inc | 3 --- .../non-existant-doubles-dir-not-double.inc | 2 +- .../tests/non-existant-doubles-dir.inc | 2 +- .../tests/non-existant-doubles-dirs.inc | 2 +- .../tests/not-double-or-mock.inc | 3 +++ .../tests/not-in-correct-custom-dir.inc | 2 +- .../tests/not-in-correct-dir-double.inc | 3 +++ .../not-in-correct-dir-interface-double.inc | 3 +++ .../tests/not-in-correct-dir-mock.inc | 3 +++ .../tests/not-in-correct-dir-trait-double.inc | 3 +++ Yoast/ruleset.xml | 1 - 27 files changed, 58 insertions(+), 30 deletions(-) diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index e8b35089..8dbe0b20 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -10,9 +10,9 @@ use YoastCS\Yoast\Utils\PathValidationHelper; /** - * Check that all mock/doubles classes are in a `doubles` directory. + * Check that all mock/doubles classes are in a dedicated directory for test fixtures. * - * Additionally, checks that all classes in the `doubles` directory/directories + * Additionally, checks that all classes in this fixtures directory/directories * have `Double` or `Mock` in the class name. * * @since 1.0.0 @@ -26,22 +26,15 @@ final class TestDoublesSniff implements Sniff { * The paths should be relative to the root/basepath of the project and can be * customized from within a custom ruleset. * - * Preferably only one path is provided per project, but in exceptional circumstances - * multiple paths can be allowed. - * - * The new PHPCS 3.4.0 array `extend` feature can be used to add to this list. - * To overrule the list, just set the property. - * {@link https://github.com/squizlabs/PHP_CodeSniffer/pull/2154} - * * @since 1.0.0 * @since 1.1.0 The property type has changed from string to array. * Use of this property with a string value has been deprecated. + * @since 3.0.0 The default value has changed to an empty array. + * This property will now always need to be set from within a ruleset. * * @var array */ - public $doubles_path = [ - '/tests/Doubles', - ]; + public $doubles_path = []; /** * Validated absolute target paths for test fixture directories or an empty array diff --git a/Yoast/Tests/Files/TestDoublesUnitTest.php b/Yoast/Tests/Files/TestDoublesUnitTest.php index 615e2071..de44c8d1 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTest.php +++ b/Yoast/Tests/Files/TestDoublesUnitTest.php @@ -61,7 +61,7 @@ public function getErrorList( string $testFile = '' ): array { // In tests/. case 'mock-not-in-correct-dir.inc': return [ - 3 => 1, + 4 => 1, ]; case 'non-existant-doubles-dir.inc': @@ -80,19 +80,19 @@ public function getErrorList( string $testFile = '' ): array { case 'not-in-correct-dir-interface-double.inc': case 'not-in-correct-dir-trait-double.inc': return [ - 3 => 1, + 4 => 1, ]; // In tests/Doubles. case 'correct-dir-not-double-or-mock.inc': return [ - 3 => 1, + 4 => 1, ]; // In tests/DoublesNotCorrect. case 'not-in-correct-subdir.inc': return [ - 3 => 1, + 4 => 1, ]; // In tests/Mocks. diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-double.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-double.inc index 0ef27d1c..f0fe58b9 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-double.inc +++ b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-double.inc @@ -1,3 +1,6 @@ +phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/Doubles - From 5374572c657c6fd8ff4faf3953ca53afc04c1b75 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 20 Oct 2023 06:57:17 +0200 Subject: [PATCH 139/212] YoastCS/TestDoubles sniff: update the `doubles_path` property value ... to be in line with the change in the test directory structure in the plugin repos and to be compliant with the change to (case-sensitive) PSR-4 for all test files. --- Yoast/ruleset.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index ccca0b91..ed2c55eb 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -119,6 +119,8 @@ + + From d4634292f411dc8c4b08c5e68cb4240134172e68 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 20 Oct 2023 09:22:53 +0200 Subject: [PATCH 140/212] Files/TestDoubles: allow for doubling PHP 8.1+ enums Includes tests covering this change. --- Yoast/Sniffs/Files/TestDoublesSniff.php | 16 ++++++++-------- Yoast/Tests/Files/TestDoublesUnitTest.php | 2 ++ .../tests/Doubles/correct-dir-enum-double.inc | 6 ++++++ .../tests/not-in-correct-dir-enum-double.inc | 6 ++++++ 4 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-enum-double.inc create mode 100644 Yoast/Tests/Files/TestDoublesUnitTests/tests/not-in-correct-dir-enum-double.inc diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index 8dbe0b20..68896439 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -4,16 +4,17 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\Utils\ObjectDeclarations; use PHPCSUtils\Utils\TextStrings; use YoastCS\Yoast\Utils\PathHelper; use YoastCS\Yoast\Utils\PathValidationHelper; /** - * Check that all mock/doubles classes are in a dedicated directory for test fixtures. + * Check that all mock/doubles OO structures are in a dedicated directory for test fixtures. * - * Additionally, checks that all classes in this fixtures directory/directories - * have `Double` or `Mock` in the class name. + * Additionally, checks that all OO structures in this fixtures directory/directories + * have `Double` or `Mock` in their name. * * @since 1.0.0 * @since 3.0.0 This sniff will no longer check for multiple OO object declarations within one file. @@ -50,11 +51,10 @@ final class TestDoublesSniff implements Sniff { * @return array */ public function register() { - return [ - \T_CLASS, - \T_INTERFACE, - \T_TRAIT, - ]; + $targets = Tokens::$ooScopeTokens; + unset( $targets[ \T_ANON_CLASS ] ); + + return $targets; } /** diff --git a/Yoast/Tests/Files/TestDoublesUnitTest.php b/Yoast/Tests/Files/TestDoublesUnitTest.php index de44c8d1..d8f2d1ac 100644 --- a/Yoast/Tests/Files/TestDoublesUnitTest.php +++ b/Yoast/Tests/Files/TestDoublesUnitTest.php @@ -77,6 +77,7 @@ public function getErrorList( string $testFile = '' ): array { case 'not-in-correct-dir-double.inc': case 'not-in-correct-dir-mock.inc': + case 'not-in-correct-dir-enum-double.inc': case 'not-in-correct-dir-interface-double.inc': case 'not-in-correct-dir-trait-double.inc': return [ @@ -118,6 +119,7 @@ public function getErrorList( string $testFile = '' ): array { case 'non-existant-doubles-dir-not-double.inc': // In tests. case 'correct-dir-double.inc': // In tests/Doubles. case 'correct-dir-mock.inc': // In tests/Doubles. + case 'correct-dir-enum.inc': // In tests/Doubles. case 'correct-dir-interface.inc': // In tests/Doubles. case 'correct-dir-trait-double.inc': // In tests/Doubles. case 'correct-custom-dir.inc': // In tests/Mocks. diff --git a/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-enum-double.inc b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-enum-double.inc new file mode 100644 index 00000000..f035a55d --- /dev/null +++ b/Yoast/Tests/Files/TestDoublesUnitTests/tests/Doubles/correct-dir-enum-double.inc @@ -0,0 +1,6 @@ +phpcs:set Yoast.Files.TestDoubles doubles_path[] /tests/Doubles + Date: Fri, 20 Oct 2023 09:26:45 +0200 Subject: [PATCH 141/212] Files/TestDoubles: minor improvements to the XML docs ... to more closely match what the sniff is looking for. --- Yoast/Docs/Files/TestDoublesStandard.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Yoast/Docs/Files/TestDoublesStandard.xml b/Yoast/Docs/Files/TestDoublesStandard.xml index 36db7a2e..c6b1b9fe 100644 --- a/Yoast/Docs/Files/TestDoublesStandard.xml +++ b/Yoast/Docs/Files/TestDoublesStandard.xml @@ -5,8 +5,9 @@ > From 363c292abb75568527de62908075940f390e22e5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 20 Oct 2023 09:34:41 +0200 Subject: [PATCH 142/212] Files/TestDoubles: minor tweaks to the error messages ... to remove the presumption that all test fixtures are classes. --- Yoast/Sniffs/Files/TestDoublesSniff.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Yoast/Sniffs/Files/TestDoublesSniff.php b/Yoast/Sniffs/Files/TestDoublesSniff.php index 68896439..1cd18f34 100644 --- a/Yoast/Sniffs/Files/TestDoublesSniff.php +++ b/Yoast/Sniffs/Files/TestDoublesSniff.php @@ -111,6 +111,7 @@ public function process( File $phpcsFile, $stackPtr ) { $name_contains_double_or_mock = true; } + $tokens = $phpcsFile->getTokens(); if ( empty( $this->target_paths ) === true ) { if ( $name_contains_double_or_mock === false ) { return; @@ -118,6 +119,7 @@ public function process( File $phpcsFile, $stackPtr ) { // Mock/Double class found, but no valid target paths found. $data = [ + $tokens[ $stackPtr ]['content'], $phpcsFile->config->basepath, ]; @@ -134,7 +136,7 @@ public function process( File $phpcsFile, $stackPtr ) { } $phpcsFile->addError( - 'Double/Mock test helper class detected, but no test doubles sub-%2$s found in "%1$s". Expected: "%3$s". Please create the sub-%2$s.', + 'Double/Mock test helper %1$s detected, but no test fixtures sub-%3$s found in "%2$s". Expected: "%4$s". Please create the sub-%3$s.', $stackPtr, 'NoDoublesDirectory', $data @@ -153,15 +155,14 @@ public function process( File $phpcsFile, $stackPtr ) { } } - $tokens = $phpcsFile->getTokens(); - $data = [ + $data = [ $tokens[ $stackPtr ]['content'], $object_name, ]; if ( $name_contains_double_or_mock === true && $is_double_dir === false ) { $phpcsFile->addError( - 'Double/Mock test helper classes should be placed in a dedicated test doubles sub-directory. Found %s: %s', + 'Double/Mock test helpers should be placed in a dedicated test fixtures sub-directory. Found %s: %s', $stackPtr, 'WrongDirectory', $data @@ -171,7 +172,7 @@ public function process( File $phpcsFile, $stackPtr ) { if ( $name_contains_double_or_mock === false && $is_double_dir === true ) { $phpcsFile->addError( - 'Double/Mock test helper classes should contain "Double" or "Mock" in the class name. Found %s: %s', + 'Double/Mock test helpers should contain "Double" or "Mock" in the class name. Found %s: %s', $stackPtr, 'InvalidClassName', $data From 6e5ddb69d4f88294b3a4ef964f2896792340fb93 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 19 Oct 2023 05:47:36 +0200 Subject: [PATCH 143/212] NamingConventions/NamespaceName: minor documentation tweaks --- .../Sniffs/NamingConventions/NamespaceNameSniff.php | 12 ++++++------ .../NamingConventions/NamespaceNameUnitTest.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index afc974ff..cf96b536 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -35,7 +35,7 @@ final class NamespaceNameSniff implements Sniff { * one or more sub-directories of the project root can be indicated * as starting points for the translation. * - * @var string[] + * @var array */ public $src_directory = []; @@ -71,7 +71,7 @@ final class NamespaceNameSniff implements Sniff { * - not be prefixed with a slash; * - have a trailing slash. * - * @var string[] + * @var array */ private $validated_src_directory = []; @@ -80,7 +80,7 @@ final class NamespaceNameSniff implements Sniff { * * Prevents having to do the same validation over and over again. * - * @var string[] + * @var array */ private $previous_src_directory = []; @@ -96,9 +96,9 @@ public function register() { /** * Filter out all prefixes which don't have namespace separators. * - * @param string[] $prefixes The unvalidated prefixes. + * @param array $prefixes The unvalidated prefixes. * - * @return string[] + * @return array */ protected function filter_prefixes( $prefixes ) { return $this->filter_allow_only_namespace_prefixes( $prefixes ); @@ -149,7 +149,7 @@ public function process( File $phpcsFile, $stackPtr ) { $this->validate_prefixes(); - // Strip off the plugin prefix. + // Strip off the (longest) plugin prefix. $namespace_name_no_prefix = $namespace_name; $found_prefix = ''; if ( ! empty( $this->validated_prefixes ) ) { diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index e2b3c904..330973c2 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -36,7 +36,7 @@ public function setCliValues( $filename, $config ): void { * * @param string $testFileBase The base path that the unit tests files will have. * - * @return string[] + * @return array */ protected function getTestFiles( $testFileBase ): array { $sep = \DIRECTORY_SEPARATOR; From ddae5aa1fe1813064df96e33be6b32a8962ef07a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 21 Oct 2023 16:04:29 +0200 Subject: [PATCH 144/212] NamingConventions/NamespaceName: minor tweak Ignore an untestable line for code coverage analysis. --- Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index cf96b536..6fe0d0b2 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -226,7 +226,7 @@ public function process( File $phpcsFile, $stackPtr ) { $file = \preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $phpcsFile->getFileName() ); if ( $file === 'STDIN' ) { - return; + return; // @codeCoverageIgnore } $directory = $this->normalize_directory_separators( \dirname( $file ) ); From 0da762d93f24fa031ef84640c97122efdd2de604 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 1 Nov 2023 00:14:31 +0100 Subject: [PATCH 145/212] NamingConventions/NamespaceName: tweak the test case files * Tie the test description comments closer together with the settings being tested. * Consolidate "mismatch" tests. No need for two test files. One path translates to one valid namespace (depending on settings), so all possible cases for one path can always be tested within one test case file. * Remove parse error from mismatch test. * Add one additional test case to the `src/sub-b/path-translation-src-sub-b.inc` test case file. --- .../NamespaceNameUnitTest.php | 9 +++------ .../src/path-translation-ignore-src.inc | 6 +++--- .../path-translation-ignore-src-sub-path.inc | 6 +++--- .../path-translation-mismatch-illegal.inc | 16 --------------- .../mismatch/path-translation-mismatch.inc | 17 +++++++++++++--- .../NamespaceNameUnitTests/no-basepath.inc | 20 +++++++++---------- .../path-translation-root.inc | 4 ++-- .../path/path-translation-sub1.inc | 4 ++-- .../path/sub-path/path-translation-sub2.inc | 4 ++-- .../secondary/path-translation-secondary.inc | 6 +++--- .../path-translation-secondary-sub-a.inc | 6 +++--- .../src/path-translation-src.inc | 6 +++--- .../src/sub-a/path-translation-src-sub-a.inc | 6 +++--- .../src/sub-b/path-translation-src-sub-b.inc | 7 ++++--- 14 files changed, 55 insertions(+), 62 deletions(-) delete mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/mismatch/path-translation-mismatch-illegal.inc diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index 330973c2..af408c3f 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -124,6 +124,7 @@ public function getErrorList( string $testFile = '' ): array { case 'path-translation-src-sub-b.inc': return [ 14 => 1, + 15 => 1, ]; // Path translation with multiple items in $src_directory tests. @@ -153,12 +154,8 @@ public function getErrorList( string $testFile = '' ): array { // Path translation with no matching $src_directory. case 'path-translation-mismatch.inc': return [ - 13 => 1, - ]; - - case 'path-translation-mismatch-illegal.inc': - return [ - 12 => 1, + 14 => 1, + 24 => 1, ]; default: diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/path-translation-ignore-src.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/path-translation-ignore-src.inc index 6c7a78cc..64ff5ae8 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/path-translation-ignore-src.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/path-translation-ignore-src.inc @@ -2,11 +2,11 @@ /* * Testing path translation in combination with multi-level src_directory. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ignore/src */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin -// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ignore/src - namespace Yoast\WP\Plugin; // OK. namespace Yoast\WP\Plugin\Ignore\Src; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/sub-path/path-translation-ignore-src-sub-path.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/sub-path/path-translation-ignore-src-sub-path.inc index 19acea65..6db9128b 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/sub-path/path-translation-ignore-src-sub-path.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/ignore/src/sub-path/path-translation-ignore-src-sub-path.inc @@ -2,11 +2,11 @@ /* * Testing path translation in combination with multi-level src_directory. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ignore/src */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin -// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ignore/src - namespace Yoast\WP\Plugin\Sub_Path; // OK. namespace Yoast\WP\Plugin; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/mismatch/path-translation-mismatch-illegal.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/mismatch/path-translation-mismatch-illegal.inc deleted file mode 100644 index d144a19e..00000000 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/mismatch/path-translation-mismatch-illegal.inc +++ /dev/null @@ -1,16 +0,0 @@ - max). + * + * @phpcs:set Yoast.NamingConventions.NamespaceName max_levels 2 + * @phpcs:set Yoast.NamingConventions.NamespaceName recommended_max_levels 5 */ -// phpcs:set Yoast.NamingConventions.NamespaceName max_levels 2 -// phpcs:set Yoast.NamingConventions.NamespaceName recommended_max_levels 5 - namespace Yoast\WP\Plugin\Foo\Bar; // OK. namespace Yoast\WP\Plugin\Foo\Bar\Baz; // Error. namespace Yoast\WP\Plugin\Foo\Bar\Baz\Fro; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc index cd40436c..f40e91e2 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc @@ -2,10 +2,10 @@ /* * Testing path translation. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin,yoast_plugin */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin,yoast_plugin - namespace Yoast\WP\Plugin; // OK. namespace Yoast\WP\Plugin\Foo; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/path-translation-sub1.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/path-translation-sub1.inc index aa01e27b..786bfe27 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/path-translation-sub1.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/path-translation-sub1.inc @@ -2,10 +2,10 @@ /* * Testing path translation. + * + * @phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin - namespace Yoast\WP\Plugin\Path; // OK. namespace Yoast\WP\Plugin; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub-path/path-translation-sub2.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub-path/path-translation-sub2.inc index 80830a74..e85fbe51 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub-path/path-translation-sub2.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub-path/path-translation-sub2.inc @@ -2,10 +2,10 @@ /* * Testing path translation. + * + * @phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin - namespace Yoast\WP\Plugin\path\sub_path; // OK. namespace Yoast\WP\Plugin\Path\Sub_Path; // OK. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/secondary/path-translation-secondary.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/secondary/path-translation-secondary.inc index f660ba53..c0026a59 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/secondary/path-translation-secondary.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/secondary/path-translation-secondary.inc @@ -3,11 +3,11 @@ /* * Testing path translation in combination with multiple src_directories. * Includes testing of correct handling of variations of src_directory settings. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ./src/,.\secondary\ */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin -// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ./src/,.\secondary\ - namespace Yoast\WP\Plugin; // OK. namespace Yoast\WP\Plugin\Secondary; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/secondary/sub-a/path-translation-secondary-sub-a.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/secondary/sub-a/path-translation-secondary-sub-a.inc index deaba43e..724718fd 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/secondary/sub-a/path-translation-secondary-sub-a.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/secondary/sub-a/path-translation-secondary-sub-a.inc @@ -2,11 +2,11 @@ /* * Testing path translation in combination with src_directory. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] src,secondary */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin -// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] src,secondary - namespace Yoast\WP\Plugin\Sub_A; // OK. namespace Yoast\WP\Plugin\Secondary\Sub_A; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/path-translation-src.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/path-translation-src.inc index adba1e7f..38cf4ca0 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/path-translation-src.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/path-translation-src.inc @@ -2,11 +2,11 @@ /* * Testing path translation in combination with src_directory. + * + * @phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * @phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] src */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin -// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] src - namespace Yoast\WP\Plugin; // OK. namespace Yoast\WP\Plugin\Src; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-a/path-translation-src-sub-a.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-a/path-translation-src-sub-a.inc index defe3b83..409f2ab3 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-a/path-translation-src-sub-a.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-a/path-translation-src-sub-a.inc @@ -3,11 +3,11 @@ /* * Testing path translation in combination with src_directory. * Includes testing of correct handling of variations of src_directory settings. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] /src/ */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin -// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] /src/ - namespace Yoast\WP\Plugin\Sub_A; // OK. namespace Yoast\WP\Plugin\Src\Sub_A; // Error. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-b/path-translation-src-sub-b.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-b/path-translation-src-sub-b.inc index cd4dcf78..7d85044f 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-b/path-translation-src-sub-b.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-b/path-translation-src-sub-b.inc @@ -4,13 +4,14 @@ * Testing path translation in combination with src_directory. * Includes testing of correct handling of variations of src_directory settings. * Includes testing of matching the longest directory first. + * + * @phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * @phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ./src , ./src/sub-b */ -// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin -// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ./src , ./src/sub-b - namespace Yoast\WP\Plugin; // OK. +namespace Yoast\WP\Plugin\Sub_B; // Error. namespace Yoast\WP\Plugin\Src\Sub_B; // Error. // Reset to default settings. From e4b50d0360db22d622ff1f877d7cfaa360fa26b4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 31 Oct 2023 16:27:35 +0100 Subject: [PATCH 146/212] NamingConventions/NamespaceName: add tests with PHP 8.0+ namespace names with reserved keywords These tests should pass without issue since PHPCS 3.7.1, though would have failed on PHP < 8.0 prior to that. Includes adding note to some pre-existing tests about the PHP 8.0 change. --- Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php | 2 ++ .../NamespaceNameUnitTests/no-basepath.inc | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index af408c3f..6f2047bf 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -80,6 +80,7 @@ public function getErrorList( string $testFile = '' ): array { 66 => 1, 70 => 1, 74 => 1, + 81 => 1, ]; case 'no-basepath-scoped.inc': @@ -184,6 +185,7 @@ public function getWarningList( string $testFile = '' ): array { 69 => 1, 72 => 1, 73 => 1, + 80 => 1, ]; case 'no-basepath-scoped.inc': diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc index 75ca334f..390184ad 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc @@ -2,8 +2,8 @@ /* * Testing namespace depth checking, including tolerance for whitespace and comments. + * Note: comments and whitespace in namespace names are a parse error since PHP 8.0. This was fine before that time. */ - namespace Foo \ Bar; // OK. namespace Foo /* comment */ \Bar @@ -73,6 +73,13 @@ namespace Yoast\WP\Plugin\Foo\Tests\Bar; // Warning, `Tests\` counted as not dir namespace Yoast\WP\Plugin\Tests\Foo\Doubles\Bar; // Warning, `Doubles\` counted as not directly after the prefix + `Tests\`. namespace Yoast\WP\Plugin\Doubles\Foo\Bar\Fro; // Error, `Doubles\` counted as not found in combination with `Tests\`. +/* + * Test handling of namespace names with reserved keywords in them (PHP 8.0+). + */ +namespace Yoast\WP\Plugin\Trait\Sub; // OK. +namespace Yoast\WP\Plugin\Foo\Bar\Global; // Warning. +namespace Yoast\WP\Plugin\Foo\Case\Bar\Default; // Error. + // Reset to default settings. // phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] From b0b0ae616c005c41de8443318a6b3575531b5162 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 4 Apr 2020 18:52:22 +0200 Subject: [PATCH 147/212] NamingConventions/NamespaceName: implement use of the PHPCSUtils `Namespaces::getDeclaredName()` method ... to replace a large chunk of code. This also fixes a bug where the original code did not correctly determine the namespace name if it contained a parse error, while the PHPCSUtils method does handle this more robustly. As a consequence of this change, one test expectation changes, but as that test is for a parse error, I'm not concerned about that. --- .../NamingConventions/NamespaceNameSniff.php | 35 +++---------------- .../NamespaceNameUnitTest.php | 1 - .../path/sub-path/path-translation-sub2.inc | 2 +- 3 files changed, 5 insertions(+), 33 deletions(-) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index 6fe0d0b2..5cd1775e 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -5,7 +5,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Common; -use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Utils\Namespaces; use YoastCS\Yoast\Utils\CustomPrefixesTrait; /** @@ -114,36 +114,9 @@ protected function filter_prefixes( $prefixes ) { */ public function process( File $phpcsFile, $stackPtr ) { - $tokens = $phpcsFile->getTokens(); - - if ( empty( $tokens[ $stackPtr ]['conditions'] ) === false ) { - // Not a namespace declaration. - return; - } - - $next_non_empty = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); - if ( $tokens[ $next_non_empty ]['code'] === \T_NS_SEPARATOR ) { - // Not a namespace declaration. - return; - } - - // Get the complete namespace name. - $namespace_name = $tokens[ $next_non_empty ]['content']; - for ( $i = ( $next_non_empty + 1 ); $i < $phpcsFile->numTokens; $i++ ) { - if ( isset( Tokens::$emptyTokens[ $tokens[ $i ]['code'] ] ) ) { - continue; - } - - if ( $tokens[ $i ]['code'] !== \T_STRING && $tokens[ $i ]['code'] !== \T_NS_SEPARATOR ) { - // Reached end of the namespace declaration. - break; - } - - $namespace_name .= $tokens[ $i ]['content']; - } - - if ( $i === $phpcsFile->numTokens ) { - // Live coding. + $namespace_name = Namespaces::getDeclaredName( $phpcsFile, $stackPtr ); + if ( empty( $namespace_name ) ) { + // Either not a namespace declaration or global namespace. return; } diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index 6f2047bf..a94e4789 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -108,7 +108,6 @@ public function getErrorList( string $testFile = '' ): array { 12 => 1, 13 => 1, 14 => 1, - 15 => 1, ]; // Path translation with $src_directory set tests. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub-path/path-translation-sub2.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub-path/path-translation-sub2.inc index e85fbe51..a63095fe 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub-path/path-translation-sub2.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub-path/path-translation-sub2.inc @@ -12,7 +12,7 @@ namespace Yoast\WP\Plugin\Path\Sub_Path; // OK. namespace Yoast\WP\Plugin; // Error. namespace Yoast\WP\Plugin\Path; // Error. namespace Yoast\WP\Plugin\Path\Bar; // Error. -namespace Yoast\WP\Plugin\Path\Sub-Path; // Error. (Parse error too, illegal dash in namespace). +namespace Yoast\WP\Plugin\Path\Sub-Path; // Parse error, illegal dash in namespace, but that's not the concern of this sniff. // Reset to default settings. // phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] From 1e8202d4eafe5cc039b5b53a4280b2b5c11e63cc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 18 Oct 2023 02:50:21 +0200 Subject: [PATCH 148/212] NamingConventions/NamespaceName: use PHPCSUtils TextStrings::stripQuotes() --- Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index 5cd1775e..8495d714 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -6,6 +6,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Common; use PHPCSUtils\Utils\Namespaces; +use PHPCSUtils\Utils\TextStrings; use YoastCS\Yoast\Utils\CustomPrefixesTrait; /** @@ -196,7 +197,7 @@ public function process( File $phpcsFile, $stackPtr ) { $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); // Stripping potential quotes to ensure `stdin_path` passed by IDEs does not include quotes. - $file = \preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $phpcsFile->getFileName() ); + $file = TextStrings::stripQuotes( $phpcsFile->getFileName() ); if ( $file === 'STDIN' ) { return; // @codeCoverageIgnore From 0335598b09410c8135b4e00eeca9383c95d9438b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 17 Nov 2023 01:11:12 +0100 Subject: [PATCH 149/212] NamingConventions/NamespaceName: no need to double trailing slashes Slashes are already being added to the `$name` two lines earlier, so no need to do it again. --- Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index 8495d714..25cb6676 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -129,7 +129,7 @@ public function process( File $phpcsFile, $stackPtr ) { if ( ! empty( $this->validated_prefixes ) ) { $name = $namespace_name . '\\'; // Validated prefixes always have a \ at the end. foreach ( $this->validated_prefixes as $prefix ) { - if ( \strpos( $name . '\\', $prefix ) === 0 ) { + if ( \strpos( $name, $prefix ) === 0 ) { $namespace_name_no_prefix = \rtrim( \substr( $name, \strlen( $prefix ) ), '\\' ); $found_prefix = \rtrim( $prefix, '\\' ); break; From 6740e7dc8ef9dab051dc21039ce7f02ff1809f4c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 31 Oct 2023 03:43:19 +0100 Subject: [PATCH 150/212] NamingConventions/NamespaceName: make non-interface methods `private` As the sniff class is now `final` (since PR 319), there is no need for any `protected` methods, so let's make these `private`. --- Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index 25cb6676..6a79eb88 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -279,7 +279,7 @@ public function process( File $phpcsFile, $stackPtr ) { * * @return void */ - protected function validate_src_directory() { + private function validate_src_directory() { if ( $this->previous_src_directory === $this->src_directory ) { return; } From 69cacbb2c8a4c00196064284a5579eefe26dd5d5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 2 Nov 2023 13:15:15 +0100 Subject: [PATCH 151/212] NamingConventions/NamespaceName: throw an error if a prefix is expected, but not used The sniff expects a `prefixes` property to be passed from a ruleset and for a valid prefix to be used in a namespace name, however, this was not checked. This commit adds a new error to the sniff which will be thrown if at least one valid prefix has been passed and no valid prefix was used in the namespace name. Includes unit tests + some minor updates to pre-existing tests which will now also throw this error. --- .../NamespaceNameStandard.xml | 21 +++++++- .../NamingConventions/NamespaceNameSniff.php | 17 ++++++ .../NamespaceNameUnitTest.php | 53 +++++++++++-------- .../NamespaceNameUnitTests/no-basepath.inc | 28 +++++++++- .../path-translation-root.inc | 4 +- 5 files changed, 96 insertions(+), 27 deletions(-) diff --git a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml index 826f530f..5dbf901d 100644 --- a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml +++ b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml @@ -1,10 +1,29 @@ + + + + Yoast\WP\Plugin\Admin\Forms; + ]]> + + + Admin\Forms; + ]]> + + + + validated_prefixes ) && $found_prefix === '' ) { + if ( \count( $this->validated_prefixes ) === 1 ) { + $error = 'A namespace name is required to start with the "%s" prefix.'; + } + else { + $error = 'A namespace name is required to start with one of the following prefixes: "%s"'; + } + + $prefixes = $this->validated_prefixes; + \natcasesort( $prefixes ); + $data = [ \implode( '", "', $prefixes ) ]; + + $phpcsFile->addError( $error, $stackPtr, 'MissingPrefix', $data ); + } + /* * Check the namespace level depth. */ diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index a94e4789..e69f4aa0 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -70,17 +70,24 @@ public function getErrorList( string $testFile = '' ): array { // Level check tests. case 'no-basepath.inc': return [ - 12 => 1, - 21 => 1, - 24 => 1, - 33 => 1, - 44 => 1, - 53 => 1, - 54 => 1, - 66 => 1, - 70 => 1, - 74 => 1, - 81 => 1, + 12 => 1, + 21 => 1, + 23 => 1, + 24 => 2, + 33 => 1, + 44 => 1, + 53 => 1, + 54 => 1, + 66 => 1, + 70 => 1, + 74 => 1, + 81 => 1, + 90 => 1, + 91 => 1, + 92 => 1, + 103 => 1, + 104 => 1, + 105 => 1, ]; case 'no-basepath-scoped.inc': @@ -93,7 +100,7 @@ public function getErrorList( string $testFile = '' ): array { case 'path-translation-root.inc': return [ 11 => 1, - 14 => 1, + 14 => 2, ]; case 'path-translation-sub1.inc': @@ -175,16 +182,18 @@ public function getWarningList( string $testFile = '' ): array { // Level check tests. case 'no-basepath.inc': return [ - 8 => 1, - 20 => 1, - 23 => 1, - 32 => 1, - 43 => 1, - 65 => 1, - 69 => 1, - 72 => 1, - 73 => 1, - 80 => 1, + 8 => 1, + 20 => 1, + 23 => 1, + 32 => 1, + 43 => 1, + 65 => 1, + 69 => 1, + 72 => 1, + 73 => 1, + 80 => 1, + 90 => 1, + 103 => 1, ]; case 'no-basepath-scoped.inc': diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc index 390184ad..449f6b6b 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc @@ -20,8 +20,8 @@ namespace Yoast\WP\ /* comment */ Plugin\Foo\Bar; // OK. namespace Yoast\WP\Plugin\Foo\Bar\Baz; // Warning. namespace Yoast \ /* comment */ WP\Plugin\Foo\Bar\Baz\Fro; // Error. -namespace Yoast_Plugin\Foo\Bar; // Warning. Not a namespace prefix, so prefix not taken into account. -namespace Yoast_Plugin\Foo\Bar\Baz\Fro; // Error. +namespace Yoast_Plugin\Foo\Bar; // Warning for object depth. Not a namespace prefix, so prefix not taken into account. Error for not using prefix. +namespace Yoast_Plugin\Foo\Bar\Baz\Fro; // Error for object depth + not using valid prefix. /* * Testing with (correct) prefix. @@ -80,6 +80,30 @@ namespace Yoast\WP\Plugin\Trait\Sub; // OK. namespace Yoast\WP\Plugin\Foo\Bar\Global; // Warning. namespace Yoast\WP\Plugin\Foo\Case\Bar\Default; // Error. +/* + * Testing an error is thrown for not using a prefix from the validated prefixes array - single valid prefix. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + */ + +namespace Yoast\WP\Plugin\Foo; // OK. +namespace Yoast\WP\Foo; // Error (+ warning for level depth as prefix not correct). +namespace Yoast\Foo; // Error. +namespace Foo\Bar; // Error. + +/* + * Testing an error is thrown for not using a prefix from the validated prefixes array - multiple valid prefixes. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin\SubA,Yoast\WP\Plugin\SubB,Yoast\WP\Plugin + */ + +namespace Yoast\WP\Plugin\SubA\Foo; // OK. +namespace Yoast\WP\Plugin\SubB; // OK. +namespace Yoast\WP\Plugin\Foo; // OK. +namespace Yoast\WP\Foo; // Error (+ warning for level depth as prefix not correct). +namespace Yoast\Foo; // Error. +namespace Foo\Bar; // Error. + // Reset to default settings. // phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc index f40e91e2..ad95da90 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path-translation-root.inc @@ -10,8 +10,8 @@ namespace Yoast\WP\Plugin; // OK. namespace Yoast\WP\Plugin\Foo; // Error. -// Make sure an error is thrown when the wrong prefix is used. -namespace Yoast_Plugin; // Error. +// Make sure an error is thrown when the wrong type of prefix (non-namespace-like) is used. +namespace Yoast_Plugin; // Error for wrong path-to-namespace + error for not using valid prefix. // Reset to default settings. // phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] From cb6e11a372be0c8b666598f629b85aa8944754a9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 17 Nov 2023 03:53:36 +0100 Subject: [PATCH 152/212] NamingConventions/NamespaceName: make the "incorrect namespace name" error more informative When no prefix was matched, the error would previously read `Expected: "[Plugin\Prefix]..."; Found: "..."`. However, if there is only one known valid prefix, we can already replace the `[Plugin\Prefix]` with the expected prefix. Fixed now. The effect of this fix can be seen in the messages thrown by the unit tests, but can't be safeguarded via the tests at this time. --- Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index 4c155787..3be94fb3 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -269,6 +269,9 @@ public function process( File $phpcsFile, $stackPtr ) { if ( $found_prefix !== '' ) { $expected = $found_prefix; } + elseif ( \count( $this->validated_prefixes ) === 1 ) { + $expected = \rtrim( $this->validated_prefixes[0], '\\' ); + } if ( $relative_directory !== '' ) { $levels = \explode( '/', $relative_directory ); From 342e7c03ced8337a93a1f3283997d5199bcb04e1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 2 Nov 2023 14:58:50 +0100 Subject: [PATCH 153/212] NamingConventions/NamespaceName: allow for more variation is test dir layouts As the test directory layout for the plugins will change from: ``` - .\ - integration-tests\ - doubles\ - tests\ - doubles\ ``` ... to .... ``` - .\ - tests\ - unit\ - doubles\ - wp\ - doubles\ ``` .. the sniff needs to widen its allowance for these extra levels in the namespace name. This commit implements this additional allowance. While the current solution should possibly be made more code base agnostic via a custom property, for now, this solution will do. Includes additional tests. --- .../NamespaceNameStandard.xml | 2 +- .../NamingConventions/NamespaceNameSniff.php | 42 +++++++++++++++---- .../NamespaceNameUnitTest.php | 9 +++- .../NamespaceNameUnitTests/no-basepath.inc | 26 +++++++++++- 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml index 5dbf901d..c79ca4f4 100644 --- a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml +++ b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml @@ -28,7 +28,7 @@ namespace Admin\Forms; It is recommended for the name to be two levels. - For unit test files, `Tests\(Doubles\)` will be regarded as part of the prefix for this check. + For unit test files, `Tests\(Unit|WP)\(Doubles\)` will be regarded as part of the prefix for this check. ]]> diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index 3be94fb3..1549ad34 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -29,6 +29,17 @@ final class NamespaceNameSniff implements Sniff { use CustomPrefixesTrait; + /** + * Double/Mock/Fixture directories to allow for. + * + * @var array Key is the subdirectory name, value the length of that name. + */ + private const DOUBLE_DIRS = [ + '\Doubles\\' => 9, + '\Mocks\\' => 7, + '\Fixtures\\' => 10, + ]; + /** * Project root(s). * @@ -161,18 +172,31 @@ public function process( File $phpcsFile, $stackPtr ) { if ( $namespace_name_no_prefix !== '' ) { $namespace_for_level_check = $namespace_name_no_prefix; - // Allow for `Tests\` and `Tests\Doubles\` after the prefix. + // Allow for a variation of `Tests\` and `Tests\*\Doubles\` after the prefix. $starts_with_tests = ( \strpos( $namespace_for_level_check, 'Tests\\' ) === 0 ); if ( $starts_with_tests === true ) { - $namespace_for_level_check = \substr( $namespace_for_level_check, 6 ); - } + $stripped = false; + foreach ( self::DOUBLE_DIRS as $dir => $length ) { + if ( \strpos( $namespace_for_level_check, $dir ) !== false ) { + $namespace_for_level_check = \substr( $namespace_for_level_check, ( \strpos( $namespace_for_level_check, $dir ) + $length ) ); + $stripped = true; + break; + } + } - if ( ( $starts_with_tests === true - // Allow for non-conventional test directory layout, like in YoastSEO Free. - || \strpos( $found_prefix, '\\Tests\\' ) !== false ) - && \strpos( $namespace_for_level_check, 'Doubles\\' ) === 0 - ) { - $namespace_for_level_check = \substr( $namespace_for_level_check, 8 ); + if ( $stripped === false ) { + // No double dir found, now check/strip typical test dirs. + if ( \strpos( $namespace_for_level_check, 'Tests\WP\\' ) === 0 ) { + $namespace_for_level_check = \substr( $namespace_for_level_check, 9 ); + } + elseif ( \strpos( $namespace_for_level_check, 'Tests\Unit\\' ) === 0 ) { + $namespace_for_level_check = \substr( $namespace_for_level_check, 11 ); + } + else { + // Okay, so this only has the `Tests` prefix, just strip it. + $namespace_for_level_check = \substr( $namespace_for_level_check, 6 ); + } + } } $parts = \explode( '\\', $namespace_for_level_check ); diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index e69f4aa0..bcad8ac4 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -190,10 +190,17 @@ public function getWarningList( string $testFile = '' ): array { 65 => 1, 69 => 1, 72 => 1, - 73 => 1, 80 => 1, 90 => 1, 103 => 1, + 122 => 1, + 123 => 1, + 124 => 1, + 125 => 1, + 126 => 1, + 127 => 1, + 128 => 1, + 129 => 1, ]; case 'no-basepath-scoped.inc': diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc index 449f6b6b..abe5e0df 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc @@ -70,7 +70,7 @@ namespace Yoast\WP\Plugin\Tests\Doubles\Foo\Bar\Baz; // Warning. namespace Yoast\WP\Plugin\Tests\Doubles\Foo\Bar\Baz\Fro; // Error. namespace Yoast\WP\Plugin\Foo\Tests\Bar; // Warning, `Tests\` counted as not directly after the prefix. -namespace Yoast\WP\Plugin\Tests\Foo\Doubles\Bar; // Warning, `Doubles\` counted as not directly after the prefix + `Tests\`. +namespace Yoast\WP\Plugin\Tests\Foo\Doubles\Bar; // OK. namespace Yoast\WP\Plugin\Doubles\Foo\Bar\Fro; // Error, `Doubles\` counted as not found in combination with `Tests\`. /* @@ -104,6 +104,30 @@ namespace Yoast\WP\Foo; // Error (+ warning for level depth as prefix not correc namespace Yoast\Foo; // Error. namespace Foo\Bar; // Error. +/* + * Test allowing for more variations of test directories and deeper double directories. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + */ + +namespace Yoast\WP\Plugin\Tests\WP\Foo\Bar; // OK. +namespace Yoast\WP\Plugin\Tests\Unit\Foo\Bar; // OK. +namespace Yoast\WP\Plugin\Tests\WP\Doubles\Foo\Bar; // OK. +namespace Yoast\WP\Plugin\Tests\Unit\Doubles\Foo\Bar; // OK. +namespace Yoast\WP\Plugin\Tests\WP\Mocks\Foo\Bar; // OK. +namespace Yoast\WP\Plugin\Tests\Unit\Mocks\Foo\Bar; // OK. +namespace Yoast\WP\Plugin\Tests\WP\Fixtures\Foo\Bar; // OK. +namespace Yoast\WP\Plugin\Tests\Unit\Fixtures\Foo\Bar; // OK. + +namespace Yoast\WP\Plugin\Tests\WP\Foo\Bar\Baz; // Warning. +namespace Yoast\WP\Plugin\Tests\Unit\Foo\Bar\Baz; // Warning. +namespace Yoast\WP\Plugin\Tests\WP\Doubles\Foo\Bar\Baz; // Warning. +namespace Yoast\WP\Plugin\Tests\Unit\Doubles\Foo\Bar\Baz; // Warning. +namespace Yoast\WP\Plugin\Tests\WP\Mocks\Foo\Bar\Baz; // Warning. +namespace Yoast\WP\Plugin\Tests\Unit\Mocks\Foo\Bar\Baz; // Warning. +namespace Yoast\WP\Plugin\Tests\WP\Fixtures\Foo\Bar\Baz; // Warning. +namespace Yoast\WP\Plugin\Tests\Unit\Fixtures\Foo\Bar\Baz; // Warning. + // Reset to default settings. // phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] From b2dcb03f9c51a42f76b9cede1b524448385c8671 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 1 Nov 2023 17:05:29 +0100 Subject: [PATCH 154/212] NamingConventions/NamespaceName: improve handling of unconventional directory names As things were, the `NamespaceName` sniff would translate the namespace to the expected directory path and then compare the directory paths. This is the wrong way round as this sniff is about namespace names, not directory names, so the directory name should be translated to a namespace name and then compared with the actual namespace name instead. Along the same lines, there was the possibility that the sniff would recommend an "Expected" namespace name which would be a parse error/invalid syntax in PHP. This commit fixes both these issues. The commit also adds an extra error for directory names which can't (for a certain definition of "can't") be translated to a valid namespace name. Includes additional unit tests demonstrating the problem. Note: if the name in use in the file is causing a problematic parse error (like in the test with the `#` in the namespace name), the sniff will stay silent. This is the normal behaviour for PHPCS check when encountering parse errors. --- .../NamespaceNameStandard.xml | 2 + .../NamingConventions/NamespaceNameSniff.php | 48 +++++++++++++------ .../NamespaceNameUnitTest.php | 13 +++++ .../sub path/path-translation-sub2-space.inc | 12 +++++ .../sub#path/path-translation-sub2-hash.inc | 15 ++++++ .../path/sub-path/path-translation-sub2.inc | 2 +- .../sub.path/path-translation-sub2-dot.inc | 15 ++++++ .../path-translation-sub2-underscore.inc | 15 ++++++ 8 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub path/path-translation-sub2-space.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub#path/path-translation-sub2-hash.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub.path/path-translation-sub2-dot.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub_path/path-translation-sub2-underscore.inc diff --git a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml index c79ca4f4..50d595bc 100644 --- a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml +++ b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml @@ -51,6 +51,8 @@ namespace Yoast\WP\Plugin\Tests\Foo\Bar\Flo\Sub; diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index 1549ad34..ce4bfeb0 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -6,6 +6,7 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Common; use PHPCSUtils\Utils\Namespaces; +use PHPCSUtils\Utils\NamingConventions; use PHPCSUtils\Utils\TextStrings; use YoastCS\Yoast\Utils\CustomPrefixesTrait; @@ -279,16 +280,6 @@ public function process( File $phpcsFile, $stackPtr ) { // Now any potential src directory has been stripped, remove the slashes again. $relative_directory = \trim( $relative_directory, '/' ); - $namespace_name_for_translation = \str_replace( - [ '_', '\\' ], // Find. - [ '-', '/' ], // Replace with. - $namespace_name_no_prefix - ); - - if ( \strcasecmp( $relative_directory, $namespace_name_for_translation ) === 0 ) { - return; - } - $expected = '[Plugin\Prefix]'; if ( $found_prefix !== '' ) { $expected = $found_prefix; @@ -297,14 +288,43 @@ public function process( File $phpcsFile, $stackPtr ) { $expected = \rtrim( $this->validated_prefixes[0], '\\' ); } + $clean = []; + $name_for_compare = ''; + if ( $relative_directory !== '' ) { $levels = \explode( '/', $relative_directory ); - $levels = \array_filter( $levels ); // Remove empties. + $levels = \array_filter( $levels ); // Remove empties, just in case. + foreach ( $levels as $level ) { - $words = \explode( '-', $level ); - $words = \array_map( 'ucfirst', $words ); - $expected .= '\\' . \implode( '_', $words ); + $cleaned_level = \preg_replace( '`[[:punct:]]`', '_', $level ); + $words = \explode( '_', $cleaned_level ); + $words = \array_map( 'ucfirst', $words ); + $cleaned_level = \implode( '_', $words ); + + if ( NamingConventions::isValidIdentifierName( $cleaned_level ) === false ) { + $phpcsFile->addError( + 'Translating the directory name to a namespace name would not yield a valid namespace name. Rename the "%s" directory.', + 0, + 'DirectoryInvalid', + [ $level ] + ); + + // Continuing would be useless as the name would be invalid anyway. + return; + } + + $clean[] = $cleaned_level; } + + $name_for_compare = \implode( '\\', $clean ); + } + + if ( \strcasecmp( $name_for_compare, $namespace_name_no_prefix ) === 0 ) { + return; + } + + if ( $name_for_compare !== '' ) { + $expected .= '\\' . $name_for_compare; } $phpcsFile->addError( diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index bcad8ac4..453d10ea 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -115,6 +115,19 @@ public function getErrorList( string $testFile = '' ): array { 12 => 1, 13 => 1, 14 => 1, + 15 => 1, + ]; + + // Path translation with unconventional chars in directory name. + case 'path-translation-sub2-dot.inc': + case 'path-translation-sub2-underscore.inc': + return [ + 12 => 1, + ]; + + case 'path-translation-sub2-space.inc': + return [ + 1 => 1, // Invalid dir error. ]; // Path translation with $src_directory set tests. diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub path/path-translation-sub2-space.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub path/path-translation-sub2-space.inc new file mode 100644 index 00000000..bc8dac55 --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/path/sub path/path-translation-sub2-space.inc @@ -0,0 +1,12 @@ + Date: Thu, 19 Oct 2023 05:43:14 +0200 Subject: [PATCH 155/212] NamingConventions/NamespaceName: implement use of new PathHelper class --- .../NamingConventions/NamespaceNameSniff.php | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index ce4bfeb0..908160bc 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -9,6 +9,7 @@ use PHPCSUtils\Utils\NamingConventions; use PHPCSUtils\Utils\TextStrings; use YoastCS\Yoast\Utils\CustomPrefixesTrait; +use YoastCS\Yoast\Utils\PathHelper; /** * Check namespace name declarations. @@ -236,7 +237,7 @@ public function process( File $phpcsFile, $stackPtr ) { return; } - $base_path = $this->normalize_directory_separators( $phpcsFile->config->basepath ); + $base_path = trim( PathHelper::normalize_directory_separators( $phpcsFile->config->basepath ), '//' ); // Stripping potential quotes to ensure `stdin_path` passed by IDEs does not include quotes. $file = TextStrings::stripQuotes( $phpcsFile->getFileName() ); @@ -245,7 +246,7 @@ public function process( File $phpcsFile, $stackPtr ) { return; // @codeCoverageIgnore } - $directory = $this->normalize_directory_separators( \dirname( $file ) ); + $directory = trim( PathHelper::normalize_directory_separators( \dirname( $file ) ), '//' ); $relative_directory = Common::stripBasepath( $directory, $base_path ); if ( $relative_directory === '.' ) { $relative_directory = ''; @@ -366,7 +367,7 @@ private function validate_src_directory() { continue; } - $directory = $this->normalize_directory_separators( $directory ); + $directory = trim( PathHelper::normalize_directory_separators( $directory ), '//' ); if ( $directory === '.' ) { // The basepath/root directory is the default, so ignore. @@ -390,15 +391,4 @@ private function validate_src_directory() { // Set the validated prefixes cache. $this->validated_src_directory = $validated; } - - /** - * Normalize all directory separators to be a forward slash and remove prefixed and suffixed slashes. - * - * @param string $path Path to normalize. - * - * @return string - */ - private function normalize_directory_separators( $path ) { - return \trim( \strtr( $path, '\\', '/' ), '/' ); - } } From d2837d18ce2390bb9c3f3082fb3a7c3e45cb3674 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 19 Oct 2023 09:20:06 +0200 Subject: [PATCH 156/212] NamingConventions/NamespaceName: improve path handling This changes the `src_directory` property handling. Originally, the file paths would always be stripped down to a relative path in relation to the `basepath`. Now, the `src_directory` path comparison(s) will always be based on the absolute path, including the `basepath`. This should have a small performance benefit, in that some of the path manipulation is moved to the `validate_src_directory()` method, the logic of which under normal circumstances, will only be run once, while the `process()` method is run for every `namespace` keyword encountered. This also implements the use of some additional functions from the `PathHelper` and the `PathValidationHelper` classes. Includes updating a pre-existing test to pass duplicate excluded files in different ways. --- .../NamingConventions/NamespaceNameSniff.php | 98 ++++++------------- .../src/sub-b/path-translation-src-sub-b.inc | 2 +- 2 files changed, 33 insertions(+), 67 deletions(-) diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index 908160bc..e7373646 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -4,12 +4,12 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Common; use PHPCSUtils\Utils\Namespaces; use PHPCSUtils\Utils\NamingConventions; use PHPCSUtils\Utils\TextStrings; use YoastCS\Yoast\Utils\CustomPrefixesTrait; use YoastCS\Yoast\Utils\PathHelper; +use YoastCS\Yoast\Utils\PathValidationHelper; /** * Check namespace name declarations. @@ -81,14 +81,14 @@ final class NamespaceNameSniff implements Sniff { /** * Project roots after validation. * - * Validated src_directories will look like `src/`, i.e.: - * - have linux slashes; - * - not be prefixed with a slash; - * - have a trailing slash. + * Validated src_directories will look like "$basepath/src/", i.e.: + * - absolute paths; + * - with linux slashes; + * - and a trailing slash. * * @var array */ - private $validated_src_directory = []; + private $validated_src_directory; /** * Cache of previously set project roots. @@ -97,7 +97,7 @@ final class NamespaceNameSniff implements Sniff { * * @var array */ - private $previous_src_directory = []; + private $previous_src_directory; /** * Returns an array of tokens this test wants to listen for. @@ -237,48 +237,32 @@ public function process( File $phpcsFile, $stackPtr ) { return; } - $base_path = trim( PathHelper::normalize_directory_separators( $phpcsFile->config->basepath ), '//' ); - // Stripping potential quotes to ensure `stdin_path` passed by IDEs does not include quotes. $file = TextStrings::stripQuotes( $phpcsFile->getFileName() ); - if ( $file === 'STDIN' ) { return; // @codeCoverageIgnore } - $directory = trim( PathHelper::normalize_directory_separators( \dirname( $file ) ), '//' ); - $relative_directory = Common::stripBasepath( $directory, $base_path ); - if ( $relative_directory === '.' ) { - $relative_directory = ''; - } - else { - if ( $relative_directory[0] !== '/' ) { - /* - * Basepath stripping appears to work differently depending on OS. - * On Windows there still is a slash at the start, on Unix/Mac there isn't. - * Normalize to allow comparison. - */ - $relative_directory = '/' . $relative_directory; - } - - // Add trailing slash to prevent matching '/sub' to '/sub-directory'. - $relative_directory .= '/'; - } + $directory = PathHelper::normalize_absolute_path( \dirname( $file ) ); + $relative_directory = ''; - $this->validate_src_directory(); + $this->validate_src_directory( $phpcsFile ); if ( empty( $this->validated_src_directory ) === false ) { - foreach ( $this->validated_src_directory as $subdirectory ) { - if ( \strpos( $relative_directory, $subdirectory ) !== 0 ) { + foreach ( $this->validated_src_directory as $absolute_src_path ) { + if ( PathHelper::path_starts_with( $directory, $absolute_src_path ) === false ) { continue; } - $relative_directory = \substr( $relative_directory, \strlen( $subdirectory ) ); + $relative_directory = PathHelper::strip_basepath( $directory, $absolute_src_path ); + if ( $relative_directory === '.' ) { + $relative_directory = ''; + } break; } } - // Now any potential src directory has been stripped, remove the slashes again. + // Now any potential src directory has been stripped, remove surrounding slashes. $relative_directory = \trim( $relative_directory, '/' ); $expected = '[Plugin\Prefix]'; @@ -342,9 +326,11 @@ public function process( File $phpcsFile, $stackPtr ) { /** * Validate a $src_directory property when set in a custom ruleset. * + * @param File $phpcsFile The file being scanned. + * * @return void */ - private function validate_src_directory() { + private function validate_src_directory( File $phpcsFile ) { if ( $this->previous_src_directory === $this->src_directory ) { return; } @@ -352,43 +338,23 @@ private function validate_src_directory() { // Set the cache *before* validation so as to not break the above compare. $this->previous_src_directory = $this->src_directory; - $src_directory = (array) $this->src_directory; - $src_directory = \array_filter( \array_map( 'trim', $src_directory ) ); + // Clear out previously validated src directories. + $this->validated_src_directory = []; - if ( empty( $src_directory ) ) { - $this->validated_src_directory = []; - return; - } + // Note: the check whether a basepath is available is done in the main `process()` routine. + $base_path = PathHelper::normalize_absolute_path( $phpcsFile->config->basepath ); - $validated = []; - foreach ( $src_directory as $directory ) { - if ( \strpos( $directory, '..' ) !== false ) { - // Do not allow walking up the directory hierarchy. - continue; - } - - $directory = trim( PathHelper::normalize_directory_separators( $directory ), '//' ); - - if ( $directory === '.' ) { - // The basepath/root directory is the default, so ignore. - continue; - } - - if ( \strpos( $directory, './' ) === 0 ) { - $directory = \substr( $directory, 2 ); - } - - if ( $directory === '' ) { - continue; - } + // Add any src directories. + $absolute_paths = PathValidationHelper::relative_to_absolute( $phpcsFile, $this->src_directory ); - $validated[] = '/' . $directory . '/'; + // The base path is always a valid src directory. + if ( isset( $absolute_paths['.'] ) === false ) { + $absolute_paths['.'] = $base_path; } - // Use reverse natural sorting to get the longest directory first. - \rsort( $validated, ( \SORT_NATURAL | \SORT_FLAG_CASE ) ); + $this->validated_src_directory = \array_unique( $absolute_paths ); - // Set the validated prefixes cache. - $this->validated_src_directory = $validated; + // Use reverse natural sorting to get the longest directory first. + \rsort( $this->validated_src_directory, ( \SORT_NATURAL | \SORT_FLAG_CASE ) ); } } diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-b/path-translation-src-sub-b.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-b/path-translation-src-sub-b.inc index 7d85044f..91f698b3 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-b/path-translation-src-sub-b.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/src/sub-b/path-translation-src-sub-b.inc @@ -6,7 +6,7 @@ * Includes testing of matching the longest directory first. * * @phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin - * @phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ./src , ./src/sub-b + * @phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] ./src , ./src/sub-b,src/sub-b,/src */ namespace Yoast\WP\Plugin; // OK. From 0fa3146d26f630979f94c57661fa82ff33b8f9c6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 23 Oct 2023 08:04:02 +0200 Subject: [PATCH 157/212] :sparkles: New `PSR4PathsTrait` This commit introduces a new `PSR4PathsTrait` to add a custom `$psr4_paths` property to sniffs. The value expected by the `$psr4_paths` property mirrors the `autoload` directive for Composer, with the prefix as the array key and a comma separated list of relative paths as the array value. Multiple PSR-4 paths can be passed (array elements). Includes utility functions to validate the property value, to verify if an arbitrary file is in one of the PSR4 paths and to retrieve the matched PSR-4 path information. Includes dedicated unit tests for this trait. --- Yoast/Tests/Utils/PSR4PathsTraitTest.php | 545 +++++++++++++++++++++++ Yoast/Utils/PSR4PathsTrait.php | 173 +++++++ 2 files changed, 718 insertions(+) create mode 100644 Yoast/Tests/Utils/PSR4PathsTraitTest.php create mode 100644 Yoast/Utils/PSR4PathsTrait.php diff --git a/Yoast/Tests/Utils/PSR4PathsTraitTest.php b/Yoast/Tests/Utils/PSR4PathsTraitTest.php new file mode 100644 index 00000000..f3713a7e --- /dev/null +++ b/Yoast/Tests/Utils/PSR4PathsTraitTest.php @@ -0,0 +1,545 @@ +psr4_paths = []; + $this->previous_psr4_paths = []; + $this->validated_psr4_paths = []; + } + + /** + * Test validating the $psr4_paths property + * + * @dataProvider data_is_get_psr4_info + * @covers ::is_in_psr4_path + * + * @param array $psr4_paths The initial input for the $psr4_paths property. + * @param string $file_path The file path to evaluate. + * @param array $expected The expected function output. + * + * @return void + */ + public function test_is_in_psr4_path( $psr4_paths, $file_path, $expected ) { + $phpcsFile = $this->get_mock_file(); + $phpcsFile->config->basepath = self::CLEAN_BASEPATH; + $phpcsFile->path = $file_path; + + $this->psr4_paths = $psr4_paths; + + $this->assertSame( $expected['is'], $this->is_in_psr4_path( $phpcsFile ) ); + } + + /** + * Test validating the $psr4_paths property + * + * @dataProvider data_is_get_psr4_info + * @covers ::get_psr4_info + * + * @param array $psr4_paths The initial input for the $psr4_paths property. + * @param string $file_path The file path to evaluate. + * @param array $expected The expected function output. + * + * @return void + */ + public function test_get_psr4_info( $psr4_paths, $file_path, $expected ) { + $phpcsFile = $this->get_mock_file(); + $phpcsFile->config->basepath = self::DIRTY_BASEPATH; + $phpcsFile->path = $file_path; + + $this->psr4_paths = $psr4_paths; + + $this->assertSame( $expected['get'], $this->get_psr4_info( $phpcsFile ) ); + } + + /** + * Test validating the $psr4_paths property + * + * @dataProvider data_is_get_psr4_info + * @covers ::get_psr4_info + * + * @param array $psr4_paths The initial input for the $psr4_paths property. + * @param string $file_path The file path to evaluate. + * @param array $expected The expected function output. + * + * @return void + */ + public function test_get_psr4_info_explicit( $psr4_paths, $file_path, $expected ) { + $phpcsFile = $this->get_mock_file(); + $phpcsFile->config->basepath = self::DIRTY_BASEPATH; + + $this->psr4_paths = $psr4_paths; + + $this->assertSame( $expected['get'], $this->get_psr4_info( $phpcsFile, $file_path ) ); + } + + /** + * Data provider. + * + * @see test_is_in_psr4_path() For the array format. + * @see test_get_psr4_info() For the array format. + * @see test_get_psr4_info_explicit() For the array format. + * + * @return array>> + */ + public static function data_is_get_psr4_info() { + $default_psr4_paths = self::data_validate_psr4_paths()['multiple prefixes, variation of paths']['psr4_paths']; + + return [ + 'path is unknown' => [ + 'psr4_paths' => [], + 'file_path' => '', + 'expected' => [ + 'is' => false, + 'get' => false, + ], + ], + 'path is STDIN' => [ + 'psr4_paths' => [], + 'file_path' => 'STDIN', + 'expected' => [ + 'is' => false, + 'get' => false, + ], + ], + 'path is single-quoted STDIN' => [ + 'psr4_paths' => [], + 'file_path' => "'STDIN'", + 'expected' => [ + 'is' => false, + 'get' => false, + ], + ], + 'path is double-quoted STDIN' => [ + 'psr4_paths' => [], + 'file_path' => '"STDIN"', + 'expected' => [ + 'is' => false, + 'get' => false, + ], + ], + 'PSR-4 paths is empty/not set' => [ + 'psr4_paths' => [], + 'file_path' => 'path/is/not/relevant', + 'expected' => [ + 'is' => false, + 'get' => false, + ], + ], + 'Linux style file path, not matching' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => 'path/will/not/match/filename.ext', + 'expected' => [ + 'is' => false, + 'get' => false, + ], + ], + 'Linux style file path, matching' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => self::DIRTY_BASEPATH . '/config/subdir/filename.ext', + 'expected' => [ + 'is' => true, + 'get' => [ + 'prefix' => 'Plugin\PrefixB', + 'basepath' => self::CLEAN_BASEPATH . 'config/', + 'relative' => 'subdir', + ], + ], + ], + 'Windows style file path, not matching' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => 'path\will\not\match\filename.ext', + 'expected' => [ + 'is' => false, + 'get' => false, + ], + ], + 'Windows style file path, matching' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => '\base\path\config\subdir\filename.ext', + 'expected' => [ + 'is' => true, + 'get' => [ + 'prefix' => 'Plugin\PrefixB', + 'basepath' => self::CLEAN_BASEPATH . 'config/', + 'relative' => 'subdir', + ], + ], + ], + 'Mixed slashes in file path, not matching' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => self::DIRTY_BASEPATH . '\subdir\filename.ext', + 'expected' => [ + 'is' => false, + 'get' => false, + ], + ], + 'Mixed slashes in file path, matching' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => self::CLEAN_BASEPATH . 'subA\sub/deeper/filename.ext', + 'expected' => [ + 'is' => true, + 'get' => [ + 'prefix' => 'Plugin\PrefixC', + 'basepath' => self::CLEAN_BASEPATH . 'subA/sub/', + 'relative' => 'deeper', + ], + ], + ], + 'Exact match, no file name' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => self::CLEAN_BASEPATH . 'tests/', + 'expected' => [ + 'is' => true, + 'get' => [ + 'prefix' => 'Plugin\PrefixB', + 'basepath' => self::CLEAN_BASEPATH . 'tests/', + 'relative' => '.', + ], + ], + ], + 'Exact match, no file name, no trailing slash' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => self::CLEAN_BASEPATH . 'subC', + 'expected' => [ + 'is' => true, + 'get' => [ + 'prefix' => 'Plugin\PrefixC', + 'basepath' => self::CLEAN_BASEPATH . 'subC/', + 'relative' => '.', + ], + ], + ], + 'Exact match, with file name' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => self::CLEAN_BASEPATH . 'subC/file.ext', + 'expected' => [ + 'is' => true, + 'get' => [ + 'prefix' => 'Plugin\PrefixC', + 'basepath' => self::CLEAN_BASEPATH . 'subC/', + 'relative' => '.', + ], + ], + ], + 'Long subdir, matching' => [ + 'psr4_paths' => $default_psr4_paths, + 'file_path' => self::CLEAN_BASEPATH . 'src/something/else/and/more/file.ext', + 'expected' => [ + 'is' => true, + 'get' => [ + 'prefix' => 'Plugin\PrefixA', + 'basepath' => self::CLEAN_BASEPATH . 'src/', + 'relative' => 'something/else/and/more', + ], + ], + ], + ]; + } + + /** + * Test validating the $psr4_paths property when no basepath is present. + * + * @covers ::validate_psr4_paths + * + * @return void + */ + public function test_validate_psr4_paths_without_basepath_doesnt_set_validated() { + $phpcsFile = $this->get_mock_file(); + + $this->psr4_paths = self::data_validate_psr4_paths()['multiple prefixes, variation of paths']['psr4_paths']; + + $this->validate_psr4_paths( $phpcsFile ); + + $this->assertSame( [], $this->previous_psr4_paths, 'Previous paths has been set' ); + $this->assertSame( [], $this->validated_psr4_paths, 'Validated paths has been set' ); + } + + /** + * Test validating the $psr4_paths property when no basepath is present while a previous run did have a basepath. + * + * @covers ::validate_psr4_paths + * + * @return void + */ + public function test_validate_psr4_paths_without_basepath_resets_validated() { + $phpcsFile = $this->get_mock_file(); + + // Initial test with basepath. + $phpcsFile->config->basepath = self::DIRTY_BASEPATH; + + $input = self::data_validate_psr4_paths()['multiple prefixes, variation of paths']; + $this->psr4_paths = $input['psr4_paths']; + + $this->validate_psr4_paths( $phpcsFile ); + + $this->assertSame( $input['psr4_paths'], $this->previous_psr4_paths, 'Previous paths has not been set correctly' ); + $this->assertSame( $input['expected'], $this->validated_psr4_paths, 'Validated paths has not been set correctly' ); + + // Now make sure that the missing basepath resets the validated paths. + $phpcsFile->config->basepath = null; + + $this->validate_psr4_paths( $phpcsFile ); + + $this->assertSame( $input['psr4_paths'], $this->previous_psr4_paths, 'Previous paths is not still the same' ); + $this->assertSame( [], $this->validated_psr4_paths, 'Validated paths has not been reset' ); + } + + /** + * Test validating the $psr4_paths property results in an exception when no prefixes are passed. + * + * @covers ::validate_psr4_paths + * + * @return void + */ + public function test_validate_psr4_paths_throws_exception_on_missing_prefixes() { + $phpcsFile = $this->get_mock_file(); + $phpcsFile->config->basepath = self::CLEAN_BASEPATH; + + // PSR-4 paths contains the same path for two different prefixes. + $this->psr4_paths = [ + 'src', + 'tests', + ]; + + $this->expectException( RuntimeException::class ); + $this->expectExceptionMessage( + 'Invalid value passed for `psr4_paths`. Path "src" is not associated with a namespace prefix' + ); + + $this->validate_psr4_paths( $phpcsFile ); + } + + /** + * Test validating the $psr4_paths property results in an exception when the same path is passed for multiple prefixes. + * + * @covers ::validate_psr4_paths + * + * @return void + */ + public function test_validate_psr4_paths_throws_exception_on_duplicate_paths_for_different_prefixes() { + $phpcsFile = $this->get_mock_file(); + $phpcsFile->config->basepath = self::DIRTY_BASEPATH; + + // PSR-4 paths contains the same path for two different prefixes. + $this->psr4_paths = [ + 'Plugin\Prefix\\' => 'src/', + 'Plugin\Prefix\Tests\\' => 'src/,tests/', + ]; + + $this->expectException( RuntimeException::class ); + $this->expectExceptionMessage( + 'Invalid value passed for `psr4_paths`. Multiple prefixes include the same path. Problem path: ' . self::CLEAN_BASEPATH . 'src/' + ); + + $this->validate_psr4_paths( $phpcsFile ); + } + + /** + * Test validating the $psr4_paths property + * + * @dataProvider data_validate_psr4_paths + * @covers ::validate_psr4_paths + * + * @param array $psr4_paths The initial input for the $psr4_paths property. + * @param array $expected The expected value for the validated property. + * + * @return void + */ + public function test_validate_psr4_paths( $psr4_paths, $expected ) { + $phpcsFile = $this->get_mock_file(); + $phpcsFile->config->basepath = self::CLEAN_BASEPATH; + + $this->psr4_paths = $psr4_paths; + + $this->validate_psr4_paths( $phpcsFile ); + + $this->assertSame( $psr4_paths, $this->previous_psr4_paths ); + $this->assertSame( $expected, $this->validated_psr4_paths ); + } + + /** + * Data provider. + * + * @see test_validate_psr4_paths() For the array format. + * + * @return array>> + */ + public static function data_validate_psr4_paths() { + return [ + 'empty array' => [ + 'psr4_paths' => [], + 'expected' => [], + ], + 'array with only empty values' => [ + 'psr4_paths' => [ + 'Plugin\PrefixA' => '', + 'Plugin\PrefixB' => ' ', + 'Plugin\PrefixC' => ' ', + 'Plugin\PrefixD' => ' ,, " ", \' \' ', + ], + 'expected' => [], + ], + + 'single prefix, single path; no trailing slash at end of prefix' => [ + 'psr4_paths' => [ + 'Plugin\Prefix' => 'src', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + ], + ], + 'single prefix, single path; trailing slash at end of prefix' => [ + 'psr4_paths' => [ + 'Plugin\Prefix\\' => 'src', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + ], + ], + 'single prefix, single path; double slashes within prefix' => [ + 'psr4_paths' => [ + 'Plugin\\Prefix\\Sub\\' => 'src', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix\Sub', + ], + ], + + 'single prefix, single path; path has leading slash' => [ + 'psr4_paths' => [ + 'Plugin\Prefix' => '/src', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + ], + ], + 'single prefix, single path; path has trailing slash' => [ + 'psr4_paths' => [ + 'Plugin\Prefix\\' => 'src/', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + ], + ], + 'single prefix, single path; path has leading dot-slash and trailing slash' => [ + 'psr4_paths' => [ + 'Plugin\Prefix\\' => './src/', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + ], + ], + + 'single prefix, multiple paths; simple comma-separated value' => [ + 'psr4_paths' => [ + 'Plugin\Prefix' => 'src,tests,config', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'tests/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'config/' => 'Plugin\Prefix', + ], + ], + 'single prefix, multiple paths; leading/trailing slash variations in comma-separated value' => [ + 'psr4_paths' => [ + 'Plugin\Prefix\\' => 'src/,./tests/,/config', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'tests/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'config/' => 'Plugin\Prefix', + ], + ], + 'single prefix, multiple paths; brackets around comma-separated value and spaces within' => [ + 'psr4_paths' => [ + 'Plugin\Prefix' => '[src, tests, config]', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'tests/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'config/' => 'Plugin\Prefix', + ], + ], + 'single prefix, multiple paths; brackets around comma-separated value and spaces and single quotes within' => [ + 'psr4_paths' => [ + 'Plugin\Prefix\\' => "[ 'src', 'tests' , ' config ']", + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'tests/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'config/' => 'Plugin\Prefix', + ], + ], + 'single prefix, multiple paths; brackets around comma-separated value and spaces and double quotes within' => [ + 'psr4_paths' => [ + 'Plugin\Prefix' => '[ "src/", " ./tests", "/config/ " ]', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'tests/' => 'Plugin\Prefix', + self::CLEAN_BASEPATH . 'config/' => 'Plugin\Prefix', + ], + ], + 'single prefix, multiple paths; duplicate values' => [ + 'psr4_paths' => [ + 'Plugin\Prefix\\' => 'src, "./src" , /src/', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\Prefix', + ], + ], + + 'multiple prefixes, variation of paths' => [ + 'psr4_paths' => [ + 'Plugin\PrefixA' => 'src', + 'Plugin\PrefixB\\' => 'tests/,config/', + 'Plugin\PrefixC' => '[ \'subA\sub\\\', "subB" , subC]', + 'Plugin\PrefixD\\' => ' ,, " ", \' \' ', + ], + 'expected' => [ + self::CLEAN_BASEPATH . 'src/' => 'Plugin\PrefixA', + self::CLEAN_BASEPATH . 'tests/' => 'Plugin\PrefixB', + self::CLEAN_BASEPATH . 'config/' => 'Plugin\PrefixB', + self::CLEAN_BASEPATH . 'subA/sub/' => 'Plugin\PrefixC', + self::CLEAN_BASEPATH . 'subB/' => 'Plugin\PrefixC', + self::CLEAN_BASEPATH . 'subC/' => 'Plugin\PrefixC', + ], + ], + ]; + } +} diff --git a/Yoast/Utils/PSR4PathsTrait.php b/Yoast/Utils/PSR4PathsTrait.php new file mode 100644 index 00000000..d40f5451 --- /dev/null +++ b/Yoast/Utils/PSR4PathsTrait.php @@ -0,0 +1,173 @@ + + * + * + * + * + * + * + * + * + * + * ``` + * + * Note: paths are handled case-sensitively! + * + * @var array Key should be the prefix, value a comma-separated list of relative paths. + */ + public $psr4_paths = []; + + /** + * Cache of previously set list of psr4 paths. + * + * Prevents having to do the same path validation over and over again. + * + * @var array + */ + private $previous_psr4_paths = []; + + /** + * Validated & cleaned up list of absolute paths to the directories expecting PSR-4 file names + * with their associated prefixes. + * + * @var array Key is the absolute path, value the applicable prefix without trailing slash. + */ + private $validated_psr4_paths = []; + + /** + * Check if the file is in one of the PSR4 directories. + * + * @param File $phpcsFile The file being scanned. + * @param string $path_to_file Optional The absolute path to the file currently being examined. + * If not provided, the file name will be retrieved from the File object. + * + * @return bool + */ + final protected function is_in_psr4_path( File $phpcsFile, $path_to_file = '' ) { + return \is_array( $this->get_psr4_info( $phpcsFile, $path_to_file ) ); + } + + /** + * Retrieve all applicable information for a PSR-4 path. + * + * @param File $phpcsFile The file being scanned. + * @param string $path_to_file Optional The absolute path to the file currently being examined. + * If not provided, the file name will be retrieved from the File object. + * + * @return array|false Array with information about the PSR-4 path. Otherwise FALSE. + */ + final protected function get_psr4_info( File $phpcsFile, $path_to_file = '' ) { + if ( $path_to_file === '' ) { + $path_to_file = TextStrings::stripQuotes( $phpcsFile->getFileName() ); + if ( $path_to_file === 'STDIN' ) { + return false; + } + } + + $this->validate_psr4_paths( $phpcsFile ); + if ( empty( $this->validated_psr4_paths ) ) { + return false; + } + + $path_to_file = PathHelper::normalize_absolute_path( $path_to_file ); + + foreach ( $this->validated_psr4_paths as $psr4_path => $prefix ) { + $remainder = PathHelper::strip_basepath( $path_to_file, $psr4_path ); + if ( $remainder === $path_to_file ) { + // Nothing was stripped, so this wasn't a match. + continue; + } + + return [ + 'prefix' => $prefix, + 'basepath' => $psr4_path, + 'relative' => \dirname( $remainder ), + ]; + } + + return false; + } + + /** + * Validate the list of PSR-4 paths passed from a custom ruleset. + * + * This will only need to be done once in a normal PHPCS run, though for + * tests the function may be called multiple times. + * + * @param File $phpcsFile The file being scanned. + * + * @return void + * + * @throws RuntimeException When the `psr4_paths` array is missing keys. + * @throws RuntimeException When the `psr4_paths` array contains duplicate paths in multiple entries. + */ + private function validate_psr4_paths( File $phpcsFile ) { + // The basepath check needs to be done first as otherwise the previous/current comparison would be broken. + if ( ! isset( $phpcsFile->config->basepath ) ) { + // Only relevant for the tests: make sure previously set validated paths are cleared out. + $this->validated_psr4_paths = []; + + // No use continuing as we can't turn relative paths into absolute paths. + return; + } + + if ( $this->previous_psr4_paths === $this->psr4_paths ) { + return; + } + + // Set the cache *before* validation so as to not break the above compare. + $this->previous_psr4_paths = $this->psr4_paths; + + $validated_paths = []; + + foreach ( $this->psr4_paths as $prefix => $paths ) { + if ( \is_string( $prefix ) === false || $prefix === '' ) { + throw new RuntimeException( + 'Invalid value passed for `psr4_paths`. Path "' . $paths . '" is not associated with a namespace prefix' + ); + } + + $prefix = \rtrim( $prefix, '\\' ); + + $paths = \rtrim( \ltrim( $paths, '[' ), ']' ); // Trim off potential [] copied over from Composer.json. + $paths = \explode( ',', $paths ); + $paths = \array_map( 'trim', $paths ); + $paths = \array_map( [ TextStrings::class, 'stripQuotes' ], $paths ); // Trim off potential quotes around the paths copied over. + $paths = \array_map( 'trim', $paths ); + $paths = PathValidationHelper::relative_to_absolute( $phpcsFile, $paths ); + $paths = \array_unique( $paths ); // Filter out multiple of the same paths for the same prefix. + + foreach ( $paths as $path ) { + if ( isset( $validated_paths[ $path ] ) ) { + throw new RuntimeException( + 'Invalid value passed for `psr4_paths`. Multiple prefixes include the same path. Problem path: ' . $path + ); + } + + $validated_paths[ $path ] = $prefix; + } + } + + // Set the validated value. + $this->validated_psr4_paths = $validated_paths; + } +} From 6c60a4e8300cb299d14e5e2ef2419109245f44d9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 23 Oct 2023 08:31:01 +0200 Subject: [PATCH 158/212] Files/FileName: add ability to check PSR-4-style file names As the Yoast plugin test directories will start to follow PSR-4. the `FileName` sniff will need to be able to enforce this. This commit adds this ability to the sniff. Notes: * It adds a new `public` `psr4_paths` ruleset property via the `PSR4PathsTrait` utility. * If the file being examined is in a path indicated as a PSR-4 path, PSR-4 based file names will be enforced. * For PSR-4 compliant file names, the file name has to match the OO name. This also means that "oo prefixes" should not be stripped and that the "excluded files" property will be ignored. * For non-OO files in a PSR-4 path, the _normal_ file name rules apply, i.e. lowercase and hyphenathed and if the file contains functions, the file name should have a `-functions` suffix. Includes ample tests for this new functionality. Includes updated XML documentation. Includes updating the YoastCS native PHPCS ruleset to enable the sniff as the YoastCS repo follows PSR-4 completely (as per the PHPCS file name rules). :point_right: The changes to the pre-existing code in the sniff will be easiest to review while ignoring whitespace changes. --- .phpcs.xml.dist | 11 +++-- Yoast/Docs/Files/FileNameStandard.xml | 27 ++++++++++++ Yoast/Sniffs/Files/FileNameSniff.php | 44 ++++++++++++------- Yoast/Tests/Files/FileNameUnitTest.php | 38 +++++++++++++++- .../PSR4/Dot_Prefixed_Path.inc | 8 ++++ .../PSR4/Illegal_PSR4_Path.inc | 8 ++++ .../FileNameUnitTests/PSR4/Multiple_Paths.inc | 8 ++++ .../FileNameUnitTests/PSR4/Not_Excluded.inc | 11 +++++ .../PSR4/Prefix_Stripped.inc | 11 +++++ .../FileNameUnitTests/PSR4/Some_Class.inc | 8 ++++ .../FileNameUnitTests/PSR4/Some_Enum.inc | 8 ++++ .../FileNameUnitTests/PSR4/Some_Interface.inc | 8 ++++ .../FileNameUnitTests/PSR4/Some_Trait.inc | 8 ++++ .../FileNameUnitTests/PSR4/WPSEO_Prefixed.inc | 11 +++++ .../FileNameUnitTests/PSR4/Wrong_Case_Too.inc | 8 ++++ .../FileNameUnitTests/PSR4/Wrong_Class.inc | 8 ++++ .../FileNameUnitTests/PSR4/Wrong_Enum.inc | 8 ++++ .../PSR4/Wrong_Interface.inc | 8 ++++ .../FileNameUnitTests/PSR4/Wrong_Trait.inc | 8 ++++ .../FileNameUnitTests/PSR4/Yoast_Prefixed.inc | 11 +++++ .../PSR4/illegal-psr4-path.inc | 8 ++++ .../FileNameUnitTests/PSR4/missing-suffix.inc | 11 +++++ .../PSR4/no-oo-functions.inc | 11 +++++ .../Files/FileNameUnitTests/PSR4/no-oo.inc | 9 ++++ .../FileNameUnitTests/PSR4/wrong_case.inc | 8 ++++ .../PSR4_Path_Is_Root_Path.inc | 8 ++++ .../PSR4_Path_Root_Wrong_Name.inc | 8 ++++ .../FileNameUnitTests/no-basepath-psr4.inc | 8 ++++ .../not-in-psr4-path-wrong-name.inc | 8 ++++ .../FileNameUnitTests/not-in-psr4-path.inc | 8 ++++ 30 files changed, 326 insertions(+), 21 deletions(-) create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Dot_Prefixed_Path.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Illegal_PSR4_Path.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Multiple_Paths.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Not_Excluded.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Prefix_Stripped.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Some_Class.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Some_Enum.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Some_Interface.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Some_Trait.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/WPSEO_Prefixed.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Wrong_Case_Too.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Wrong_Class.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Wrong_Enum.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Wrong_Interface.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Wrong_Trait.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/Yoast_Prefixed.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/illegal-psr4-path.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/missing-suffix.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/no-oo-functions.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/no-oo.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4/wrong_case.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4_Path_Is_Root_Path.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/PSR4_Path_Root_Wrong_Name.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/no-basepath-psr4.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/not-in-psr4-path-wrong-name.inc create mode 100644 Yoast/Tests/Files/FileNameUnitTests/not-in-psr4-path.inc diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 5e48d56e..ef8837bc 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -39,9 +39,6 @@ - - - @@ -77,6 +74,14 @@ ############################################################################# --> + + + + + + + + diff --git a/Yoast/Docs/Files/FileNameStandard.xml b/Yoast/Docs/Files/FileNameStandard.xml index 1d185498..957a4c34 100644 --- a/Yoast/Docs/Files/FileNameStandard.xml +++ b/Yoast/Docs/Files/FileNameStandard.xml @@ -65,6 +65,33 @@ class WPSEO_Utils {} + + + + Yoast_Output_Thing.php --> +Yoast_Output_Thing {} + ]]> + + + outline-something.php --> +Yoast_Outline_Something {} + ]]> + + + + diff --git a/Yoast/Sniffs/Files/FileNameSniff.php b/Yoast/Sniffs/Files/FileNameSniff.php index 2946d390..6192c2e1 100644 --- a/Yoast/Sniffs/Files/FileNameSniff.php +++ b/Yoast/Sniffs/Files/FileNameSniff.php @@ -9,6 +9,7 @@ use PHPCSUtils\Utils\TextStrings; use YoastCS\Yoast\Utils\PathHelper; use YoastCS\Yoast\Utils\PathValidationHelper; +use YoastCS\Yoast\Utils\PSR4PathsTrait; /** * Ensures files comply with the Yoast file name rules. @@ -24,10 +25,15 @@ * have a "-functions" suffix. * * @since 0.5 - * @since 3.0.0 The sniff will now also be enforced for files only using the PHP short open tag. + * @since 3.0.0 - The sniff will now also be enforced for files only using the PHP short open tag. + * - The sniff now also has the ability to check for PSR-4 compliant file names. + * + * @uses \YoastCS\Yoast\Utils\PSR4PathsTrait::$psr4_paths */ final class FileNameSniff implements Sniff { + use PSR4PathsTrait; + /** * Object tokens to search for in a file. * @@ -192,13 +198,19 @@ public function process( File $phpcsFile, $stackPtr ) { $this->add_missing_basepath_warning( $phpcsFile ); } - if ( $this->is_file_excluded( $phpcsFile, $file ) === false ) { - $oo_structure = $phpcsFile->findNext( self::NAMED_OO_TOKENS, $stackPtr ); - if ( $oo_structure !== false ) { + $oo_structure = $phpcsFile->findNext( self::NAMED_OO_TOKENS, $stackPtr ); + if ( $oo_structure !== false ) { + + $oo_name = ObjectDeclarations::getName( $phpcsFile, $oo_structure ); - $oo_name = ObjectDeclarations::getName( $phpcsFile, $oo_structure ); + if ( ! empty( $oo_name ) ) { - if ( ! empty( $oo_name ) ) { + if ( $this->is_in_psr4_path( $phpcsFile, $file ) ) { + $error = 'Directory marked as a PSR-4 path. File names should 100%% match the name of the OO structure contained in the file for PSR-4 compliance.'; + $error_code = 'InvalidPSR4FileName'; + $expected = $oo_name; + } + elseif ( $this->is_file_excluded( $phpcsFile, $file ) === false ) { $this->validate_oo_prefixes(); if ( ! empty( $this->clean_oo_prefixes ) ) { foreach ( $this->clean_oo_prefixes as $prefix ) { @@ -239,15 +251,15 @@ public function process( File $phpcsFile, $stackPtr ) { } } } - else { - $has_function = $phpcsFile->findNext( \T_FUNCTION, $stackPtr ); - if ( $has_function !== false && $file_name !== 'functions' ) { - $error = 'Files containing function declarations should have "-functions" as a suffix.'; - $error_code = 'InvalidFunctionsFileName'; - - if ( \substr( $expected, -10 ) !== '-functions' ) { - $expected .= '-functions'; - } + } + elseif ( $this->is_file_excluded( $phpcsFile, $file ) === false ) { + $has_function = $phpcsFile->findNext( \T_FUNCTION, $stackPtr ); + if ( $has_function !== false && $file_name !== 'functions' ) { + $error = 'Files containing function declarations should have "-functions" as a suffix.'; + $error_code = 'InvalidFunctionsFileName'; + + if ( \substr( $expected, -10 ) !== '-functions' ) { + $expected .= '-functions'; } } } @@ -374,7 +386,7 @@ private function add_missing_basepath_warning( File $phpcsFile ) { } $phpcsFile->addWarning( - 'For the exclude property to work with relative file path files, the --basepath needs to be set.', + 'For the excluded files and the psr4 paths properties to work with relative file paths, the --basepath needs to be set.', 0, 'MissingBasePath' ); diff --git a/Yoast/Tests/Files/FileNameUnitTest.php b/Yoast/Tests/Files/FileNameUnitTest.php index 4711af0e..0b04c8b0 100644 --- a/Yoast/Tests/Files/FileNameUnitTest.php +++ b/Yoast/Tests/Files/FileNameUnitTest.php @@ -101,12 +101,41 @@ final class FileNameUnitTest extends AbstractSniffUnitTest { 'partial-file-disable.inc' => 1, 'Errorcode_Disable.inc' => 1, // The sniff can only be disabled completely, not by error code. + // PSR4 file names. + 'Some_Class.inc' => 0, + 'Some_Enum.inc' => 0, + 'Some_Interface.inc' => 0, + 'Some_Trait.inc' => 0, + 'Wrong_Class.inc' => 1, // Filename not in line with class name. + 'Wrong_Enum.inc' => 1, // Filename not in line with enum name. + 'Wrong_Interface.inc' => 1, // Filename not in line with interface name. + 'Wrong_Trait.inc' => 1, // Filename not in line with trait name. + 'wrong_case.inc' => 1, // Names are case-sensitive. + 'Wrong_Case_Too.inc' => 1, // Names are case-sensitive. + 'WPSEO_Prefixed.inc' => 0, // Prefixes should not be stripped for PSR4 file names. + 'Yoast_Prefixed.inc' => 0, // Prefixes should not be stripped for PSR4 file names. + 'Prefix_Stripped.inc' => 1, // Prefixes should not be stripped for PSR4 file names. + 'Not_Excluded.inc' => 1, // Exclusions do not apply to files where prefix stripping is not supported. + 'no-oo.inc' => 0, // Files not containing OO should follow the normal rules for PSR4 dirs. + 'no-oo-functions.inc' => 0, // Files containing only functions should follow the normal rules for PSR4 dirs. + 'missing-suffix.inc' => 1, // Files containing only functions should follow the normal rules for PSR4 dirs. + 'Multiple_Paths.inc' => 0, + 'Dot_Prefixed_Path.inc' => 0, + 'illegal-psr4-path.inc' => 0, // PSR4 path ignored, so normal rules apply. + 'Illegal_PSR4_Path.inc' => 1, // PSR4 path ignored, so normal rules apply. + /* * In /. */ // Fall-back file in case glob() fails. 'FileNameUnitTest.inc' => 1, + + // PSR4 related, PSR4 dir is root dir. + 'not-in-psr4-path.inc' => 0, + 'not-in-psr4-path-wrong-name.inc' => 1, // Filename not in line with class name, non-PSR4. + 'PSR4_Path_Is_Root_Path.inc' => 0, + 'PSR4_Path_Root_Wrong_Name.inc' => 1, // Filename not in line with class name, PSR4. ]; /** @@ -118,7 +147,7 @@ final class FileNameUnitTest extends AbstractSniffUnitTest { * @return void */ public function setCliValues( $filename, $config ): void { - if ( $filename === 'no-basepath.inc' ) { + if ( $filename === 'no-basepath.inc' || $filename === 'no-basepath-psr4.inc' ) { return; } @@ -169,7 +198,12 @@ public function getErrorList( string $testFile = '' ): array { * @return array Key is the line number, value is the number of expected warnings. */ public function getWarningList( string $testFile = '' ): array { - if ( $testFile === 'no-basepath.inc' ) { + /* + * Note: no warning for the 'no-basepath.inc' file as the warning will only be thrown once. + * Also note that in which file the warning is thrown, relies on the sorting order of the + * test files, which could change. + */ + if ( $testFile === 'no-basepath-psr4.inc' ) { return [ 1 => 1, ]; diff --git a/Yoast/Tests/Files/FileNameUnitTests/PSR4/Dot_Prefixed_Path.inc b/Yoast/Tests/Files/FileNameUnitTests/PSR4/Dot_Prefixed_Path.inc new file mode 100644 index 00000000..790bc3c4 --- /dev/null +++ b/Yoast/Tests/Files/FileNameUnitTests/PSR4/Dot_Prefixed_Path.inc @@ -0,0 +1,8 @@ + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>./PSR4 + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>PSR4/../PSR4/../ + + +phpcs:set Yoast.Files.FileName psr4_paths[] PrefixA\=>Other_Path/,PrefixB=>/PSR4/,PrefixC\=>/Deep/Path/Sub/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>/PSR4/ +phpcs:set Yoast.Files.FileName excluded_files_strict_check[] PSR4/Not_Excluded.inc + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>/PSR4/ +phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\=>PSR4 + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>/PSR4 + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>/PSR4/ +phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>PSR4 + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>/PSR4 + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] PrefixA=>/src/,PrefixB=>/PSR4/ +phpcs:set Yoast.Files.FileName oo_prefixes[] wpseo,yoast + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>PSR4/../PSR4/../ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>. + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>. + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>/PSR4/ + + +phpcs:set Yoast.Files.FileName psr4_paths[] Prefix\\=>/PSR4/ + + Date: Fri, 17 Nov 2023 05:09:57 +0100 Subject: [PATCH 159/212] NamingConventions/NamespaceName: add support for strict PSR-4 compliance checking As the Yoast plugin test directories will start to follow PSR-4. the `NamespaceName` sniff will need to be able to enforce this. This commit adds this ability to the sniff. Notes: * It adds a new `public` `psr4_paths` ruleset property via the `PSR4PathsTrait` utility. * If the file being examined is in a path indicated as a PSR-4 path, PSR-4 based namespace names will be enforced. The differences between the "old-style" enforcement and PSR-4 are in case-sensitivity and in how characters which are allowed in paths, but not allowed in namespace names are handled. * Includes updated error message/code for the "missing prefix" check when the file is in a PSR-4 path. * Includes updated/adjusted logic for the "not counting of `Tests`/`Doubles`" directories as the `Tests` may be part of the PSR-4 namespace. Also note that when both a `psr4_paths` as well as the `src_directory` and `prefixes` properties are set, the `psr4_paths` property will take precedence and the sniff will only fall back to the previous logic if the file is not in a path matching one of the PSR-4 directories. Includes ample tests for this new functionality. Includes updated XML documentation. Includes updating the YoastCS native PHPCS ruleset to indicate that the YoastCS repo follows PSR-4 (as per the PHPCS file name rules). Note: if the name in use in the file is causing a problematic parse error (like in the test with the `#` in the namespace name), the sniff will stay silent. This is the normal behaviour for a PHPCS check when encountering parse errors. :point_right: The sniff changes are probably easiest to review while ignoring whitespace. --- .phpcs.xml.dist | 7 +- .../NamespaceNameStandard.xml | 28 ++- .../NamingConventions/NamespaceNameSniff.php | 188 +++++++++++++----- .../NamespaceNameUnitTest.php | 94 +++++++++ ...h-translation-src-deeper-than-psr4-sub.inc | 22 ++ ...translation-psr4-case-sensitive-proper.inc | 74 +++++++ ...-translation-psr4-case-sensitive-lower.inc | 14 ++ .../PSR4_Path/path-translation-psr4.inc | 78 ++++++++ .../sub path/path-translation-psr4-space.inc | 12 ++ .../sub#path/path-translation-psr4-hash.inc | 12 ++ .../sub-path/path-translation-psr4-dash.inc | 12 ++ .../sub.path/path-translation-psr4-dot.inc | 12 ++ .../NamespaceNameUnitTests/no-basepath.inc | 51 +++++ 13 files changed, 544 insertions(+), 60 deletions(-) create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/ProperCase/SubPath/path-translation-src-deeper-than-psr4-sub.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/ProperCase/path-translation-psr4-case-sensitive-proper.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/lowercase/path-translation-psr4-case-sensitive-lower.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/path-translation-psr4.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub path/path-translation-psr4-space.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub#path/path-translation-psr4-hash.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub-path/path-translation-psr4-dash.inc create mode 100644 Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub.path/path-translation-psr4-dot.inc diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 5e48d56e..fb90a0ec 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -79,11 +79,8 @@ - - - - - + + diff --git a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml index 50d595bc..be5f3bf0 100644 --- a/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml +++ b/Yoast/Docs/NamingConventions/NamespaceNameStandard.xml @@ -50,10 +50,18 @@ namespace Yoast\WP\Plugin\Tests\Foo\Bar\Flo\Sub; @@ -72,4 +80,20 @@ namespace Yoast\WP\Plugin\Unrelated; ]]> + + + User_Forms/file.php --> +User_Forms; + ]]> + + + User_forms/file.php --> +user_Forms; + ]]> + + diff --git a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php index e7373646..293aff39 100644 --- a/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php +++ b/Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php @@ -10,6 +10,7 @@ use YoastCS\Yoast\Utils\CustomPrefixesTrait; use YoastCS\Yoast\Utils\PathHelper; use YoastCS\Yoast\Utils\PathValidationHelper; +use YoastCS\Yoast\Utils\PSR4PathsTrait; /** * Check namespace name declarations. @@ -23,13 +24,16 @@ * placed in. * * @since 2.0.0 - * @since 3.0.0 Added new check to verify a prefix is used. + * @since 3.0.0 - Added new check to verify a prefix is used. + * - The sniff now also has the ability to check for PSR-4 compliant namespace names. * * @uses \YoastCS\Yoast\Utils\CustomPrefixesTrait::$prefixes + * @uses \YoastCS\Yoast\Utils\PSR4PathsTrait::$psr4_paths */ final class NamespaceNameSniff implements Sniff { use CustomPrefixesTrait; + use PSR4PathsTrait; /** * Double/Mock/Fixture directories to allow for. @@ -135,14 +139,42 @@ public function process( File $phpcsFile, $stackPtr ) { return; } - $this->validate_prefixes(); + // Stripping potential quotes to ensure `stdin_path` passed by IDEs does not include quotes. + $file = TextStrings::stripQuotes( $phpcsFile->getFileName() ); + if ( $file === 'STDIN' ) { + $file = ''; // @codeCoverageIgnore + } + else { + $file = PathHelper::normalize_absolute_path( $file ); + } + + $valid_prefixes = []; + $psr4_info = false; + if ( $file !== '' ) { + $psr4_info = $this->get_psr4_info( $phpcsFile, $file ); + } + + if ( \is_array( $psr4_info ) ) { + // If a PSR4 path matched, there will only ever be one valid prefix for the matched path. + $valid_prefixes = [ $psr4_info['prefix'] . '\\' ]; + } + else { + // Safeguard that the PSR-4 prefixes are always included. + // Makes sure level depth check still happens even if there is no basepath or path doesn't match PSR-4 path. + if ( empty( $this->prefixes ) && ! empty( $this->psr4_paths ) ) { + $this->prefixes = \array_keys( $this->psr4_paths ); + } + + $this->validate_prefixes(); + $valid_prefixes = $this->validated_prefixes; + } // Strip off the (longest) plugin prefix. $namespace_name_no_prefix = $namespace_name; $found_prefix = ''; - if ( ! empty( $this->validated_prefixes ) ) { + if ( ! empty( $valid_prefixes ) ) { $name = $namespace_name . '\\'; // Validated prefixes always have a \ at the end. - foreach ( $this->validated_prefixes as $prefix ) { + foreach ( $valid_prefixes as $prefix ) { if ( \strpos( $name, $prefix ) === 0 ) { $namespace_name_no_prefix = \rtrim( \substr( $name, \strlen( $prefix ) ), '\\' ); $found_prefix = \rtrim( $prefix, '\\' ); @@ -153,19 +185,31 @@ public function process( File $phpcsFile, $stackPtr ) { } // Check if a prefix is used. - if ( ! empty( $this->validated_prefixes ) && $found_prefix === '' ) { - if ( \count( $this->validated_prefixes ) === 1 ) { - $error = 'A namespace name is required to start with the "%s" prefix.'; + if ( ! empty( $valid_prefixes ) && $found_prefix === '' ) { + $prefixes = $valid_prefixes; + + if ( $psr4_info !== false ) { + $error = 'PSR-4 namespace name for this path is required to start with the "%1$s" prefix.'; + $errorcode = 'MissingPSR4Prefix'; } else { - $error = 'A namespace name is required to start with one of the following prefixes: "%s"'; + $error = 'A namespace name is required to start with one of the following prefixes: "%s"'; + $errorcode = 'MissingPrefix'; + + $prefixes = \array_merge( $prefixes, \array_keys( $this->psr4_paths ) ); + $prefixes = \array_unique( $prefixes ); + + if ( \count( $prefixes ) === 1 ) { + $error = 'A namespace name is required to start with the "%s" prefix.'; + } + else { + \natcasesort( $prefixes ); + } } - $prefixes = $this->validated_prefixes; - \natcasesort( $prefixes ); $data = [ \implode( '", "', $prefixes ) ]; - $phpcsFile->addError( $error, $stackPtr, 'MissingPrefix', $data ); + $phpcsFile->addError( $error, $stackPtr, $errorcode, $data ); } /* @@ -175,8 +219,9 @@ public function process( File $phpcsFile, $stackPtr ) { $namespace_for_level_check = $namespace_name_no_prefix; // Allow for a variation of `Tests\` and `Tests\*\Doubles\` after the prefix. - $starts_with_tests = ( \strpos( $namespace_for_level_check, 'Tests\\' ) === 0 ); - if ( $starts_with_tests === true ) { + $starts_with_tests = ( \strpos( $namespace_for_level_check, 'Tests\\' ) === 0 ); + $prefix_ends_with_tests = ( \substr( $found_prefix, -6 ) === '\Tests' ); + if ( $starts_with_tests === true || $prefix_ends_with_tests === true ) { $stripped = false; foreach ( self::DOUBLE_DIRS as $dir => $length ) { if ( \strpos( $namespace_for_level_check, $dir ) !== false ) { @@ -187,16 +232,27 @@ public function process( File $phpcsFile, $stackPtr ) { } if ( $stripped === false ) { - // No double dir found, now check/strip typical test dirs. - if ( \strpos( $namespace_for_level_check, 'Tests\WP\\' ) === 0 ) { - $namespace_for_level_check = \substr( $namespace_for_level_check, 9 ); - } - elseif ( \strpos( $namespace_for_level_check, 'Tests\Unit\\' ) === 0 ) { - $namespace_for_level_check = \substr( $namespace_for_level_check, 11 ); + if ( $starts_with_tests === true ) { + // No double dir found, now check/strip typical test dirs. + if ( \strpos( $namespace_for_level_check, 'Tests\WP\\' ) === 0 ) { + $namespace_for_level_check = \substr( $namespace_for_level_check, 9 ); + } + elseif ( \strpos( $namespace_for_level_check, 'Tests\Unit\\' ) === 0 ) { + $namespace_for_level_check = \substr( $namespace_for_level_check, 11 ); + } + else { + // Okay, so this only has the `Tests` prefix, just strip it. + $namespace_for_level_check = \substr( $namespace_for_level_check, 6 ); + } } - else { - // Okay, so this only has the `Tests` prefix, just strip it. - $namespace_for_level_check = \substr( $namespace_for_level_check, 6 ); + elseif ( $prefix_ends_with_tests === true ) { + // Prefix which already includes `Tests`. + if ( \strpos( $namespace_for_level_check, 'WP\\' ) === 0 ) { + $namespace_for_level_check = \substr( $namespace_for_level_check, 3 ); + } + elseif ( \strpos( $namespace_for_level_check, 'Unit\\' ) === 0 ) { + $namespace_for_level_check = \substr( $namespace_for_level_check, 5 ); + } } } } @@ -237,40 +293,50 @@ public function process( File $phpcsFile, $stackPtr ) { return; } - // Stripping potential quotes to ensure `stdin_path` passed by IDEs does not include quotes. - $file = TextStrings::stripQuotes( $phpcsFile->getFileName() ); - if ( $file === 'STDIN' ) { + if ( $file === '' ) { + // STDIN. return; // @codeCoverageIgnore } - $directory = PathHelper::normalize_absolute_path( \dirname( $file ) ); $relative_directory = ''; + if ( \is_array( $psr4_info ) ) { + $relative_directory = $psr4_info['relative']; + } + else { + $directory = PathHelper::normalize_absolute_path( \dirname( $file ) ); - $this->validate_src_directory( $phpcsFile ); + $this->validate_src_directory( $phpcsFile ); - if ( empty( $this->validated_src_directory ) === false ) { - foreach ( $this->validated_src_directory as $absolute_src_path ) { - if ( PathHelper::path_starts_with( $directory, $absolute_src_path ) === false ) { - continue; - } + if ( empty( $this->validated_src_directory ) === false ) { + foreach ( $this->validated_src_directory as $absolute_src_path ) { + if ( PathHelper::path_starts_with( $directory, $absolute_src_path ) === false ) { + continue; + } - $relative_directory = PathHelper::strip_basepath( $directory, $absolute_src_path ); - if ( $relative_directory === '.' ) { - $relative_directory = ''; + $relative_directory = PathHelper::strip_basepath( $directory, $absolute_src_path ); + break; } - break; } } + if ( $relative_directory === '.' ) { + $relative_directory = ''; + } + // Now any potential src directory has been stripped, remove surrounding slashes. $relative_directory = \trim( $relative_directory, '/' ); + // Directory to namespace translation. $expected = '[Plugin\Prefix]'; if ( $found_prefix !== '' ) { $expected = $found_prefix; } - elseif ( \count( $this->validated_prefixes ) === 1 ) { - $expected = \rtrim( $this->validated_prefixes[0], '\\' ); + // Namespace name doesn't have the correct prefix, but we do know what the prefix should be. + elseif ( \is_array( $psr4_info ) ) { + $expected = $psr4_info['prefix']; + } + elseif ( \count( $valid_prefixes ) === 1 ) { + $expected = \rtrim( $valid_prefixes[0], '\\' ); } $clean = []; @@ -281,10 +347,13 @@ public function process( File $phpcsFile, $stackPtr ) { $levels = \array_filter( $levels ); // Remove empties, just in case. foreach ( $levels as $level ) { - $cleaned_level = \preg_replace( '`[[:punct:]]`', '_', $level ); - $words = \explode( '_', $cleaned_level ); - $words = \array_map( 'ucfirst', $words ); - $cleaned_level = \implode( '_', $words ); + $cleaned_level = $level; + if ( $psr4_info === false ) { + $cleaned_level = \preg_replace( '`[[:punct:]]`', '_', $cleaned_level ); + $words = \explode( '_', $cleaned_level ); + $words = \array_map( 'ucfirst', $words ); + $cleaned_level = \implode( '_', $words ); + } if ( NamingConventions::isValidIdentifierName( $cleaned_level ) === false ) { $phpcsFile->addError( @@ -304,23 +373,36 @@ public function process( File $phpcsFile, $stackPtr ) { $name_for_compare = \implode( '\\', $clean ); } - if ( \strcasecmp( $name_for_compare, $namespace_name_no_prefix ) === 0 ) { - return; + // Check whether the namespace name complies with the rules. + if ( $psr4_info !== false ) { + // Check for PSR-4 compliant namespace. + if ( \strcmp( $name_for_compare, $namespace_name_no_prefix ) === 0 ) { + return; + } + + $error = 'Directory marked as a PSR-4 path. The namespace name should match the path exactly in a case-sensitive manner. Expected namespace name: "%s"; found: "%s"'; + $code = 'NotPSR4Compliant'; + } + else { + // Check for "old-style" namespace. + if ( \strcasecmp( $name_for_compare, $namespace_name_no_prefix ) === 0 ) { + return; + } + + $error = 'The namespace (sub)level(s) should reflect the directory path to the file. Expected: "%s"; Found: "%s"'; + $code = 'Invalid'; } if ( $name_for_compare !== '' ) { $expected .= '\\' . $name_for_compare; } - $phpcsFile->addError( - 'The namespace (sub)level(s) should reflect the directory path to the file. Expected: "%s"; Found: "%s"', - $stackPtr, - 'Invalid', - [ - $expected, - $namespace_name, - ] - ); + $data = [ + $expected, + $namespace_name, + ]; + + $phpcsFile->addError( $error, $stackPtr, $code, $data ); } /** diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php index 453d10ea..a2add417 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTest.php @@ -171,6 +171,75 @@ public function getErrorList( string $testFile = '' ): array { 14 => 1, ]; + // Path translation with $psr4_paths set tests. + case 'path-translation-psr4.inc': + return [ + 11 => 2, + 12 => 2, + 21 => 2, + 22 => 1, + 23 => 1, + 33 => 2, + 34 => 2, + 35 => 1, + 36 => 1, + 37 => 1, + 53 => 2, + 54 => 2, + 70 => 2, + 71 => 2, + 72 => 2, + 73 => 2, + ]; + + case 'path-translation-psr4-case-sensitive-lower.inc': + return [ + 11 => 1, + ]; + + case 'path-translation-psr4-case-sensitive-proper.inc': + return [ + 12 => 3, + 13 => 2, + 14 => 1, + 15 => 1, + 16 => 1, + 26 => 2, + 27 => 2, + 28 => 1, + 29 => 1, + 39 => 3, + 40 => 2, + 41 => 1, + 42 => 1, + 52 => 2, + 53 => 2, + 54 => 3, + 55 => 1, + 56 => 1, + 66 => 2, + 67 => 3, + 68 => 1, + 69 => 1, + ]; + + case 'path-translation-src-deeper-than-psr4-sub.inc': + return [ + 13 => 1, + 14 => 1, + 15 => 1, + 16 => 3, + 17 => 3, + ]; + + // PSR4 path translation with unconventional chars in directory name. + case 'path-translation-psr4-dash.inc': + case 'path-translation-psr4-dot.inc': + case 'path-translation-psr4-space.inc': + return [ + 1 => 1, // Invalid dir error. + ]; + // Path translation with no matching $src_directory. case 'path-translation-mismatch.inc': return [ @@ -214,6 +283,22 @@ public function getWarningList( string $testFile = '' ): array { 127 => 1, 128 => 1, 129 => 1, + 147 => 1, + 148 => 1, + 149 => 1, + 150 => 1, + 151 => 1, + 152 => 1, + 153 => 1, + 154 => 1, + 172 => 1, + 173 => 1, + 174 => 1, + 175 => 1, + 176 => 1, + 177 => 1, + 178 => 1, + 179 => 1, ]; case 'no-basepath-scoped.inc': @@ -226,6 +311,15 @@ public function getWarningList( string $testFile = '' ): array { 14 => 1, ]; + case 'path-translation-psr4-case-sensitive-proper.inc': + return [ + 13 => 1, + 27 => 1, + 40 => 1, + 53 => 1, + 66 => 1, + ]; + default: return []; } diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/ProperCase/SubPath/path-translation-src-deeper-than-psr4-sub.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/ProperCase/SubPath/path-translation-src-deeper-than-psr4-sub.inc new file mode 100644 index 00000000..8b1c5c4e --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/ProperCase/SubPath/path-translation-src-deeper-than-psr4-sub.inc @@ -0,0 +1,22 @@ +PSR4_Path + */ + +namespace Prefix\ProperCase\SubPath; // Error. + +namespace Prefix\SubPath; // Error. +namespace Prefix\ProperCase\Subpath; // Error. +namespace Prefix\ProperCase\subpath; // Error. +namespace Yoast\WP\Plugin\SubPath; // Error. +namespace Yoast\WP\Plugin\PSR4_Path\ProperCase\SubPath; // Error. + +// Reset to default settings. +// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] +// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/ProperCase/path-translation-psr4-case-sensitive-proper.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/ProperCase/path-translation-psr4-case-sensitive-proper.inc new file mode 100644 index 00000000..81cf6293 --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/ProperCase/path-translation-psr4-case-sensitive-proper.inc @@ -0,0 +1,74 @@ +PSR4_Path + */ +namespace Yoast\WP\Plugin\ProperCase; // OK. + +namespace Yoast\Plugin\PSR4_Path\ProperCase; // Error x 3. +namespace Yoast\Plugin\ProperCase; // Error x 2 + warning. +namespace Yoast\WP\Plugin\propercase; // Error. +namespace Yoast\WP\Plugin\Propercase; // Error. +namespace Yoast\WP\Plugin\PROPERCASE; // Error. + +/* + * Testing that PSR-4 path translation takes precedence over src_directory [1]. + * + * @phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] PSR4_Path/ProperCase + * @phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] Yoast\WP\Plugin=>PSR4_Path + */ +namespace Yoast\WP\Plugin\ProperCase; // OK. + +namespace Yoast\Plugin; // Error x 2. +namespace Yoast\Plugin\ProperCase; // Error x 2 + warning. +namespace Yoast\WP\Plugin; // Error. +namespace Yoast\WP\Plugin\PSR4_Path\ProperCase; // Error. + +/* + * Testing that PSR-4 path translation takes precedence over src_directory [2]. + * + * @phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] PSR4_Path + * @phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] Yoast\WP\Plugin\\=>. + */ +namespace Yoast\WP\Plugin\PSR4_Path\ProperCase; // OK. + +namespace Yoast\Plugin\PSR4_Path\ProperCase; // Error x 3. +namespace Yoast\Plugin\ProperCase; // Error x 2 + warning. +namespace Yoast\WP\Plugin; // Error. +namespace Yoast\WP\Plugin\ProperCase; // Error. + +/* + * Testing that PSR-4 path translation takes precedence over src_directory [3]. + * + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] PSR4_Path + * phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] Yoast\WP\Plugin=>PSR4_Path/ProperCase + */ +namespace Yoast\WP\Plugin; // OK. + +namespace Yoast\Plugin; // Error x 2. +namespace Yoast\Plugin\ProperCase; // Error x 2 + warning. +namespace Yoast\Plugin\PSR4_Path\ProperCase; // Error x 3. +namespace Yoast\WP\Plugin\ProperCase; // Error. +namespace Yoast\WP\Plugin\PSR4_Path\ProperCase; // Error. + +/* + * Testing that PSR-4 path translation takes precedence over src_directory [4]. + * + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] . + * phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] Yoast\WP\Plugin\\=>PSR4_Path/ + */ +namespace Yoast\WP\Plugin\ProperCase; // OK. + +namespace Yoast\Plugin\ProperCase; // Error x 2 + warning. +namespace Yoast\Plugin\PSR4_Path\ProperCase; // Error x 3. +namespace Yoast\WP\Plugin; // Error. +namespace Yoast\WP\Plugin\PSR4_Path\ProperCase; // Error. + +// Reset to default settings. +// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] +// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/lowercase/path-translation-psr4-case-sensitive-lower.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/lowercase/path-translation-psr4-case-sensitive-lower.inc new file mode 100644 index 00000000..f9221d38 --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/lowercase/path-translation-psr4-case-sensitive-lower.inc @@ -0,0 +1,14 @@ +PSR4_Path + */ + +namespace Yoast\WP\Plugin\lowercase; // OK. + +namespace Yoast\WP\Plugin\Lowercase; // Error. + +// Reset to default settings. +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/path-translation-psr4.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/path-translation-psr4.inc new file mode 100644 index 00000000..8c9a05ca --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/path-translation-psr4.inc @@ -0,0 +1,78 @@ +PSR4_Path + */ +namespace PSR4_Path; // OK. + +// PSR-4 names are case-sensitive. +namespace psr4_Path; // Error x 2. +namespace Psr4_PATH; // Error x 2. + +/* + * Testing path translation in combination with psr4_paths, no src directory, no prefixes. + * + * phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] PrefixA\\=>PSR4_Path,PrefixB\\=>OtherPath + */ +namespace PrefixA; // OK. + +namespace PrefixB; // Error x 2. +namespace PrefixA\PSR4_PATH; // Error. +namespace PrefixA\Psr4_path; // Error. + +/* + * Testing path translation in combination with multiple psr4_paths + src_directory, no prefixes. + * + * phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] PrefixA=>Some_Other,PrefixB=>PSR4_Path,PrefixC=>MyMy + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] PSR4_Path + */ +namespace PrefixB; // OK. + +namespace PrefixA; // Error x 2. +namespace PrefixC; // Error x 2. +namespace PrefixB\PSR4_Path; // Error. +namespace PrefixB\psr4_path; // Error. +namespace PrefixB\PSR4_PATH; // Error. + +/* + * Testing path translation in combination with psr4_paths + src_directory, with prefix, + * when there is no matching PSR4 path due to a case-mismatch. + * + * In that case, the "normal" rules apply. + * + * @phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] . + * phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] Prefix\\=>pSr4_path + */ +namespace Yoast\WP\Plugin\PSR4_Path; // OK. +namespace Yoast\WP\Plugin\Psr4_PATH; // OK. +namespace Yoast\WP\Plugin\psr4_path; // OK. + +namespace Prefix; // Error x 2. +namespace Prefix\PSR4_Path; // Error x 2. + +/* + * Testing path translation in combination with psr4_paths + src_directory, with prefix, + * when there is no matching PSR4 path. + * + * In that case, the "normal" rules apply. + * + * @phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] Yoast\WP\Plugin + * phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] . + * phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] PrefixA\\=>not-this-path,PrefixB\\=>Invalid + */ +namespace Yoast\WP\Plugin\PSR4_Path; // OK. +namespace Yoast\WP\Plugin\Psr4_PATH; // OK. +namespace Yoast\WP\Plugin\psr4_path; // OK. + +namespace PrefixA; // Error x 2. +namespace PrefixA\PSR4_Path; // Error x 2. +namespace PrefixB; // Error x 2. +namespace PrefixB\PSR4_Path; // Error x 2. + +// Reset to default settings. +// phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] +// phpcs:set Yoast.NamingConventions.NamespaceName src_directory[] +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub path/path-translation-psr4-space.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub path/path-translation-psr4-space.inc new file mode 100644 index 00000000..e85ce7bc --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub path/path-translation-psr4-space.inc @@ -0,0 +1,12 @@ +PSR4_Path + */ + +namespace Yoast\WP\Plugin\PSR4_Path\Sub Path; // Error: invalid path. Would cause parse error, illegal space in namespace. + +// Reset to default settings. +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub#path/path-translation-psr4-hash.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub#path/path-translation-psr4-hash.inc new file mode 100644 index 00000000..ec19dff3 --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub#path/path-translation-psr4-hash.inc @@ -0,0 +1,12 @@ +PSR4_Path + */ + +namespace Yoast\WP\Plugin\PSR4_Path\Sub#Path; // Error: invalid path. Would cause parse error, unexpected end of file, sniff won't be able to reliably retrieve the namespace. + +// Reset to default settings. +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub-path/path-translation-psr4-dash.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub-path/path-translation-psr4-dash.inc new file mode 100644 index 00000000..fe0513e5 --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub-path/path-translation-psr4-dash.inc @@ -0,0 +1,12 @@ +PSR4_Path + */ + +namespace Yoast\WP\Plugin\PSR4_Path\Sub-Path; // Error: invalid path. Would cause parse error, illegal dash in namespace. + +// Reset to default settings. +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub.path/path-translation-psr4-dot.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub.path/path-translation-psr4-dot.inc new file mode 100644 index 00000000..040e4801 --- /dev/null +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/PSR4_Path/sub.path/path-translation-psr4-dot.inc @@ -0,0 +1,12 @@ +PSR4_Path + */ + +namespace Yoast\WP\Plugin\PSR4_Path\Sub.Path; // Error: invalid path. Would cause parse error, illegal concatenation in namespace. + +// Reset to default settings. +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] diff --git a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc index abe5e0df..6da16342 100644 --- a/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc +++ b/Yoast/Tests/NamingConventions/NamespaceNameUnitTests/no-basepath.inc @@ -128,8 +128,59 @@ namespace Yoast\WP\Plugin\Tests\Unit\Mocks\Foo\Bar\Baz; // Warning. namespace Yoast\WP\Plugin\Tests\WP\Fixtures\Foo\Bar\Baz; // Warning. namespace Yoast\WP\Plugin\Tests\Unit\Fixtures\Foo\Bar\Baz; // Warning. +/* + * Test allowing for more variations of test directories and deeper double directories based on PSR4 paths. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] + * phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] Prefix\Foo=>PSR4_Path,Prefix\Bar=>OtherPath + */ + +namespace Prefix\Foo\Tests\WP\Foo\Bar; // OK. +namespace Prefix\Bar\Tests\Unit\Foo\Bar; // OK. +namespace Prefix\Foo\Tests\WP\Doubles\Foo\Bar; // OK. +namespace Prefix\Bar\Tests\Unit\Doubles\Foo\Bar; // OK. +namespace Prefix\Foo\Tests\WP\Mocks\Foo\Bar; // OK. +namespace Prefix\Bar\Tests\Unit\Mocks\Foo\Bar; // OK. +namespace Prefix\Foo\Tests\WP\Fixtures\Foo\Bar; // OK. +namespace Prefix\Bar\Tests\Unit\Fixtures\Foo\Bar; // OK. + +namespace Prefix\Foo\Tests\WP\Foo\Bar\Baz; // Warning. +namespace Prefix\Bar\Tests\Unit\Foo\Bar\Baz; // Warning. +namespace Prefix\Foo\Tests\WP\Doubles\Foo\Bar\Baz; // Warning. +namespace Prefix\Bar\Tests\Unit\Doubles\Foo\Bar\Baz; // Warning. +namespace Prefix\Foo\Tests\WP\Mocks\Foo\Bar\Baz; // Warning. +namespace Prefix\Bar\Tests\Unit\Mocks\Foo\Bar\Baz; // Warning. +namespace Prefix\Foo\Tests\WP\Fixtures\Foo\Bar\Baz; // Warning. +namespace Prefix\Bar\Tests\Unit\Fixtures\Foo\Bar\Baz; // Warning. + +/* + * Test with PSR4 paths where the prefix already includes `Tests`. + * + * phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] + * phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] PrefixA\Tests\\=>PSR4_Path + */ + +namespace PrefixA\Tests\WP\Foo\Bar; // OK. +namespace PrefixA\Tests\Unit\Foo\Bar; // OK. +namespace PrefixA\Tests\WP\Doubles\Foo\Bar; // OK. +namespace PrefixA\Tests\Unit\Doubles\Foo\Bar; // OK. +namespace PrefixA\Tests\WP\Mocks\Foo\Bar; // OK. +namespace PrefixA\Tests\Unit\Mocks\Foo\Bar; // OK. +namespace PrefixA\Tests\WP\Fixtures\Foo\Bar; // OK. +namespace PrefixA\Tests\Unit\Fixtures\Foo\Bar; // OK. + +namespace PrefixA\Tests\WP\Foo\Bar\Baz; // Warning. +namespace PrefixA\Tests\Unit\Foo\Bar\Baz; // Warning. +namespace PrefixA\Tests\WP\Doubles\Foo\Bar\Baz; // Warning. +namespace PrefixA\Tests\Unit\Doubles\Foo\Bar\Baz; // Warning. +namespace PrefixA\Tests\WP\Mocks\Foo\Bar\Baz; // Warning. +namespace PrefixA\Tests\Unit\Mocks\Foo\Bar\Baz; // Warning. +namespace PrefixA\Tests\WP\Fixtures\Foo\Bar\Baz; // Warning. +namespace PrefixA\Tests\Unit\Fixtures\Foo\Bar\Baz; // Warning. + // Reset to default settings. // phpcs:set Yoast.NamingConventions.NamespaceName prefixes[] +// phpcs:set Yoast.NamingConventions.NamespaceName psr4_paths[] /* * Test against false positives for namespace operator and incorrect namespace declarations. From cbc03d2b2f0d5f99ce6905696a4152a6f895f1e0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 16 Nov 2023 19:50:31 +0100 Subject: [PATCH 160/212] CS: minor cleanup --- Yoast/Sniffs/Commenting/FileCommentSniff.php | 1 - Yoast/Tests/Utils/PSR4PathsTraitTest.php | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Yoast/Sniffs/Commenting/FileCommentSniff.php b/Yoast/Sniffs/Commenting/FileCommentSniff.php index 095c0118..b7f42de8 100644 --- a/Yoast/Sniffs/Commenting/FileCommentSniff.php +++ b/Yoast/Sniffs/Commenting/FileCommentSniff.php @@ -5,7 +5,6 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Standards\Squiz\Sniffs\Commenting\FileCommentSniff as Squiz_FileCommentSniff; use PHP_CodeSniffer\Util\Tokens; -use PHPCSUtils\Tokens\Collections; /** * Namespaced files containing an OO structure do not need a file docblock in YoastCS. diff --git a/Yoast/Tests/Utils/PSR4PathsTraitTest.php b/Yoast/Tests/Utils/PSR4PathsTraitTest.php index f3713a7e..25811d96 100644 --- a/Yoast/Tests/Utils/PSR4PathsTraitTest.php +++ b/Yoast/Tests/Utils/PSR4PathsTraitTest.php @@ -35,6 +35,8 @@ final class PSR4PathsTraitTest extends NonSniffTestCase { * Clean up the trait after each test. * * @after + * + * @return void */ protected function clean_up() { $this->psr4_paths = []; @@ -114,7 +116,7 @@ public function test_get_psr4_info_explicit( $psr4_paths, $file_path, $expected * @see test_get_psr4_info() For the array format. * @see test_get_psr4_info_explicit() For the array format. * - * @return array>> + * @return array>>> */ public static function data_is_get_psr4_info() { $default_psr4_paths = self::data_validate_psr4_paths()['multiple prefixes, variation of paths']['psr4_paths']; From 7260b567621dda07f6503db0aed76c20131b9033 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 11:36:35 +0200 Subject: [PATCH 161/212] YoastCS rules: remove exclusion for empty lines The `Squiz.WhiteSpace.SuperfluousWhitespace.EmptyLines` error code flags consecutive blank lines within function declarations. This rule was previously silenced in YoastCS, but will now be enabled. Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | 1 | Duplicate Post | 1 | Yst ACF | 2 | Yst WooCommerce | -- | Yst News | 1 | Yst Local | 9 | Yst Video | 23 | Yst Premium | 20 | Yst Free | 11 --- Yoast/ruleset.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index ed2c55eb..1b1f9f97 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -44,8 +44,6 @@ - - From 23ee1028155d4f0758cfb8e55cb3dc3a3054c9d9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 24 Sep 2023 12:42:23 +0200 Subject: [PATCH 162/212] YoastCS rules: exclude files from other plugins Other plugins may be installed in a `wp-content/plugins` subdirectory to be available for test situations. Those files should not be included in a PHPCS scan. --- Yoast/ruleset.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index ed2c55eb..89016d0f 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -18,6 +18,9 @@ */node_modules/* */vendor(_prefixed)?/* + + */wp-content/plugins* + diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a5ff6a3..102e63b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -468,7 +468,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) and [Keep a C Initial public release as a stand-alone package. -[PHP_CodeSniffer]: https://github.com/squizlabs/PHP_CodeSniffer/releases +[PHP_CodeSniffer]: https://github.com/PHPCSStandards/PHP_CodeSniffer/releases [WordPressCS]: https://github.com/WordPress/WordPress-Coding-Standards/blob/develop/CHANGELOG.md [PHPCompatibilityWP]: https://github.com/PHPCompatibility/PHPCompatibilityWP#changelog [PHPCompatibility]: https://github.com/PHPCompatibility/PHPCompatibility/blob/master/CHANGELOG.md diff --git a/README.md b/README.md index cacf2234..dea2a1e6 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Typically, (a variation on) the following snippet would be added to the `compose ## PHP Code Sniffer -Set of [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) rules. +Set of [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) rules. Severity levels: @@ -65,7 +65,7 @@ Severity levels: The `Yoast` standard for PHP_CodeSniffer is comprised of the following: * The `WordPress` ruleset from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) implementing the official [WordPress PHP Coding Standards](https://make.wordpress.org/core/handbook/coding-standards/php/), with some [select exclusions](https://github.com/Yoast/yoastcs/blob/develop/Yoast/ruleset.xml#L29-L75). * The [`PHPCompatibilityWP`](https://github.com/PHPCompatibility/PHPCompatibilityWP) ruleset which checks code for PHP cross-version compatibility while preventing false positives for functionality polyfilled within WordPress. -* Select additional sniffs taken from [`PHP_CodeSniffer`](https://github.com/squizlabs/PHP_CodeSniffer). +* Select additional sniffs taken from [`PHP_CodeSniffer`](https://github.com/PHPCSStandards/PHP_CodeSniffer). * Select additional sniffs taken from [`PHPCSExtra`](https://github.com/PHPCSStandards/PHPCSExtra). * A number of custom Yoast specific sniffs. @@ -93,7 +93,7 @@ Not all sniffs have documentation available about what they sniff for, but for t "vendor/bin/phpcs" --extensions=php /path/to/folder/ ``` -For more command-line options, please have a read through the [PHP_CodeSniffer documentation](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage). +For more command-line options, please have a read through the [PHP_CodeSniffer documentation](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Usage). #### Yoast plugin repositories diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 06954324..33b7eed9 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -6,7 +6,7 @@ From 74fc4659f5dd4dd66171a1a9a4f5e9af2cb1cf87 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 14 Nov 2023 03:51:28 +0100 Subject: [PATCH 167/212] Composer: raise minimum PHPCS version and update various version constraints ... after the tooling has also updated to the PHPCSStandards version of PHPCS. Refs: * https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/tag/3.8.0 * https://github.com/PHPCSStandards/PHPCSUtils/releases * https://github.com/PHPCSStandards/PHPCSExtra/releases --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 04f7c89a..bc6c5f67 100644 --- a/composer.json +++ b/composer.json @@ -29,9 +29,9 @@ "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/phpcompatibility-wp": "^2.1.4", - "phpcsstandards/phpcsextra": "^1.1.2", - "phpcsstandards/phpcsutils": "^1.0.8", - "squizlabs/php_codesniffer": "^3.7.2", + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.9", + "squizlabs/php_codesniffer": "^3.8.0", "wp-coding-standards/wpcs": "^3.0.1" }, "require-dev": { From 8de22f4f370a4555a6c9da257f9e27f5ef11a3ed Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 12 Dec 2023 15:58:53 +0100 Subject: [PATCH 168/212] Tests: allow for PHPUnit 8/9 PHP_CodeSniffer 3.8.0 now allows for running the tests, which are based on the PHPCS native test suite, with PHPUnit 8 and 9. This commit updates the package to take advantage of that. Includes: * Update the PHPUnit version requirements. As this package no longer supports PHP < 7.2, PHPUnit 8 + 9 is all that's needed. * Adding the PHPUnit 8+ cache file to `.gitignore`. * Simplifications to the `quicktest` and `test` workflows. Also, the code coverage "high" run can now be run against PHP 8.3. * Removing a no longer needed `--ignore-platform*` argument. * MInor tweaks to the CS run on the code of YoastCS itself. * Normalize a teardown method. This was set up with an `@after` tag in anticipation of this PR, but before dropping support for PHP < 7.2. As PHP < 7.2 has been dropped, we don't need to support PHPUnit 7 or lower anymore, so don't need this tweak to make the fixture methods cross-version compatible anymore. Ref: * PHPCSStandards/PHP_CodeSniffer 59 --- .github/workflows/basics.yml | 6 +---- .github/workflows/quicktest.yml | 22 +++------------- .github/workflows/test.yml | 33 +++++++----------------- .gitignore | 1 + Yoast/Tests/Utils/PSR4PathsTraitTest.php | 4 +-- composer.json | 2 +- 6 files changed, 17 insertions(+), 51 deletions(-) diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 11e135ac..452821b1 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -33,7 +33,7 @@ jobs: - name: Install PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' # Can be updated to "latest" once PHPCS 3.8.0 has been released. + php-version: 'latest' coverage: none tools: cs2pr @@ -42,10 +42,6 @@ jobs: - name: Validate Composer installation run: composer validate --no-check-all --strict - # The sniff stage doesn't run the unit tests, so no need for PHPUnit. - - name: 'Composer: adjust dependencies - remove PHPUnit' - run: composer remove --no-update --dev phpunit/phpunit --no-scripts --no-interaction - # Use the WIP/develop branches of all CS dependencies as an early detection system for bugs upstream. - name: 'Composer: adjust dependencies - use dev versions of CS dependencies' run: > diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index f547d346..2a437bae 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -59,32 +59,23 @@ jobs: - name: "Composer: set PHPCS dependencies for tests (dev)" if: ${{ matrix.cs_dependencies == 'dev' }} run: > - composer require --no-update --no-scripts --no-interaction --ignore-platform-req=php+ + composer require --no-update --no-scripts --no-interaction squizlabs/php_codesniffer:"dev-master" phpcsstandards/phpcsutils:"dev-develop" wp-coding-standards/wpcs:"dev-develop" # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer - - name: Install Composer dependencies - normal - if: ${{ startsWith( matrix.php_version, '8' ) == false && matrix.php_version != 'latest' }} + - name: Install Composer dependencies uses: ramsey/composer-install@v2 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") - # For PHP 8.0 and higher, we need to install with ignore platform reqs as not all dependencies allow it. - - name: Install Composer dependencies - with ignore platform - if: ${{ startsWith( matrix.php_version, '8' ) || matrix.php_version == 'latest' }} - uses: ramsey/composer-install@v2 - with: - composer-options: --ignore-platform-req=php+ - custom-cache-suffix: $(date -u "+%Y-%m") - - name: "Composer: downgrade PHPCS dependencies for tests (lowest) (with ignore platform)" if: ${{ matrix.cs_dependencies == 'lowest' }} run: > - composer update --prefer-lowest --no-scripts --no-interaction --ignore-platform-req=php+ + composer update --prefer-lowest --no-scripts --no-interaction squizlabs/php_codesniffer phpcsstandards/phpcsutils wp-coding-standards/wpcs @@ -96,10 +87,5 @@ jobs: if: matrix.cs_dependencies == 'stable' run: composer lint - - name: Run the unit tests - PHP 7.2 - 8.0 - if: ${{ matrix.php_version < '8.1' && matrix.php_version != 'latest' }} + - name: Run the unit tests run: composer test - - - name: Run the unit tests - PHP 8.1+ - if: ${{ matrix.php_version >= '8.1' || matrix.php_version == 'latest'}} - run: composer test -- --no-configuration --bootstrap=./phpunit-bootstrap.php --dont-report-useless-tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74cab388..bba6ad7b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,20 +37,20 @@ jobs: # @link https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix # # The matrix is set up so as not to duplicate the builds which are run for code coverage. - php_version: ['7.3', '8.0', '8.1', '8.2', '8.3'] + php_version: ['7.3', '7.4', '8.0', '8.1', '8.2'] cs_dependencies: ['lowest', 'stable'] include: # Make the matrix complete (when combined with the code coverage builds). - php_version: '7.2' cs_dependencies: 'stable' - - php_version: '7.4' + - php_version: '8.3' cs_dependencies: 'stable' # Test against dev versions of all CS dependencies with select PHP versions for early detection of issues. - - php_version: '8.0' + - php_version: '7.4' cs_dependencies: 'dev' - - php_version: '8.3' + - php_version: '8.1' cs_dependencies: 'dev' # Experimental build(s). These are allowed to fail. @@ -88,7 +88,7 @@ jobs: - name: "Composer: set PHPCS dependencies for tests (dev)" if: ${{ matrix.cs_dependencies == 'dev' }} run: > - composer require --no-update --no-scripts --no-interaction --ignore-platform-req=php+ + composer require --no-update --no-scripts --no-interaction squizlabs/php_codesniffer:"${{ env.PHPCS_HIGHEST }}" phpcsstandards/phpcsutils:"${{ env.UTILS_HIGHEST }}" wp-coding-standards/wpcs:"${{ env.WPCS_HIGHEST }}" @@ -96,24 +96,15 @@ jobs: # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer - name: Install Composer dependencies - normal - if: ${{ startsWith( matrix.php_version, '8' ) == false }} uses: ramsey/composer-install@v2 with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") - # For PHP 8/"nightly", we need to install with ignore platform reqs as we're still using PHPUnit 7. - - name: Install Composer dependencies - with ignore platform - if: ${{ startsWith( matrix.php_version, '8' ) }} - uses: ramsey/composer-install@v2 - with: - composer-options: --ignore-platform-req=php+ - custom-cache-suffix: $(date -u "+%Y-%m") - - - name: "Composer: downgrade PHPCS dependencies for tests (lowest) (with ignore platform)" + - name: "Composer: downgrade PHPCS dependencies for tests (lowest)" if: ${{ matrix.cs_dependencies == 'lowest' }} run: > - composer update --prefer-lowest --no-scripts --no-interaction --ignore-platform-req=php+ + composer update --prefer-lowest --no-scripts --no-interaction squizlabs/php_codesniffer phpcsstandards/phpcsutils wp-coding-standards/wpcs @@ -130,14 +121,9 @@ jobs: if: ${{ matrix.cs_dependencies == 'stable' }} run: composer lint -- --checkstyle | cs2pr - - name: Run the unit tests - PHP 7.2 - 8.0 - if: ${{ matrix.php_version < '8.1' }} + - name: Run the unit tests run: composer test - - name: Run the unit tests - PHP 8.1+ - if: ${{ matrix.php_version >= '8.1' }} - run: composer test -- --no-configuration --bootstrap=./phpunit-bootstrap.php --dont-report-useless-tests - #### CODE COVERAGE STAGE #### # N.B.: Coverage is only checked on the lowest and highest stable PHP versions # and a low/high of each major for PHPCS. @@ -152,8 +138,7 @@ jobs: strategy: matrix: - # 7.4 should be updated to 8.x when higher PHPUnit versions can be supported. - php_version: ['7.2', '7.4'] + php_version: ['7.2', '8.3'] cs_dependencies: ['lowest', 'dev'] name: "Coverage${{ matrix.cs_dependencies == 'stable' && ' + Lint' || '' }}: PHP ${{ matrix.php_version }} - CS Deps ${{ matrix.cs_dependencies }}" diff --git a/.gitignore b/.gitignore index cdd94a4b..d8d349e2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ composer.lock .phpcs.xml phpcs.xml phpunit.xml +.phpunit.result.cache build/ diff --git a/Yoast/Tests/Utils/PSR4PathsTraitTest.php b/Yoast/Tests/Utils/PSR4PathsTraitTest.php index 25811d96..4ba41730 100644 --- a/Yoast/Tests/Utils/PSR4PathsTraitTest.php +++ b/Yoast/Tests/Utils/PSR4PathsTraitTest.php @@ -34,11 +34,9 @@ final class PSR4PathsTraitTest extends NonSniffTestCase { /** * Clean up the trait after each test. * - * @after - * * @return void */ - protected function clean_up() { + protected function tearDown(): void { $this->psr4_paths = []; $this->previous_psr4_paths = []; $this->validated_psr4_paths = []; diff --git a/composer.json b/composer.json index bc6c5f67..de919945 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "require-dev": { "phpcompatibility/php-compatibility": "^9.3.5", "phpcsstandards/phpcsdevtools": "^1.2.1", - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^8.0 || ^9.0", "roave/security-advisories": "dev-master" }, "minimum-stability": "dev", From 430f36543666a7dd7cc6907bd52f19719f371381 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 06:21:43 +0200 Subject: [PATCH 169/212] Utils/CustomPrefixes: minor documentation improvements --- Yoast/Utils/CustomPrefixesTrait.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Yoast/Utils/CustomPrefixesTrait.php b/Yoast/Utils/CustomPrefixesTrait.php index a4b4f79e..41bb2300 100644 --- a/Yoast/Utils/CustomPrefixesTrait.php +++ b/Yoast/Utils/CustomPrefixesTrait.php @@ -11,7 +11,7 @@ trait CustomPrefixesTrait { /** - * The prefix which are allowed to be used. + * The prefixes which are allowed to be used. * * The prefix(es) should be in the exact case as expected. * @@ -21,16 +21,16 @@ trait CustomPrefixesTrait { * hook names are being deprecated and the new hooks put in place -, * two prefixes (old and new) will be allowed. * At a future point in time, this property should be changed - * to allow only a single string.}} + * to allow only a single string.} * - * @var string[]|string + * @var array */ public $prefixes = []; /** * Target prefixes after validation. * - * @var string[] + * @var array */ protected $validated_prefixes = []; @@ -39,7 +39,7 @@ trait CustomPrefixesTrait { * * Prevents having to do the same prefix validation over and over again. * - * @var string[] + * @var array */ protected $previous_prefixes = []; @@ -94,9 +94,9 @@ protected function validate_prefixes() { /** * Overloadable method to do custom prefix filtering prior to validation. * - * @param string[] $prefixes The unvalidated prefixes. + * @param array $prefixes The unvalidated prefixes. * - * @return string[] + * @return array */ protected function filter_prefixes( $prefixes ) { return $prefixes; @@ -105,9 +105,9 @@ protected function filter_prefixes( $prefixes ) { /** * Filter out all prefixes which don't contain a namespace separator. * - * @param string[] $prefixes The unvalidated prefixes. + * @param array $prefixes The unvalidated prefixes. * - * @return string[] + * @return array */ protected function filter_allow_only_namespace_prefixes( $prefixes ) { $filtered = []; @@ -125,9 +125,9 @@ protected function filter_allow_only_namespace_prefixes( $prefixes ) { /** * Filter out all prefixes which only contain lowercase characters. * - * @param string[] $prefixes The unvalidated prefixes. + * @param array $prefixes The unvalidated prefixes. * - * @return string[] + * @return array */ protected function filter_exclude_lowercase_prefixes( $prefixes ) { $filtered = []; From baf14f257ab430d2dffdba269b784f44416cf5e7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 29 Oct 2023 21:29:36 +0100 Subject: [PATCH 170/212] Utils/CustomPrefixes: make methods `final` Traits cannot be `final`, but their methods can be, though this will only have an effect if a class `use`-ing the trait is being extended. All the same, the `final` keyword on the methods should serve as a warning to anyone who wants to overload these methods that they probably shouldn't. --- Yoast/Utils/CustomPrefixesTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Yoast/Utils/CustomPrefixesTrait.php b/Yoast/Utils/CustomPrefixesTrait.php index 41bb2300..e17f18d0 100644 --- a/Yoast/Utils/CustomPrefixesTrait.php +++ b/Yoast/Utils/CustomPrefixesTrait.php @@ -52,7 +52,7 @@ trait CustomPrefixesTrait { * * @return void */ - protected function validate_prefixes() { + final protected function validate_prefixes() { if ( $this->previous_prefixes === $this->prefixes ) { return; } @@ -109,7 +109,7 @@ protected function filter_prefixes( $prefixes ) { * * @return array */ - protected function filter_allow_only_namespace_prefixes( $prefixes ) { + final protected function filter_allow_only_namespace_prefixes( $prefixes ) { $filtered = []; foreach ( $prefixes as $prefix ) { if ( \strpos( $prefix, '\\' ) === false ) { @@ -129,7 +129,7 @@ protected function filter_allow_only_namespace_prefixes( $prefixes ) { * * @return array */ - protected function filter_exclude_lowercase_prefixes( $prefixes ) { + final protected function filter_exclude_lowercase_prefixes( $prefixes ) { $filtered = []; foreach ( $prefixes as $prefix ) { if ( \strtolower( $prefix ) === $prefix ) { From ffda3056c73370df0395f26fc7538f1b867de04e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 29 Nov 2023 10:56:35 +0100 Subject: [PATCH 171/212] YoastCS: minor ruleset tweaks ... to keep the ruleset organized and stable, what with a barrage of new sniffs being added. --- Yoast/ruleset.xml | 54 ++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 33b7eed9..f6077f54 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -19,7 +19,7 @@ */vendor(_prefixed)?/* - */wp-content/plugins* + */wp-content/plugins/* - + + + + + + + + error @@ -126,12 +134,6 @@ - - - - - - + + + @@ -160,12 +165,6 @@ - - - - - - @@ -182,18 +181,30 @@ - - - - + + + + + + + + - + - */index\.php + */index\.php$ - */index\.php + */index\.php$ @@ -202,7 +213,6 @@ SNIFFS TO ENFORCE CODE MODERNIZATION ############################################################################# --> - 5 From 1af16e298441886ddee106c98653ea83abe434ab Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 10:52:20 +0200 Subject: [PATCH 172/212] Composer: add Slevomat Coding Standards requirement --- .github/workflows/basics.yml | 1 + README.md | 1 + Yoast/ruleset.xml | 13 +++++++++++++ autoload-bootstrap.php | 23 +++++++++++++++++++++++ composer.json | 1 + 5 files changed, 39 insertions(+) create mode 100644 autoload-bootstrap.php diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 452821b1..7d9b1288 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -50,6 +50,7 @@ jobs: phpcsstandards/phpcsutils:"dev-develop" phpcsstandards/phpcsextra:"dev-develop" wp-coding-standards/wpcs:"dev-develop" + slevomat/coding-standard:"dev-master" # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer diff --git a/README.md b/README.md index dea2a1e6..9460fb3e 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ The `Yoast` standard for PHP_CodeSniffer is comprised of the following: * The [`PHPCompatibilityWP`](https://github.com/PHPCompatibility/PHPCompatibilityWP) ruleset which checks code for PHP cross-version compatibility while preventing false positives for functionality polyfilled within WordPress. * Select additional sniffs taken from [`PHP_CodeSniffer`](https://github.com/PHPCSStandards/PHP_CodeSniffer). * Select additional sniffs taken from [`PHPCSExtra`](https://github.com/PHPCSStandards/PHPCSExtra). +* Select additional sniffs taken from [`SlevomatCodingStandard`](https://github.com/slevomat/coding-standard). * A number of custom Yoast specific sniffs. Files within version management and dependency related directories, such as the Composer `vendor` directory, are excluded from the scans by default. diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 33b7eed9..f06f3c4b 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -22,6 +22,19 @@ */wp-content/plugins* + + + ./../autoload-bootstrap.php + + + + + + From c86a0ec6966482bb7efd9c10bc9776c6bbc3a490 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 10:00:27 +0200 Subject: [PATCH 174/212] YoastCS rules: disallow alternative control structure syntax ... except when there is inline HTML nested within the control structure body. Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | -- | Yst Video | -- | Yst Premium | -- | Yst Free | -- Note: this rule was previously already "silently" enforced via clean-up sweeps. --- Yoast/ruleset.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 6dd69a48..e9307dbf 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -176,6 +176,13 @@ + + + + + + + From 2cac3fa467276f35196e9462e2865da3c37417ca Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 10:48:02 +0200 Subject: [PATCH 175/212] YoastCS rules: disallow the use of the logical and/or operators Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | -- | Yst Video | -- | Yst Premium | -- | Yst Free | -- Note: this rule was previously already "silently" enforced via clean-up sweeps. --- Yoast/ruleset.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index e9307dbf..10c2f630 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -196,6 +196,9 @@ + + + From f97a7eddf80f8f04eedbfe00422afb212e65a2b2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 26 Oct 2023 21:26:34 +0200 Subject: [PATCH 176/212] YoastCS rules: forbid double negation Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | -- | Yst Video | -- | Yst Premium | -- | Yst Free | -- Note: this rule was previously already "silently" enforced via clean-up sweeps. --- Yoast/ruleset.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 10c2f630..00dd3ebf 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -199,6 +199,9 @@ + + + From cb82faa543348cdc385de094dd0024108472e531 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 29 Nov 2023 07:44:41 +0100 Subject: [PATCH 177/212] YoastCS rules: enforce consistent concatenation operator position ... for multi-line concatenations. Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | -- | Yst Video | -- | Yst Premium | -- | Yst Free | 90 Note: this rule was previously already "silently" enforced via clean-up sweeps. --- Yoast/ruleset.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 00dd3ebf..9f4c5d24 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -211,6 +211,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 02d8905e782fd58e5e095f3f6b7b1673998da7c9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 10:41:13 +0200 Subject: [PATCH 183/212] YoastCS rules: disallow long closures This sniff will throw an error when a closure is more than 10 lines long. Note: blank lines and comment-only lines are not includes in the closure line count. Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | 1 error (11 lines) | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | 1 error (17 lines) | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | 1 error (16 lines) | Yst Video | -- | Yst Premium | 1 error (15 lines) | Yst Free | 3 errors (21/25/37 lines) --- Yoast/ruleset.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index e0fb0684..5cede6e7 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -243,6 +243,16 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + From e68c513c620c86e27d753eade23846758b6fb03e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 13 Dec 2023 12:41:37 +0100 Subject: [PATCH 191/212] YoastCS rules: enforce proper grammar/punctuation in param description This rule is disabled in WordPressCS as it doesn't play nice with the convoluted WP native array annotation. For YoastCS it has been decided to apply the rule anyway. We'll need to evaluate on a case-by-case basis how to convert from the WP array annotation to another form of documenting the array format. Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | 1 | Yst News | -- | Yst Local | 1 | Yst Video | -- | Yst Premium | -- | Yst Free | 11 Note: this rule was previously already "silently" enforced via clean-up sweeps. --- Yoast/ruleset.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index cc224c46..60bb24ac 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -317,10 +317,13 @@ SNIFFS RELATED TO COMMENTS AND DOCBLOCKS ############################################################################# --> - + 5 + + 5 + From 5bd407832481d58c84f7d2edfc1211bf84bb641d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 13 Dec 2023 12:50:58 +0100 Subject: [PATCH 192/212] YoastCS rules: enforce that docblocks should have a valid `@return` tag This rule is disabled in WordPressCS as WP Core does not want `@return void`, but consistency and explicitness of _intend_ is important, so we're going to turn it back on. Note: `@return` tags will still not be enforced for constructors which should always be "never" methods anyway. Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | 57 // = Test fixtures. | WP Test Utils | -- | YoastCS | -- | WHIP | 1 | Yoast Test Helper | 18 | Duplicate Post | 280 | Yst ACF | 14 | Yst WooCommerce | 172 | Yst News | 92 | Yst Local | 204 (but there are also still 54 functions without a docblock altogether) | Yst Video | 381 | Yst Premium | 804 + 2 invalid void | Yst Free | 4162 + 3 invalid void (and 4 function without a docblock altogether) --- Yoast/ruleset.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 60bb24ac..5992c49d 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -325,6 +325,14 @@ 5 + + + 5 + + + 5 + + From eee751e9604fa5a795f28c523041f9c9a5c0a7d1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 19 Sep 2023 07:20:02 +0200 Subject: [PATCH 193/212] YoastCS rules: enforce short form type annotations I.e. `bool` not `boolean`, `int` not `integer`. Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | -- | Yst Video | -- | Yst Premium | -- | Yst Free | 1 Note: this rule was previously already "silently" enforced via clean-up sweeps. --- Yoast/ruleset.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 5992c49d..6f2dafcf 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -333,6 +333,9 @@ 5 + + + From 522753ad76b6b11c21e3ada4b68dcf9167c064c1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 19 Sep 2023 07:15:52 +0200 Subject: [PATCH 194/212] YoastCS rules: enforce type annotations to have `null` as last type (whenever relevant) Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | -- | Yst Video | -- | Yst Premium | -- | Yst Free | -- Note: this rule was previously already "silently" enforced via clean-up sweeps. --- Yoast/ruleset.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 6f2dafcf..13c52947 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -336,6 +336,9 @@ + + + From 30c5f8ec9afd1791499a053e67957dacf6742b71 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 19 Sep 2023 07:53:52 +0200 Subject: [PATCH 195/212] YoastCS rules: verify property type annotations This sniff: * Disallows plain `array` type annotations. These should always be made specific. * Flags missing type annotations. Optionally the sniff can enforce PHP native type declarations instead of annotations in a docblock. This functionality has been **disabled** for the following reasons: * Adding type declarations without using `strict_types` leads to bugs as the original (scalar) data will be juggled by PHP, even if the original data was never correct to start with. The loss of the type information of the original data passed, means that the function can no longer discern whether the input is invalid/incorrect. Think: a function expecting an integer, but being passed `false`. This will be juggled to `0`, while it should have been flagged as unusable input. * Adding `strict_types` in the context of WP is a no-no to begin with as it can cause fatal errors (= white screen of death) when the exceptions are not caught and they generally are not caught and WP doesn't have a native exception handler in place either. * Adding `strict_types` is useless anyway as it is only enforced for files containing such a declaration, which means that parameters passed to a function call from an external source which doesn't have the `strict_types` declaration (callback called from WP) will still be juggled, which again leads to bugs. * Adding type declarations in functions hooked into WP filters is especially dangerous due to the loss of type info. All in all, with the type system offered by PHP as-is, adding PHP native type declarations is next to useless unless you have full control of all code + the environment the code is being run on, which for the Yoast code is just not the case (with the exception of Platform). Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | 2 | Yoast Test Helper | 2 | Duplicate Post | 1 | Yst ACF | 4 | Yst WooCommerce | 7 | Yst News | 3 | Yst Local | 40 | Yst Video | 44 | Yst Premium | 85 | Yst Free | 366 --- Yoast/ruleset.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 13c52947..51213f69 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -339,6 +339,21 @@ + + + + + + + + + + + + + + + From f982b952f7f6c05df11ed438a494ac493e18217a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 19 Sep 2023 07:58:10 +0200 Subject: [PATCH 196/212] YoastCS rules: verify parameter type annotations This sniff: * Disallows plain `array` type annotations. These should always be made specific. * Flags missing type annotations. Optionally the sniff can enforce PHP native type declarations instead of annotations in a docblock. This functionality has been **disabled** for the following reasons: * Adding type declarations without using `strict_types` leads to bugs as the original (scalar) data will be juggled by PHP, even if the original data was never correct to start with. The loss of the type information of the original data passed, means that the function can no longer discern whether the input is invalid/incorrect. Think: a function expecting an integer, but being passed `false`. This will be juggled to `0`, while it should have been flagged as unusable input. * Adding `strict_types` in the context of WP is a no-no to begin with as it can cause fatal errors (= white screen of death) when the exceptions are not caught and they generally are not caught and WP doesn't have a native exception handler in place either. * Adding `strict_types` is useless anyway as it is only enforced for files containing such a declaration, which means that parameters passed to a function call from an external source which doesn't have the `strict_types` declaration (callback called from WP) will still be juggled, which again leads to bugs. * Adding type declarations in functions hooked into WP filters is especially dangerous due to the loss of type info. All in all, with the type system offered by PHP as-is, adding PHP native type declarations is next to useless unless you have full control of all code + the environment the code is being run on, which for the Yoast code is just not the case (with the exception of Platform). Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | 4 | WP Test Utils | 3 | YoastCS | -- | WHIP | 4 | Yoast Test Helper | 10 | Duplicate Post | 37 | Yst ACF | 1 | Yst WooCommerce | 37 | Yst News | 17 | Yst Local | 176 | Yst Video | 147 | Yst Premium | 263 | Yst Free | 913 --- Yoast/ruleset.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 51213f69..1a4239d5 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -354,6 +354,21 @@ + + + + + + + + + + + + + + + From e5eb7c036c38804d059f3f302ff21068daaae2b8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 19 Sep 2023 08:04:13 +0200 Subject: [PATCH 197/212] YoastCS rules: verify return type annotations This sniff: * Disallows plain `array` type annotations. These should always be made specific. * Flags missing type annotations. Optionally the sniff can enforce PHP native type declarations instead of annotations in a docblock. This functionality has been **disabled** for the following reasons: * Adding type declarations without using `strict_types` leads to bugs as the original (scalar) data will be juggled by PHP, even if the original data was never correct to start with. The loss of the type information of the original data passed, means that the function can no longer discern whether the input is invalid/incorrect. Think: a function expecting an integer, but being passed `false`. This will be juggled to `0`, while it should have been flagged as unusable input. * Adding `strict_types` in the context of WP is a no-no to begin with as it can cause fatal errors (= white screen of death) when the exceptions are not caught and they generally are not caught and WP doesn't have a native exception handler in place either. * Adding `strict_types` is useless anyway as it is only enforced for files containing such a declaration, which means that parameters passed to a function call from an external source which doesn't have the `strict_types` declaration (callback called from WP) will still be juggled, which again leads to bugs. * Adding type declarations in functions hooked into WP filters is especially dangerous due to the loss of type info. All in all, with the type system offered by PHP as-is, adding PHP native type declarations is next to useless unless you have full control of all code + the environment the code is being run on, which for the Yoast code is just not the case (with the exception of Platform). Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | 22 | WP Test Utils | 9 | YoastCS | -- | WHIP | 5 | Yoast Test Helper | 21 | Duplicate Post | 44 | Yst ACF | 5 | Yst WooCommerce | 48 | Yst News | 22 | Yst Local | 132 | Yst Video | 237 | Yst Premium | 269 | Yst Free | 1066 --- Yoast/ruleset.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 1a4239d5..3520489f 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -369,6 +369,24 @@ + + + + + + + + + + + + + + + + + + From 5b939773eab81ff0802e8f3bc434d223f7f00281 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 19 Sep 2023 07:34:03 +0200 Subject: [PATCH 198/212] YoastCS rules: disallow the "mixed" type in annotations An exception is made for the tests, as a data provider for input validation within a function could well be passing every single different type, in which case, `mixed` is perfectly valid. Related to 303 Fixes 196 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | 49 (this is valid though as these are assertion declarations) | WP Test Utils | -- | YoastCS | 2 | WHIP | -- | Yoast Test Helper | 4 | Duplicate Post | 12 | Yst ACF | 1 | Yst WooCommerce | 2 | Yst News | 1 | Yst Local | 42 | Yst Video | 12 | Yst Premium | 17 | Yst Free | 151 --- Yoast/Reports/Threshold.php | 1 + Yoast/ruleset.xml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/Yoast/Reports/Threshold.php b/Yoast/Reports/Threshold.php index 1334af75..6894cd67 100644 --- a/Yoast/Reports/Threshold.php +++ b/Yoast/Reports/Threshold.php @@ -21,6 +21,7 @@ * @since 2.2.0 * * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Flags unused params which are required via the interface. Invalid. + * @phpcs:disable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint -- Type is too complex to document properly. */ final class Threshold implements Report { diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 3520489f..2a0faaf2 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -387,6 +387,12 @@ + + + + */tests/*\.php$ + + From 383c37b50862aeb84cbf4976578c2765b9af9133 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 19 Sep 2023 05:10:28 +0200 Subject: [PATCH 199/212] Composer: add Automattic VIP sniffs requirement --- .github/workflows/basics.yml | 3 ++- .github/workflows/quicktest.yml | 2 +- .github/workflows/test.yml | 2 +- README.md | 1 + composer.json | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 4505996f..8eb698ab 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -49,9 +49,10 @@ jobs: squizlabs/php_codesniffer:"dev-master" phpcsstandards/phpcsutils:"dev-develop" phpcsstandards/phpcsextra:"dev-develop" - wp-coding-standards/wpcs:"dev-develop" + wp-coding-standards/wpcs:"dev-develop as 3.99" # Alias needed to prevent composer conflict with VIPCS. slevomat/coding-standard:"dev-master" sirbrillig/phpcs-variable-analysis:"2.x" + automattic/vipwpcs:"dev-develop" # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index 2a437bae..24e925a5 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -62,7 +62,7 @@ jobs: composer require --no-update --no-scripts --no-interaction squizlabs/php_codesniffer:"dev-master" phpcsstandards/phpcsutils:"dev-develop" - wp-coding-standards/wpcs:"dev-develop" + wp-coding-standards/wpcs:"dev-develop as 3.99" # Alias needed to prevent composer conflict with VIPCS. # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bba6ad7b..b7f05df4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ concurrency: env: PHPCS_HIGHEST: 'dev-master' UTILS_HIGHEST: 'dev-develop' - WPCS_HIGHEST: 'dev-develop' + WPCS_HIGHEST: 'dev-develop as 3.99' # Alias needed to prevent composer conflict with VIPCS. jobs: #### TEST STAGE #### diff --git a/README.md b/README.md index 33fa7134..70551e43 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ The `Yoast` standard for PHP_CodeSniffer is comprised of the following: * Select additional sniffs taken from [`PHP_CodeSniffer`](https://github.com/PHPCSStandards/PHP_CodeSniffer). * Select additional sniffs taken from [`PHPCSExtra`](https://github.com/PHPCSStandards/PHPCSExtra). * Select additional sniffs taken from [`SlevomatCodingStandard`](https://github.com/slevomat/coding-standard). +* Select additional sniffs taken from [WordPress VIP Coding Standards](https://github.com/Automattic/VIP-Coding-Standards/). * A number of custom Yoast specific sniffs. Files within version management and dependency related directories, such as the Composer `vendor` directory, are excluded from the scans by default. diff --git a/composer.json b/composer.json index 33f1946b..8a9d9028 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "require": { "php": ">=7.2", "ext-tokenizer": "*", + "automattic/vipwpcs": "^3.0.0", "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/phpcompatibility-wp": "^2.1.4", From 44c961f3a70d3d08bc7ae4adf670ddcaf508716b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 19 Sep 2023 05:36:48 +0200 Subject: [PATCH 200/212] YoastCS rules: add a few VIP sniffs to prevent some typical WP compat bugs There is one known bug in the `WordPressVIPMinimum.Hooks.AlwaysReturnInFilter` sniff related to a redirect being done from within the filter. This has been reported upstream Automattic/VIP-Coding-Standards 719. Related to 303 Fixes 229 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | -- | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | 2 (proper escaping) | Yst Video | -- | Yst Premium | 1 (false positive/reported bug) | Yst Free | 2 (1 false positive/reported bug) Note: these rules were previously already "silently" enforced via clean-up sweeps. --- Yoast/ruleset.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 2a0faaf2..2c938a68 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -405,6 +405,26 @@ + + + + + + + + + + + + + + + + + + + + + + + 5 From 26f44747ef3eaceb1b8ee352e08b60ffb6b987ad Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 11:18:21 +0200 Subject: [PATCH 202/212] YoastCS rules: enforce visibility on class constants Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | _N/A_ (minimum PHP version too low) | WP Test Utils | _N/A_ (minimum PHP version too low) | YoastCS | -- | WHIP | _N/A_ (minimum PHP version too low) | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | 2 | Yst WooCommerce | 11 | Yst News | 1 | Yst Local | 11 | Yst Video | 1 | Yst Premium | 107 | Yst Free | 325 --- Yoast/ruleset.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 0ec955cf..257b7cba 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -444,4 +444,7 @@ 5 + + + From 471765178029edee45eeef4c1303a811790658c1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 29 Nov 2023 19:35:52 +0100 Subject: [PATCH 203/212] YoastCS rules: enforce nullable type for param with `null` default Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | _N/A_ (minimum PHP version too low) | WP Test Utils | _N/A_ (minimum PHP version too low) | YoastCS | -- | WHIP | _N/A_ (minimum PHP version too low) | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | -- | Yst News | -- | Yst Local | -- | Yst Video | -- | Yst Premium | 20 | Yst Free | 9 --- Yoast/ruleset.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 257b7cba..6589eb1e 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -447,4 +447,7 @@ + + + From bd6871fa767a51e4b0adb644252894b96eadcae3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 20 Sep 2023 06:42:58 +0200 Subject: [PATCH 204/212] YoastCS rules: selectively exempt test files [1] While WordPressCS makes a best effort to try to prevent false positives on test code for the `WordPress.WP.GlobalVariablesOverride`, it is simpler to just plain exclude the test code from being scanned with this sniff. --- Yoast/ruleset.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 6589eb1e..149a271a 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -450,4 +450,18 @@ + + + + + */tests/*\.php$ + + From a49b7f1ca7fdf2b9c511b93fe223c32cea0ffc71 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 17 Oct 2023 00:20:37 +0200 Subject: [PATCH 205/212] YoastCS rules: selectively exempt test files [2] For mock-based unit tests, it is fine to use the PHP native `json_encode()` function to mock the WP/Yoast native alternative for this function, so let's prevent the test code needing lots of ignore annotations when the issue is not solvable. --- Yoast/ruleset.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 149a271a..6566b48c 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -464,4 +464,10 @@ */tests/*\.php$ + + + */tests/*\.php$ + + From 2634681c73d4eccee97092fb89d294d15cc3fe84 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Dec 2023 12:20:37 +0100 Subject: [PATCH 206/212] YoastCS rules: selectively exempt test files [3] Double classes may overload methods from a parent class just to change the visibility of the method from `protected` to `public` to allow for testing the method directly. This is fine. --- Yoast/ruleset.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 6566b48c..3d722e67 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -470,4 +470,9 @@ */tests/*\.php$ + + + */tests(/*)?/Doubles/*\.php$ + + From 4154116545686ab812d6f9766278447d2b8a3be6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 20 Sep 2023 06:03:25 +0200 Subject: [PATCH 207/212] YoastCS rules: enforce namespaces for all test files Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | -- | WP Test Utils | -- | YoastCS | -- | WHIP | 9 | Yoast Test Helper | -- | Duplicate Post | -- | Yst ACF | -- | Yst WooCommerce | 5 | Yst News | 9 | Yst Local | 1 | Yst Video | 108 | Yst Premium | 76 | Yst Free | 118 --- .phpcs.xml.dist | 5 ++++- Yoast/ruleset.xml | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 06a84839..d55bc3e2 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -62,7 +62,10 @@ - + + + *\.php$ + diff --git a/Yoast/ruleset.xml b/Yoast/ruleset.xml index 3d722e67..690a3a0e 100644 --- a/Yoast/ruleset.xml +++ b/Yoast/ruleset.xml @@ -475,4 +475,12 @@ */tests(/*)?/Doubles/*\.php$ + + + */tests/*\.php$ + + + + + From 80662b1c4bd27cf5dc4fb81e4e709884fc750bb1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 20 Sep 2023 06:09:58 +0200 Subject: [PATCH 208/212] YoastCS rules: enforce final classes for all test files ... while allowing `abstract` TestCases. Note: the typical/default "Doubles" directories within the "tests" directory are exempt from this rule as "Double" classes are often still combined with mocking and making the class `final` would break those tests. Related to 303 Impact on Yoast packages: | Plugin/Tool | Errors/Warnings | |-------------------|-----------------| | PHPUnit Polyfills | 25 (fixtures for the tests, these will be exempt) | WP Test Utils | 9 (mostly fixtures for the tests) | YoastCS | -- | WHIP | 9 | Yoast Test Helper | -- | Duplicate Post | 25 | Yst ACF | 10 | Yst WooCommerce | 25 | Yst News | 13 | Yst Local | 11 | Yst Video | 101 | Yst Premium | 126 | Yst Free | 647 --- .phpcs.xml.dist | 5 ++++- Yoast/ruleset.xml | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index d55bc3e2..03b2fb86 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -68,7 +68,10 @@ - + + + *\.php$ + + + */tests/*\.php$ + + */tests(/*)?/Doubles/*\.php$ + + From f6eda066dac78324c08547aab8fb3379820b7f25 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 18 Sep 2023 03:01:05 +0200 Subject: [PATCH 209/212] Add PHPStan to QA checks PHPStan is a good addition to our QA toolkit and with improvements PHPStan has made over the years is now a viable tool for YoastCS to use (previously it would give way too many false positives). This commit: * Adds a separate job to the `basics` workflow in GH Actions. Notes: - I've chosen **not** to add PHPStan to the Composer dependencies as it would add dependencies which could conflict/cause problems for our test runs due to those defining token constants too (PHP-Parser). - We could potentially use [Phive](https://phar.io/) to still have a setup which can be used locally, but just running locally from a PHPStan PHAR file should work just fine. - For CI, PHPStan will be installed as a PHAR file by `setup-php` now. This does carry a risk _if_ PHPStan would make breaking changes or if a new release adds rules for the levels being scanned as, in that case, builds could unexpectedly start failing. We could fix the version `setup-php` action installs to the current release `1.10.50`, but that adds an additional maintenance burden of having to keep updating the version as PHPStan releases pretty often. So, for now, I've elected to run the risk of random failures. If and when those start happening, we can re-evaluate. * Adds a configuration file for PHPStan. Notes: - PHPStan needs to know about our dependencies (PHPCS et al), so I'm (re-)using the bootstrap file we have for our tests to load the PHPCS autoloader and register the standard with the PHPCS autoloader as we can't add an `autoload` directive to our `composer.json` file as it would cause problems with the PHPCS autoloader. * Adds the configuration file to `.gitattributes` and the typical overload file for the configuration file to `.gitignore`. Refs: * https://phpstan.org/ * https://phpstan.org/config-reference --- .gitattributes | 15 +++--- .github/workflows/basics.yml | 28 ++++++++++ .gitignore | 1 + phpstan.neon.dist | 102 +++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes index 978d51ea..7707243d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,13 +5,14 @@ # https://www.reddit.com/r/PHP/comments/2jzp6k/i_dont_need_your_tests_in_my_production # https://blog.madewithlove.be/post/gitattributes/ # -/.cache export-ignore -/.github export-ignore -/Yoast/Tests export-ignore -.gitattributes export-ignore -.gitignore export-ignore -.phpcs.xml.dist export-ignore -phpunit.xml.dist export-ignore +/.cache export-ignore +/.github export-ignore +/Yoast/Tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.phpcs.xml.dist export-ignore +phpstan.neon.dist export-ignore +phpunit.xml.dist export-ignore # # Auto detect text files and perform LF normalization diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 8eb698ab..50de3085 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -100,3 +100,31 @@ jobs: # Check that the sniffs available are feature complete. - name: Check sniff feature completeness run: composer check-complete + + phpstan: + name: "PHPStan" + + runs-on: "ubuntu-latest" + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 'latest' + coverage: none + tools: phpstan + + # Install dependencies and handle caching in one go. + # 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@v2" + with: + # Bust the cache at least once a month - output format: YYYY-MM. + custom-cache-suffix: $(date -u "+%Y-%m") + + - name: Run PHPStan + run: phpstan analyse diff --git a/.gitignore b/.gitignore index d8d349e2..ad33565e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ phpcs.xml phpunit.xml .phpunit.result.cache build/ +phpstan.neon diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..1795848f --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,102 @@ +parameters: + phpVersion: 70200 + level: 9 + paths: + - Yoast + bootstrapFiles: + - phpunit-bootstrap.php + scanDirectories: + - vendor/wp-coding-standards/wpcs/WordPress +# treatPhpDocTypesAsCertain: false +# reportUnmatchedIgnoredErrors: false + dynamicConstantNames: + - YOASTCS_ABOVE_THRESHOLD + + ignoreErrors: + # Level 3 + - + # This is an issue with the PHPCS docs. Can't be helped. + message: '`^Property PHP_CodeSniffer\\Config::\$basepath \(string\) does not accept null\.$`' + path: Yoast/Tests/Utils/PSR4PathsTraitTest.php + count: 1 + - + # This is a test specifically checking the handling when an invalid value is passed, so this is okay. + message: '`^Property YoastCS\\Yoast\\Tests\\Utils\\PSR4PathsTraitTest::\$psr4_paths \(array\\) does not accept array\\.$`' + path: Yoast/Tests/Utils/PSR4PathsTraitTest.php + count: 1 + + # Level 4 + - + # Bug in PHPStan: PHPStan doesn't seem to like uninitialized properties... + message: '`^Property \S+Sniff::\$target_paths \(array\) in isset\(\) is not nullable\.$`' + path: Yoast/Sniffs/Files/TestDoublesSniff.php + count: 1 + - + # Defensive coding in the PSR4PathsTrait as the property value is user provided via the ruleset. This is okay. + message: '`^Strict comparison using === between true and false will always evaluate to false\.$`' + paths: + - Yoast/Sniffs/Files/FileNameSniff.php + - Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php + - Yoast/Tests/Utils/PSR4PathsTraitTest.php + + # Level 5 + # We're not using strict types, so this will be juggled without any issues. + - '#^Parameter \#3 \$value of method \S+File::recordMetric\(\) expects string, \(?(float|int|bool)(<[^>]+>)?(\|(float|int|bool)(<[^>]+>)?)*\)? given\.$#' + + # Level 7 + - + # False positive: the explode will never return `false` as it is never given an empty string separator. + message: '`^Parameter #2 \$array of function array_map expects array, array\|false given\.$`' + path: Yoast/Sniffs/Commenting/CoversTagSniff.php + count: 1 + - + # False positive: the preg_replace will never return `null` as the regex is valid. + message: '`^Parameter #1 \$str of function strtolower expects string, string\|null given\.$`' + path: Yoast/Sniffs/Files/FileNameSniff.php + count: 1 + - + # False positive: the preg_replace will never return `null` as the regex is valid. + message: '`^Parameter #2 \$str of function explode expects string, string\|null given\.$`' + path: Yoast/Sniffs/NamingConventions/NamespaceNameSniff.php + count: 1 + - + # False positive: the passed value will only ever be an integer, PHPStan just doesn't know the shape of the array. + message: '`^Binary operation "\+" between int\|string and 1 results in an error\.$`' + count: 2 + path: Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php + - + # False positive: the passed value will only ever be an integer, PHPStan just doesn't know the shape of the array. + message: '`^Parameter #2 \$start of method PHP_CodeSniffer\\Files\\File::findNext\(\) expects int, int\|string given\.$`' + count: 2 + path: Yoast/Sniffs/NamingConventions/ValidHookNameSniff.php + - + # False positive: the passed value will only ever be an integer, PHPStan just doesn't know the shape of the array. + message: '`^Parameter #1 \$stackPtr of method PHP_CodeSniffer\\Fixer::replaceToken\(\) expects int, int\|string given\.$`' + path: Yoast/Sniffs/Yoast/JsonEncodeAlternativeSniff.php + count: 1 + - + # This is a test specifically checking type handling, so this is okay. + message: '`^Parameter #1 \$path of static method YoastCS\\Yoast\\Utils\\PathHelper::trailingslashit\(\) expects string, bool\|string given\.$`' + path: Yoast/Tests/Utils/PathHelperTest.php + count: 1 + + # Not a real issue (x 4), PHPstan just doesn't know the format of the array well enough. + - + message: '`^Binary operation "\+" between int\|string and 1 results in an error\.$`' + count: 2 + path: Yoast/Sniffs/Tools/BrainMonkeyRaceConditionSniff.php + + - + message: '`^Parameter #2 \$start of method PHP_CodeSniffer\\Files\\File::findNext\(\) expects int, int\|string given\.$`' + count: 2 + path: Yoast/Sniffs/Tools/BrainMonkeyRaceConditionSniff.php + + - + message: '`^Binary operation "\+" between int\|string and 1 results in an error\.$`' + count: 1 + path: Yoast/Sniffs/Yoast/JsonEncodeAlternativeSniff.php + + - + message: '`^Parameter #2 \$start of method PHP_CodeSniffer\\Files\\File::findNext\(\) expects int, int\|string given\.$`' + count: 1 + path: Yoast/Sniffs/Yoast/JsonEncodeAlternativeSniff.php From 0e4a0aea5b6a0ba4854fbcb01f60e71c6474d24f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Dec 2023 13:29:13 +0100 Subject: [PATCH 210/212] Remove PR templates While PR templates are a nice idea, they are unwieldy in practice as the only way to get a PR to use a template is to manually add a query variable to the new PR URL. This means that these were not used in practice, so we may as well remove them. --- .../dependency_change.md | 19 --------- .github/PULL_REQUEST_TEMPLATE/generic.md | 6 --- .github/PULL_REQUEST_TEMPLATE/sniff_change.md | 41 ------------------- 3 files changed, 66 deletions(-) delete mode 100644 .github/PULL_REQUEST_TEMPLATE/dependency_change.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/generic.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/sniff_change.md diff --git a/.github/PULL_REQUEST_TEMPLATE/dependency_change.md b/.github/PULL_REQUEST_TEMPLATE/dependency_change.md deleted file mode 100644 index d7848654..00000000 --- a/.github/PULL_REQUEST_TEMPLATE/dependency_change.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Dependency change -about: Update one or more of the YoastCS dependencies -labels: "yoast cs/qa" ---- - -## Description - -* - -Refs: - -* - - diff --git a/.github/PULL_REQUEST_TEMPLATE/generic.md b/.github/PULL_REQUEST_TEMPLATE/generic.md deleted file mode 100644 index ab92da69..00000000 --- a/.github/PULL_REQUEST_TEMPLATE/generic.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: General -about: Change which doesn't fit any of the other categories -labels: "yoast cs/qa" ---- - diff --git a/.github/PULL_REQUEST_TEMPLATE/sniff_change.md b/.github/PULL_REQUEST_TEMPLATE/sniff_change.md deleted file mode 100644 index add6a4e6..00000000 --- a/.github/PULL_REQUEST_TEMPLATE/sniff_change.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: Sniff change -about: Introduce a new sniff or update an existing sniff -labels: "yoast cs/qa" ---- - -## Description - -* - - -Refs: - -* - - -## Test instructions - -This PR can be tested by following these steps: - -* - - -## Sniff feature completeness - -* [ ] **Documentation**: I have added/updated the sniff `Standard.xml` documentation to match this change. - * [ ] Not applicable. -* [ ] **Functionality**: This change adds auto-fixer(s). - * [ ] Not applicable. -* [ ] **Unit tests**: I have added unit tests to verify the code works as intended. -* [ ] **End-to-end tests**: I have run the new/updated sniff against one or more of the Yoast plugin repositories to find false positives/negatives. - -Fixes # From aa1f058e4b0d55c239b72a5e2f3ee76472b1eec2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Dec 2023 13:35:03 +0100 Subject: [PATCH 211/212] README: updates for the release of YoastCS 3.0.0 --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 70551e43..91ae9f00 100644 --- a/README.md +++ b/README.md @@ -11,21 +11,20 @@ Yoast Coding Standards (YoastCS) is a project with rulesets for code style and q Standards are provided as a [Composer](https://getcomposer.org/) package and can be installed with: ```bash -composer create-project yoast/yoastcs:"dev-main" +composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true +composer global require --dev yoast/yoastcs:"^3.0" ``` -Composer will automatically install dependencies, register standards paths, and set default PHP Code Sniffer standard to `Yoast`. - ### As dependency To include standards as part of a project require them as development dependencies: ```bash composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true -composer require --dev yoast/yoastcs:"^2.0" +composer require --dev yoast/yoastcs:"^3.0" ``` -Composer will automatically install dependencies and register the YoastCS and other external standards with PHP_CodeSniffer. +Composer will automatically install dependencies and register YoastCS and other external standards with PHP_CodeSniffer. ## Tools provided via YoastCS @@ -63,7 +62,7 @@ Severity levels: ### The YoastCS Standard The `Yoast` standard for PHP_CodeSniffer is comprised of the following: -* The `WordPress` ruleset from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) implementing the official [WordPress PHP Coding Standards](https://make.wordpress.org/core/handbook/coding-standards/php/), with some [select exclusions](https://github.com/Yoast/yoastcs/blob/develop/Yoast/ruleset.xml#L29-L75). +* The `WordPress` ruleset from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) implementing the official [WordPress PHP Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/), with some [select exclusions](https://github.com/Yoast/yoastcs/blob/develop/Yoast/ruleset.xml#L29-L75). * The [`PHPCompatibilityWP`](https://github.com/PHPCompatibility/PHPCompatibilityWP) ruleset which checks code for PHP cross-version compatibility while preventing false positives for functionality polyfilled within WordPress. * The [`VariableAnalysis`](https://github.com/sirbrillig/phpcs-variable-analysis/) ruleset. * Select additional sniffs taken from [`PHP_CodeSniffer`](https://github.com/PHPCSStandards/PHP_CodeSniffer). @@ -104,7 +103,7 @@ All Yoast plugin repositories contain a `[.]phpcs.xml.dist` file which contains From the root of these repositories, you can run PHPCS by using: ```bash -composer check-cs +composer check-cs-warnings ``` #### PhpStorm @@ -139,6 +138,10 @@ After the report has run, a global `YOASTCS_ABOVE_THRESHOLD` constant (boolean) To use this report, run PHPCS with the following command-line argument: `--report=YoastCS\Yoast\Reports\Threshold`. _Note: depending on the OS the command is run on, the backslashes in the report name may need to be escaped (doubled)._ +For those Yoast plugin repositories which use thresholds, the status can be checked locally by running: +```bash +composer check-cs-thresholds +``` ## Changelog From 8748cbb6326829a8b6c50a1686675b4d6e9bc2dc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Dec 2023 13:36:00 +0100 Subject: [PATCH 212/212] Changelog for the release of YoastCS 3.0.0 --- CHANGELOG.md | 151 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 142 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 102e63b4..2765c03a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,134 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/). +### [3.0.0] - 2023-12-14 + +#### Added +* Composer/PHPCS: Dependencies on the following external PHPCS standards packages: [PHPCSUtils], [PHPCSExtra], [SlevomatCodingStandard], [VariableAnalysis] and [WordPressVIP Coding Standards]. +* PHPCS: A best effort has been made to add support for new PHP syntaxes/features to all YoastCS native sniffs and utility functions (or to verify/improve existing support). + YoastCS native sniffs and utilities have received fixes for the following syntaxes: + * PHP 5.6 + - Parameter unpacking in function calls. + * PHP 8.0 + - Named arguments in function calls. + * PHP 8.1 + - Enumerations. + - First class callables. + * PHP 8.2 + - Readonly classes. +* PHPCS: The `Yoast.Commenting.CoversTag` sniff includes a new warning for the use of `ClassName<*>` type `@covers` annotations, as these have been deprecated as of PHPUnit 9.0. +* PHPCS: The `Yoast.Files.FileName` sniff now has the (optional) ability to check whether file names comply with PSR-4. + To enable this ability, add the custom `psr4_paths` property to your ruleset. The `psr4_paths` property is an array property and mirrors the `psr4` setting in the Composer `autoload` directive. It expects a namespace prefix as the array key and a comma separated list of relative paths as the array value. Multiple PSR-4 paths can be passed (array elements). + For files containing OO structures in a "PSR4 path", the `oo_prefixes` and the `excluded_files_strict_check` properties will be ignored. +* PHPCS: The `Yoast.NamingConventions.NamespaceName` sniff will now throw a `MissingPrefix` error if a prefix is expected, but the namespace name does not include a prefix. +* PHPCS: The `Yoast.NamingConventions.NamespaceName` sniff will now throw a `DirectoryInvalid` error if a file is in a directory which would not result in a valid namespace name. +* PHPCS: The `Yoast.NamingConventions.NamespaceName` sniff now has the (optional) ability to check whether namespace names comply with PSR-4. + To enable this ability, add the custom `psr4_paths` property to your ruleset. The `psr4_paths` property is an array property and mirrors the `psr4` setting in the Composer `autoload` directive. It expects a namespace prefix as the array key and a comma separated list of relative paths as the array value. Multiple PSR-4 paths can be passed (array elements). + A `psr4_paths` property will take precedence over the, potentially set, `src_directory` and `prefixes` properties. +* PHPCS: The following sniffs/error codes have been added to/enabled in the YoastCS ruleset (with appropriate configuration): + - All new sniffs which were added/included in [WordPressCS 3.0.0](https://github.com/WordPress/WordPress-Coding-Standards/releases/tag/3.0.0). + - `PSR1.Classes.ClassDeclaration` (for tests only) + - `PSR12.Properties.ConstantVisibility` + - `SlevomatCodingStandard.Arrays.DisallowImplicitArrayCreation` + - `SlevomatCodingStandard.Classes.ClassStructure` + - `SlevomatCodingStandard.Classes.ModernClassNameReference` + - `SlevomatCodingStandard.Functions.StaticClosure` + - `SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses` + - `SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalConstants` + - `SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions` + - `SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly` + - `SlevomatCodingStandard.Namespaces.UnusedUses` + - `SlevomatCodingStandard.Namespaces.UseFromSameNamespace` + - `SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint` (tests excluded) + - `SlevomatCodingStandard.TypeHints.LongTypeHints` + - `SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue` + - `SlevomatCodingStandard.TypeHints.NullTypeHintOnLastPosition` + - `SlevomatCodingStandard.TypeHints.ParameterTypeHint` + - `SlevomatCodingStandard.TypeHints.PropertyTypeHint` + - `SlevomatCodingStandard.TypeHints.ReturnTypeHint` + - `Squiz.Commenting.FunctionComment.InvalidReturnNotVoid` + - `Squiz.Commenting.FunctionComment.MissingReturn` + - `Squiz.Commenting.FunctionComment.ParamCommentNotCapital` + - `Squiz.Commenting.FunctionComment.SpacingAfterParamName` + - `Squiz.WhiteSpace.SuperfluousWhitespace.EmptyLines` + - `Universal.Classes.RequireFinalClass` (for tests only, doubles/mocks excluded) + - `Universal.CodeAnalysis.NoDoubleNegative` + - `Universal.ControlStructures.DisallowAlternativeSyntax` + - `Universal.ControlStructures.IfElseDeclaration` + - `Universal.FunctionDeclarations.NoLongClosures` + - `Universal.Operators.ConcatPosition` + - `Universal.Operators.DisallowLogicalAndOr` + - `Universal.PHP.LowercasePHPTag` + - `Universal.UseStatements.DisallowUseConst` + - `Universal.UseStatements.DisallowUseFunction` + - `VariableAnalysis.CodeAnalysis.VariableAnalysis` + - `WordPressVIPMinimum.Classes.DeclarationCompatibility` + - `WordPressVIPMinimum.Hooks.AlwaysReturnInFilter` + - `WordPressVIPMinimum.Security.EscapingVoidReturnFunctions` + - `WordPressVIPMinimum.Security.ProperEscapingFunction` +* PHPCS: New `PathHelper`, `PathValidationHelper` and `PSR4PathsTraits` classes/traits for use by the sniffs. +* Readme: section on the YoastCS `Threshold` report. + +#### Changed +* :warning: The minimum supported PHP version for this package is now PHP 7.2 (was 5.4). +* PHPCS: all sniffs are now runtime compatible with PHP 7.2 - 8.3. +* :warning: PHPCS: All non-abstract classes in YoastCS are now `final` and all non-`public` methods and properties are now `private`. + Additionally, all non-private methods in traits have also been made `final`. +* Composer: Supported version of [PHP_CodeSniffer] has been changed from `^3.7.1` to `^3.8.0`. +* :warning: Composer: Supported version of [WordPressCS] has been changed from `^2.3.0` to `^3.0.1`. + YoastCS is now fully compatible with WordPressCS 3.0. + Note: WordPressCS 3.0.0 contains breaking changes. Please read the [WordPressCS release announcement](https://make.wordpress.org/core/2023/08/21/wordpresscs-3-0-0-is-now-available/) and follow the [WordPressCS upgrade guides](https://github.com/WordPress/WordPress-Coding-Standards/wiki/). +* PHPCS: The default value for the `minimum_wp_version` (previously `minimum_supported_wp_version`) property which is used by various WPCS sniffs has been updated to WP `6.2` (was `6.0`). +* PHPCS: Files in a `wp-content/plugins/` subdirectory will now always be ignored for PHPCS scans. +* PHPCS: The ruleset included value for the `doubles_path` property in the `Yoast.Files.TestDoubles` sniff has been updated to include the typical Yoast `test/Unit/Doubles` and `test/WP/Doubles` directories as per the restructured tests. +* PHPCS: The `Yoast.Commenting.CodeCoverageIgnoreDeprecated` sniff will now also examine class docblocks. +* PHPCS: The `Yoast.Commenting.FileComment` sniff will no longer flag a file docblock in a namespaced file which doesn't contain an OO structure as redundant. +* PHPCS: The `Yoast.Files.FileName` sniff will now also examine the file name of PHP files using only the PHP short open tag (`` annotations as an invalid format when combined with a `@coversDefaultClass` tag. +* PHPCS: The `Yoast.Commenting.TestHaveCoversTag` sniff will no longer examine global functions. +* PHPCS: The `Yoast.Files.FileName` sniff will now handle the values for the `excluded_files_strict_check` property in a case-sensitive manner (as file names are case-sensitive on most operating systems). +* PHPCS: The `Yoast.Files.TestDoubles` sniff will now handle the values for the `doubles_path` property in a case-sensitive manner (as directory names are case-sensitive on most operating systems). +* PHPCS: The `Yoast.NamingConventions.NamespaceName` sniff will now bow out earlier if the namespace name is invalid (parse error). +* PHPCS: The `Yoast.NamingConventions.NamespaceName` sniff will now handle "directory to namespace name" translations more accurately and will no longer throw an error if the directory name contains an underscore. +* PHPCS: The `Yoast.NamingConventions.ObjectNameDepth` sniff now has a more accurate object name depth calculation for OO structures with a name in `CamelCaps`. + This should prevent various false positives for test classes/test doubles. +* PHPCS: The `Yoast.NamingConventions.ObjectNameDepth` sniff will no longer check if a class extends a known "TestCase" to determine whether to allow for extra object name depth, it will just base itself on the name of the object under examination, which should prevent some false positives. +* PHPCS: The fixer in the `Yoast.Yoast.JsonEncodeAlternative` sniff (previously `Yoast.Yoast.AlternativeFunctions`) will no longer inadvertently create a parse error when fixing fully qualified function calls. +* PHPCS: The `Yoast.Yoast.JsonEncodeAlternative` sniff (previously `Yoast.Yoast.AlternativeFunctions`) will now no longer try to auto-fix when it encounters PHP 5.6+ parameter unpacking. +* PHPCS: The `Yoast.Yoast.JsonEncodeAlternative` sniff (previously `Yoast.Yoast.AlternativeFunctions`) will now no longer try to auto-fix when it encounters a PHP 8.1+ first class callable. + ### [2.3.1] - 2023-03-09 #### Changed @@ -468,15 +596,20 @@ This project adheres to [Semantic Versioning](https://semver.org/) and [Keep a C Initial public release as a stand-alone package. -[PHP_CodeSniffer]: https://github.com/PHPCSStandards/PHP_CodeSniffer/releases -[WordPressCS]: https://github.com/WordPress/WordPress-Coding-Standards/blob/develop/CHANGELOG.md -[PHPCompatibilityWP]: https://github.com/PHPCompatibility/PHPCompatibilityWP#changelog -[PHPCompatibility]: https://github.com/PHPCompatibility/PHPCompatibility/blob/master/CHANGELOG.md -[PHP Mess Detector]: https://github.com/phpmd/phpmd/blob/master/CHANGELOG -[Composer PHPCS plugin]: https://github.com/PHPCSStandards/composer-installer/releases -[PHP Parallel Lint]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/releases -[PHP Console Highlighter]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/releases - +[PHP_CodeSniffer]: https://github.com/PHPCSStandards/PHP_CodeSniffer/releases +[Composer PHPCS plugin]: https://github.com/PHPCSStandards/composer-installer/releases +[PHPCompatibilityWP]: https://github.com/PHPCompatibility/PHPCompatibilityWP#changelog +[PHPCompatibility]: https://github.com/PHPCompatibility/PHPCompatibility/blob/master/CHANGELOG.md +[PHPCSExtra]: https://github.com/PHPCSStandards/PHPCSExtra/releases +[SlevomatCodingStandard]: https://github.com/slevomat/coding-standard/releases +[VariableAnalysis]: https://github.com/sirbrillig/phpcs-variable-analysis/releases +[WordPressCS]: https://github.com/WordPress/WordPress-Coding-Standards/blob/develop/CHANGELOG.md +[WordPressVIP Coding Standards]: https://github.com/Automattic/VIP-Coding-Standards/releases +[PHP Mess Detector]: https://github.com/phpmd/phpmd/blob/master/CHANGELOG +[PHP Parallel Lint]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/releases +[PHP Console Highlighter]: https://github.com/php-parallel-lint/PHP-Console-Highlighter/releases + +[3.0.0]: https://github.com/Yoast/yoastcs/compare/2.3.1...3.0.0 [2.3.1]: https://github.com/Yoast/yoastcs/compare/2.3.0...2.3.1 [2.3.0]: https://github.com/Yoast/yoastcs/compare/2.2.1...2.3.0 [2.2.1]: https://github.com/Yoast/yoastcs/compare/2.2.0...2.2.1