diff --git a/.gitattributes b/.gitattributes index 5727a3b9..a4ac8db3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,7 +8,6 @@ /.gitattributes export-ignore /.gitignore export-ignore /.phpcs.xml.dist export-ignore -/.travis.yml export-ignore /phpunit.xml.dist export-ignore /.github export-ignore /bin export-ignore diff --git a/.github/ISSUE_TEMPLATE/release-template.md b/.github/ISSUE_TEMPLATE/release-template.md index 3d1347ce..563f2d2e 100644 --- a/.github/ISSUE_TEMPLATE/release-template.md +++ b/.github/ISSUE_TEMPLATE/release-template.md @@ -7,13 +7,14 @@ assignees: GaryJones, rebeccahum --- -⚠️ DO NOT MERGE (YET) ⚠️ +:warning: DO NOT MERGE (YET) :warning: [Remaining work for this Milestone](https://github.com/Automattic/VIP-Coding-Standards/milestone/X) PR for tracking changes for the X.Y.Z release. Target release date: DOW DD MMMM YYYY. - [ ] Add change log for this release: PR #XXX +- [ ] Double-check whether any dependencies need bumping. - [ ] Merge this PR. - [ ] Add signed release tag against `master`. - [ ] Close the current milestone. diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml new file mode 100644 index 00000000..dc0e0006 --- /dev/null +++ b/.github/workflows/basics.yml @@ -0,0 +1,65 @@ +name: BasicQA + +on: + # Run on all pushes and on all pull requests. + # Prevent the "push" build from running when there are only irrelevant changes. + push: + paths-ignore: + - '**.md' + pull_request: + # Allow manually triggering the workflow. + workflow_dispatch: + +jobs: + checkcs: + name: 'Basic CS and QA checks' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + tools: cs2pr + + - name: Install xmllint + run: sudo apt-get install --no-install-recommends -y libxml2-utils + + # Show XML violations inline in the file diff. + # @link https://github.com/marketplace/actions/xmllint-problem-matcher + - uses: korelstar/xmllint-problem-matcher@v1 + + # Validate the composer.json file. + # @link https://getcomposer.org/doc/03-cli.md#validate + - name: Validate Composer installation + run: composer validate --no-check-all --strict + + - name: 'Composer: adjust dependencies' + # Using PHPCS `master` as an early detection system for bugs upstream. + run: composer require --no-update --no-scripts squizlabs/php_codesniffer:"dev-master" + + # 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@v1" + + - name: 'Validate XML against schema and check code style' + run: ./bin/xml-lint + + # Check the code-style consistency of the PHP files. + - name: Check PHP code style + continue-on-error: true + run: vendor/bin/phpcs --report-full --report-checkstyle=./phpcs-report.xml + + - name: Show PHPCS results in PR + run: cs2pr ./phpcs-report.xml + + # Check that the sniffs available are feature complete. + # For now, just check that all sniffs have unit tests. + # At a later stage the documentation check can be activated. + - name: Check sniff feature completeness + run: composer check-complete diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml new file mode 100644 index 00000000..b0b332ad --- /dev/null +++ b/.github/workflows/quicktest.yml @@ -0,0 +1,90 @@ +name: Quicktest + +on: + # Run on pushes, including merges, to all branches except `master`. + push: + branches-ignore: + - master + paths-ignore: + - '**.md' + # Allow manually triggering the workflow. + workflow_dispatch: + +jobs: + #### QUICK TEST STAGE #### + # This is a much quicker test which only runs the unit tests and linting against the low/high + # supported PHP/PHPCS combinations. + quicktest: + runs-on: ubuntu-latest + + strategy: + matrix: + include: + - php: '5.4' + phpcs_version: 'dev-master' + wpcs_version: '2.3.*' + - php: '5.4' + phpcs_version: '3.5.5' + wpcs_version: '2.3.*' + + - php: 'latest' + phpcs_version: 'dev-master' + wpcs_version: '2.3.*' + - php: 'latest' + # PHPCS 3.5.7 is the lowest version of PHPCS which supports PHP 8.0. + phpcs_version: '3.5.7' + wpcs_version: '2.3.*' + + name: "QTest${{ matrix.phpcs_version == 'dev-master' && ' + Lint' || '' }}: PHP ${{ matrix.php }} - PHPCS ${{ matrix.phpcs_version }}" + + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + # 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.phpcs_version }}" != "dev-master" ]]; then + echo '::set-output name=PHP_INI::error_reporting=E_ALL & ~E_DEPRECATED' + else + echo '::set-output name=PHP_INI::error_reporting=E_ALL' + fi + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + ini-values: ${{ steps.set_ini.outputs.PHP_INI }} + coverage: none + + - name: 'Composer: set PHPCS and WPCS versions for tests' + run: | + composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" + composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ matrix.wpcs_version }}" + + # Install dependencies and handle caching in one go. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies - normal + if: ${{ startsWith( matrix.php, '8' ) == false && matrix.php != 'latest' }} + uses: "ramsey/composer-install@v1" + + # PHPUnit 7.x does not allow for installation on PHP 8, so ignore platform + # requirements to get PHPUnit 7.x to install on nightly. + - name: Install Composer dependencies - with ignore platform + if: ${{ startsWith( matrix.php, '8' ) || matrix.php == 'latest' }} + uses: "ramsey/composer-install@v1" + with: + composer-options: --ignore-platform-reqs + + - name: Lint against parse errors + if: matrix.phpcs_version == 'dev-master' + run: ./bin/php-lint + + - name: Run the unit tests + run: ./bin/unit-tests + + - name: Run the ruleset tests + run: ./bin/ruleset-tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..3353e971 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,141 @@ +name: Test + +on: + # Run on pushes to `master` and on all pull requests. + # Prevent the "push" build from running when there are only irrelevant changes. + push: + branches: + - master + paths-ignore: + - '**.md' + pull_request: + # Allow manually triggering the workflow. + workflow_dispatch: + +jobs: + #### PHP LINT STAGE #### + # Linting against high/low PHP versions should catch everything. + # If needs be, we can always add interim versions at a later point in time. + lint: + runs-on: ubuntu-latest + + strategy: + matrix: + php: ['5.4', 'latest'] + experimental: [false] + + include: + - php: '8.1' + experimental: true + + name: "Lint: PHP ${{ matrix.php }}" + continue-on-error: ${{ matrix.experimental }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + tools: cs2pr + + - name: Install Composer dependencies + uses: "ramsey/composer-install@v1" + + - name: Lint against parse errors + run: ./bin/php-lint --checkstyle | cs2pr + + test: + # No use running the tests if there is a linting error somewhere as they would fail anyway. + needs: lint + + runs-on: ubuntu-latest + + strategy: + # Keys: + # - php: The PHP versions to test against. + # - phpcs_version: The PHPCS versions to test against. + # IMPORTANT: test runs shouldn't fail because of PHPCS being incompatible with a PHP version. + # - PHPCS will run without errors on PHP 5.4 - 7.4 on any supported version. + # - PHP 8.0 needs PHPCS 3.5.7+ to run without errors. + # - The `wpcs_version` key is added to allow additional test builds when multiple WPCS versions + # would be supported. As, at this time, only the latest stable release of WPCS is supported, + # no additional versions are included in the array. + # - experimental: Whether the build is "allowed to fail". + matrix: + php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] + phpcs_version: ['3.5.5', 'dev-master'] + wpcs_version: ['2.3.*'] + experimental: [false] + + include: + # Complete the matrix by adding PHP 8.0, but only test against compatible PHPCS versions. + - php: '8.0' + phpcs_version: 'dev-master' + wpcs_version: '2.3.*' + experimental: false + - php: '8.0' + # PHPCS 3.5.7 is the lowest version of PHPCS which supports PHP 8.0. + phpcs_version: '3.5.7' + wpcs_version: '2.3.*' + experimental: false + + # Experimental builds. These are allowed to fail. + #- php: '8.1' + # phpcs_version: 'dev-master' + # wpcs_version: '2.3.*' + # experimental: true + + name: "Test: PHP ${{ matrix.php }} - PHPCS ${{ matrix.phpcs_version }} - WPCS ${{ matrix.wpcs_version }}" + + continue-on-error: ${{ matrix.experimental }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + # 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.phpcs_version }}" != "dev-master" ]]; then + echo '::set-output name=PHP_INI::error_reporting=E_ALL & ~E_DEPRECATED' + else + echo '::set-output name=PHP_INI::error_reporting=E_ALL' + fi + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + ini-values: ${{ steps.set_ini.outputs.PHP_INI }} + coverage: none + + - name: 'Composer: set PHPCS and WPCS versions for tests' + run: | + composer require --no-update --no-scripts squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" + composer require --no-update --no-scripts wp-coding-standards/wpcs:"${{ matrix.wpcs_version }}" + + # Install dependencies and handle caching in one go. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies - normal + if: ${{ startsWith( matrix.php, '8' ) == false }} + uses: "ramsey/composer-install@v1" + + # PHPUnit 7.x does not allow for installation on PHP 8, so ignore platform + # requirements to get PHPUnit 7.x to install on nightly. + - name: Install Composer dependencies - with ignore platform + if: ${{ startsWith( matrix.php, '8' ) }} + uses: "ramsey/composer-install@v1" + with: + composer-options: --ignore-platform-reqs + + - name: Run the unit tests + run: ./bin/unit-tests + + - name: Run the ruleset tests + run: ./bin/ruleset-tests diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 65f73b2d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,114 +0,0 @@ -language: php -os: linux -dist: xenial - -env: - # `master` is now 3.x. - - PHPCS_BRANCH="dev-master" - # Lowest supported release in the 3.x series with which VIPCS is compatible. - - PHPCS_BRANCH="3.5.5" - -cache: - directories: - - $HOME/.cache/composer/files - -php: - - 5.6 - - 7.0 - - 7.1 - - 7.2 - - 7.3 - - 7.4 - - "nightly" - -# Rather than a `matrix` property, we use build stages. This allows early -# build failure for basic linting and sniffing issues. -# @link https://docs.travis-ci.com/user/build-stages/ - -stages: - - lint - - sniff - - test - -jobs: - allow_failures: - - php: "nightly" - include: - - - stage: lint - php: 7.4 - env: PHPCS_BRANCH="dev-master" - before_install: phpenv config-rm xdebug.ini || echo 'No xdebug config.' - install: skip - cache: skip - script: - # Lint the PHP files against parse errors. - - ./bin/php-lint - - # Add PHPCS locally for the XSD. - - composer require squizlabs/php_codesniffer - # Validate the XML files and check the code-style consistency of the XML files. - - ./bin/xml-lint - - # Validate the composer.json file. - # @link https://getcomposer.org/doc/03-cli.md#validate - - composer validate --no-check-all --strict - addons: - apt: - packages: - - libxml2-utils - - - stage: sniff - php: 7.4 - env: PHPCS_BRANCH="dev-master" - before_install: phpenv config-rm xdebug.ini || echo 'No xdebug config.' - install: composer install --no-suggest - script: - # Run PHPCS against VIPCS. - - ./bin/phpcs - - # Builds which need a different distro. - - stage: test - - php: 5.5 - dist: trusty - env: PHPCS_BRANCH="dev-master" - - php: 5.5 - dist: trusty - env: PHPCS_BRANCH="3.5.5" - - php: 5.4 - dist: trusty - env: PHPCS_BRANCH="dev-master" - - php: 5.4 - dist: trusty - env: PHPCS_BRANCH="3.5.5" - -before_install: - # Speed up build time by disabling Xdebug. - # https://johnblackbourn.com/reducing-travis-ci-build-times-for-wordpress-projects/ - # https://twitter.com/kelunik/status/954242454676475904 - - phpenv config-rm xdebug.ini || echo 'No xdebug config.' - - # 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. - - | - if [[ "$TRAVIS_BUILD_STAGE_NAME" != "Sniff" && $PHPCS_BRANCH != "dev-master" ]]; then - echo 'error_reporting = E_ALL & ~E_DEPRECATED' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - fi - -install: - - composer require squizlabs/php_codesniffer:"$PHPCS_BRANCH" --no-update --no-suggest --no-scripts - - | - if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then - # PHPUnit 7.x does not allow for installation on PHP 8, so ignore platform - # requirements to get PHPUnit 7.x to install on nightly. - composer install --ignore-platform-reqs --no-suggest - else - composer install --no-suggest - fi - -script: - # Run the unit tests. - - ./bin/unit-tests - - # Run ruleset tests. - - ./bin/ruleset-tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 70a2051b..e3c3744d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,71 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.3.0] - 2021-04-19 + +Props: jrfnl, rebeccahum, kevinfodness, GaryJones. + +** There is a minor breaking change in the ProperEscapingFunction sniff from PR [#624](https://github.com/Automattic/VIP-Coding-Standards/pull/624). The `escaping_function` property can no longer be overruled via custom rulesets. Please remove any usages of the property in custom rulesets. + +** Composer now requires the [phpcodesniffer-composer-installer](https://github.com/Dealerdirect/phpcodesniffer-composer-installer) plugin per [#583](https://github.com/Automattic/VIP-Coding-Standards/pull/583). Note: If you either include it in the "require-dev" of your `composer.json`, use another Composer PHPCS plugin, or run bash commands to register PHPCS standards, please remove it from those sources to prevent interferences or version constraint conflicts. + +### Added +- [#581](https://github.com/Automattic/VIP-Coding-Standards/pull/581): AlwaysReturnInFilter: flag abstract methods for manual inspection. +- [#583](https://github.com/Automattic/VIP-Coding-Standards/pull/583): Composer: require phpcs-composer-installer plugin. +- [#586](https://github.com/Automattic/VIP-Coding-Standards/pull/586): IncludingNonPHPFile: recognition of .phar file extensions. +- [#589](https://github.com/Automattic/VIP-Coding-Standards/pull/589): WPQueryParams: flags 'exclude' array key. +- [#595](https://github.com/Automattic/VIP-Coding-Standards/pull/595): Underscorejs: checks for additional print syntaxes and now throws an additional error for each occurrence of unescaped notation. +- [#624](https://github.com/Automattic/VIP-Coding-Standards/pull/624): ProperEscapingFunction: account for additional escaping functions and check for `esc_attr()` usage in non-HTML attributes. +- [#638](https://github.com/Automattic/VIP-Coding-Standards/pull/638): IncludingFile: new public property `$allowedKeywords` for allowing custom partial keywords in constants to reduce false positives. + +### Changed +- [#586](https://github.com/Automattic/VIP-Coding-Standards/pull/586): IncludingNonPHPFile: various performance improvements. +- [#587](https://github.com/Automattic/VIP-Coding-Standards/pull/587): LowExpiryCacheTime: new warning added for manual inspection along with various improvements. +- [#592](https://github.com/Automattic/VIP-Coding-Standards/pull/592): DynamicCalls: various improvements. +- [#595](https://github.com/Automattic/VIP-Coding-Standards/pull/595): Underscorejs: various improvements. +- [#618](https://github.com/Automattic/VIP-Coding-Standards/pull/618): RestrictedFunctions: upgrade setcookie() to error at sniff level and remove Batcache references from messaging. +- [#620](https://github.com/Automattic/VIP-Coding-Standards/pull/620): Ruleset: silence UnusedVariable from VariableAnalysis to reduce noise. +- [#630](https://github.com/Automattic/VIP-Coding-Standards/pull/630): VariableAnalysis: fix incompatibility for VariableAnalysis standard with previously deprecated native VIPCS sniff. +- [#639](https://github.com/Automattic/VIP-Coding-Standards/pull/639): RestrictedFunctions: remove site_option group. +- [#644](https://github.com/Automattic/VIP-Coding-Standards/pull/644): RestrictedFunctions: remove wp_cache_get_multi group. +- [#645](https://github.com/Automattic/VIP-Coding-Standards/pull/645): Ruleset: silence WordPress.WP.AlternativeFunctions.file_system_read_readfile. +- [#646](https://github.com/Automattic/VIP-Coding-Standards/pull/646): Ruleset: silence WordPress.WP.AlternativeFunctions.file_system_read_fclose. +- [#647](https://github.com/Automattic/VIP-Coding-Standards/pull/647): RestrictedFunctions: remove get_super_admins group. +- [#649](https://github.com/Automattic/VIP-Coding-Standards/pull/649): RestrictedFunctions: downgrade switch_to_blog() to warning and change messaging. +- [#652](https://github.com/Automattic/VIP-Coding-Standards/pull/652): RestrictedFunctions/RestrictedVariables: remove usermeta related errors. + +### Fixed +- [#444](https://github.com/Automattic/VIP-Coding-Standards/pull/444): ConstantString: only error when a plain constant is passed as constant name parameter. +- [#581](https://github.com/Automattic/VIP-Coding-Standards/pull/581): AlwaysReturnInFilter: fix runtime failure on abstract methods. +- [#584](https://github.com/Automattic/VIP-Coding-Standards/pull/584): Performance: more selective sniffing for efficiency. +- [#586](https://github.com/Automattic/VIP-Coding-Standards/pull/586): IncludingNonPHPFile: various bug fixes such as recognition of interpolated strings and case insensitivity in file extensions. +- [#587](https://github.com/Automattic/VIP-Coding-Standards/pull/587): LowExpiryCacheTime: allow arithmetic operators, simple floats, numerical strings, zeroes and parentheses in calculations, and FQN time constants. +- [#592](https://github.com/Automattic/VIP-Coding-Standards/pull/592): DynamicCalls: ignore comments, allow double quotes and remove potential memory leak. +- [#595](https://github.com/Automattic/VIP-Coding-Standards/pull/595): Underscorejs: fixed false positive for when a variable is `_.escape()`-ed. +- [#624](https://github.com/Automattic/VIP-Coding-Standards/pull/624): ProperEscapingFunction: slash escaped quotes and non-quoted strings in HTML attributes are now parsed as expected. + +### Removed +- [#624](https://github.com/Automattic/VIP-Coding-Standards/pull/624): ProperEscapingFunction: remove `$escaping_functions` public property. + +### Maintenance +- [#582](https://github.com/Automattic/VIP-Coding-Standards/pull/582): CI: re-try composer install on failure. +- [#599](https://github.com/Automattic/VIP-Coding-Standards/pull/599): CI: add build against PHP 8. +- [#606](https://github.com/Automattic/VIP-Coding-Standards/pull/606): Ruleset: remove redundant rule ref. +- [#607](https://github.com/Automattic/VIP-Coding-Standards/pull/607): Ruleset: remove redundant rule ref. +- [#608](https://github.com/Automattic/VIP-Coding-Standards/pull/608): Ruleset: remove duplicate rule ref. +- [#611](https://github.com/Automattic/VIP-Coding-Standards/pull/611): Ruleset: remove redundant notice type declaration. +- [#617](https://github.com/Automattic/VIP-Coding-Standards/pull/617): Ruleset: remove redundant notice type declaration. +- [#619](https://github.com/Automattic/VIP-Coding-Standards/pull/619): Docs: Update links to wpvip.com. +- [#631](https://github.com/Automattic/VIP-Coding-Standards/pull/631): QA: remove unused use statements. +- [#632](https://github.com/Automattic/VIP-Coding-Standards/pull/632): Docs: various minor improvements (typos, alignment and code examples). +- [#633](https://github.com/Automattic/VIP-Coding-Standards/pull/633): CI: switch to GitHub Actions. +- [#635](https://github.com/Automattic/VIP-Coding-Standards/pull/635): Ruleset: remove redundant rule ref. +- [#653](https://github.com/Automattic/VIP-Coding-Standards/pull/653): CI: use parallel linting of PHP files. +- [#655](https://github.com/Automattic/VIP-Coding-Standards/pull/655): QA: remove redundant ignore annotations. +- [#656](https://github.com/Automattic/VIP-Coding-Standards/pull/656): CI: always check that sniffs are feature complete. +- [#657](https://github.com/Automattic/VIP-Coding-Standards/pull/657): CI: add "quicktest" stage for non-PR/merge builds. +- [#658](https://github.com/Automattic/VIP-Coding-Standards/pull/658): Release template: add checkbox for dependency check. + ## [2.2.0] - 2020-09-09 Props: GaryJones, jrfnl, rebeccahum. @@ -474,6 +539,7 @@ Initial release. Props: david-binda, pkevan. +[2.3.0]: https://github.com/Automattic/VIP-Coding-Standards/compare/2.2.0...2.3.0 [2.2.0]: https://github.com/Automattic/VIP-Coding-Standards/compare/2.1.0...2.2.0 [2.1.0]: https://github.com/Automattic/VIP-Coding-Standards/compare/2.0.0...2.1.0 [2.0.0]: https://github.com/Automattic/VIP-Coding-Standards/compare/1.0.0...2.0.0 diff --git a/README.md b/README.md index 064eabe4..8a7c31ae 100644 --- a/README.md +++ b/README.md @@ -7,31 +7,28 @@ This project contains two rulesets: - `WordPressVIPMinimum` - for use with projects on the (older) WordPress.com VIP platform. - `WordPress-VIP-Go` - for use with projects on the (newer) VIP Go platform. -These rulesets contain only the rules which are considered to be "errors" and "warnings" according to the [WordPress VIP Go documentation](https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/) +These rulesets contain only the rules which are considered to be ["errors"](https://docs.wpvip.com/technical-references/code-review/vip-errors/) and ["warnings"](https://docs.wpvip.com/technical-references/code-review/vip-warnings/) according to the WordPress VIP Go documentation. -The rulesets use rules from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) (WPCS) project. +The rulesets use rules from the [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) (WPCS) project, as well as the [VariableAnalysis](https://github.com/sirbrillig/phpcs-variable-analysis) standard. -Go to https://wpvip.com/documentation/phpcs-review-feedback/ to learn about why violations are flagged as errors vs warnings and what the levels mean. +Go to https://docs.wpvip.com/technical-references/code-review/phpcs-report/ to learn about why violations are flagged as errors vs warnings and what the levels mean. ## Minimal requirements * PHP 5.4+ * [PHPCS 3.5.5+](https://github.com/squizlabs/PHP_CodeSniffer/releases) * [WPCS 2.3.0+](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/releases) +* [VariableAnalysis 2.8.3+](https://github.com/sirbrillig/phpcs-variable-analysis/releases) ## Installation `composer require automattic/vipwpcs`, or `composer g require automattic/vipwpcs` if installing globally. -This will install the latest compatible versions of PHPCS and WPCS. +This will install the latest compatible versions of PHPCS, WPCS and VariableAnalysis and register the external standards with PHP_CodeSniffer. -Please refer to the [installation instructions for installing PHP_CodeSniffer for WordPress.com VIP](https://wpvip.com/documentation/how-to-install-php-code-sniffer-for-wordpress-com-vip/) for more details. +Please refer to the [installation instructions for installing PHP_CodeSniffer for WordPress.com VIP](https://docs.wpvip.com/how-tos/code-review/php_codesniffer/) for more details. -We recommend the [PHP_CodeSniffer Standards Composer Installer Plugin](https://github.com/Dealerdirect/phpcodesniffer-composer-installer), which handles the registration of all of the installed standards, so there is no need to set the `installed_paths` config value manually, for single or multiple standards. - -Alternatively, you should register the standard to PHPCS by appending the VIPCS directory to the end of the installed paths. e.g. - -`phpcs --config-set installed_paths /path/to/wpcsstandard,path/to/vipcsstandard` +As of VIPCS version 2.3.0, there is no need to `require` the [PHP_CodeSniffer Standards Composer Installer Plugin](https://github.com/Dealerdirect/phpcodesniffer-composer-installer) anymore as it is now a requirement of VIPCS itself. ## Contribution diff --git a/WordPress-VIP-Go/ruleset-test.inc b/WordPress-VIP-Go/ruleset-test.inc index 4706542e..876a9c6d 100644 --- a/WordPress-VIP-Go/ruleset-test.inc +++ b/WordPress-VIP-Go/ruleset-test.inc @@ -47,17 +47,17 @@ touch( $file ); // Warning + Message. unlink( $file ); // Warning + Message. // WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie -setcookie( 'cookie[three]', 'cookiethree' ); // Error + Message. +setcookie( 'cookie[three]', 'cookiethree' ); // Error. // WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE -$x = sanitize_key( $_COOKIE['bar'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Error + Message. +$x = sanitize_key( $_COOKIE['bar'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Error. // WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__ -if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && $_SERVER['HTTP_USER_AGENT'] === 'some_value' ) { // Error + Message. +if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && $_SERVER['HTTP_USER_AGENT'] === 'some_value' ) { // Error. } -// WordPress.WP.AlternativeFunctions.file_system_read_fclose -fclose( $fp ); // Warning + Message. + + // WordPress.WP.AlternativeFunctions.file_system_read_fopen fopen( 'file.txt', 'r' ); // Warning + Message. @@ -67,7 +67,7 @@ $external_resource = file_get_contents( $test ); // Warning + Message. $file_content = file_get_contents( 'my-file.svg' ); // Ok. wpcom_vip_file_get_contents( $bar ); // Ok. -// WordPress.Security.NonceVerification.NoNonceVerification +// WordPress.Security.NonceVerification (inherited from parent) function bar_foo() { if ( ! isset( $_POST['test'] ) ) { // Error. return; @@ -214,7 +214,7 @@ function foo_bar_bar() { // VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable function foo_bar_foo() { - $a = 'Hello'; // Warning + $a = 'Hello'; // OK. Unused variables warning silenced. } // WordPressVIPMinimum.UserExperience.AdminBarRemoval @@ -284,20 +284,20 @@ if ( isset( $_GET['migSource'] ) && wp_verify_nonce( sanitize_text_field( $_GET[ $test = sanitize_text_field( $_GET['migSource'] ); // Ok. } -// WordPressVIPMinimum.Functions.RestrictedFunctions.user_meta_add_user_meta -add_user_meta( 123, $foo, $bar ); // Ok. -// WordPressVIPMinimum.Functions.RestrictedFunctions.user_meta_delete_user_meta -delete_user_meta( $foo, $bar, '123' ); // Ok. -// WordPressVIPMinimum.Functions.RestrictedFunctions.user_meta_get_user_meta -get_user_meta(); // Ok. -// WordPressVIPMinimum.Functions.RestrictedFunctions.user_meta_update_user_meta -update_user_meta( $bar, '123', $foo ); // Ok. -// WordPressVIPMinimum.Variables.RestrictedVariables.user_meta__wpdb__usermeta -$query = "SELECT * FROM $wpdb->usermeta"; // Ok. + + + + + + + + + + // WordPressVIPMinimum.Functions.RestrictedFunctions.site_option_delete_site_option delete_site_option( $foo ); // Ok. @@ -346,7 +346,7 @@ $my_notokay_func = 'extract'; $my_notokay_func(); // Error. // WordPressVIPMinimum.Functions.RestrictedFunctions -wp_cache_get_multi(); // Error. + opcache_reset(); // Error. opcache_invalidate( 'test_script.php' ); // Error. opcache_compile_file( $var ); // Error. @@ -417,7 +417,7 @@ the_sub_field( 'field' ); // Warning. the_field( 'field' ); // Warning. wp_remote_get( $url ); // Warning. get_posts(); // Warning. -function test_function( $a, $b ) { +function test_function( $a, $b ) { // OK. Unused variables warning silenced. return create_function( '$a, $b', 'return ( $b / $a ); '); // Warning. } wpcom_vip_get_term_link(); // Warning. diff --git a/WordPress-VIP-Go/ruleset-test.php b/WordPress-VIP-Go/ruleset-test.php index d15f245e..6bca6c91 100644 --- a/WordPress-VIP-Go/ruleset-test.php +++ b/WordPress-VIP-Go/ruleset-test.php @@ -37,7 +37,6 @@ 341 => 1, 342 => 1, 346 => 1, - 349 => 1, 350 => 1, 351 => 1, 352 => 1, @@ -134,7 +133,6 @@ 41 => 1, 44 => 1, 47 => 1, - 60 => 1, 63 => 1, 66 => 1, 85 => 1, @@ -184,7 +182,6 @@ 207 => 1, 208 => 1, 212 => 1, - 217 => 1, 221 => 1, 223 => 1, 225 => 1, @@ -222,7 +219,6 @@ 417 => 1, 418 => 1, 419 => 1, - 420 => 2, 421 => 1, 423 => 1, 424 => 1, @@ -243,49 +239,49 @@ ], 'messages' => [ 4 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as delete(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as delete(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 7 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as file_put_contents(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as file_put_contents(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 10 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as flock(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as flock(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 14 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fputcsv(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fputcsv(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 17 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fputs(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fputs(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 20 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fwrite(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fwrite(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 23 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as ftruncate(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as ftruncate(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 26 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as is_writable(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as is_writable(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 29 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as is_writeable(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as is_writeable(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 32 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as link(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as link(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 35 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as rename(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as rename(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 38 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as symlink(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as symlink(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 41 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as tempnam(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as tempnam(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 44 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as touch(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as touch(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 47 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as unlink(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as unlink(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 50 => [ 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', @@ -296,14 +292,11 @@ 56 => [ 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', ], - 60 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fclose(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', - ], 63 => [ - 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fopen(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/', + 'File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as fopen(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/', ], 66 => [ - 'file_get_contents() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see https://wpvip.com/documentation/vip-go/fetching-remote-data/', + 'file_get_contents() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/', ], 90 => [ 'Having more than 100 posts returned per page may lead to severe performance problems.', @@ -321,7 +314,7 @@ 'get_page_by_title() is uncached, please use wpcom_vip_get_page_by_title() instead.', ], 139 => [ - 'get_children() is uncached and performs a no limit query. Please use get_posts or WP_Query instead. More Info: https://wpvip.com/documentation/vip-go/uncached-functions/', + 'get_children() is uncached and performs a no limit query. Please use get_posts or WP_Query instead. Please see: https://docs.wpvip.com/technical-references/caching/uncached-functions/', ], 150 => [ 'url_to_postid() is uncached, please use wpcom_vip_url_to_postid() instead.', @@ -338,9 +331,6 @@ 196 => [ 'Stylesheets should be registered/enqueued via `wp_enqueue_style`. This can improve the site\'s performance due to styles concatenation.', ], - 269 => [ - 'Switch to blog may not work as expected since it only changes the database context for the blog and does not load the plugins or theme of that site. This means that filters or hooks that the blog you are switching to uses will not run.', - ], ], ]; @@ -349,7 +339,6 @@ // Run the tests! $test = new RulesetTest( 'WordPress-VIP-Go', $expected ); if ( $test->passes() ) { - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped printf( 'All WordPress-VIP-Go tests passed!' . PHP_EOL ); exit( 0 ); } diff --git a/WordPress-VIP-Go/ruleset.xml b/WordPress-VIP-Go/ruleset.xml index 240994bf..ba9099a2 100644 --- a/WordPress-VIP-Go/ruleset.xml +++ b/WordPress-VIP-Go/ruleset.xml @@ -10,77 +10,77 @@ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning 6 - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ warning @@ -93,47 +93,31 @@ Hiding of admin bar is highly discouraged for user roles of "administrator" and "vip_support" -- if these roles are already excluded, this warning can be ignored. - error 6 - Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead. error 6 - Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead. error 6 - Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead. - - warning - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ - - - warning - File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://wpvip.com/documentation/vip-go/writing-files-on-vip-go/ + File system operations only work on the `/tmp/` and `wp-content/uploads/` directories. To avoid unexpected results, please use helper functions like `get_temp_dir()` or `wp_get_upload_dir()` to get the proper directory path when using functions such as %s(). For more details, please see: https://docs.wpvip.com/technical-references/vip-go-files-system/local-file-operations/ - warning - %s() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see https://wpvip.com/documentation/vip-go/fetching-remote-data/ + %s() is uncached. If the function is being used to fetch a remote file (e.g. a URL starting with https://), please use wpcom_vip_file_get_contents() to ensure the results are cached. For more details, please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/ - - - warning - 10 - warning @@ -184,7 +168,7 @@ warning 3 - %s() is uncached and performs a no limit query. Please use get_posts or WP_Query instead. More Info: https://wpvip.com/documentation/vip-go/uncached-functions/ + %s() is uncached and performs a no limit query. Please use get_posts or WP_Query instead. Please see: https://docs.wpvip.com/technical-references/caching/uncached-functions/ 3 @@ -223,10 +207,6 @@ 1 - - - 3 - warning 3 @@ -247,9 +227,6 @@ 3 - - 1 - 3 @@ -264,15 +241,16 @@ 1 - warning 3 - Switch to blog may not work as expected since it only changes the database context for the blog and does not load the plugins or theme of that site. This means that filters or hooks that the blog you are switching to uses will not run. 3 + + 0 + 0 @@ -281,34 +259,13 @@ 0 - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - + 0 - + 0 - + 0 diff --git a/WordPressVIPMinimum/Sniffs/Compatibility/ZoninatorSniff.php b/WordPressVIPMinimum/Sniffs/Compatibility/ZoninatorSniff.php index 7d18a7f5..1dae2b96 100644 --- a/WordPressVIPMinimum/Sniffs/Compatibility/ZoninatorSniff.php +++ b/WordPressVIPMinimum/Sniffs/Compatibility/ZoninatorSniff.php @@ -22,7 +22,7 @@ class ZoninatorSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } diff --git a/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php b/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php index 24a13635..d6506c8f 100644 --- a/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php +++ b/WordPressVIPMinimum/Sniffs/Constants/ConstantStringSniff.php @@ -55,14 +55,26 @@ public function process_token( $stackPtr ) { return; } - $nextToken = $this->phpcsFile->findNext( Tokens::$emptyTokens, $nextToken + 1, null, true, null, true ); + $param = $this->get_function_call_parameter( $stackPtr, 1 ); + if ( $param === false ) { + // Target parameter not found. + return; + } + + $search = Tokens::$emptyTokens; + $search[ T_STRING ] = T_STRING; - if ( $this->tokens[ $nextToken ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { - $message = 'Constant name, as a string, should be used along with `%s()`.'; - $data = [ $this->tokens[ $stackPtr ]['content'] ]; - $this->phpcsFile->addError( $message, $nextToken, 'NotCheckingConstantName', $data ); + $has_only_tstring = $this->phpcsFile->findNext( $search, $param['start'], $param['end'] + 1, true ); + if ( $has_only_tstring !== false ) { + // Came across something other than a T_STRING token. Ignore. return; } + + $tstring_token = $this->phpcsFile->findNext( T_STRING, $param['start'], $param['end'] + 1 ); + + $message = 'Constant name, as a string, should be used along with `%s()`.'; + $data = [ $this->tokens[ $stackPtr ]['content'] ]; + $this->phpcsFile->addError( $message, $tstring_token, 'NotCheckingConstantName', $data ); } } diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingFileSniff.php index 0451dafb..4cbbfb12 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingFileSniff.php @@ -13,8 +13,7 @@ /** * WordPressVIPMinimum_Sniffs_Files_IncludingFileSniff. * - * Checks that __DIR__, dirname( __FILE__ ) or plugin_dir_path( __FILE__ ) - * is used when including or requiring files. + * Checks for custom variables, functions and constants, and external URLs used in file inclusion. * * @package VIPCS\WordPressVIPMinimum */ @@ -55,6 +54,17 @@ class IncludingFileSniff extends AbstractFunctionRestrictionsSniff { 'WP_PLUGIN_DIR', ]; + /** + * List of keywords allowed for use in custom constants. + * Note: Customizing this property will overwrite current default values. + * + * @var array + */ + public $allowedKeywords = [ + 'PATH', + 'DIR', + ]; + /** * Functions used for modify slashes. * @@ -122,6 +132,11 @@ public function process_token( $stackPtr ) { return; } + if ( $this->has_custom_path( $this->tokens[ $nextToken ]['content'] ) === true ) { + // The construct is using a constant with an allowed keyword. + return; + } + if ( array_key_exists( $this->tokens[ $nextToken ]['content'], $this->restrictedConstants ) === true ) { // The construct is using one of the restricted constants. $message = '`%s` constant might not be defined or available. Use `%s()` instead.'; @@ -172,4 +187,21 @@ public function process_token( $stackPtr ) { $message = 'Absolute include path must be used. Use `get_template_directory()`, `get_stylesheet_directory()` or `plugin_dir_path()`.'; $this->phpcsFile->addError( $message, $nextToken, 'NotAbsolutePath' ); } + + /** + * Check if a content string contains a keyword in custom paths. + * + * @param string $content Content string. + * + * @return bool True if the string partially matches a keyword in $allowedCustomKeywords, false otherwise. + */ + private function has_custom_path( $content ) { + foreach ( $this->allowedKeywords as $keyword ) { + if ( strpos( $content, $keyword ) !== false ) { + return true; + } + } + + return false; + } } diff --git a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php index ba1735c1..de30c6e5 100644 --- a/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php +++ b/WordPressVIPMinimum/Sniffs/Files/IncludingNonPHPFileSniff.php @@ -7,20 +7,41 @@ namespace WordPressVIPMinimum\Sniffs\Files; -use PHP_CodeSniffer\Files\File; use WordPressVIPMinimum\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; /** - * WordPressVIPMinimum_Sniffs_Files_IncludingNonPHPFileSniff. + * Ensure that non-PHP files are included via `file_get_contents()` instead of using `include/require[_once]`. * - * Checks that __DIR__, dirname( __FILE__ ) or plugin_dir_path( __FILE__ ) - * is used when including or requiring files. + * This prevents potential PHP code embedded in those files from being automatically executed. * * @package VIPCS\WordPressVIPMinimum */ class IncludingNonPHPFileSniff extends Sniff { + /** + * File extensions used for PHP files. + * + * Files with these extensions are allowed to be `include`d. + * + * @var array Key is the extension, value is irrelevant. + */ + private $php_extensions = [ + 'php' => true, + 'inc' => true, + 'phar' => true, + ]; + + /** + * File extensions used for SVG and CSS files. + * + * @var array Key is the extension, value is irrelevant. + */ + private $svg_css_extensions = [ + 'css' => true, + 'svg' => true, + ]; + /** * Returns an array of tokens this test wants to listen for. * @@ -30,7 +51,6 @@ public function register() { return Tokens::$includeTokens; } - /** * Processes this test, when one of its tokens is encountered. * @@ -39,39 +59,47 @@ public function register() { * @return void */ public function process_token( $stackPtr ) { - $curStackPtr = $stackPtr; - while ( $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, null, false, null, true ) !== false ) { - $curStackPtr = $this->phpcsFile->findNext( Tokens::$stringTokens, $curStackPtr + 1, null, false, null, true ); - - if ( $this->tokens[ $curStackPtr ]['code'] === T_CONSTANT_ENCAPSED_STRING ) { - $stringWithoutEnclosingQuotationMarks = trim( $this->tokens[ $curStackPtr ]['content'], "\"'" ); - } else { - $stringWithoutEnclosingQuotationMarks = $this->tokens[ $curStackPtr ]['content']; + $end_of_statement = $this->phpcsFile->findEndOfStatement( $stackPtr ); + $curStackPtr = ( $end_of_statement + 1 ); + + do { + $curStackPtr = $this->phpcsFile->findPrevious( Tokens::$stringTokens, $curStackPtr - 1, $stackPtr ); + if ( $curStackPtr === false ) { + return; } - $isFileName = preg_match( '/.*(\.[a-z]{2,})$/i', $stringWithoutEnclosingQuotationMarks, $regexMatches ); + $stringWithoutEnclosingQuotationMarks = trim( $this->tokens[ $curStackPtr ]['content'], "\"'" ); + + $isFileName = preg_match( '`\.([a-z]{2,})$`i', $stringWithoutEnclosingQuotationMarks, $regexMatches ); - if ( $isFileName === false || $isFileName === 0 ) { + if ( $isFileName !== 1 ) { continue; } - $extension = $regexMatches[1]; - if ( in_array( $extension, [ '.php', '.inc' ], true ) === true ) { + $extension = strtolower( $regexMatches[1] ); + if ( isset( $this->php_extensions[ $extension ] ) === true ) { return; } - $message = 'Local non-PHP file should be loaded via `file_get_contents` rather than via `%s`.'; - $data = [ $this->tokens[ $stackPtr ]['content'] ]; + $message = 'Local non-PHP file should be loaded via `file_get_contents` rather than via `%s`. Found: %s'; + $data = [ + strtolower( $this->tokens[ $stackPtr ]['content'] ), + $this->tokens[ $curStackPtr ]['content'], + ]; $code = 'IncludingNonPHPFile'; - if ( in_array( $extension, [ '.svg', '.css' ], true ) === true ) { + if ( isset( $this->svg_css_extensions[ $extension ] ) === true ) { // Be more specific for SVG and CSS files. - $message = 'Local SVG and CSS files should be loaded via `file_get_contents` rather than via `%s`.'; + $message = 'Local SVG and CSS files should be loaded via `file_get_contents` rather than via `%s`. Found: %s'; $code = 'IncludingSVGCSSFile'; } $this->phpcsFile->addError( $message, $curStackPtr, $code, $data ); - } + + // Don't throw more than one error for any one statement. + return; + + } while ( $curStackPtr > $stackPtr ); } } diff --git a/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php b/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php index a95c2acf..86a7348e 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/CheckReturnValueSniff.php @@ -60,7 +60,7 @@ class CheckReturnValueSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** @@ -86,7 +86,7 @@ public function process_token( $stackPtr ) { */ private function isFunctionCall( $stackPtr ) { - if ( in_array( $this->tokens[ $stackPtr ]['code'], Tokens::$functionNameTokens, true ) === false ) { + if ( $this->tokens[ $stackPtr ]['code'] !== T_STRING ) { return false; } @@ -162,14 +162,14 @@ public function findDirectFunctionCalls( $stackPtr ) { $closeBracket = $this->tokens[ $openBracket ]['parenthesis_closer']; $startNext = $openBracket + 1; - $next = $this->phpcsFile->findNext( Tokens::$functionNameTokens, $startNext, $closeBracket, false, null, true ); + $next = $this->phpcsFile->findNext( T_STRING, $startNext, $closeBracket, false, null, true ); while ( $next ) { if ( in_array( $this->tokens[ $next ]['content'], $this->catch[ $functionName ], true ) === true ) { $message = "`%s`'s return type must be checked before calling `%s` using that value."; $data = [ $this->tokens[ $next ]['content'], $functionName ]; $this->phpcsFile->addError( $message, $next, 'DirectFunctionCall', $data ); } - $next = $this->phpcsFile->findNext( Tokens::$functionNameTokens, $next + 1, $closeBracket, false, null, true ); + $next = $this->phpcsFile->findNext( T_STRING, $next + 1, $closeBracket, false, null, true ); } } @@ -269,7 +269,7 @@ public function findNonCheckedVariables( $stackPtr ) { foreach ( $callees as $callee ) { $notFunctionsCallee = array_key_exists( $callee, $this->notFunctions ) ? (array) $this->notFunctions[ $callee ] : []; // Check whether the found token is one of the function calls (or foreach call) we are interested in. - if ( in_array( $this->tokens[ $nextFunctionCallWithVariable ]['code'], array_merge( Tokens::$functionNameTokens, $notFunctionsCallee ), true ) === true + if ( in_array( $this->tokens[ $nextFunctionCallWithVariable ]['code'], array_merge( [ T_STRING ], $notFunctionsCallee ), true ) === true && $this->tokens[ $nextFunctionCallWithVariable ]['content'] === $callee ) { $this->addNonCheckedVariableError( $nextFunctionCallWithVariable, $variableName, $callee ); @@ -278,7 +278,7 @@ public function findNonCheckedVariables( $stackPtr ) { $search = array_merge( Tokens::$emptyTokens, [ T_EQUAL ] ); $next = $this->phpcsFile->findNext( $search, $nextVariableOccurrence + 1, null, true ); - if ( in_array( $this->tokens[ $next ]['code'], Tokens::$functionNameTokens, true ) === true + if ( $this->tokens[ $next ]['code'] === T_STRING && $this->tokens[ $next ]['content'] === $callee ) { $this->addNonCheckedVariableError( $next, $variableName, $callee ); @@ -291,7 +291,7 @@ public function findNonCheckedVariables( $stackPtr ) { * Function used as as callback for the array_reduce call. * * @param string $carry The final string. - * @param array $item Processed item. + * @param array $item Processed item. * * @return string */ diff --git a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php index 660bd0e1..c069696f 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php @@ -7,47 +7,47 @@ namespace WordPressVIPMinimum\Sniffs\Functions; +use PHP_CodeSniffer\Util\Tokens; use WordPressVIPMinimum\Sniffs\Sniff; /** - * This sniff enforces that certain functions are not - * dynamically called. + * This sniff enforces that certain functions are not dynamically called. * * An example: - * - * + * ```php * $func = 'func_num_args'; * $func(); - * + * ``` * - * See here: http://php.net/manual/en/migration71.incompatible.php + * Note that this sniff does not catch all possible forms of dynamic calling, only some. * - * Note that this sniff does not catch all possible forms of dynamic - * calling, only some. + * @link http://php.net/manual/en/migration71.incompatible.php */ class DynamicCallsSniff extends Sniff { + /** * Functions that should not be called dynamically. * * @var array */ - private $blacklisted_functions = [ - 'assert', - 'compact', - 'extract', - 'func_get_args', - 'func_get_arg', - 'func_num_args', - 'get_defined_vars', - 'mb_parse_str', - 'parse_str', + private $disallowed_functions = [ + 'assert' => true, + 'compact' => true, + 'extract' => true, + 'func_get_args' => true, + 'func_get_arg' => true, + 'func_num_args' => true, + 'get_defined_vars' => true, + 'mb_parse_str' => true, + 'parse_str' => true, ]; /** - * Array of functions encountered, along with their values. - * Populated on run-time. + * Array of variable assignments encountered, along with their values. * - * @var array + * Populated at run-time. + * + * @var array The key is the name of the variable, the value, its assigned value. */ private $variables_arr = []; @@ -61,8 +61,6 @@ class DynamicCallsSniff extends Sniff { /** * Returns the token types that this sniff is interested in. * - * We want everything variable- and function-related. - * * @return array(int) */ public function register() { @@ -87,35 +85,21 @@ public function process_token( $stackPtr ) { } /** - * Finds any variable-definitions in the file being processed, - * and stores them internally in a private array. The data stored - * is the name of the variable and its assigned value. + * Finds any variable-definitions in the file being processed and stores them + * internally in a private array. * * @return void */ private function collect_variables() { - /* - * Make sure we are working with a variable, - * get its value if so. - */ - - if ( - $this->tokens[ $this->stackPtr ]['type'] !== - 'T_VARIABLE' - ) { - return; - } $current_var_name = $this->tokens[ $this->stackPtr ]['content']; /* - * Find assignments ( $foo = "bar"; ) - * -- do this by finding all non-whitespaces, and - * check if the first one is T_EQUAL. + * Find assignments ( $foo = "bar"; ) by finding all non-whitespaces, + * and checking if the first one is T_EQUAL. */ - $t_item_key = $this->phpcsFile->findNext( - [ T_WHITESPACE ], + Tokens::$emptyTokens, $this->stackPtr + 1, null, true, @@ -123,127 +107,82 @@ private function collect_variables() { true ); - if ( $t_item_key === false ) { + if ( $t_item_key === false || $this->tokens[ $t_item_key ]['code'] !== T_EQUAL ) { return; } - if ( $this->tokens[ $t_item_key ]['type'] !== 'T_EQUAL' ) { - return; + /* + * Find assignments which only assign a plain text string. + */ + $end_of_statement = $this->phpcsFile->findNext( [ T_SEMICOLON, T_CLOSE_TAG ], ( $t_item_key + 1 ) ); + $value_ptr = null; + + for ( $i = $t_item_key + 1; $i < $end_of_statement; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { + continue; + } + + if ( $this->tokens[ $i ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { + // Not a plain text string value. Value cannot be determined reliably. + return; + } + + $value_ptr = $i; } - if ( $this->tokens[ $t_item_key ]['length'] !== 1 ) { + if ( isset( $value_ptr ) === false ) { + // Parse error. Bow out. return; } /* - * Find encapsulated string ( "" ) + * If we reached the end of the loop and the $value_ptr was set, we know for sure + * this was a plain text string variable assignment. */ - $t_item_key = $this->phpcsFile->findNext( - [ T_CONSTANT_ENCAPSED_STRING ], - $t_item_key + 1, - null, - false, - null, - true - ); + $current_var_value = $this->strip_quotes( $this->tokens[ $value_ptr ]['content'] ); - if ( $t_item_key === false ) { + if ( isset( $this->disallowed_functions[ $current_var_value ] ) === false ) { + // Text string is not one of the ones we're looking for. return; } /* - * We have found variable-assignment, - * register its name and value in the - * internal array for later usage. + * Register the variable name and value in the internal array for later usage. */ - - $current_var_value = - $this->tokens[ $t_item_key ]['content']; - - $this->variables_arr[ $current_var_name ] = - str_replace( "'", '', $current_var_value ); + $this->variables_arr[ $current_var_name ] = $current_var_value; } /** * Find any dynamic calls being made using variables. - * Report on this when found, using name of the function - * in the message. + * + * Report on this when found, using the name of the function in the message. * * @return void */ private function find_dynamic_calls() { - /* - * No variables detected; no basis for doing - * anything - */ - + // No variables detected; no basis for doing anything. if ( empty( $this->variables_arr ) ) { return; } /* - * Make sure we do have a variable to work with. - */ - - if ( - $this->tokens[ $this->stackPtr ]['type'] !== - 'T_VARIABLE' - ) { - return; - } - - /* - * If variable is not found in our registry of - * variables, do nothing, as we cannot be - * sure that the function being called is one of the - * blacklisted ones. + * If variable is not found in our registry of variables, do nothing, as we cannot be + * sure that the function being called is one of the disallowed ones. */ - - if ( ! isset( - $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] - ) ) { + if ( ! isset( $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ) ) { return; } /* - * Check if we have an '(' next, or separated by whitespaces - * from our current position. + * Check if we have an '(' next. */ - - $i = 0; - - do { - $i++; - } while ( - $this->tokens[ $this->stackPtr + $i ]['type'] === - 'T_WHITESPACE' - ); - - if ( - $this->tokens[ $this->stackPtr + $i ]['type'] !== - 'T_OPEN_PARENTHESIS' - ) { - return; - } - - $t_item_key = $this->stackPtr + $i; - - /* - * We have a variable match, but make sure it contains name - * of a function which is on our blacklist. - */ - - if ( ! in_array( - $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ], - $this->blacklisted_functions, - true - ) ) { + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $this->stackPtr + 1 ), null, true ); + if ( $next === false || $this->tokens[ $next ]['code'] !== T_OPEN_PARENTHESIS ) { return; } - // We do, so report. - $message = 'Dynamic calling is not recommended in the case of %s.'; + $message = 'Dynamic calling is not recommended in the case of %s().'; $data = [ $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ]; - $this->phpcsFile->addError( $message, $t_item_key, 'DynamicCalls', $data ); + $this->phpcsFile->addError( $message, $this->stackPtr, 'DynamicCalls', $data ); } } diff --git a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php index 74995407..9893c1b3 100644 --- a/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php @@ -25,13 +25,6 @@ class RestrictedFunctionsSniff extends AbstractFunctionRestrictionsSniff { public function getGroups() { $groups = [ - 'wp_cache_get_multi' => [ - 'type' => 'error', - 'message' => '`%s` is not supported on the WordPress.com VIP platform.', - 'functions' => [ - 'wp_cache_get_multi', - ], - ], 'opcache' => [ 'type' => 'error', 'message' => '`%s` is prohibited on the WordPress VIP platform due to memory corruption.', @@ -50,13 +43,6 @@ public function getGroups() { 'opcache_get_configuration', ], ], - 'get_super_admins' => [ - 'type' => 'error', - 'message' => '`%s` is prohibited on the WordPress.com VIP platform.', - 'functions' => [ - 'get_super_admins', - ], - ], 'internal' => [ 'type' => 'error', 'message' => '`%1$s()` is for internal use only.', @@ -64,7 +50,6 @@ public function getGroups() { 'wpcom_vip_irc', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#flush_rewrite_rules 'flush_rewrite_rules' => [ 'type' => 'error', 'message' => '`%s` should not be used in any normal circumstances in the theme code.', @@ -96,11 +81,10 @@ public function getGroups() { 'dbDelta', ], ], - // @link WordPress.com: https://vip.wordpress.com/documentation/vip/code-review-what-we-look-for/#switch_to_blog - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#switch_to_blog + // @link https://docs.wpvip.com/technical-references/code-review/vip-notices/#h-switch_to_blog 'switch_to_blog' => [ - 'type' => 'error', - 'message' => '%s() is not something you should ever need to do in a VIP theme context. Instead use an API (XML-RPC, REST) to interact with other sites if needed.', + 'type' => 'warning', + 'message' => '%s() may not work as expected since it only changes the database context for the blog and does not load the plugins or theme of that site. Filters or hooks on the blog you are switching to will not run.', 'functions' => [ 'switch_to_blog', ], @@ -119,8 +103,7 @@ public function getGroups() { 'url_to_postid', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#custom-roles - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#custom-roles + // @link https://docs.wpvip.com/how-tos/customize-user-roles/ 'custom_role' => [ 'type' => 'error', 'message' => 'Use wpcom_vip_add_role() instead of %s().', @@ -128,17 +111,6 @@ public function getGroups() { 'add_role', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#wp_users-and-user_meta - 'user_meta' => [ - 'type' => 'error', - 'message' => '%s() usage is highly discouraged on WordPress.com VIP due to it being a multisite, please see https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#wp_users-and-user_meta.', - 'functions' => [ - 'get_user_meta', - 'update_user_meta', - 'delete_user_meta', - 'add_user_meta', - ], - ], 'term_exists' => [ 'type' => 'error', 'message' => '%s() is highly discouraged due to not being cached; please use wpcom_vip_term_exists() instead.', @@ -178,8 +150,7 @@ public function getGroups() { 'get_intermediate_image_sizes', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#mobile-detection - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#mobile-detection + // @link https://docs.wpvip.com/technical-references/code-review/vip-warnings/#h-mobile-detection 'wp_is_mobile' => [ 'type' => 'error', 'message' => '%s() found. When targeting mobile visitors, jetpack_is_mobile() should be used instead of wp_is_mobile. It is more robust and works better with full page caching.', @@ -259,15 +230,6 @@ public function getGroups() { 'lchown', ], ], - 'site_option' => [ - 'type' => 'error', - 'message' => '%s() will overwrite network option values, please use the `*_option()` equivalent instead (e.g. `update_option()`).', - 'functions' => [ - 'add_site_option', - 'update_site_option', - 'delete_site_option', - ], - ], 'stats_get_csv' => [ 'type' => 'error', 'message' => 'Using `%s` outside of Jetpack context pollutes the stats_cache entry in the wp_options table. We recommend building a custom function instead.', @@ -298,8 +260,7 @@ public function getGroups() { 'the_field', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#remote-calls - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#remote-calls + // @link https://docs.wpvip.com/technical-references/code-review/vip-warnings/#h-remote-calls 'wp_remote_get' => [ 'type' => 'warning', 'message' => '%s() is highly discouraged. Please use vip_safe_wp_remote_get() instead which is designed to more gracefully handle failure than wp_remote_get() does.', @@ -307,11 +268,10 @@ public function getGroups() { 'wp_remote_get', ], ], - // @link WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#custom-roles - // @link VIP Go: https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#cache-constraints + // @link https://docs.wpvip.com/technical-references/code-review/vip-errors/#h-cache-constraints 'cookies' => [ - 'type' => 'warning', - 'message' => 'Due to using Batcache, server side based client related logic will not work, use JS instead.', + 'type' => 'error', + 'message' => 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', 'functions' => [ 'setcookie', ], @@ -319,7 +279,7 @@ public function getGroups() { // @todo Introduce a sniff specific to get_posts() that checks for suppress_filters=>false being supplied. 'get_posts' => [ 'type' => 'warning', - 'message' => '%s() is uncached unless the "suppress_filters" parameter is set to false. If the suppress_filter parameter is set to false this can be safely ignored. More Info: https://wpvip.com/documentation/vip-go/uncached-functions/.', + 'message' => '%s() is uncached unless the "suppress_filters" parameter is set to false. If the suppress_filter parameter is set to false this can be safely ignored. More Info: https://docs.wpvip.com/technical-references/caching/uncached-functions/.', 'functions' => [ 'get_posts', 'wp_get_recent_posts', @@ -328,7 +288,7 @@ public function getGroups() { ], 'create_function' => [ 'type' => 'warning', - 'message' => '%s() is highly discouraged, as it can execute arbritary code (additionally, it\'s deprecated as of PHP 7.2): https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#eval-and-create_function. )', + 'message' => '%s() is highly discouraged, as it can execute arbritary code (additionally, it\'s deprecated as of PHP 7.2): https://docs.wpvip.com/technical-references/code-review/vip-warnings/#h-eval-and-create_function. )', 'functions' => [ 'create_function', ], @@ -403,7 +363,7 @@ public function is_targetted_token( $stackPtr ) { if ( isset( $skipped[ $this->tokens[ $prev ]['code'] ] ) ) { return false; } - // Skip namespaced functions, ie: \foo\bar() not \bar(). + // Skip namespaced functions, ie: `\foo\bar()` not `\bar()`. if ( $this->tokens[ $prev ]['code'] === \T_NS_SEPARATOR ) { $pprev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $prev - 1, null, true ); if ( $pprev !== false && $this->tokens[ $pprev ]['code'] === \T_STRING ) { diff --git a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php index f5ea5734..8029e732 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php @@ -30,7 +30,7 @@ class AlwaysReturnInFilterSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** @@ -122,15 +122,15 @@ private function processArray( $stackPtr ) { * Process string. * * @param int $stackPtr The position in the stack where the token was found. - * @param int $start The start of the token. - * @param int $end The end of the token. + * @param int $start The start of the token. + * @param int $end The end of the token. */ private function processString( $stackPtr, $start = 0, $end = null ) { $callbackFunctionName = substr( $this->tokens[ $stackPtr ]['content'], 1, -1 ); $callbackFunctionPtr = $this->phpcsFile->findNext( - Tokens::$functionNameTokens, + T_STRING, $start, $end, false, @@ -149,8 +149,8 @@ private function processString( $stackPtr, $start = 0, $end = null ) { * Process function. * * @param int $stackPtr The position in the stack where the token was found. - * @param int $start The start of the token. - * @param int $end The end of the token. + * @param int $start The start of the token. + * @param int $end The end of the token. */ private function processFunction( $stackPtr, $start = 0, $end = null ) { @@ -175,6 +175,21 @@ private function processFunction( $stackPtr, $start = 0, $end = null ) { */ private function processFunctionBody( $stackPtr ) { + $filterName = $this->tokens[ $this->filterNamePtr ]['content']; + + $methodProps = $this->phpcsFile->getMethodProperties( $stackPtr ); + if ( $methodProps['is_abstract'] === true ) { + $message = 'The callback for the `%s` filter hook-in points to an abstract method. Please ensure that child class implementations of this method always return a value.'; + $data = [ $filterName ]; + $this->phpcsFile->addWarning( $message, $stackPtr, 'AbstractMethod', $data ); + return; + } + + if ( isset( $this->tokens[ $stackPtr ]['scope_opener'], $this->tokens[ $stackPtr ]['scope_closer'] ) === false ) { + // Live coding, parse or tokenizer error. + return; + } + $argPtr = $this->phpcsFile->findNext( array_merge( Tokens::$emptyTokens, [ T_STRING, T_OPEN_PARENTHESIS ] ), $stackPtr + 1, @@ -189,8 +204,6 @@ private function processFunctionBody( $stackPtr ) { return; } - $filterName = $this->tokens[ $this->filterNamePtr ]['content']; - $functionBodyScopeStart = $this->tokens[ $stackPtr ]['scope_opener']; $functionBodyScopeEnd = $this->tokens[ $stackPtr ]['scope_closer']; diff --git a/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php index eb1dce4e..75b54729 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/PreGetPostsSniff.php @@ -25,7 +25,7 @@ class PreGetPostsSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** @@ -120,7 +120,7 @@ private function processString( $stackPtr ) { $callbackFunctionName = substr( $this->tokens[ $stackPtr ]['content'], 1, -1 ); $callbackFunctionPtr = $this->phpcsFile->findNext( - Tokens::$functionNameTokens, + T_STRING, 0, null, false, @@ -195,7 +195,7 @@ private function processClosure( $stackPtr ) { /** * Process function's body * - * @param int $stackPtr The position in the stack where the token was found. + * @param int $stackPtr The position in the stack where the token was found. * @param string $variableName Variable name. */ private function processFunctionBody( $stackPtr, $variableName ) { @@ -363,7 +363,7 @@ private function isEarlyMainQueryCheck( $stackPtr ) { * Is the current code a WP_Query call? * * @param int $stackPtr The position in the stack where the token was found. - * @param null $method Method. + * @param null $method Method. * * @return bool */ @@ -394,7 +394,7 @@ private function isWPQueryMethodCall( $stackPtr, $method = null ) { true ); - return $next && in_array( $this->tokens[ $next ]['code'], Tokens::$functionNameTokens, true ) === true && $method === $this->tokens[ $next ]['content']; + return $next && $this->tokens[ $next ]['code'] === T_STRING && $method === $this->tokens[ $next ]['content']; } /** diff --git a/WordPressVIPMinimum/Sniffs/Hooks/RestrictedHooksSniff.php b/WordPressVIPMinimum/Sniffs/Hooks/RestrictedHooksSniff.php index 5b44c2df..43054c2b 100644 --- a/WordPressVIPMinimum/Sniffs/Hooks/RestrictedHooksSniff.php +++ b/WordPressVIPMinimum/Sniffs/Hooks/RestrictedHooksSniff.php @@ -53,8 +53,7 @@ class RestrictedHooksSniff extends AbstractFunctionParameterSniff { ], ], 'http_request' => [ - // WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. - // VIP Go: https://vip.wordpress.com/documentation/vip-go/fetching-remote-data/. + // https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/. 'type' => 'Warning', 'msg' => 'Please ensure that the timeout being filtered is not greater than 3s since remote requests require the user to wait for completion before the rest of the page will load. Manual inspection required.', 'hooks' => [ @@ -63,7 +62,7 @@ class RestrictedHooksSniff extends AbstractFunctionParameterSniff { ], ], 'robotstxt' => [ - // WordPress.com + VIP Go: https://wpvip.com/documentation/robots-txt/. + // https://docs.wpvip.com/how-tos/modify-the-robots-txt-file/. 'type' => 'Warning', 'msg' => 'Don\'t forget to flush the robots.txt cache by going to Settings > Reading and toggling the privacy settings.', 'hooks' => [ diff --git a/WordPressVIPMinimum/Sniffs/JS/StringConcatSniff.php b/WordPressVIPMinimum/Sniffs/JS/StringConcatSniff.php index d0ace2ee..74fab5fc 100644 --- a/WordPressVIPMinimum/Sniffs/JS/StringConcatSniff.php +++ b/WordPressVIPMinimum/Sniffs/JS/StringConcatSniff.php @@ -65,8 +65,8 @@ public function process_token( $stackPtr ) { /** * Consolidated violation. * - * @param int $stackPtr The position of the current token in the stack passed in $tokens. - * @param array $data Replacements for the error message. + * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * @param array $data Replacements for the error message. */ private function addFoundError( $stackPtr, array $data ) { $message = 'HTML string concatenation detected, this is a security risk, use DOM node construction or a templating language instead: %s.'; diff --git a/WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php b/WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php index b140d8f6..8017fe58 100644 --- a/WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php +++ b/WordPressVIPMinimum/Sniffs/JS/StrippingTagsSniff.php @@ -7,7 +7,6 @@ namespace WordPressVIPMinimum\Sniffs\JS; -use PHP_CodeSniffer\Files\File; use WordPressVIPMinimum\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; diff --git a/WordPressVIPMinimum/Sniffs/JS/WindowSniff.php b/WordPressVIPMinimum/Sniffs/JS/WindowSniff.php index 602d342c..e233e479 100644 --- a/WordPressVIPMinimum/Sniffs/JS/WindowSniff.php +++ b/WordPressVIPMinimum/Sniffs/JS/WindowSniff.php @@ -7,7 +7,6 @@ namespace WordPressVIPMinimum\Sniffs\JS; -use PHP_CodeSniffer\Files\File; use WordPressVIPMinimum\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; diff --git a/WordPressVIPMinimum/Sniffs/Performance/CacheValueOverrideSniff.php b/WordPressVIPMinimum/Sniffs/Performance/CacheValueOverrideSniff.php index 96696437..806bebef 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/CacheValueOverrideSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/CacheValueOverrideSniff.php @@ -29,7 +29,7 @@ class CacheValueOverrideSniff extends Sniff { * @return array(int) */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } @@ -97,10 +97,6 @@ public function process_token( $stackPtr ) { */ private function isFunctionCall( $stackPtr ) { - if ( in_array( $this->tokens[ $stackPtr ]['code'], Tokens::$functionNameTokens, true ) === false ) { - return false; - } - // Find the next non-empty token. $openBracket = $this->phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, null, true ); diff --git a/WordPressVIPMinimum/Sniffs/Performance/FetchingRemoteDataSniff.php b/WordPressVIPMinimum/Sniffs/Performance/FetchingRemoteDataSniff.php index 62d1d090..e3fc4330 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/FetchingRemoteDataSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/FetchingRemoteDataSniff.php @@ -24,7 +24,7 @@ class FetchingRemoteDataSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** diff --git a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php index 34ea78e6..23639560 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/LowExpiryCacheTimeSniff.php @@ -7,11 +7,14 @@ namespace WordPressVIPMinimum\Sniffs\Performance; +use PHP_CodeSniffer\Util\Tokens; use WordPressCS\WordPress\AbstractFunctionParameterSniff; /** * This sniff throws a warning when low cache times are set. * + * {@internal VIP uses the Memcached object cache implementation. {@link https://github.com/Automattic/wp-memcached}} + * * @package VIPCS\WordPressVIPMinimum * * @since 0.4.0 @@ -69,21 +72,135 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p return; } - $time = $parameters[4]['raw']; + $param = $parameters[4]; + $tokensAsString = ''; + $reportPtr = null; + $openParens = 0; + + $message = 'Cache expiry time could not be determined. Please inspect that the fourth parameter passed to %s() evaluates to 300 seconds or more. Found: "%s"'; + $error_code = 'CacheTimeUndetermined'; + $data = [ $matched_content, $parameters[4]['raw'] ]; + + for ( $i = $param['start']; $i <= $param['end']; $i++ ) { + if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { + $tokensAsString .= ' '; + continue; + } + + if ( $this->tokens[ $i ]['code'] === T_NS_SEPARATOR ) { + /* + * Ignore namespace separators. If it's part of a global WP time constant, it will be + * handled correctly. If it's used in any other context, another token *will* trigger the + * "undetermined" warning anyway. + */ + continue; + } + + if ( isset( $reportPtr ) === false ) { + // Set the report pointer to the first non-empty token we encounter. + $reportPtr = $i; + } + + if ( $this->tokens[ $i ]['code'] === T_LNUMBER + || $this->tokens[ $i ]['code'] === T_DNUMBER + ) { + // Integer or float. + $tokensAsString .= $this->tokens[ $i ]['content']; + continue; + } + + if ( $this->tokens[ $i ]['code'] === T_FALSE + || $this->tokens[ $i ]['code'] === T_NULL + ) { + $tokensAsString .= 0; + continue; + } + + if ( $this->tokens[ $i ]['code'] === T_TRUE ) { + $tokensAsString .= 1; + continue; + } + + if ( isset( Tokens::$arithmeticTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { + $tokensAsString .= $this->tokens[ $i ]['content']; + continue; + } - if ( is_numeric( $time ) === false ) { // If using time constants, we need to convert to a number. - $time = str_replace( array_keys( $this->wp_time_constants ), $this->wp_time_constants, $time ); + if ( $this->tokens[ $i ]['code'] === T_STRING + && isset( $this->wp_time_constants[ $this->tokens[ $i ]['content'] ] ) === true + ) { + $tokensAsString .= $this->wp_time_constants[ $this->tokens[ $i ]['content'] ]; + continue; + } + + if ( $this->tokens[ $i ]['code'] === T_OPEN_PARENTHESIS ) { + $tokensAsString .= $this->tokens[ $i ]['content']; + ++$openParens; + continue; + } - if ( preg_match( '#^[\s\d+*/-]+$#', $time ) > 0 ) { - $time = eval( "return $time;" ); // phpcs:ignore Squiz.PHP.Eval -- No harm here. + if ( $this->tokens[ $i ]['code'] === T_CLOSE_PARENTHESIS ) { + $tokensAsString .= $this->tokens[ $i ]['content']; + --$openParens; + continue; } + + if ( $this->tokens[ $i ]['code'] === T_CONSTANT_ENCAPSED_STRING ) { + $content = $this->strip_quotes( $this->tokens[ $i ]['content'] ); + if ( is_numeric( $content ) === true ) { + $tokensAsString .= $content; + continue; + } + } + + // Encountered an unexpected token. Manual inspection needed. + $this->phpcsFile->addWarning( $message, $reportPtr, $error_code, $data ); + + return; } - if ( $time < 300 ) { - $message = 'Low cache expiry time of "%s", it is recommended to have 300 seconds or more.'; - $data = [ $parameters[4]['raw'] ]; - $this->phpcsFile->addWarning( $message, $stackPtr, 'LowCacheTime', $data ); + if ( $tokensAsString === '' ) { + // Nothing found to evaluate. + return; + } + + $tokensAsString = trim( $tokensAsString ); + + if ( $openParens !== 0 ) { + /* + * Shouldn't be possible as that would indicate a parse error in the original code, + * but let's prevent getting parse errors in the `eval`-ed code. + */ + if ( $openParens > 0 ) { + $tokensAsString .= str_repeat( ')', $openParens ); + } else { + $tokensAsString = str_repeat( '(', abs( $openParens ) ) . $tokensAsString; + } + } + + $time = @eval( "return $tokensAsString;" ); // phpcs:ignore Squiz.PHP.Eval,WordPress.PHP.NoSilencedErrors -- No harm here. + + if ( $time === false ) { + /* + * The eval resulted in a parse error. This will only happen for backfilled + * arithmetic operator tokens, like T_POW, on PHP versions in which the token + * did not exist. In that case, flag for manual inspection. + */ + $this->phpcsFile->addWarning( $message, $reportPtr, $error_code, $data ); + return; + } + + if ( $time < 300 && (int) $time !== 0 ) { + $message = 'Low cache expiry time of %s seconds detected. It is recommended to have 300 seconds or more.'; + $data = [ $time ]; + + if ( (string) $time !== $tokensAsString ) { + $message .= ' Found: "%s"'; + $data[] = $tokensAsString; + } + + $this->phpcsFile->addWarning( $message, $reportPtr, 'LowCacheTime', $data ); } } } diff --git a/WordPressVIPMinimum/Sniffs/Performance/NoPagingSniff.php b/WordPressVIPMinimum/Sniffs/Performance/NoPagingSniff.php index 124b3aeb..9e23fc4f 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/NoPagingSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/NoPagingSniff.php @@ -14,7 +14,7 @@ /** * Flag returning high or infinite posts_per_page. * - * @link https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#no-limit-queries + * @link https://docs.wpvip.com/technical-references/code-review/#no-limit-queries * * @package VIPCS\WordPressVIPMinimum * diff --git a/WordPressVIPMinimum/Sniffs/Performance/OrderByRandSniff.php b/WordPressVIPMinimum/Sniffs/Performance/OrderByRandSniff.php index 47d3c604..e6e64c6f 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/OrderByRandSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/OrderByRandSniff.php @@ -14,7 +14,7 @@ /** * Flag using orderby => rand. * - * @link https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#order-by-rand + * @link https://docs.wpvip.com/technical-references/code-review/#order-by-rand * * @package VIPCS\WordPressVIPMinimum * diff --git a/WordPressVIPMinimum/Sniffs/Performance/RegexpCompareSniff.php b/WordPressVIPMinimum/Sniffs/Performance/RegexpCompareSniff.php index 9e828677..dea5fd5f 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/RegexpCompareSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/RegexpCompareSniff.php @@ -18,16 +18,14 @@ class RegexpCompareSniff extends AbstractArrayAssignmentRestrictionsSniff { /** * Groups of variables to restrict. - * This should be overridden in extending classes. * * Example: groups => array( - * 'wpdb' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'variables' => array( '$val', '$var' ), - * 'object_vars' => array( '$foo->bar', .. ), - * 'array_members' => array( '$foo['bar']', .. ), - * ) + * 'groupname' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'keys' => array( 'key1', 'another_key' ), + * 'callback' => array( 'class', 'method' ), // Optional. + * ) * ) * * @return array diff --git a/WordPressVIPMinimum/Sniffs/Performance/RemoteRequestTimeoutSniff.php b/WordPressVIPMinimum/Sniffs/Performance/RemoteRequestTimeoutSniff.php index e0c17f95..974532f4 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/RemoteRequestTimeoutSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/RemoteRequestTimeoutSniff.php @@ -18,16 +18,14 @@ class RemoteRequestTimeoutSniff extends AbstractArrayAssignmentRestrictionsSniff /** * Groups of variables to restrict. - * This should be overridden in extending classes. * * Example: groups => array( - * 'wpdb' => array( - * 'type' => 'error' | 'warning', - * 'message' => 'Dont use this one please!', - * 'variables' => array( '$val', '$var' ), - * 'object_vars' => array( '$foo->bar', .. ), - * 'array_members' => array( '$foo['bar']', .. ), - * ) + * 'groupname' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'keys' => array( 'key1', 'another_key' ), + * 'callback' => array( 'class', 'method' ), // Optional. + * ) * ) * * @return array diff --git a/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php b/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php index 071dc641..c9c815be 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php @@ -51,7 +51,7 @@ class TaxonomyMetaInOptionsSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** diff --git a/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php b/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php index 949e3f5b..9b15ef63 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/WPQueryParamsSniff.php @@ -8,7 +8,7 @@ namespace WordPressVIPMinimum\Sniffs\Performance; -use WordPressVIPMinimum\Sniffs\Sniff; +use WordPressCS\WordPress\AbstractArrayAssignmentRestrictionsSniff; use PHP_CodeSniffer\Util\Tokens; /** @@ -16,7 +16,7 @@ * * @package VIPCS\WordPressVIPMinimum */ -class WPQueryParamsSniff extends Sniff { +class WPQueryParamsSniff extends AbstractArrayAssignmentRestrictionsSniff { /** * Returns an array of tokens this test wants to listen for. @@ -24,8 +24,38 @@ class WPQueryParamsSniff extends Sniff { * @return array */ public function register() { + $targets = parent::register(); + + // Add the target for the "old" implementation. + $targets[] = T_CONSTANT_ENCAPSED_STRING; + + return $targets; + } + + /** + * Groups of variables to restrict. + * This should be overridden in extending classes. + * + * Example: groups => array( + * 'groupname' => array( + * 'type' => 'error' | 'warning', + * 'message' => 'Dont use this one please!', + * 'keys' => array( 'key1', 'another_key' ), + * 'callback' => array( 'class', 'method' ), // Optional. + * ) + * ) + * + * @return array + */ + public function getGroups() { return [ - T_CONSTANT_ENCAPSED_STRING, + 'PostNotIn' => [ + 'type' => 'warning', + 'message' => 'Using `exclude`, which is subsequently used by `post__not_in`, should be done with caution, see https://docs.wpvip.com/how-tos/improve-performance-by-removing-usage-of-post__not_in/ for more information.', + 'keys' => [ + 'exclude', + ], + ], ]; } @@ -43,17 +73,32 @@ public function process_token( $stackPtr ) { $next_token = $this->phpcsFile->findNext( array_merge( Tokens::$emptyTokens, [ T_EQUAL, T_CLOSE_SQUARE_BRACKET, T_DOUBLE_ARROW ] ), $stackPtr + 1, null, true ); if ( $this->tokens[ $next_token ]['code'] === T_TRUE ) { - // WordPress.com: https://lobby.vip.wordpress.com/wordpress-com-documentation/uncached-functions/. - // VIP Go: https://wpvip.com/documentation/vip-go/uncached-functions/. + // https://docs.wpvip.com/technical-references/caching/uncached-functions/. $message = 'Setting `suppress_filters` to `true` is prohibited.'; $this->phpcsFile->addError( $message, $stackPtr, 'SuppressFiltersTrue' ); } } if ( trim( $this->tokens[ $stackPtr ]['content'], '\'' ) === 'post__not_in' ) { - $message = 'Using `post__not_in` should be done with caution, see https://wpvip.com/documentation/performance-improvements-by-removing-usage-of-post__not_in/ for more information.'; + $message = 'Using `post__not_in` should be done with caution, see https://docs.wpvip.com/how-tos/improve-performance-by-removing-usage-of-post__not_in/ for more information.'; $this->phpcsFile->addWarning( $message, $stackPtr, 'PostNotIn' ); } + + parent::process_token( $stackPtr ); + } + + /** + * Callback to process a confirmed key which doesn't need custom logic, but should always error. + * + * @param string $key Array index / key. + * @param mixed $val Assigned value. + * @param int $line Token line. + * @param array $group Group definition. + * @return mixed FALSE if no match, TRUE if matches, STRING if matches + * with custom error message passed to ->process(). + */ + public function callback( $key, $val, $line, $group ) { + return true; } } diff --git a/WordPressVIPMinimum/Sniffs/Security/ExitAfterRedirectSniff.php b/WordPressVIPMinimum/Sniffs/Security/ExitAfterRedirectSniff.php index 5d76976a..dfacc425 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ExitAfterRedirectSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ExitAfterRedirectSniff.php @@ -24,7 +24,7 @@ class ExitAfterRedirectSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** diff --git a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php index f94abdf2..9db2983e 100644 --- a/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/ProperEscapingFunctionSniff.php @@ -23,10 +23,56 @@ class ProperEscapingFunctionSniff extends Sniff { * * @var array */ - public $escaping_functions = [ - 'esc_url', - 'esc_attr', - 'esc_html', + protected $escaping_functions = [ + 'esc_url' => 'url', + 'esc_attr' => 'attr', + 'esc_attr__' => 'attr', + 'esc_attr_x' => 'attr', + 'esc_attr_e' => 'attr', + 'esc_html' => 'html', + 'esc_html__' => 'html', + 'esc_html_x' => 'html', + 'esc_html_e' => 'html', + ]; + + /** + * List of tokens we can skip. + * + * @var array + */ + private $echo_or_concat_tokens = + [ + T_ECHO => T_ECHO, + T_OPEN_TAG => T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO => T_OPEN_TAG_WITH_ECHO, + T_STRING_CONCAT => T_STRING_CONCAT, + T_COMMA => T_COMMA, + T_NS_SEPARATOR => T_NS_SEPARATOR, + ]; + + /** + * List of attributes associated with url outputs. + * + * @var array + */ + private $url_attrs = [ + 'href', + 'src', + 'url', + 'action', + ]; + + /** + * List of syntaxes for inside attribute detection. + * + * @var array + */ + private $attr_endings = [ + '=', + '="', + "='", + "=\\'", + '=\\"', ]; /** @@ -35,7 +81,9 @@ class ProperEscapingFunctionSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + $this->echo_or_concat_tokens += Tokens::$emptyTokens; + + return [ T_STRING ]; } /** @@ -47,47 +95,47 @@ public function register() { */ public function process_token( $stackPtr ) { - if ( in_array( $this->tokens[ $stackPtr ]['content'], $this->escaping_functions, true ) === false ) { + $function_name = strtolower( $this->tokens[ $stackPtr ]['content'] ); + + if ( isset( $this->escaping_functions[ $function_name ] ) === false ) { return; } - $function_name = $this->tokens[ $stackPtr ]['content']; + $next_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( $next_non_empty === false || $this->tokens[ $next_non_empty ]['code'] !== T_OPEN_PARENTHESIS ) { + // Not a function call. + return; + } - $echo_or_string_concat = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true ); + $html = $this->phpcsFile->findPrevious( $this->echo_or_concat_tokens, $stackPtr - 1, null, true ); - if ( $this->tokens[ $echo_or_string_concat ]['code'] === T_ECHO ) { - // Very likely inline HTML with phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_string_concat - 1, null, true ); + // Use $textStringTokens b/c heredoc and nowdoc tokens will never be encountered in this context anyways.. + if ( $html === false || isset( Tokens::$textStringTokens[ $this->tokens[ $html ]['code'] ] ) === false ) { + return; + } - if ( $this->tokens[ $php_open ]['code'] !== T_OPEN_TAG ) { - return; - } + $data = [ $function_name ]; - $html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $php_open - 1, null, true ); + $content = $this->tokens[ $html ]['content']; + if ( isset( Tokens::$stringTokens[ $this->tokens[ $html ]['code'] ] ) === true ) { + $content = Sniff::strip_quotes( $content ); + } - if ( $this->tokens[ $html ]['code'] !== T_INLINE_HTML ) { - return; - } - } elseif ( $this->tokens[ $echo_or_string_concat ]['code'] === T_STRING_CONCAT ) { - // Very likely string concatenation mixing strings and functions/variables. - $html = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $echo_or_string_concat - 1, null, true ); + $escaping_type = $this->escaping_functions[ $function_name ]; - if ( $this->tokens[ $html ]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { - return; - } - } else { - // Neither - bailing. + if ( $escaping_type === 'attr' && $this->is_outside_html_attr_context( $content ) ) { + $message = 'Wrong escaping function, using `%s()` in a context outside of HTML attributes may not escape properly.'; + $this->phpcsFile->addError( $message, $html, 'notAttrEscAttr', $data ); return; } - $data = [ $function_name ]; - - if ( $function_name !== 'esc_url' && $this->attr_expects_url( $this->tokens[ $html ]['content'] ) ) { + if ( $escaping_type !== 'url' && $this->attr_expects_url( $content ) ) { $message = 'Wrong escaping function. href, src, and action attributes should be escaped by `esc_url()`, not by `%s()`.'; $this->phpcsFile->addError( $message, $stackPtr, 'hrefSrcEscUrl', $data ); return; } - if ( $function_name === 'esc_html' && $this->is_html_attr( $this->tokens[ $html ]['content'] ) ) { + + if ( $escaping_type === 'html' && $this->is_html_attr( $content ) ) { $message = 'Wrong escaping function. HTML attributes should be escaped by `esc_attr()`, not by `%s()`.'; $this->phpcsFile->addError( $message, $stackPtr, 'htmlAttrNotByEscHTML', $data ); return; @@ -99,17 +147,12 @@ public function process_token( $stackPtr ) { * * @param string $content Haystack in which we look for an open attribute which exects a URL value. * - * @return bool True if string ends with open attribute which exects a URL value. + * @return bool True if string ends with open attribute which expects a URL value. */ public function attr_expects_url( $content ) { $attr_expects_url = false; - foreach ( [ 'href', 'src', 'url', 'action' ] as $attr ) { - foreach ( [ - '="', - "='", - '=\'"', // The tokenizer does some fun stuff when it comes to mixing double and single quotes. - '="\'', // The tokenizer does some fun stuff when it comes to mixing double and single quotes. - ] as $ending ) { + foreach ( $this->url_attrs as $attr ) { + foreach ( $this->attr_endings as $ending ) { if ( $this->endswith( $content, $attr . $ending ) === true ) { $attr_expects_url = true; break; @@ -128,12 +171,7 @@ public function attr_expects_url( $content ) { */ public function is_html_attr( $content ) { $is_html_attr = false; - foreach ( [ - '="', - "='", - '=\'"', // The tokenizer does some fun stuff when it comes to mixing double and single quotes. - '="\'', // The tokenizer does some fun stuff when it comes to mixing double and single quotes. - ] as $ending ) { + foreach ( $this->attr_endings as $ending ) { if ( $this->endswith( $content, $ending ) === true ) { $is_html_attr = true; break; @@ -142,11 +180,22 @@ public function is_html_attr( $content ) { return $is_html_attr; } + /** + * Tests whether an attribute escaping function is being used outside of an HTML tag. + * + * @param string $content Haystack where we look for the end of a HTML tag. + * + * @return bool True if the passed string ends a HTML tag. + */ + public function is_outside_html_attr_context( $content ) { + return $this->endswith( trim( $content ), '>' ); + } + /** * A helper function which tests whether string ends with some other. * * @param string $haystack String which is being tested. - * @param string $needle The substring, which we try to locate on the end of the $haystack. + * @param string $needle The substring, which we try to locate on the end of the $haystack. * * @return bool True if haystack ends with needle. */ diff --git a/WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php b/WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php index 09159a07..3d57edcc 100644 --- a/WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/StaticStrreplaceSniff.php @@ -24,7 +24,7 @@ class StaticStrreplaceSniff extends Sniff { * @return array */ public function register() { - return Tokens::$functionNameTokens; + return [ T_STRING ]; } /** diff --git a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php index fb1c0af0..6ea36135 100644 --- a/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/UnderscorejsSniff.php @@ -8,6 +8,7 @@ namespace WordPressVIPMinimum\Sniffs\Security; +use PHP_CodeSniffer\Util\Tokens; use WordPressVIPMinimum\Sniffs\Sniff; /** @@ -17,6 +18,29 @@ */ class UnderscorejsSniff extends Sniff { + /** + * Regex to match unescaped output notations containing variable interpolation + * and retrieve a code snippet. + * + * @var string + */ + const UNESCAPED_INTERPOLATE_REGEX = '`<%=\s*(?:.+?%>|$)`'; + + /** + * Regex to match execute notations containing a print command + * and retrieve a code snippet. + * + * @var string + */ + const UNESCAPED_PRINT_REGEX = '`<%\s*(?:print\s*\(.+?\)\s*;|__p\s*\+=.+?)\s*%>`'; + + /** + * Regex to match the "interpolate" keyword when used to overrule the ERB-style delimiters. + * + * @var string + */ + const INTERPOLATE_KEYWORD_REGEX = '`(?:templateSettings\.interpolate|\.interpolate\s*=\s*/|interpolate\s*:\s*/)`'; + /** * A list of tokenizers this sniff supports. * @@ -30,12 +54,11 @@ class UnderscorejsSniff extends Sniff { * @return array */ public function register() { - return [ - T_CONSTANT_ENCAPSED_STRING, - T_PROPERTY, - T_INLINE_HTML, - T_HEREDOC, - ]; + $targets = Tokens::$textStringTokens; + $targets[] = T_PROPERTY; + $targets[] = T_STRING; + + return $targets; } /** @@ -46,15 +69,91 @@ public function register() { * @return void */ public function process_token( $stackPtr ) { + /* + * Ignore Gruntfile.js files as they are configuration, not code. + */ + $file_name = $this->strip_quotes( $this->phpcsFile->getFileName() ); + $file_name = strtolower( basename( $file_name ) ); + + if ( $file_name === 'gruntfile.js' ) { + return; + } + + /* + * Check for delimiter change in JS files. + */ + if ( $this->tokens[ $stackPtr ]['code'] === T_STRING + || $this->tokens[ $stackPtr ]['code'] === T_PROPERTY + ) { + if ( $this->phpcsFile->tokenizerType !== 'JS' ) { + // These tokens are only relevant for JS files. + return; + } + + if ( $this->tokens[ $stackPtr ]['content'] !== 'interpolate' ) { + return; + } + + // Check the context to prevent false positives. + if ( $this->tokens[ $stackPtr ]['code'] === T_STRING ) { + $prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + if ( $prev === false || $this->tokens[ $prev ]['code'] !== T_OBJECT_OPERATOR ) { + return; + } + + $prevPrev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true ); + if ( ( $prevPrev === false + || $this->tokens[ $prevPrev ]['code'] !== T_STRING + || $this->tokens[ $prevPrev ]['content'] !== 'templateSettings' ) + && ( $next === false + || $this->tokens[ $next ]['code'] !== T_EQUAL ) + ) { + return; + } + } + + // Underscore.js delimiter change. + $message = 'Found Underscore.js delimiter change notation.'; + $this->phpcsFile->addWarning( $message, $stackPtr, 'InterpolateFound' ); + + return; + } + + $content = $this->strip_quotes( $this->tokens[ $stackPtr ]['content'] ); + + $match_count = preg_match_all( self::UNESCAPED_INTERPOLATE_REGEX, $content, $matches ); + if ( $match_count > 0 ) { + foreach ( $matches[0] as $match ) { + if ( strpos( $match, '_.escape(' ) !== false ) { + continue; + } + + // Underscore.js unescaped output. + $message = 'Found Underscore.js unescaped output notation: "%s".'; + $data = [ $match ]; + $this->phpcsFile->addWarning( $message, $stackPtr, 'OutputNotation', $data ); + } + } + + $match_count = preg_match_all( self::UNESCAPED_PRINT_REGEX, $content, $matches ); + if ( $match_count > 0 ) { + foreach ( $matches[0] as $match ) { + if ( strpos( $match, '_.escape(' ) !== false ) { + continue; + } - if ( strpos( $this->tokens[ $stackPtr ]['content'], '<%=' ) !== false ) { - // Underscore.js unescaped output. - $message = 'Found Underscore.js unescaped output notation: "<%=".'; - $this->phpcsFile->addWarning( $message, $stackPtr, 'OutputNotation' ); + // Underscore.js unescaped output. + $message = 'Found Underscore.js unescaped print execution: "%s".'; + $data = [ $match ]; + $this->phpcsFile->addWarning( $message, $stackPtr, 'PrintExecution', $data ); + } } - if ( strpos( $this->tokens[ $stackPtr ]['content'], 'interpolate' ) !== false ) { - // Underscore.js unescaped output. + if ( $this->phpcsFile->tokenizerType !== 'JS' + && preg_match( self::INTERPOLATE_KEYWORD_REGEX, $content ) > 0 + ) { + // Underscore.js delimiter change. $message = 'Found Underscore.js delimiter change notation.'; $this->phpcsFile->addWarning( $message, $stackPtr, 'InterpolateFound' ); } diff --git a/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php b/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php index 6945c2f4..6b985f07 100644 --- a/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php +++ b/WordPressVIPMinimum/Sniffs/UserExperience/AdminBarRemovalSniff.php @@ -15,7 +15,7 @@ /** * Discourages removal of the admin bar. * - * @link https://wpvip.com/documentation/vip-go/code-review-blockers-warnings-notices/#removing-the-admin-bar + * @link https://docs.wpvip.com/technical-references/code-review/vip-warnings/#h-removing-the-admin-bar * * @package VIPCS\WordPressVIPMinimum * @@ -319,7 +319,7 @@ public function process_text_for_style( $stackPtr, $file_name ) { /** * Processes this test for T_STYLE tokens in CSS files. * - * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack passed in $tokens. * * @return void */ @@ -370,7 +370,7 @@ protected function process_css_style( $stackPtr ) { /** * Consolidated violation. * - * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * @param int $stackPtr The position of the current token in the stack passed in $tokens. */ private function addHidingDetectedError( $stackPtr ) { $message = 'Hiding of the admin bar is not allowed.'; diff --git a/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php b/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php index 7b6d7917..fe19452d 100644 --- a/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php +++ b/WordPressVIPMinimum/Sniffs/Variables/RestrictedVariablesSniff.php @@ -37,13 +37,11 @@ class RestrictedVariablesSniff extends AbstractVariableRestrictionsSniff { */ public function getGroups() { return [ - // @link https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#wp_users-and-user_meta 'user_meta' => [ 'type' => 'error', - 'message' => 'Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.', + 'message' => 'Usage of users tables is highly discouraged in VIP context', 'object_vars' => [ '$wpdb->users', - '$wpdb->usermeta', ], ], 'session' => [ @@ -54,10 +52,10 @@ public function getGroups() { ], ], - // @link https://lobby.vip.wordpress.com/wordpress-com-documentation/code-review-what-we-look-for/#caching-constraints + // @link https://docs.wpvip.com/technical-references/code-review/vip-errors/#h-cache-constraints 'cache_constraints' => [ 'type' => 'warning', - 'message' => 'Due to using Batcache, server side based client related logic will not work, use JS instead.', + 'message' => 'Due to server-side caching, server-side based client related logic might not work. We recommend implementing client side logic in JavaScript instead.', 'variables' => [ '$_COOKIE', ], diff --git a/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php b/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php index 1296ea31..18e1ed27 100644 --- a/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php +++ b/WordPressVIPMinimum/Sniffs/Variables/VariableAnalysisSniff.php @@ -17,7 +17,7 @@ use PHP_CodeSniffer\Files\File; /** - * Checks the for undefined function variables. + * Checks for undefined function variables. * * This sniff checks that all function variables * are defined in the function body. @@ -46,21 +46,6 @@ class VariableAnalysisSniff extends \VariableAnalysis\Sniffs\CodeAnalysis\Variab 'FoundPropertyForDeprecatedSniff' => false, ]; - /** - * Returns an array of tokens this test wants to listen for. - * - * @return int[] - */ - public function register() { - return [ - T_VARIABLE, - T_DOUBLE_QUOTED_STRING, - T_HEREDOC, - T_CLOSE_CURLY_BRACKET, - T_STRING, - ]; - } - /** * Don't use. * diff --git a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc index a330da69..ec4ab2f4 100644 --- a/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Constants/ConstantStringUnitTest.inc @@ -4,6 +4,30 @@ if ( ! defined( 'WPCOM_VIP' ) ) { // Okay. define( 'WPCOM_VIP', true ); // Okay. } -if ( ! defined( WPCOM_VIP ) ) { // NOK. - define( WPCOM_VIP ); // NOK. -} \ No newline at end of file +if ( ! defined( WPCOM_VIP ) ) { // Error. + define( WPCOM_VIP, true ); // Error. +} + +namespace Foo\Bar; +const REST_ALLOWED_META_PREFIXES = [ 'foo-', 'bar-', 'baz-' ]; +if ( defined( __NAMESPACE__ . '\REST_ALLOWED_META_PREFIXES' ) && in_array( 'foo-', REST_ALLOWED_META_PREFIXES, true ) ) { // Ok. + define( __NAMESPACE__ . '\\' . REST_ALLOWED_META_PREFIXES[1], $value ); // OK. +} + +define( __NAMESPACE__ . '\PLUGIN_URL', \plugins_url( '/', __FILE__ ) ); // OK. +if ( defined( __NAMESPACE__ . '\\LOADED' ) ) {} // OK. + +if ( defined( $obj->constant_name_property ) === false ) { // OK. + define( $variable_containing_constant_name, $constant_value ); // OK. +} + +if ( defined( MY_PREFIX . '_CONSTANT_NAME' ) === false ) { // OK. + define( 'PREFIX_' . $variable_part, $constant_value ); // OK. +} + +if ( ! defined($generator->get()) { // OK. + define( $generator->getLast(), 'value'); // OK. +} + +$defined = defined(); // OK, ignore. +$defined = defined( /*comment*/ ); // OK, ignore. diff --git a/WordPressVIPMinimum/Tests/Files/IncludingFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingFileUnitTest.inc index b225a4a7..63dcf460 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingFileUnitTest.inc @@ -25,4 +25,13 @@ require_once "my_file.php"; // Not absolute path. require '../my_file.php'; // Not absolute path. require '../../my_file.php'; // Not absolute path. include( 'http://www.google.com/bad_file.php' ); // External URL. -include_once("http://www.google.com/bad_file.php"); // External URL. \ No newline at end of file +include_once("http://www.google.com/bad_file.php"); // External URL. + +// Allowed keywords +include 'https://path.com/bad_file.php'; // Error - external URL with keyword from $allowedKeywords. +require $path; // Warning - custom variable with keyword from $allowedKeywords. +include_once dir_function(); // Error - custom functionm with keyword from $allowedKeywords. +require CUSTOM_CONSTANT_DIR . 'file.php'; // OK. +require_once ( VIPCS_PATH ) . 'file.php'; // OK. +include_once + DIR_CUSTOM , 'file.php'; // OK. \ No newline at end of file diff --git a/WordPressVIPMinimum/Tests/Files/IncludingFileUnitTest.php b/WordPressVIPMinimum/Tests/Files/IncludingFileUnitTest.php index 3253a83a..15aed707 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingFileUnitTest.php +++ b/WordPressVIPMinimum/Tests/Files/IncludingFileUnitTest.php @@ -29,6 +29,7 @@ public function getErrorList() { 26 => 1, 27 => 1, 28 => 1, + 31 => 1, ]; } @@ -43,6 +44,8 @@ public function getWarningList() { 19 => 1, 20 => 1, 21 => 1, + 32 => 1, + 33 => 1, ]; } diff --git a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc index ae1e4cf0..b4226a25 100644 --- a/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Files/IncludingNonPHPFileUnitTest.inc @@ -2,45 +2,45 @@ require_once __DIR__ . "/my_file.php"; // OK. -require_once "my_file.php"; // OK. +require "my_file.php"; // OK. -include( __DIR__ . "/my_file.php" ); // OK. +include_once( __DIR__ . "/my_file.php" ); // OK. -include ( MY_CONSTANT . "my_file.php" ); // OK. +include ( MY_CONSTANT . "my_file.INC" ); // OK. require_once ( MY_CONSTANT . "my_file.php" ); // OK. -include( locate_template('index-loop.php') ); // OK. +Include( locate_template('index-loop.PHP') ); // OK. -require_once __DIR__ . "/my_file.svg"; // NOK. +require_once __DIR__ . "/my_file.SVG"; // NOK. -require_once "my_file.svg"; // NOK. +Require_Once "my_file.svg"; // NOK. include( __DIR__ . "/my_file.svg" ); // NOK. include ( MY_CONSTANT . "my_file.svg" ); // NOK. -require_once ( MY_CONSTANT . "my_file.svg" ); // NOK. +require ( MY_CONSTANT . "my_file.svg" ); // NOK. include( locate_template('index-loop.svg') ); // NOK. -require_once __DIR__ . "/my_file.css"; // NOK. +require_once __DIR__ . "/my_file.CSS"; // NOK. require_once "my_file.css"; // NOK. -include( __DIR__ . "/my_file.css" ); // NOK. +include_once( __DIR__ . "/my_file.css" ); // NOK. include ( MY_CONSTANT . "my_file.css" ); // NOK. require_once ( MY_CONSTANT . "my_file.css" ); // NOK. -include( locate_template('index-loop.css') ); // NOK. +include( locate_template('index-loop.Css') ); // NOK. -require_once __DIR__ . "/my_file.csv"; // NOK. +REQUIRE_ONCE __DIR__ . "/my_file.csv"; // NOK. require_once "my_file.inc"; // OK. -include( __DIR__ . "/my_file.csv" ); // NOK. +include( __DIR__ . "/my_file.CSV" ); // NOK. include ( MY_CONSTANT . "my_file.csv" ); // NOK. @@ -50,4 +50,36 @@ include( locate_template('index-loop.csv') ); // NOK. echo file_get_contents( 'index-loop.svg' ); // XSS OK. -echo file_get_contents( 'index-loop.css' ); // XSS OK. \ No newline at end of file +echo file_get_contents( 'index-loop.css' ); // XSS OK. + +include_once 'path/to/geoip.phar'; // OK. + +require dirname(__DIR__) . '/externals/aws-sdk.phar'; // OK. + +require "$path/$file.inc"; // OK. + +require "$path/$file.css"; // NOK. + +include_once $path . '/' . "$file.js" ?> + 1, 47 => 1, 49 => 1, + 61 => 1, + 63 => 1, + 69 => 1, + 72 => 1, + 75 => 1, + 77 => 1, ]; } diff --git a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc index 7cde79e4..fc307b2f 100644 --- a/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc @@ -6,10 +6,33 @@ function my_test() { $my_notokay_func = 'extract'; -$my_notokay_func(); +$my_notokay_func(); // Bad. $my_okay_func = 'my_test'; -$my_okay_func(); +$my_okay_func(); // OK. +$test_with_comment /*comment*/ = 'func_get_args'; +$test_with_comment /*comment*/ (); // Bad. +$test_getting_the_actual_value_1 = function_call( 'extract' ); +$test_getting_the_actual_value_1(); // OK. Unclear what the actual variable value will be. +$test_getting_the_actual_value_2 = $array['compact']; +$test_getting_the_actual_value_2(); // OK. Unclear what the actual variable value will be. + +$test_getting_the_actual_value_3 = 10 ?> +
html
+ 1, + 9 => 1, + 15 => 1, + 35 => 1, ]; } diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc index 7c1ba946..fece7131 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc @@ -15,8 +15,8 @@ $allowed_html = [ ]; $url = 'http://www.google.ca'; -wp_cache_get_mult(); // Ok - similarly-named function to wp_cache_get_multi(). -wp_cache_get_multi(); // Error. + + opcache_resets(); // Ok - similarly-named custom function to opcache_reset(). opcach_invalidate ( $test_script ); // Ok - similarly-named custom function to opcache_invalidate(). @@ -33,8 +33,8 @@ opcache_get_status(); // Error. opcache_get_status( false ); // Error. opcache_get_configuration(); // Error. -get_super_admin(); // Ok - similarly-named function to get_super_admins(). -get_super_admins(); // Error. + + vip_irc(); // Ok - similarly-named function to wpcom_vip_irc(). wpcom_vip_irc(); // Error. @@ -53,7 +53,7 @@ db_delta(); // Ok - similarly-named function to dbDelta(). dbDelta(); // Error. switch_blog(); // Ok - similarly-named function to switch_to_blog(). -switch_to_blog( $blogid ); // Error. +switch_to_blog( $blogid ); // Warning. wpcom_vip_get_page_by_title(); // Ok - VIP recommended version of get_page_by_title(). get_page_by_title( $page_title ); // Error. @@ -75,14 +75,14 @@ $y = Bar::add_role(); // Ok - calling static function of another class and not a add_role( 'test' ); // Error. \add_role(); // Error. -get_post_meta( 123, 'test' ); // Ok - not using get_user_meta(). -update_post_meta( 1234, 'test', $test ); // Ok - not using update_user_meta(). -delete_post_meta( $int, $test ); // Ok - not using delete_user_meta(). -add_post_meta( $int, $test, $test ); // Ok - not using add_user_meta(). -get_user_meta(); // Error. -update_user_meta(); // Error. -delete_user_meta(); // Error. -add_user_meta(); // Error. + + + + + + + + wpcom_vip_term_exists(); // Ok - VIP recommended version of term_exists(). term_exists(); // Error. @@ -122,7 +122,7 @@ vip_safe_wp_remote_get(); // Ok - VIP recommended version of wp_remote_get(). wp_remote_get( $url ); // Warning. cookie( $_GET['test'] ); // Ok - similarly-named function to setcookie(). -setcookie( 'cookie[three]', 'cookiethree' ); // Warning. +setcookie( 'cookie[three]', 'cookiethree' ); // Error. get_post( 123 ); // Ok - not using get_posts(). wp_get_recent_post(); // Ok - similarly-named function to wp_get_recent_posts(). @@ -215,12 +215,12 @@ $c->create_function = 'bar' . ( 1 === 1 ?? 'foo' ); // Ok. $wp_random_testing = create_function2( '$a, $b', 'return ( $b / $a ); '); // Ok. $wp_random_testing = create_function3( '$a, $b', 'return ( $b / $a ); '); // Ok. -add_site_option( $foo, 'bar' ); // Warning. -add_option( $foo, 'bar' ); // Ok. -update_site_option( $bar, 'foo' ); // Warning. -update_option( 'foo', $bar ); // Ok. -delete_site_option( $foo ); // Warning. -delete_option( $bar ); // Ok. + + + + + + wpcom_vip_get_page_by_path(); // Ok - VIP recommended version of get_page_by_path(). get_page_by_path( $page_path ); // Warning. diff --git a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php index 493b9270..e907eae3 100644 --- a/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php @@ -25,7 +25,6 @@ class RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { */ public function getErrorList() { return [ - 19 => 1, 27 => 1, 28 => 1, 29 => 1, @@ -34,21 +33,15 @@ public function getErrorList() { 32 => 1, 33 => 1, 34 => 1, - 37 => 1, 40 => 1, 43 => 1, 46 => 1, 50 => 1, 53 => 1, - 56 => 1, 59 => 1, 62 => 1, 75 => 1, 76 => 1, - 82 => 1, - 83 => 1, - 84 => 1, - 85 => 1, 88 => 1, 91 => 1, 94 => 1, @@ -59,6 +52,7 @@ public function getErrorList() { 101 => 1, 104 => 1, 107 => 1, + 125 => 1, 141 => 1, 142 => 1, 143 => 1, @@ -108,9 +102,6 @@ public function getErrorList() { 198 => 1, 199 => 1, 200 => 1, - 218 => 1, - 220 => 1, - 222 => 1, 228 => 1, ]; } @@ -122,13 +113,13 @@ public function getErrorList() { */ public function getWarningList() { return [ + 56 => 1, 110 => 1, 111 => 1, 114 => 1, 118 => 1, 119 => 1, 122 => 1, - 125 => 1, 130 => 1, 131 => 1, 132 => 1, diff --git a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc index 4f9d6d00..77930dd5 100644 --- a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.inc @@ -172,3 +172,26 @@ class bad_example_class_short_array { // Error. } } +abstract class warning_filter_points_to_abstract_method { + public function __construct() { + add_filter( 'good_example_abstract_class', [ $this, 'abstract_method' ] ); + } + + abstract public function abstract_method( $param ); // Warning. +} + +class tokenizer_bug_test { + public function __construct() { + add_filter( 'tokenizer_bug', [ $this, 'tokenizer_bug' ] ); + } + + public function tokenizer_bug( $param ): namespace\Foo {} // Ok (tokenizer bug PHPCS < 3.5.7) / Error in PHPCS >= 3.5.7. +} + +// Intentional parse error. This has to be the last test in the file! +class parse_error_test { + public function __construct() { + add_filter( 'parse_error', [ $this, 'parse_error' ] ); + } + + public function parse_error( $param ) // Ok, parse error ignored. diff --git a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php index 747e0b83..8cc2cef4 100644 --- a/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php +++ b/WordPressVIPMinimum/Tests/Hooks/AlwaysReturnInFilterUnitTest.php @@ -7,7 +7,9 @@ namespace WordPressVIPMinimum\Tests\Hooks; +use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + /** * Unit test class for the Hooks/AlwaysReturn sniff. * @@ -31,6 +33,7 @@ public function getErrorList() { 105 => 1, 129 => 1, 163 => 1, + 188 => version_compare( Config::VERSION, '3.5.7', '>=' ) ? 1 : 0, ]; } @@ -40,7 +43,9 @@ public function getErrorList() { * @return array => */ public function getWarningList() { - return []; + return [ + 180 => 1, + ]; } } diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc index eb5a63cd..a6572d6d 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.inc @@ -38,3 +38,86 @@ wp_cache_replace( 'test', $data, $group, 100 ); // Lower than 300. wp_cache_replace( 'test', $data, $group, 2*MINUTE_IN_SECONDS ); // Lower than 300. wp_cache_replace( 123, $data, null, 1.5 * MINUTE_IN_SECONDS ); // Lower than 300. wp_cache_replace( $testing, $data, '', 1.5 * MINUTE_IN_SECONDS ); // Lower than 300. + +// Test error being reported on the line containing the parameter. +wp_cache_replace( + $testing, + $data, + '', + 1.5 * MINUTE_IN_SECONDS // Lower than 300. +); + +// Test calculations with floats. +wp_cache_replace( $testing, $data, '', 7.5 * MINUTE_IN_SECONDS ); // OK. +wp_cache_replace( $testing, $data, '', 500 * 0.1 ); // Bad. + +// Test comment handling. +wp_cache_add( 'test', $data, $group, /* Deliberately left empty */ ); // OK. +wp_cache_add( 'test', $data, $group, 600 * 0.1 /* = 1 minute */ ); // Bad. +wp_cache_add( + 'test', + $data, + $group, + // Cache for 10 minutes. + 600 +); // OK. + +wp_cache_add( + 'test', + $data, + $group, + // phpcs:ignore Stnd.Cat.Sniff -- Just here for testing purposes. + 600 +); // OK. + +// Test variable/constant with or without calculation being passed. +wp_cache_set( $key, $data, '', $time ); // Manual inspection warning. +wp_cache_set( $key, $data, '', PREFIX_FIVE_MINUTES ); // Manual inspection warning. +wp_cache_set( $key, $data, '', 20 * $time ); // Manual inspection warning. +wp_cache_set( $key, $data, '', $base + $extra ); // Manual inspection warning. +wp_cache_set( $key, $data, '', 300 + $extra ); // Manual inspection warning. +wp_cache_set( $key, $data, '', PREFIX_CUSTOM_TIME * 5); // Manual inspection warning. + +// Test calculations with additional aritmetic operators. +wp_cache_replace( 'test', $data, $group, +5 ** MINUTE_IN_SECONDS ); // OK. +wp_cache_add( 'test', $data, $group, WEEK_IN_SECONDS / 3 + HOUR_IN_SECONDS ); // OK. + +// Test calculations grouped with parentheses. +wp_cache_set( $key, $data, '', (24 * 60 * 60) ); // OK. +wp_cache_set( $key, $data, '', (-(2 * 60) + 600) ); // OK. +wp_cache_set( $key, $data, '', (2 * 60) ); // Bad. +wp_cache_set( $key, $data, '', (-(2 * 60) + 600 ); // OK - includes parse error, close parenthesis missing. + +// Test handling of numbers passed as strings. +wp_cache_set( 'test', $data, $group, '300' ); // OK - type cast to integer within the function. +wp_cache_set( 'test', $data, $group, '100' * 3 ); // OK - type cast to integer by PHP during the calculation. +wp_cache_set( 'test', $data, $group, '-10' ); // Bad - type cast to integer within the function. +wp_cache_replace( $testing, $data, '', '1.5' * MINUTE_IN_SECONDS ); // Bad - type cast to integer by PHP during the calculation. + +// Test handling of 0 values. `0` is the default value for the parameter and translates internally to "no expiration". +wp_cache_add( 'test', $data, $group, 0 ); // OK. +wp_cache_add( 'test', $data, $group, 0.0 ); // OK. +wp_cache_add( 'test', $data, $group, '0' ); // OK. +wp_cache_add( 'test', $data, $group, false ); // OK. +wp_cache_add( 'test', $data, $group, null ); // OK. + +// Test handling of other scalar values. +wp_cache_add( 'test', $data, $group, true ); // Bad - becomes integer 1. + +// Test passing just and only one of the time constants, including passing it as an FQN. +wp_cache_set( 'test', $data, $group, HOUR_IN_SECONDS ); // OK. +wp_cache_set( 'test', $data, $group, \MONTH_IN_SECONDS ); // OK. + +// Test passing something which may look like one of the time constants, but isn't. +wp_cache_set( 'test', $data, $group, month_in_seconds ); // Bad - constants are case-sensitive. +wp_cache_set( 'test', $data, $group, HOUR_IN_SECONDS::methodName() ); // Bad - not a constant. +wp_cache_set( 'test', $data, $group, $obj->MONTH_IN_SECONDS ); // Bad - not a constant. +wp_cache_set( 'test', $data, $group, $obj::MONTH_IN_SECONDS ); // Bad - not the WP constant. +wp_cache_set( 'test', $data, $group, PluginNamespace\SubLevel\DAY_IN_SECONDS ); // Bad - not the WP constant. + +// Test passing negative number as cache time. +wp_cache_set( 'test', $data, $group, -300 ); // Bad. +wp_cache_add( $testing, $data, 'test_group', -6 * MINUTE_IN_SECONDS ); // Bad. + +// Test more complex logic in the parameter. +wp_cache_add( $key, $data, '', ($toggle ? 200 : 400) ); // Manual inspection warning. diff --git a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php index 81a4da60..f3f6928d 100644 --- a/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/LowExpiryCacheTimeUnitTest.php @@ -34,18 +34,40 @@ public function getErrorList() { */ public function getWarningList() { return [ - 27 => 1, - 28 => 1, - 29 => 1, - 30 => 1, - 32 => 1, - 33 => 1, - 34 => 1, - 35 => 1, - 37 => 1, - 38 => 1, - 39 => 1, - 40 => 1, + 27 => 1, + 28 => 1, + 29 => 1, + 30 => 1, + 32 => 1, + 33 => 1, + 34 => 1, + 35 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 40 => 1, + 47 => 1, + 52 => 1, + 56 => 1, + 74 => 1, + 75 => 1, + 76 => 1, + 77 => 1, + 78 => 1, + 79 => 1, + 82 => ( PHP_VERSION_ID > 50600 ) ? 0 : 1, + 88 => 1, + 94 => 1, + 95 => 1, + 105 => 1, + 112 => 1, + 113 => 1, + 114 => 1, + 115 => 1, + 116 => 1, + 119 => 1, + 120 => 1, + 123 => 1, ]; } } diff --git a/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.inc index 51590746..2aecfee5 100644 --- a/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.inc @@ -16,4 +16,8 @@ $q = new WP_Query( $query_args ); $query_args[ 'suppress_filters' ] = true; -$q = new WP_query( $query_args ); \ No newline at end of file +$q = new WP_query( $query_args ); + +get_posts( [ 'exclude' => $post_ids ] ); // Warning. + +$exclude = [ 1, 2, 3 ]; diff --git a/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.php b/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.php index 1ccbfc4c..7d09c201 100644 --- a/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/WPQueryParamsUnitTest.php @@ -39,6 +39,7 @@ public function getWarningList() { return [ 4 => 1, 11 => 1, + 21 => 1, ]; } diff --git a/WordPressVIPMinimum/Tests/Security/Gruntfile.js b/WordPressVIPMinimum/Tests/Security/Gruntfile.js new file mode 100644 index 00000000..b423d7af --- /dev/null +++ b/WordPressVIPMinimum/Tests/Security/Gruntfile.js @@ -0,0 +1,100 @@ + +module.exports = function(grunt) { + + require('load-grunt-tasks')(grunt); + + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + checktextdomain: { + options:{ + text_domain: '<%= pkg.name %>', + correct_domain: true, + keywords: [ + '__:1,2d', + '_e:1,2d', + '_x:1,2c,3d', + 'esc_html__:1,2d', + 'esc_html_e:1,2d', + 'esc_html_x:1,2c,3d', + 'esc_attr__:1,2d', + 'esc_attr_e:1,2d', + 'esc_attr_x:1,2c,3d', + '_ex:1,2c,3d', + '_n:1,2,4d', + '_nx:1,2,4c,5d', + '_n_noop:1,2,3d', + '_nx_noop:1,2,3c,4d' + ] + }, + files: { + src: [ + '**/*.php', + ], + expand: true + } + }, + + makepot: { + target: { + options: { + domainPath: '/languages/', // Where to save the POT file. + mainFile: 'style.css', // Main project file. + potFilename: '<%= pkg.name %>.pot', // Name of the POT file. + type: 'wp-theme', // Type of project (wp-plugin or wp-theme). + processPot: function( pot, options ) { + pot.headers['plural-forms'] = 'nplurals=2; plural=n != 1;'; + pot.headers['x-poedit-basepath'] = '.\n'; + pot.headers['x-poedit-language'] = 'English\n'; + pot.headers['x-poedit-country'] = 'UNITED STATES\n'; + pot.headers['x-poedit-sourcecharset'] = 'utf-8\n'; + pot.headers['X-Poedit-KeywordsList'] = '__;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;\n'; + pot.headers['x-textdomain-support'] = 'yes\n'; + return pot; + } + } + } + }, + + // Clean up build directory + clean: { + main: ['build/<%= pkg.name %>'] + }, + + // Copy the theme into the build directory + copy: { + main: { + src: [ + '**', + '!build/**', + '!.git/**', + '!Gruntfile.js', + '!package.json', + '!.gitignore', + '!.gitmodules', + ], + dest: 'build/<%= pkg.name %>/' + } + }, + + //Compress build directory into .zip and -.zip + compress: { + main: { + options: { + mode: 'zip', + archive: './build/<%= pkg.name %>.zip' + }, + expand: true, + cwd: 'build/<%= pkg.name %>/', + src: ['**/*'], + dest: '<%= pkg.name %>/' + } + } + + }); + + // Default task(s). + grunt.registerTask( 'build', [ 'clean', 'copy', 'compress' ] ); + grunt.registerTask( 'i18n', [ 'checktextdomain', 'makepot' ] ); +}; diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc index 415ebd90..a0ff39cc 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.inc @@ -1,26 +1,26 @@ '; // NOK. +echo ''; // Error. -echo ""; // NOK. +echo ""; // Error. -echo ''; // OK. +echo ''; // OK. echo ""; // OK. echo ''; // OK. -echo ""; // OK. +echo ""; // OK. -echo ''; // NOK. +echo ''; // Error. -echo ""; // NOK. +echo ""; // Error. ?> -Hello +Hello -Hey +Hey @@ -30,12 +30,55 @@ echo ""; // NOK. echo ''; // OK. -echo ''; // NOK. +echo ''; // Error. echo 'data-param-url="' . esc_url( $share_url ) . '"'; // OK. -echo 'data-param-url="' . esc_html( $share_url ) . '"'; // NOK. +echo 'data-param-url="' . Esc_HTML( $share_url ) . '"'; // Error. ?>
+ + + + + + " , esc_attr( $heading ) . ""; // Error. +?> + +
+Test +
+ +

