diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a0ac7179..b6cd7972 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -60,7 +60,7 @@ You should be able to get the version numbers using the `composer info` command. --> | Environment | Answer | -|-------------------------|--------------------------------------------------------------------------------| +| ----------------------- | ------------------------------------------------------------------------------ | | PHP version | x.y.z | | PHP_CodeSniffer version | x.y.z | | PHPCSExtra version | x.y.z | diff --git a/.github/release-checklist.md b/.github/release-checklist.md index c684f9e7..2c57bac0 100644 --- a/.github/release-checklist.md +++ b/.github/release-checklist.md @@ -24,4 +24,5 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH - [ ] Fast-forward `develop` to be equal to `stable` ### Publicize +- [ ] Toot about the release. - [ ] Tweet about the release. diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 265d7720..b39046bb 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -20,7 +20,6 @@ jobs: runs-on: ubuntu-latest env: - XMLLINT_INDENT: ' ' # - COMPOSER_ROOT_VERSION is needed to get round the recursive dependency when using CI. COMPOSER_ROOT_VERSION: '1.99.99' @@ -48,36 +47,41 @@ jobs: # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer - name: Install Composer dependencies - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") - - name: Install xmllint - run: | - sudo apt-get update - sudo apt-get install --no-install-recommends -y libxml2-utils + # Validate XML files against schema. + - name: Validate XML rulesets against schema + uses: phpcsstandards/xmllint-validate@v1 + with: + pattern: "./*/ruleset.xml" + xsd-file: "vendor/squizlabs/php_codesniffer/phpcs.xsd" - # Show XML violations inline in the file diff. - # @link https://github.com/marketplace/actions/xmllint-problem-matcher - - name: Enable showing XML issues inline - uses: korelstar/xmllint-problem-matcher@v1 + - name: Validate documentation XML against schema + uses: phpcsstandards/xmllint-validate@v1 + with: + pattern: "./*/Docs/*/*Standard.xml" + xsd-file: "vendor/phpcsstandards/phpcsdevtools/DocsXsd/phpcsdocs.xsd" - # Validate the Ruleset XML file. - # @link http://xmlsoft.org/xmllint.html - - name: Validate rulesets against schema - run: xmllint --noout --schema vendor/squizlabs/php_codesniffer/phpcs.xsd ./*/ruleset.xml + - name: Validate Project PHPCS ruleset against schema + uses: phpcsstandards/xmllint-validate@v1 + with: + pattern: "phpcs.xml.dist" + xsd-file: "vendor/squizlabs/php_codesniffer/phpcs.xsd" - # Check the code-style consistency of the XML ruleset files. - - name: Check XML code style - run: | - diff -B ./Modernize/ruleset.xml <(xmllint --format "./Modernize/ruleset.xml") - diff -B ./NormalizedArrays/ruleset.xml <(xmllint --format "./NormalizedArrays/ruleset.xml") - diff -B ./Universal/ruleset.xml <(xmllint --format "./Universal/ruleset.xml") + - name: "Validate PHPUnit config for use with PHPUnit 8" + uses: phpcsstandards/xmllint-validate@v1 + with: + pattern: "phpunit.xml.dist" + xsd-file: "vendor/phpunit/phpunit/schema/8.5.xsd" - # Validate the Documentation XML files. - - name: Validate documentation against schema - run: xmllint --noout --schema vendor/phpcsstandards/phpcsdevtools/DocsXsd/phpcsdocs.xsd ./*/Docs/*/*Standard.xml + - name: "Validate PHPUnit config for use with PHPUnit 9" + uses: phpcsstandards/xmllint-validate@v1 + with: + pattern: "phpunit.xml.dist" + xsd-file: "vendor/phpunit/phpunit/schema/9.2.xsd" # Check the code-style consistency of the PHP files. - name: Check PHP code style @@ -94,107 +98,50 @@ jobs: - name: Check sniff feature completeness run: composer check-complete - phpstan: - name: "PHPStan" - runs-on: "ubuntu-latest" + xml-cs: + name: 'XML Code style' + runs-on: ubuntu-latest + + env: + XMLLINT_INDENT: ' ' steps: - name: Checkout code uses: actions/checkout@v4 - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 'latest' - coverage: none - tools: phpstan + # Updating the lists can fail intermittently, typically after Microsoft has released a new package. + # This should not be blocking for this job, so ignore any errors from this step. + # Ref: https://github.com/dotnet/core/issues/4167 + - name: Update the available packages list + continue-on-error: true + run: sudo apt-get update - # Install dependencies and handle caching in one go. - # Dependencies need to be installed to make sure the PHPCS and PHPUnit classes are recognized. - # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer - - name: Install Composer dependencies - uses: "ramsey/composer-install@v2" - with: - # Bust the cache at least once a month - output format: YYYY-MM. - custom-cache-suffix: $(date -u "+%Y-%m") + - name: Install xmllint + run: sudo apt-get install --no-install-recommends -y libxml2-utils - - name: Run PHPStan - run: phpstan analyse + # Show XML violations inline in the file diff. + - name: Enable showing XML issues inline + uses: korelstar/xmllint-problem-matcher@v1 - remark: - name: 'QA Markdown' - runs-on: ubuntu-latest + # Check the code-style consistency of the XML ruleset files. + - name: Check XML code style + run: | + diff -B ./Modernize/ruleset.xml <(xmllint --format "./Modernize/ruleset.xml") + diff -B ./NormalizedArrays/ruleset.xml <(xmllint --format "./NormalizedArrays/ruleset.xml") + diff -B ./Universal/ruleset.xml <(xmllint --format "./Universal/ruleset.xml") - steps: - - name: Checkout code - uses: actions/checkout@v4 + phpstan: + name: "PHPStan" + uses: PHPCSStandards/.github/.github/workflows/reusable-phpstan.yml@main + with: + phpstanVersion: '2.x' - - name: Set up node and enable caching of dependencies - uses: actions/setup-node@v4 - with: - node-version: '16' - - # To make the command available on CLI, it needs to be installed globally. - - name: Install Remark CLI globally - run: npm install --global remark-cli --foreground-scripts true --fund false - - # To allow for creating a custom config which references rules which are included - # in the presets, without having to install all rules individually, a local install - # works best (and installing the presets in the first place, of course). - # - # Note: the first group of packages are all part of the mono "Remark lint" repo. - # The second group of packages (heading-whitespace and down) are additional - # "external" rules/plugins. - - name: Install Remark rules locally - run: > - npm install --foreground-scripts true --fund false - remark-lint - remark-gfm - remark-preset-lint-consistent - remark-preset-lint-recommended - remark-preset-lint-markdown-style-guide - remark-lint-checkbox-content-indent - remark-lint-linebreak-style - remark-lint-no-duplicate-defined-urls - remark-lint-no-empty-url - remark-lint-no-heading-like-paragraph - remark-lint-no-reference-like-url - remark-lint-no-unneeded-full-reference-image - remark-lint-no-unneeded-full-reference-link - remark-lint-strikethrough-marker - remark-lint-heading-whitespace - remark-lint-list-item-punctuation - remark-lint-match-punctuation - remark-lint-no-dead-urls - remark-lint-no-hr-after-heading - remark-lint-are-links-valid-duplicate - remark-validate-links - - - name: Run Remark-lint - run: remark . --frail - - # @link https://github.com/reviewdog/action-remark-lint - - name: Show Remark-lint annotations in PR - if: ${{ failure() && github.event_name == 'pull_request' }} - uses: reviewdog/action-remark-lint@v5 - with: - fail_on_error: true - install_deps: false - level: info - reporter: github-pr-check + remark: + name: 'QA Markdown' + uses: PHPCSStandards/.github/.github/workflows/reusable-remark.yml@main yamllint: name: 'Lint Yaml' - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - # Ref: https://yamllint.readthedocs.io/en/stable/ - - name: Run Yamllint on all yaml files in repo - run: yamllint . --format colored - - - name: Pipe Yamllint results on to GH for inline display - if: ${{ failure() }} - run: yamllint . --format github + uses: PHPCSStandards/.github/.github/workflows/reusable-yamllint.yml@main + with: + strict: true diff --git a/.github/workflows/label-remove-outdated.yml b/.github/workflows/label-remove-outdated.yml index ce4bc4ce..d3d3f6ac 100644 --- a/.github/workflows/label-remove-outdated.yml +++ b/.github/workflows/label-remove-outdated.yml @@ -17,7 +17,7 @@ jobs: name: Clean up labels on issue close steps: - - uses: mondeja/remove-labels-gh-action@v1 + - uses: mondeja/remove-labels-gh-action@v2 with: token: ${{ secrets.GITHUB_TOKEN }} labels: | @@ -31,7 +31,7 @@ jobs: name: Clean up labels on PR merge steps: - - uses: mondeja/remove-labels-gh-action@v1 + - uses: mondeja/remove-labels-gh-action@v2 with: token: ${{ secrets.GITHUB_TOKEN }} labels: | @@ -45,7 +45,7 @@ jobs: name: Clean up labels on PR close steps: - - uses: mondeja/remove-labels-gh-action@v1 + - uses: mondeja/remove-labels-gh-action@v2 with: token: ${{ secrets.GITHUB_TOKEN }} labels: | diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index e914cd17..249cc0c6 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -43,9 +43,9 @@ jobs: id: set_ini run: | if [ "${{ matrix.phpcs_version }}" != "dev-master" ]; then - echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT + echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> "$GITHUB_OUTPUT" else - echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT + echo 'PHP_INI=error_reporting=-1, display_errors=On' >> "$GITHUB_OUTPUT" fi - name: Install PHP @@ -59,10 +59,14 @@ jobs: if: ${{ matrix.phpcs_version != 'lowest' }} run: composer require squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-update --no-scripts --no-interaction + - name: "Composer: use lock file when necessary" + if: ${{ matrix.phpcs_version == 'lowest' }} + run: composer config --unset lock + # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer - name: Install Composer dependencies - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -70,8 +74,9 @@ jobs: - name: "Composer: set PHPCS/PHPCSUtils version for tests (lowest)" if: ${{ matrix.phpcs_version == 'lowest' }} run: > - composer update squizlabs/php_codesniffer phpcsstandards/phpcsutils - --prefer-lowest --ignore-platform-req=php+ --no-scripts --no-interaction + composer update --prefer-lowest --no-scripts --no-interaction + squizlabs/php_codesniffer + phpcsstandards/phpcsutils - name: Lint against parse errors if: matrix.phpcs_version == 'dev-master' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3075e039..bf2bb4e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,12 +32,12 @@ jobs: # @link https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix # # The matrix is set up so as not to duplicate the builds which are run for code coverage. - php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.4'] + php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.5'] phpcs_version: ['lowest', 'dev-master'] name: "Test${{ matrix.phpcs_version == 'dev-master' && ' + Lint' || '' }}: PHP ${{ matrix.php }} - PHPCS ${{ matrix.phpcs_version }}" - continue-on-error: ${{ matrix.php == '8.4' }} + continue-on-error: ${{ matrix.php == '8.5' }} steps: - name: Checkout code @@ -49,9 +49,9 @@ jobs: id: set_ini run: | if [[ "${{ matrix.phpcs_version }}" != "dev-master" ]]; then - echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT + echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> "$GITHUB_OUTPUT" else - echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT + echo 'PHP_INI=error_reporting=-1, display_errors=On' >> "$GITHUB_OUTPUT" fi - name: Install PHP @@ -65,10 +65,14 @@ jobs: if: ${{ matrix.phpcs_version != 'lowest' }} run: composer require squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-update --no-scripts --no-interaction + - name: "Composer: use lock file when necessary" + if: ${{ matrix.phpcs_version == 'lowest' }} + run: composer config --unset lock + # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer - name: Install Composer dependencies - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -76,8 +80,9 @@ jobs: - name: "Composer: set PHPCS/PHPCSUtils version for tests (lowest)" if: ${{ matrix.phpcs_version == 'lowest' }} run: > - composer update squizlabs/php_codesniffer phpcsstandards/phpcsutils - --prefer-lowest --ignore-platform-req=php+ --no-scripts --no-interaction + composer update --prefer-lowest --no-scripts --no-interaction + squizlabs/php_codesniffer + phpcsstandards/phpcsutils - name: Lint against parse errors if: matrix.phpcs_version == 'dev-master' @@ -100,7 +105,7 @@ jobs: strategy: matrix: - php: ['5.4', '8.3'] + php: ['5.4', '8.4'] phpcs_version: ['lowest', 'dev-master'] name: "Coverage${{ matrix.phpcs_version == 'dev-master' && ' + Lint' || '' }}: PHP ${{ matrix.php }} - PHPCS ${{ matrix.phpcs_version }}" @@ -115,9 +120,9 @@ jobs: id: set_ini run: | if [ "${{ matrix.phpcs_version }}" != "dev-master" ]; then - echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT + echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> "$GITHUB_OUTPUT" else - echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT + echo 'PHP_INI=error_reporting=-1, display_errors=On' >> "$GITHUB_OUTPUT" fi - name: Install PHP @@ -132,10 +137,14 @@ jobs: if: ${{ matrix.phpcs_version != 'lowest' }} run: composer require squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-update --no-scripts --no-interaction + - name: "Composer: use lock file when necessary" + if: ${{ matrix.phpcs_version == 'lowest' }} + run: composer config --unset lock + # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer - name: Install Composer dependencies - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: # Bust the cache at least once a month - output format: YYYY-MM. custom-cache-suffix: $(date -u "+%Y-%m") @@ -143,8 +152,9 @@ jobs: - name: "Composer: set PHPCS/PHPCSUtils version for tests (lowest)" if: ${{ matrix.phpcs_version == 'lowest' }} run: > - composer update squizlabs/php_codesniffer phpcsstandards/phpcsutils - --prefer-lowest --ignore-platform-req=php+ --no-scripts --no-interaction + composer update --prefer-lowest --no-scripts --no-interaction + squizlabs/php_codesniffer + phpcsstandards/phpcsutils - name: Lint against parse errors if: matrix.phpcs_version == 'dev-master' diff --git a/.remarkrc b/.remarkrc index 568f5885..f5bc35c1 100644 --- a/.remarkrc +++ b/.remarkrc @@ -13,8 +13,12 @@ { "skipUrlPatterns": [ "https://www.php.net/", - "^https?://github\\.com/PHPCSStandards/PHPCSExtra/compare/[0-9\\.]+?\\.{3}[0-9\\.]+" - ] + "^https?://github\\.com/PHPCSStandards/PHPCSExtra/compare/[0-9\\.]+?\\.{3}[0-9\\.]+", + "https://packagist.org/packages/phpcsstandards/phpcsextra#dev-develop" + ], + "deadOrAliveOptions": { + "maxRetries": 3 + } } ], "remark-lint-no-duplicate-defined-urls", diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ee3258b..c1cf7625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -This projects adheres to [Keep a CHANGELOG](http://keepachangelog.com/) and uses [Semantic Versioning](http://semver.org/). +This projects adheres to [Keep a CHANGELOG](https://keepachangelog.com/) and uses [Semantic Versioning](https://semver.org/). **Legend**: :wrench: = Includes auto-fixer. @@ -14,6 +14,35 @@ This projects adheres to [Keep a CHANGELOG](http://keepachangelog.com/) and uses _Nothing yet._ + +## [1.3.0] - 2025-04-21 + +### Added + +#### Universal + +* :wrench: :books: New `Universal.PHP.NoFQNTrueFalseNull` sniff to forbid using `true`, `false` and `null` as fully qualified constants. Thanks [@rodrigoprimo] for reviewing. [#327] +* `Universal.Operators.TypeSeparatorSpacing`: support for checking the spacing around the parentheses used in PHP 8.2+ DNF types. [#329] + +### Changed + +#### Universal + +* The `Universal.WhiteSpace.DisallowInlineTabs` sniff will now also check for inline tabs in heredoc/nowdoc openers and between the `yield` and `from` keywords. [#320] + +#### Other + +* Composer: The minimum `PHP_CodeSniffer` requirement has been updated to `^3.12.1` (was `^3.8.0`). [#330] +* Developer happiness: prevent creating a `composer.lock` file. Thanks [@fredden]! [#307] +* Various housekeeping, including improvements to the documentation and tests. + +[#307]: https://github.com/PHPCSStandards/PHPCSExtra/pull/307 +[#320]: https://github.com/PHPCSStandards/PHPCSExtra/pull/320 +[#327]: https://github.com/PHPCSStandards/PHPCSExtra/pull/327 +[#329]: https://github.com/PHPCSStandards/PHPCSExtra/pull/329 +[#330]: https://github.com/PHPCSStandards/PHPCSExtra/pull/330 + + ## [1.2.1] - 2023-12-08 ### Changed @@ -401,7 +430,7 @@ The upgrade to PHPCSUtils 1.0.0-alpha4 took care of a number of bugs, which pote [php-manual-dirname]: https://www.php.net/function.dirname [php-rfc-negative_array_index]: https://wiki.php.net/rfc/negative_array_index -[ESLint "no lonely if"]: https://eslint.org/docs/rules/no-lonely-if +[ESLint "no lonely if"]: https://eslint.org/docs/latest/rules/no-lonely-if [PHPCSUtils 1.0.0-alpha4]: https://github.com/PHPCSStandards/PHPCSUtils/releases/tag/1.0.0-alpha4 @@ -567,6 +596,7 @@ This initial alpha release contains the following sniffs: [php_version-config]: https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Configuration-Options#setting-the-php-version [Unreleased]: https://github.com/PHPCSStandards/PHPCSExtra/compare/stable...HEAD +[1.3.0]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.2.1...1.3.0 [1.2.1]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.1.2...1.2.0 [1.1.2]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.1.1...1.1.2 @@ -581,10 +611,11 @@ This initial alpha release contains the following sniffs: [1.0.0-alpha3]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.0-alpha2...1.0.0-alpha3 [1.0.0-alpha2]: https://github.com/PHPCSStandards/PHPCSExtra/compare/1.0.0-alpha1...1.0.0-alpha2 -[@anomiex]: https://github.com/anomiex -[@derickr]: https://github.com/derickr -[@diedexx]: https://github.com/diedexx -[@fredden]: https://github.com/fredden -[@GaryJones]: https://github.com/GaryJones -[@stronk7]: https://github.com/stronk7 -[@szepeviktor]: https://github.com/szepeviktor +[@anomiex]: https://github.com/anomiex +[@derickr]: https://github.com/derickr +[@diedexx]: https://github.com/diedexx +[@fredden]: https://github.com/fredden +[@GaryJones]: https://github.com/GaryJones +[@rodrigoprimo]: https://github.com/rodrigoprimo +[@stronk7]: https://github.com/stronk7 +[@szepeviktor]: https://github.com/szepeviktor diff --git a/Modernize/Docs/FunctionCalls/DirnameStandard.xml b/Modernize/Docs/FunctionCalls/DirnameStandard.xml index 5f486646..a4c65f7e 100644 --- a/Modernize/Docs/FunctionCalls/DirnameStandard.xml +++ b/Modernize/Docs/FunctionCalls/DirnameStandard.xml @@ -14,7 +14,7 @@ $path = __DIR__; ]]> - + dirname(__FILE__); ]]> diff --git a/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml b/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml index e8a006f0..3c655486 100644 --- a/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml +++ b/NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml @@ -5,7 +5,7 @@ > @@ -22,7 +22,7 @@ $args = array (1, 2); @@ -43,7 +43,7 @@ $args = [ ]; @@ -64,7 +64,7 @@ $args = [ 1, 2 ]; diff --git a/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml b/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml index c5094c5c..9afebbd6 100644 --- a/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml +++ b/NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml @@ -5,25 +5,25 @@ > no comma after the last array item. + For single-line arrays, there should be no comma after the last array item. - However, for multi-line arrays, there should be a comma after the last array item. + However, for multi-line arrays, there should be a comma after the last array item. ]]> - + - + , ); ]]> - + 'foo', @@ -31,7 +31,7 @@ $args = [ ]; ]]> - + 'foo', diff --git a/README.md b/README.md index a82fa893..df37064d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ PHPCSExtra @@ -46,7 +46,7 @@ Minimum Requirements ------------------------------------------- * PHP 5.4 or higher. -* [PHP_CodeSniffer][phpcs-gh] version **3.8.0** or higher. +* [PHP_CodeSniffer][phpcs-gh] version **3.12.1** or higher. * [PHPCSUtils][phpcsutils-gh] version **1.0.9** or higher. @@ -55,7 +55,7 @@ Installation Installing via Composer is highly recommended. -[Composer](http://getcomposer.org/) will automatically install the project dependencies and register the rulesets from PHPCSExtra and other external standards with PHP_CodeSniffer using the [Composer PHPCS plugin][composer-installer-gh]. +[Composer](https://getcomposer.org/) will automatically install the project dependencies and register the rulesets from PHPCSExtra and other external standards with PHP_CodeSniffer using the [Composer PHPCS plugin][composer-installer-gh]. ### Composer Project-based Installation @@ -403,14 +403,22 @@ This is considered a **_risky_ fixer**. #### `Universal.Operators.TypeSeparatorSpacing` :wrench: :bar_chart: :books: -Enforce no spaces around the union type and intersection type operators. +Enforce spacing rules around the union, intersection and DNF type operators. +* No space on either side of a union or intersection type operator. +* No space on the inside of DNF type parenthesis or before/after if the previous/next "thing" is part of the type. +* One space before a DNF open parenthesis when it is at the start of a type. +* One space after a DNF close parenthesis when it is at the end of a type. -The available error codes are: `UnionTypeSpacesBefore`, `UnionTypeSpacesAfter`, `IntersectionTypeSpacesBefore`, `IntersectionTypeSpacesAfter`. +The available error codes are: `UnionTypeSpacesBefore`, `UnionTypeSpacesAfter`, `IntersectionTypeSpacesBefore`, `IntersectionTypeSpacesAfter`, `DNFOpenSpacesBefore`, `DNFOpenSpacesAfter`, `DNFCloseTypeSpacesBefore`, `DNFCloseTypeSpacesAfter`. #### `Universal.PHP.LowercasePHPTag` :wrench: :bar_chart: :books: Enforces that the "PHP" in a PHP open tag is lowercase. +#### `Universal.PHP.NoFQNTrueFalseNull` :wrench: :books: + +Forbids using `true`, `false` and `null` as fully qualified constants. + #### `Universal.PHP.OneStatementInShortEchoTag` :wrench: :books: Disallow short open echo tags ` diff --git a/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml b/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml index f7efdda1..f66b235a 100644 --- a/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml +++ b/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml @@ -5,7 +5,7 @@ > diff --git a/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml b/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml index 79897c19..5f2dbda5 100644 --- a/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml +++ b/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml @@ -5,7 +5,7 @@ > diff --git a/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml b/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml index b44b4777..35b76785 100644 --- a/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml +++ b/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml @@ -9,14 +9,14 @@ ]]> - + - + : int {} @@ -31,7 +31,7 @@ class Foo { ]]> - + - + - + $v ) {} ]]> - + $k ) {} ]]> diff --git a/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml b/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml index 953149b7..972a304b 100644 --- a/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml +++ b/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml @@ -9,14 +9,14 @@ ]]> - + ! $b; if((bool) callMe($a)) {} ]]> - + ! ! $b; diff --git a/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml b/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml index 914eb65d..08812140 100644 --- a/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml +++ b/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml @@ -9,13 +9,13 @@ ]]> - + printf('text %s text', $var); echo callMe('text %s text', $var); ]]> - + echo sprintf('text %s text', $var); echo vsprintf('text %s text', [$var]); diff --git a/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml b/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml index 7b61c8fa..6d34f128 100644 --- a/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml +++ b/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml @@ -9,12 +9,12 @@ ]]> - + - + MyClass::CLASS; ]]> diff --git a/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml b/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml index 2a9583fa..e89dceff 100644 --- a/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml +++ b/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml @@ -9,13 +9,13 @@ ]]> - + __LINE__; include __DIR__ . '/file.php'; ]]> - + __NameSpace__; include dirname(__file__) . '/file.php'; diff --git a/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml b/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml index 4be7e6df..816e812f 100644 --- a/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml +++ b/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml @@ -9,7 +9,7 @@ ]]> - + { $var = 1; @@ -20,7 +20,7 @@ while (++$i < 10) { } ]]> - + : $var = 1; diff --git a/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml b/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml index 88d09228..8acb7bfa 100644 --- a/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml +++ b/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml @@ -11,7 +11,7 @@ ]]> - + - + - + - + diff --git a/Universal/Docs/Operators/ConcatPositionStandard.xml b/Universal/Docs/Operators/ConcatPositionStandard.xml index 4d2761af..4936f04a 100644 --- a/Universal/Docs/Operators/ConcatPositionStandard.xml +++ b/Universal/Docs/Operators/ConcatPositionStandard.xml @@ -13,14 +13,14 @@ ]]> - + . $b . 'text' . $c; ]]> - + . $b . 'text' diff --git a/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml b/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml index 3d3c328e..58a3b561 100644 --- a/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml +++ b/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml @@ -5,25 +5,31 @@ > - + |string $paramA, - TypeA&TypeB $paramB + TypeA&TypeB $paramB, + (TypeA&TypeB)|null $paramC ): int|false {} ]]> - + | string $paramA, - TypeA & TypeB $paramB + TypeA & TypeB $paramB, + ( TypeA&TypeB ) |null $paramC ): int | false {} diff --git a/Universal/Docs/PHP/NoFQNTrueFalseNullStandard.xml b/Universal/Docs/PHP/NoFQNTrueFalseNullStandard.xml new file mode 100644 index 00000000..205b4661 --- /dev/null +++ b/Universal/Docs/PHP/NoFQNTrueFalseNullStandard.xml @@ -0,0 +1,27 @@ + + + + + + + + null; + +if ($something === false) {} + ]]> + + + \null; + +if ($something === \false) {} + ]]> + + + diff --git a/Universal/Docs/UseStatements/DisallowUseConstStandard.xml b/Universal/Docs/UseStatements/DisallowUseConstStandard.xml index 0dbec70b..b4340954 100644 --- a/Universal/Docs/UseStatements/DisallowUseConstStandard.xml +++ b/Universal/Docs/UseStatements/DisallowUseConstStandard.xml @@ -15,7 +15,7 @@ use Vendor\Sub\ClassName; use function Vendor\Sub\functionName; ]]> - + - + - + [space][space][space][space]$foo = 'bar'; @@ -17,7 +17,7 @@ [tab]$foo = 'bar'; ]]> - + [space][space]$foo = 'bar'; diff --git a/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php b/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php index 7097fad7..1fe6841a 100644 --- a/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php +++ b/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php @@ -80,9 +80,6 @@ final class DuplicateArrayKeySniff extends AbstractArrayDeclarationSniff /** * Process every part of the array declaration. * - * This contains the default logic for the sniff, but can be overloaded in a concrete child class - * if needed. - * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the diff --git a/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php b/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php index 6f78cb66..147d46e7 100644 --- a/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php +++ b/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php @@ -166,7 +166,7 @@ public function process(File $phpcsFile, $stackPtr) $current = $tokens[$stackPtr]['scope_opener']; $end = $tokens[$stackPtr]['scope_closer']; - // Not searching for arrow functions as those have an implicit return, so no + // Not searching for arrow functions as those have an implicit return, so won't use the `return` keyword. $search = Collections::functionDeclarationTokens(); $search[\T_RETURN] = \T_RETURN; diff --git a/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php b/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php index 58c81ae1..f29a1fea 100644 --- a/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php +++ b/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php @@ -121,7 +121,7 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$nextAfter]['code'] === \T_SEMICOLON) { $innerScopeCloser = $nextAfter; } else { - // Missing semi-colon. Report, but don't auto-fix. + // Missing semicolon. Report, but don't auto-fix. $autoFixable = false; } } else { diff --git a/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php b/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php index c274e751..9cf191e0 100644 --- a/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php +++ b/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php @@ -81,8 +81,7 @@ public function register() * @param int $stackPtr The position of the current token * in the stack passed in $tokens. * - * @return int|void Integer stack pointer to skip forward or void to continue - * normal file processing. + * @return int Integer stack pointer to skip forward. */ public function process(File $phpcsFile, $stackPtr) { @@ -185,6 +184,6 @@ public function process(File $phpcsFile, $stackPtr) } // Ignore the rest of the file. - return ($phpcsFile->numTokens + 1); + return $phpcsFile->numTokens; } } diff --git a/Universal/Sniffs/Operators/ConcatPositionSniff.php b/Universal/Sniffs/Operators/ConcatPositionSniff.php index 093785ad..1a035766 100644 --- a/Universal/Sniffs/Operators/ConcatPositionSniff.php +++ b/Universal/Sniffs/Operators/ConcatPositionSniff.php @@ -96,8 +96,7 @@ public function register() * @param int $stackPtr The position of the current token * in the stack passed in $tokens. * - * @return int|void Integer stack pointer to skip forward or void to continue - * normal file processing. + * @return void */ public function process(File $phpcsFile, $stackPtr) { diff --git a/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php b/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php index 440ecc86..34d9c476 100644 --- a/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php +++ b/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php @@ -14,15 +14,31 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\Fixers\SpacesFixer; +use PHPCSUtils\Tokens\Collections; /** - * Enforce no space around union type and intersection type separators. + * Enforce spacing rules around union, intersection and DNF type separators. * * @since 1.0.0 + * @since 1.3.0 Support for DNF types. */ final class TypeSeparatorSpacingSniff implements Sniff { + /** + * Tokens this sniff targets. + * + * @since 1.3.0 + * + * @var array + */ + private $targetTokens = [ + \T_TYPE_UNION => \T_TYPE_UNION, + \T_TYPE_INTERSECTION => \T_TYPE_INTERSECTION, + \T_TYPE_OPEN_PARENTHESIS => \T_TYPE_OPEN_PARENTHESIS, + \T_TYPE_CLOSE_PARENTHESIS => \T_TYPE_CLOSE_PARENTHESIS, + ]; + /** * Returns an array of tokens this test wants to listen for. * @@ -32,10 +48,7 @@ final class TypeSeparatorSpacingSniff implements Sniff */ public function register() { - return [ - \T_TYPE_UNION, - \T_TYPE_INTERSECTION, - ]; + return $this->targetTokens; } /** @@ -53,28 +66,78 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $type = ($tokens[$stackPtr]['code'] === \T_TYPE_UNION) ? 'union' : 'intersection'; - $code = \ucfirst($type) . 'Type'; + $type = 'union'; + $code = 'UnionType'; + if ($tokens[$stackPtr]['code'] === \T_TYPE_INTERSECTION) { + $type = 'intersection'; + $code = 'IntersectionType'; + } elseif ($tokens[$stackPtr]['code'] === \T_TYPE_OPEN_PARENTHESIS) { + $type = 'DNF parenthesis open'; + $code = 'DNFOpen'; + } elseif ($tokens[$stackPtr]['code'] === \T_TYPE_CLOSE_PARENTHESIS) { + $type = 'DNF parenthesis close'; + $code = 'DNFClose'; + } - $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); - SpacesFixer::checkAndFix( - $phpcsFile, - $stackPtr, - $prevNonEmpty, - 0, // Expected spaces. - 'Expected %s before the ' . $type . ' type separator. Found: %s', - $code . 'SpacesBefore', - 'error', - 0, // Severity. - 'Space before ' . $type . ' type separator' - ); + $expectedSpaces = 0; + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($tokens[$stackPtr]['code'] === \T_TYPE_OPEN_PARENTHESIS) { + if ($tokens[$prevNonEmpty]['code'] === \T_COLON + || $tokens[$prevNonEmpty]['code'] === \T_CONST + || isset(Collections::propertyModifierKeywords()[$tokens[$prevNonEmpty]['code']]) === true + ) { + // Start of return type or property/const type. Always demand 1 space. + $expectedSpaces = 1; + } + + if ($tokens[$prevNonEmpty]['code'] === \T_OPEN_PARENTHESIS + || $tokens[$prevNonEmpty]['code'] === \T_COMMA + ) { + // Start of parameter type. Allow new line/indent before. + if ($tokens[$prevNonEmpty]['line'] === $tokens[$stackPtr]['line']) { + $expectedSpaces = 1; + } else { + $expectedSpaces = 'skip'; + } + } + } + + if (isset($this->targetTokens[$tokens[$prevNonEmpty]['code']]) === true) { + // Prevent duplicate errors when there are two adjacent operators. + $expectedSpaces = 'skip'; + } + + if ($expectedSpaces !== 'skip') { + SpacesFixer::checkAndFix( + $phpcsFile, + $stackPtr, + $prevNonEmpty, + $expectedSpaces, + 'Expected %s before the ' . $type . ' type separator. Found: %s', + $code . 'SpacesBefore', + 'error', + 0, // Severity. + 'Space before ' . $type . ' type separator' + ); + } + + $expectedSpaces = 0; + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($tokens[$stackPtr]['code'] === \T_TYPE_CLOSE_PARENTHESIS) { + if ($tokens[$nextNonEmpty]['code'] === \T_OPEN_CURLY_BRACKET + || $tokens[$nextNonEmpty]['code'] === \T_VARIABLE + || $tokens[$nextNonEmpty]['code'] === \T_STRING + ) { + // End of return type, parameter or property/const type. Always demand 1 space. + $expectedSpaces = 1; + } + } - $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); SpacesFixer::checkAndFix( $phpcsFile, $stackPtr, $nextNonEmpty, - 0, // Expected spaces. + $expectedSpaces, 'Expected %s after the ' . $type . ' type separator. Found: %s', $code . 'SpacesAfter', 'error', diff --git a/Universal/Sniffs/PHP/NoFQNTrueFalseNullSniff.php b/Universal/Sniffs/PHP/NoFQNTrueFalseNullSniff.php new file mode 100644 index 00000000..6aa3b188 --- /dev/null +++ b/Universal/Sniffs/PHP/NoFQNTrueFalseNullSniff.php @@ -0,0 +1,92 @@ + + */ + public function register() + { + return [ + // PHP < 8.0. + \T_TRUE, + \T_FALSE, + \T_NULL, + + // PHP >= 8.0. + \T_STRING, + ]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @since 1.3.0 + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $content = $tokens[$stackPtr]['content']; + $contentLC = \strtolower($content); + + if ($contentLC !== 'true' && $contentLC !== 'false' && $contentLC !== 'null') { + return; + } + + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($tokens[$prev]['code'] !== \T_NS_SEPARATOR) { + return; + } + + $prevPrev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true); + if ($tokens[$prevPrev]['code'] === \T_STRING || $tokens[$prevPrev]['code'] === \T_NAMESPACE) { + return; + } + + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($tokens[$next]['code'] === \T_NS_SEPARATOR) { + return; + } + + $fix = $phpcsFile->addFixableError( + 'The special PHP constant "%s" should not be fully qualified.', + $prev, + 'Found', + [$contentLC] + ); + + if ($fix === true) { + $phpcsFile->fixer->replaceToken($prev, ''); + } + } +} diff --git a/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php b/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php index cbccb4c6..d68c38ee 100644 --- a/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php +++ b/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php @@ -75,7 +75,7 @@ public function process(File $phpcsFile, $stackPtr) return; } - // Semi-colon, so check for any code between it and the close tag. + // Semicolon, so check for any code between it and the close tag. $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($endOfStatement + 1), null, true); if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] === \T_CLOSE_TAG diff --git a/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php b/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php index 817a44e1..c94934f1 100644 --- a/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php +++ b/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php @@ -63,6 +63,9 @@ final class DisallowInlineTabsSniff implements Sniff \T_DOC_COMMENT_WHITESPACE => true, \T_DOC_COMMENT_STRING => true, \T_COMMENT => true, + \T_START_HEREDOC => true, + \T_START_NOWDOC => true, + \T_YIELD_FROM => true, ]; /** @@ -102,7 +105,7 @@ public function process(File $phpcsFile, $stackPtr) $dummy = new DummyTokenizer('', $phpcsFile->config); for ($i = 0; $i < $phpcsFile->numTokens; $i++) { - // Skip all non-whitespace tokens and skip whitespace at the start of a new line. + // Skip all non-target tokens and skip whitespace at the start of a new line. if (isset($this->find[$tokens[$i]['code']]) === false || (($tokens[$i]['code'] === \T_WHITESPACE || $tokens[$i]['code'] === \T_DOC_COMMENT_WHITESPACE) diff --git a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc index 530a14a5..ae6d1916 100644 --- a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc +++ b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc @@ -36,6 +36,38 @@ $closure = function (TypeA | TypeB $p, TypeA &namespace\TypeB $q): \ $arrow = fn (TypeA | TypeB $p, TypeA & namespace\TypeB $q): string | int => $p * $q; +/* + * PHP 8.2 DNF types. + */ +class DNFTypes { + private const (A&B)|false DNF_CORRECT_START = false; + protected const false|(A&B) DNF_CORRECT_END = false; + public const ( A&B ) |false DNF_INCORRECT_START = false; + final const false| ( A&B ) DNF_INCORRECT_END = false; + + private (A&B)|false $dnfCorrectStart = false; + readonly false|(A&B) $dnfCorrectEnd = false; + static ( A&B ) |false $dnfIncorrectStart = false; + final false| ( A&B ) $dnfIncorrectEnd = false; + + public function DNFCorrect( + (A&B)|false $paramA, (A&B)|false $paramB, + null|(\FQN&\Partially\Qualified) $paramC, + ): (A&B)|(C&D) { + } + + public function DNFIncorrect( + ( A&B ) | false $paramA, (A&B) |false $paramB, + null| ( \FQN&\Partially\Qualified ) + $paramC, + ): ( A &B ) + | + ( C&D ) { + } + + public function DNFIncorrectReturnTypeNoSpace():(A&B)|(C&D){} +} + // Live coding. // This test should be the last test in the file. function foo(int| diff --git a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc.fixed b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc.fixed index a1ec0ebb..a86fcc82 100644 --- a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc.fixed +++ b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc.fixed @@ -32,6 +32,35 @@ $closure = function (TypeA|TypeB $p, TypeA&namespace\TypeB $q): \Fully\Qualified $arrow = fn (TypeA|TypeB $p, TypeA&namespace\TypeB $q): string|int => $p * $q; +/* + * PHP 8.2 DNF types. + */ +class DNFTypes { + private const (A&B)|false DNF_CORRECT_START = false; + protected const false|(A&B) DNF_CORRECT_END = false; + public const (A&B)|false DNF_INCORRECT_START = false; + final const false|(A&B) DNF_INCORRECT_END = false; + + private (A&B)|false $dnfCorrectStart = false; + readonly false|(A&B) $dnfCorrectEnd = false; + static (A&B)|false $dnfIncorrectStart = false; + final false|(A&B) $dnfIncorrectEnd = false; + + public function DNFCorrect( + (A&B)|false $paramA, (A&B)|false $paramB, + null|(\FQN&\Partially\Qualified) $paramC, + ): (A&B)|(C&D) { + } + + public function DNFIncorrect( + (A&B)|false $paramA, (A&B)|false $paramB, + null|(\FQN&\Partially\Qualified) $paramC, + ): (A&B)|(C&D) { + } + + public function DNFIncorrectReturnTypeNoSpace(): (A&B)|(C&D) {} +} + // Live coding. // This test should be the last test in the file. function foo(int| diff --git a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.php b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.php index 76c2ad7d..e42fb6aa 100644 --- a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.php +++ b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.php @@ -38,6 +38,16 @@ public function getErrorList() 33 => 1, 35 => 5, 37 => 6, + 45 => 4, + 46 => 4, + 50 => 4, + 51 => 4, + 60 => 6, + 61 => 4, + 63 => 5, + 64 => 1, + 65 => 3, + 68 => 2, ]; } diff --git a/Universal/Tests/PHP/NoFQNTrueFalseNullUnitTest.inc b/Universal/Tests/PHP/NoFQNTrueFalseNullUnitTest.inc new file mode 100644 index 00000000..a537a293 --- /dev/null +++ b/Universal/Tests/PHP/NoFQNTrueFalseNullUnitTest.inc @@ -0,0 +1,27 @@ + Key is the line number, value is the number of expected errors. + */ + public function getErrorList() + { + return [ + 13 => 1, + 14 => 1, + 15 => 1, + 17 => 1, + 18 => 1, + 19 => 1, + 22 => 1, + 24 => 2, + 26 => 2, + ]; + } + + /** + * Returns the lines where warnings should occur. + * + * @return array Key is the line number, value is the number of expected warnings. + */ + public function getWarningList() + { + return []; + } +} diff --git a/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc b/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc index 162e98b3..a12f3abb 100644 --- a/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc +++ b/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc @@ -5,7 +5,5 @@ * For PHP < 8.0, it will correctly flag this as "no space" after the `use` keyword. * For PHP 8.0+, this will no longer be flagged as keywords are allowed in namespaced names, * so the `use` is tokenized as `T_STRING` instead of `T_USE` and won't be flagged. - * - * For this reason, there is no "fixed" file included as the test would not be able to pass on PHP 8.0+. */ use\Util\MyOtherClass; diff --git a/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc.fixed b/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc.fixed new file mode 100644 index 00000000..e15065e9 --- /dev/null +++ b/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc.fixed @@ -0,0 +1,9 @@ + + */ + protected function getTestFiles($testFileBase) + { + $testFiles = parent::getTestFiles($testFileBase); + + if (\PHP_VERSION_ID < 80000) { + return $testFiles; + } + + // The issue being tested in the "2" test case file cannot be flagged/fixed on PHP 8.0+. + $target = 'KeywordSpacingUnitTest.2.inc'; + $length = \strlen($target); + foreach ($testFiles as $i => $fileName) { + if (\substr($fileName, -$length) === $target) { + unset($testFiles[$i]); + break; + } + } + + return $testFiles; + } + /** * Returns the lines where errors should occur. * @@ -55,12 +83,8 @@ public function getErrorList($testFile = '') ]; case 'KeywordSpacingUnitTest.2.inc': - if (\PHP_VERSION_ID >= 80000) { - return []; - } - return [ - 11 => 1, + 9 => 1, ]; default: diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc index 0d065c08..f50b2102 100644 --- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc +++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc @@ -59,3 +59,24 @@ $aaaaaaaa = true; // Tab indented, no tabs. // phpcs:ignore Stnd.Cat.SniffName -- testing mixed comment + annotations don't trigger on indentation. // Tab indented, no tabs. + +$a = <<< TAB_BETWEEN +text +TAB_BETWEEN; + +$a = <<< 'TABS_BETWEEN' +text +TABS_BETWEEN; + +function myGenerator() { + yield from tabsBetweenShouldBeFixed(); + +yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1(); + + yield + from tabIndentationShouldBeIgnored(); + + yield + /*comment*/ + from tabIndentationShouldBeIgnored(); +} diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc.fixed b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc.fixed index 63726813..37c19527 100644 --- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc.fixed +++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc.fixed @@ -59,3 +59,24 @@ $aaaaaaaa = true; // Tab indented, no tabs. // phpcs:ignore Stnd.Cat.SniffName -- testing mixed comment + annotations don't trigger on indentation. // Tab indented, no tabs. + +$a = <<< TAB_BETWEEN +text +TAB_BETWEEN; + +$a = <<< 'TABS_BETWEEN' +text +TABS_BETWEEN; + +function myGenerator() { + yield from tabsBetweenShouldBeFixed(); + +yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1(); + + yield + from tabIndentationShouldBeIgnored(); + + yield + /*comment*/ + from tabIndentationShouldBeIgnored(); +} diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc index 7227c96c..d79d8d3f 100644 --- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc +++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc @@ -33,3 +33,17 @@ $aaaaaaaa = true; // Tab indented and inline tabs. // Tab indented and inline tabs. + +$a = <<< TAB_BETWEEN +text +TAB_BETWEEN; + +$a = <<< 'TABS_BETWEEN' +text +TABS_BETWEEN; + +function myGenerator() { + yield from tabsBetweenShouldBeFixed(); + +yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1(); +} diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc.fixed b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc.fixed index 982e48ea..715f13e0 100644 --- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc.fixed +++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc.fixed @@ -33,3 +33,17 @@ $aaaaaaaa = true; // Tab indented and inline tabs. // Tab indented and inline tabs. + +$a = <<< TAB_BETWEEN +text +TAB_BETWEEN; + +$a = <<< 'TABS_BETWEEN' +text +TABS_BETWEEN; + +function myGenerator() { + yield from tabsBetweenShouldBeFixed(); + +yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1(); +} diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc index 139b27fd..1904a508 100644 --- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc +++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc @@ -33,3 +33,17 @@ $aaaaaaaa = true; // Tab indented and inline tabs. // Tab indented and inline tabs. + +$a = <<< TAB_BETWEEN +text +TAB_BETWEEN; + +$a = <<< 'TABS_BETWEEN' +text +TABS_BETWEEN; + +function myGenerator() { + yield from tabsBetweenShouldBeFixed(); + +yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1(); +} diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc.fixed b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc.fixed index ce001601..e2bd84e0 100644 --- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc.fixed +++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc.fixed @@ -33,3 +33,17 @@ $aaaaaaaa = true; // Tab indented and inline tabs. // Tab indented and inline tabs. + +$a = <<< TAB_BETWEEN +text +TAB_BETWEEN; + +$a = <<< 'TABS_BETWEEN' +text +TABS_BETWEEN; + +function myGenerator() { + yield from tabsBetweenShouldBeFixed(); + +yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1(); +} diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.6.inc b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.6.inc index 05b55208..9dcecd1b 100644 --- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.6.inc +++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.6.inc @@ -41,3 +41,24 @@ $aaaaaaaa = true; * @param int $var Description. * @param string $string Another description. */ + +$a = <<< TAB_BETWEEN +text +TAB_BETWEEN; + +$a = <<< 'TABS_BETWEEN' +text +TABS_BETWEEN; + +function myGenerator() { + yield from tabsBetweenShouldBeFixed(); + + yield /*comment*/ from tabsBetweenShouldBeFixed(); + + yield + from tabIndentationShouldBeIgnored(); + + yield + /*comment*/ + from tabIndentationShouldBeIgnored(); +} diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php index 147e3361..4de61162 100644 --- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php +++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php @@ -10,6 +10,7 @@ namespace PHPCSExtra\Universal\Tests\WhiteSpace; +use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; /** @@ -73,6 +74,12 @@ public function setCliValues($testFile, $config) */ public function getErrorList($testFile = '') { + // As of PHP 8.3, comments may be tokenized within a "yield from" token, but only for PHPCS < 3.11.0. + $commentsInYieldFrom = false; + if (\PHP_VERSION_ID >= 80300 && \version_compare(Config::VERSION, '3.11.0', '<')) { + $commentsInYieldFrom = true; + } + switch ($testFile) { case 'DisallowInlineTabsUnitTest.1.inc': return [ @@ -93,6 +100,10 @@ public function getErrorList($testFile = '') 49 => 1, 52 => 1, 53 => 1, + 63 => 1, + 67 => 1, + 72 => 1, + 74 => ($commentsInYieldFrom === true ? 1 : 2), ]; case 'DisallowInlineTabsUnitTest.2.inc': @@ -126,6 +137,10 @@ public function getErrorList($testFile = '') 31 => 1, 34 => 1, 35 => 1, + 37 => 1, + 41 => 1, + 46 => 1, + 48 => ($commentsInYieldFrom === true ? 1 : 2), ]; case 'DisallowInlineTabsUnitTest.5.inc': @@ -145,6 +160,10 @@ public function getErrorList($testFile = '') 31 => 1, 34 => 1, 35 => 1, + 37 => 1, + 41 => 1, + 46 => 1, + 48 => ($commentsInYieldFrom === true ? 1 : 2), ]; default: diff --git a/composer.json b/composer.json index 92122571..b565df6a 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ }, "require" : { "php" : ">=5.4", - "squizlabs/php_codesniffer" : "^3.8.0", + "squizlabs/php_codesniffer" : "^3.12.1", "phpcsstandards/phpcsutils" : "^1.0.9" }, "require-dev" : { @@ -64,6 +64,7 @@ "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true - } + }, + "lock": false } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 060dda93..95991de0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -16,36 +16,41 @@ parameters: # Keep to stay in line with parent class. - message: '`^Constructor of class PHPCSExtra\\Universal\\Helpers\\DummyTokenizer has an unused parameter \$content\.$`' - path: Universal\Helpers\DummyTokenizer.php + path: Universal/Helpers/DummyTokenizer.php count: 1 # Level 4 # PHPStan doesn't seem to like uninitialized properties... + # Ref: https://github.com/phpstan/phpstan/issues/10305 - message: '`^Property \S+Sniff::\$(phpVersion|tabWidth) \(int\) in isset\(\) is not nullable\.$`' paths: - - Modernize\Sniffs\FunctionCalls\DirnameSniff.php - - Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php - - Universal\Sniffs\CodeAnalysis\ConstructorDestructorReturnSniff.php - - Universal\Sniffs\WhiteSpace\CommaSpacingSniff.php - - Universal\Sniffs\WhiteSpace\DisallowInlineTabsSniff.php - - Universal\Sniffs\WhiteSpace\PrecisionAlignmentSniff.php + - Modernize/Sniffs/FunctionCalls/DirnameSniff.php + - Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php + - Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php + - Universal/Sniffs/WhiteSpace/CommaSpacingSniff.php + - Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php + - Universal/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php - message: '`^Strict comparison using === between true and false will always evaluate to false\.$`' paths: - - Modernize\Sniffs\FunctionCalls\DirnameSniff.php - - Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php - - Universal\Sniffs\CodeAnalysis\ConstructorDestructorReturnSniff.php - - Universal\Sniffs\WhiteSpace\CommaSpacingSniff.php - - Universal\Sniffs\WhiteSpace\DisallowInlineTabsSniff.php - - Universal\Sniffs\WhiteSpace\PrecisionAlignmentSniff.php + - Modernize/Sniffs/FunctionCalls/DirnameSniff.php + - Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php + - Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php + - Universal/Sniffs/WhiteSpace/CommaSpacingSniff.php + - Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php + - Universal/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php - message: '`^Property PHPCSExtra\\Universal\\Sniffs\\Arrays\\DuplicateArrayKeySniff\:\:\$currentMaxIntKey[GL]t8 \(int\) in isset\(\) is not nullable\.$`' - path: Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php + path: Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php count: 5 + - + message: '`^Strict comparison using === between true and true will always evaluate to true\.$`' + path: Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php + count: 1 - message: '`^Result of && is always false\.$`' - path: Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php + path: Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php count: 1 # Level 5