?> +' . esc_attr__( $some_var, 'domain' ) . ''; // Error. +echo '

', \esc_attr_x( $title, 'domain' ), '

'; // Error. +echo "<$tag> " , esc_attr( $test ) , ""; // Error. +?> +

'; ?> // Error. +
+
+
+" . $test . "
"; // OK. +echo "<{$tag}>" . esc_attr( $tag_content ) . ""; // Error. +echo "<$tag" . ' >' . esc_attr( $tag_content ) . ""; // Error. +echo '
'; // Error. +echo "
'; // Error. +echo "
'; // Error. +echo ''; // Error. +echo "'; // Error. +echo "
'; // Error. +echo ''; // Error. + +echo ''; // Error. + +echo 'data-param-url="' . Esc_HTML::static_method( $share_url ) . '"'; // OK. diff --git a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php index bf47a11e..8db41f41 100644 --- a/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/ProperEscapingFunctionUnitTest.php @@ -34,6 +34,25 @@ public function getErrorList() { 33 => 1, 37 => 1, 41 => 1, + 45 => 1, + 48 => 1, + 62 => 1, + 63 => 1, + 64 => 1, + 65 => 1, + 67 => 1, + 68 => 1, + 69 => 1, + 72 => 1, + 73 => 1, + 74 => 1, + 75 => 1, + 76 => 1, + 77 => 1, + 78 => 1, + 79 => 1, + 80 => 1, + 82 => 1, ]; } diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc index 2e8b7cd6..addf103d 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.inc @@ -12,6 +12,109 @@ EOT; + +" data-origsrc="<%- data.originalSrc %>">'. // NOK x 1. + '<%=data.alt%>'. // NOK x 1. + '
'; +} + +function single_quoted_string_with_concatenation( $data ) { + echo '<%- data.alt %>'; // NOK x 1. +} + +function double_quoted_string( $name, $value, $is_template ) { + echo $is_template ? "<%={$name}%>" : esc_attr( $value ); // NOK. +} + +$nowdoc = <<<'EOT' + +EOT; + +$heredoc = << + <%= name %> + +EOD; + +// Make sure the JS specific check does not trigger on PHP code. +$obj->interpolate = true; + +// Test matching the "interpolate" keyword with higher precision (mirrors same check in JS). +function test_interpolate_match_precision() { + ?> + + <%= _.escape(name) %>', { name: 'John Smith' }); // OK. + + var html = _.template( + "
The \"<% __p+=_.escape(o.text) %>\" is the same
" + // OK. + "as the \"<%= _.escape(o.text) %>\" and the same
" + // OK. + "as the \"<%- o.text %>\"
", // OK. + { + text: "some text and \n it's a line break" + }, + { + variable: "o" + } + ); +EOD; + + echo $script; +} + +function display_foo { +?> + + + +<%- name %>', { name: 'John Smith' }); // OK. + +var html = _.template('
  • <%= name %>
  • ', { name: 'John Smith' }); // NOK. +var html = _.template('
  • <%=type.item%>
  • ', { name: 'John Smith' }); // NOK. + +_.templateSettings.interpolate = /\{\{(.+?)\}\}/g; /* NOK */ +_.templateSettings = { + interpolate: /\{\{(.+?)\}\}/g /* NOK */ +}; + +options.interpolate=_.templateSettings.interpolate; /* NOK */ +var interpolate = options.interpolate || reNoMatch, /* Ignore */ + source = "__p += '"; + +var template = _.template('
  • {{ name }}
  • '); /* NOK, due to the interpolate, but not flagged. */ + +// Prevent false positives on "interpolate". +var preventMisidentification = 'text interpolate text'; // OK. +var interpolate = THREE.CurveUtils.interpolate; // OK. + +var p = function(f, d) { + return s.interpolate(m(f), _(d), 0.5, e.color_space) // OK. +} + +y.interpolate.bezier = b; // OK. + +// Recognize escaping. +var html = _.template('
  • <%= _.escape(name) %>
  • ', { name: 'John Smith' }); // OK. + +var html = _.template( + "
    The \"<% __p+=_.escape(o.text) %>\" is the same
    " + // OK. + "as the \"<%= _.escape(o.text) %>\" and the same
    " + // OK. + "as the \"<%- o.text %>\"
    ", // OK. + { + text: "some text and \n it's a line break" + }, + { + variable: "o" + } +); + +var compiled = _.template("<% print('Hello ' + _.escape(epithet)); %>"); /* OK */ +var compiled = _.template("<% print('Hello ' + epithet); %>"); /* NOK */ +var compiled = _.template("<% __p+=o.text %>"); /* NOK */ diff --git a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php index 1043c3e8..0c0e08c5 100644 --- a/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/UnderscorejsUnitTest.php @@ -18,6 +18,21 @@ */ class UnderscorejsUnitTest extends AbstractSniffUnitTest { + /** + * Get a list of all test files to check. + * + * @param string $testFileBase The base path that the unit tests files will have. + * + * @return string[] + */ + protected function getTestFiles( $testFileBase ) { + return [ + $testFileBase . 'inc', + $testFileBase . 'js', + __DIR__ . DIRECTORY_SEPARATOR . 'Gruntfile.js', + ]; + } + /** * Returns the lines where errors should occur. * @@ -30,13 +45,44 @@ public function getErrorList() { /** * Returns the lines where warnings should occur. * + * @param string $testFile The name of the file being tested. + * * @return array => */ - public function getWarningList() { - return [ - 6 => 1, - 14 => 1, - ]; + public function getWarningList( $testFile = '' ) { + switch ( $testFile ) { + case 'UnderscorejsUnitTest.inc': + return [ + 6 => 1, + 14 => 1, + 22 => 1, + 23 => 1, + 28 => 1, + 32 => 1, + 38 => 3, + 45 => 1, + 46 => 1, + 47 => 1, + 58 => 1, + 60 => 1, + 114 => 1, + 115 => 1, + ]; + + case 'UnderscorejsUnitTest.js': + return [ + 4 => 1, + 5 => 1, + 7 => 1, + 9 => 1, + 12 => 1, + 44 => 1, + 45 => 1, + ]; + + default: + return []; + } } } diff --git a/WordPressVIPMinimum/Tests/Variables/RestrictedVariablesUnitTest.inc b/WordPressVIPMinimum/Tests/Variables/RestrictedVariablesUnitTest.inc index 2126a037..39abb1bb 100644 --- a/WordPressVIPMinimum/Tests/Variables/RestrictedVariablesUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Variables/RestrictedVariablesUnitTest.inc @@ -4,9 +4,9 @@ $query = "SELECT * FROM $wpdb->users"; // Error. $wp_db->update( $wpdb->users, array( 'displayname' => 'Kanobe!' ), array( 'ID' => 1 ) ); // Error. -$query = "SELECT * FROM $wpdb->usermeta"; // Error. -$wp_db->update( $wpdb->usermeta, array( 'meta_value' => 'bar!' ), array( 'user_id' => 1, 'meta_key' => 'foo' ) ); // Error. + + $query = "SELECT * FROM $wpdb->posts"; // Ok. @@ -20,7 +20,7 @@ $y = $_SERVER['REQUEST_URI']; // Ok. // Error. $query = <<usermeta +SELECT * FROM $wpdb->users EOD; // Warning @@ -29,7 +29,7 @@ Your user-agent is {$_SERVER['HTTP_USER_AGENT']} EOD; // phpcs:set WordPressVIPMinimum.Variables.RestrictedVariables exclude[] user_meta -$query = "SELECT * FROM $wpdb->usermeta"; // Ok, excluded. +$query = "SELECT * FROM $wpdb->users"; // Ok, excluded. // phpcs:set WordPressVIPMinimum.Functions.RestrictedFunctions exclude[] diff --git a/WordPressVIPMinimum/Tests/Variables/RestrictedVariablesUnitTest.php b/WordPressVIPMinimum/Tests/Variables/RestrictedVariablesUnitTest.php index 487c1ee0..78b19a60 100644 --- a/WordPressVIPMinimum/Tests/Variables/RestrictedVariablesUnitTest.php +++ b/WordPressVIPMinimum/Tests/Variables/RestrictedVariablesUnitTest.php @@ -30,8 +30,6 @@ public function getErrorList() { return [ 3 => 1, 5 => 1, - 7 => 1, - 9 => 1, 23 => 1, 36 => 1, 37 => 1, diff --git a/WordPressVIPMinimum/ruleset-test.inc b/WordPressVIPMinimum/ruleset-test.inc index 9953d432..5c89e02a 100644 --- a/WordPressVIPMinimum/ruleset-test.inc +++ b/WordPressVIPMinimum/ruleset-test.inc @@ -308,27 +308,27 @@ $my_notokay_func = 'extract'; $my_notokay_func(); // Error. // WordPressVIPMinimum.Functions.RestrictedFunctions -wp_cache_get_multi(); // Error. + opcache_reset(); // Error. opcache_invalidate( 'test_script.php' ); // Error. opcache_compile_file( $var ); // Error. opcache_is_script_cached( 'test_script.php' ); // Error. opcache_get_status(); // Error. opcache_get_configuration(); // Error. -get_super_admins(); // Error. + wpcom_vip_irc(); // Error. flush_rewrite_rules(); // Error. $wp_rewrite->flush_rules(); // Error. attachment_url_to_postid( $url ); // Error. dbDelta(); // Error. -switch_to_blog( $blogid ); // Error. +switch_to_blog( $blogid ); // Warning. get_page_by_title( $page_title ); // Error. url_to_postid( $url ); // Error. \add_role(); // Error. -get_user_meta(); // Error. -update_user_meta(); // Error. -delete_user_meta(); // Error. -add_user_meta(); // Error. + + + + term_exists(); // Error. count_user_posts(); // Error. wp_old_slug_redirect(); // Error. @@ -390,16 +390,16 @@ chown(); // Error. chmod(); // Error. lchgrp(); // Error. lchown(); // Error. -add_site_option( 'foo', $bar ); // Error. -update_site_option( $bar, $foo, true ); // Error. -delete_site_option( $foo ); // Error. + + + wp_mail(); // Warning. mail(); // Warning. is_multi_author(); // Warning. the_sub_field( 'field' ); // Warning. the_field( 'field' ); // Warning. wp_remote_get( $url ); // Warning. -setcookie( 'cookie[three]', 'cookiethree' ); // Warning. +setcookie( 'cookie[three]', 'cookiethree' ); // Error. get_posts(); // Warning. wp_get_recent_posts(); // Warning. $wp_random_testing = create_function( '$a, $b', 'return ( $b / $a ); '); // Warning. @@ -443,7 +443,7 @@ add_filter( 'robots_txt', function() { // Warning. } ); // WordPressVIPMinimum.Performance.BatcacheWhitelistedParams -// phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotValidated +// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated $test = sanitize_text_field( $_GET["utm_medium"] ); // Warning. // WordPressVIPMinimum.Performance.CacheValueOverride diff --git a/WordPressVIPMinimum/ruleset-test.php b/WordPressVIPMinimum/ruleset-test.php index 4c9f073e..8320825c 100644 --- a/WordPressVIPMinimum/ruleset-test.php +++ b/WordPressVIPMinimum/ruleset-test.php @@ -82,27 +82,20 @@ 303 => 1, 304 => 1, 308 => 1, - 311 => 1, 312 => 1, 313 => 1, 314 => 1, 315 => 1, 316 => 1, 317 => 1, - 318 => 1, 319 => 1, 320 => 1, 321 => 1, 322 => 1, 323 => 1, - 324 => 1, 325 => 1, 326 => 1, 327 => 1, - 328 => 1, - 329 => 1, - 330 => 1, - 331 => 1, 332 => 1, 333 => 1, 334 => 1, @@ -161,9 +154,7 @@ 390 => 1, 391 => 1, 392 => 1, - 393 => 1, - 394 => 1, - 395 => 1, + 402 => 1, 415 => 1, 425 => 1, 451 => 1, @@ -263,13 +254,13 @@ 288 => 1, 293 => 1, 294 => 1, + 324 => 1, 396 => 1, 397 => 1, 398 => 1, 399 => 1, 400 => 1, 401 => 1, - 402 => 1, 403 => 1, 404 => 1, 405 => 1, @@ -309,13 +300,13 @@ '`eval()` is a security risk, please refrain from using it.', ], 242 => [ - 'Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/.', + 'Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/.', ], 243 => [ - 'Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/.', + 'Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/.', ], 244 => [ - 'Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/.', + 'Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/.', ], 259 => [ '`get_children()` performs a no-LIMIT query by default, make sure to set a reasonable `posts_per_page`. `get_children()` will do a -1 query by default, a maximum of 100 should be used.', @@ -328,7 +319,7 @@ // Run the tests! $test = new RulesetTest( 'WordPressVIPMinimum', $expected ); if ( $test->passes() ) { - printf( 'All WordPressVIPMinimum tests passed!' . PHP_EOL ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + printf( 'All WordPressVIPMinimum tests passed!' . PHP_EOL ); exit( 0 ); } diff --git a/WordPressVIPMinimum/ruleset.xml b/WordPressVIPMinimum/ruleset.xml index ea062d2e..2ce2511c 100644 --- a/WordPressVIPMinimum/ruleset.xml +++ b/WordPressVIPMinimum/ruleset.xml @@ -1,11 +1,6 @@ WordPress VIP Minimum Coding Standards - - - @@ -57,15 +52,13 @@ - - + error `eval()` is a security risk, please refrain from using it. - - + @@ -108,14 +101,12 @@ error - - + - - + error @@ -123,12 +114,9 @@ error - - - - - - + + + @@ -141,16 +129,13 @@ - Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. + Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/. - Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. - - - Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. + Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/. - Using cURL functions is highly discouraged within VIP context. Please see: https://lobby.vip.wordpress.com/wordpress-com-documentation/fetching-remote-data/. + Using cURL functions is highly discouraged within VIP context. Please see: https://docs.wpvip.com/technical-references/code-quality-and-best-practices/retrieving-remote-data/. diff --git a/bin/php-lint b/bin/php-lint index 4da761cf..0528ec77 100755 --- a/bin/php-lint +++ b/bin/php-lint @@ -2,21 +2,9 @@ # # Lint the PHP files against parse errors. # -# The find utility recursively descends the directory tree for each path listed. -# -# find . = Start with all files in this package -# -path ./vendor -prune = But remove those in the vendor directory -# -o -path ./bin -prune = And remove those in the bin directory -# -o -path ./.git -prune = And remove those in the .git directory -# -o -name "*.php" = And only consider those with a .php extension -# -exec php -l {} = Run PHP linter on the remaining files -# | grep "^[Parse error|Fatal error]" = Look in the results for any parse or fatal errors. -# # EXAMPLE TO RUN LOCALLY: # # ./bin/php-lint # -if find . -path ./vendor -prune -o -path ./bin -prune -o -path ./.git -prune -o -name "*.php" -exec php -l -f {} \; | grep "^[Errors parsing]"; then - exit 1; -fi +"$(pwd)/vendor/bin/parallel-lint" . -e php --exclude vendor --exclude .git $@ diff --git a/bin/xml-lint b/bin/xml-lint index a8e295a3..6ab4e113 100755 --- a/bin/xml-lint +++ b/bin/xml-lint @@ -9,7 +9,6 @@ # ./bin/xml-lint # Validate the ruleset XML files. -#xmllint --noout --schema ./vendor/squizlabs/php_codesniffer/phpcs.xsd ./*/ruleset.xml xmllint --noout --schema ./vendor/squizlabs/php_codesniffer/phpcs.xsd ./*/ruleset.xml # Check the code-style consistency of the XML files. diff --git a/composer.json b/composer.json index 9c52020c..a1d7f0be 100644 --- a/composer.json +++ b/composer.json @@ -16,18 +16,18 @@ ], "require": { "php": ">=5.4", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7", "sirbrillig/phpcs-variable-analysis": "^2.8.3", "squizlabs/php_codesniffer": "^3.5.5", "wp-coding-standards/wpcs": "^2.3" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "php-parallel-lint/php-parallel-lint": "^1.0", + "php-parallel-lint/php-console-highlighter": "^0.5", "phpcompatibility/php-compatibility": "^9", + "phpcsstandards/phpcsdevtools": "^1.0", "phpunit/phpunit": "^4 || ^5 || ^6 || ^7" }, - "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will manage the PHPCS 'installed_paths' automatically." - }, "scripts": { "install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run", "ruleset": "bin/ruleset-tests", @@ -38,11 +38,15 @@ "phpcs": "bin/phpcs", "phpunit": "bin/unit-tests", "coverage": "bin/unit-tests-coverage", + "check-complete": [ + "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness -q ./WordPressVIPMinimum" + ], "test": [ "@lint", "@ruleset", "@phpunit", - "@phpcs" + "@phpcs", + "@check-complete" ] }, "support": { diff --git a/tests/RulesetTest.php b/tests/RulesetTest.php index 23ea2921..d67a3454 100644 --- a/tests/RulesetTest.php +++ b/tests/RulesetTest.php @@ -92,7 +92,7 @@ public function __construct( $ruleset, $expected = [] ) { // Travis and Windows support. $phpcs_bin = getenv( 'PHPCS_BIN' ); if ( $phpcs_bin === false ) { - // phpcs:ignore + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- This is test code, not production. putenv( 'PHPCS_BIN=phpcs' ); } else { $this->phpcs_bin = realpath( $phpcs_bin ); @@ -101,7 +101,6 @@ public function __construct( $ruleset, $expected = [] ) { $output = $this->collect_phpcs_result(); if ( ! is_object( $output ) || empty( $output ) ) { - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped printf( 'The PHPCS command checking the ruleset hasn\'t returned any issues. Bailing ...' . PHP_EOL ); exit( 1 ); // Die early, if we don't have any output. } @@ -149,7 +148,7 @@ private function collect_phpcs_result() { $this->phpcs_bin, $this->ruleset ); - // phpcs:ignore + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec -- This is test code, not production. $output = shell_exec( $shell ); return json_decode( $output ); @@ -158,7 +157,7 @@ private function collect_phpcs_result() { /** * Process the Decoded JSON output from PHP_CodeSniffer. * - * @param stdClass $output Deconded JSON output from PHP_CodeSniffer. + * @param \stdClass $output Decoded JSON output from PHP_CodeSniffer. */ private function process_output( $output ) { foreach ( $output->files as $file ) { @@ -310,9 +309,9 @@ private function check_messages() { * Print out the message reporting found issues. * * @param int $expected Expected number of issues. - * @param string $type The type of the issue. - * @param int $number Real number of issues. - * @param int $line Line number. + * @param string $type The type of the issue. + * @param int $number Real number of issues. + * @param int $line Line number. */ private function error_warning_message( $expected, $type, $number, $line ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped