diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
index 0d33a0d8ca..65e9a6427b 100644
--- a/.github/workflows/phpstan.yml
+++ b/.github/workflows/phpstan.yml
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install PHP
uses: shivammathur/setup-php@v2
@@ -33,7 +33,7 @@ jobs:
# Dependencies need to be installed to make sure the PHPUnit classes are recognized.
# @link https://github.com/marketplace/actions/install-composer-dependencies
- name: Install Composer dependencies
- uses: "ramsey/composer-install@v1"
+ uses: "ramsey/composer-install@v2"
- name: Run PHPStan
run: phpstan analyse --configuration=phpstan.neon
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4b6736d980..f9366e074f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,8 +13,60 @@ on:
workflow_dispatch:
jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
+
+ name: "Build Phar on PHP: ${{ matrix.php }}"
+
+ continue-on-error: ${{ matrix.php == '8.2' }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+ ini-values: phar.readonly=Off, error_reporting=-1, display_errors=On
+
+ - name: Build the phar
+ run: php scripts/build-phar.php
+
+ - name: Upload the PHPCS phar
+ uses: actions/upload-artifact@v3
+ if: ${{ success() && matrix.php == '8.0' }}
+ with:
+ name: phpcs-phar
+ path: ./phpcs.phar
+ if-no-files-found: error
+ retention-days: 28
+
+ - name: Upload the PHPCBF phar
+ uses: actions/upload-artifact@v3
+ if: ${{ success() && matrix.php == '8.0' }}
+ with:
+ name: phpcbf-phar
+ path: ./phpcbf.phar
+ if-no-files-found: error
+ retention-days: 28
+
+ # Both the below only check a few files which are rarely changed and therefore unlikely to have issues.
+ # This test is about testing that the phars are functional, *not* about whether the code style complies.
+ - name: 'PHPCS: check code style using the Phar file to test the Phar is functional'
+ run: php phpcs.phar ./scripts
+
+ - name: 'PHPCBF: fix code style using the Phar file to test the Phar is functional'
+ run: php phpcbf.phar ./scripts
+
test:
runs-on: ubuntu-latest
+ needs: build
strategy:
# Keys:
@@ -37,7 +89,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Setup ini config
id: set_ini
@@ -45,11 +97,11 @@ jobs:
# Set the "short_open_tag" ini to make sure specific conditions are tested.
# Also turn on error_reporting to ensure all notices are shown.
if [[ ${{ matrix.custom_ini }} == true && "${{ matrix.php }}" == '5.5' ]]; then
- echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=-1, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On, asp_tags=On'
+ echo 'PHP_INI=error_reporting=-1, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On, asp_tags=On' >> $GITHUB_OUTPUT
elif [[ ${{ matrix.custom_ini }} == true && "${{ matrix.php }}" == '7.0' ]]; then
- echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=-1, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On'
+ echo 'PHP_INI=error_reporting=-1, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On' >> $GITHUB_OUTPUT
else
- echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=-1, display_errors=On'
+ echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT
fi
- name: Install PHP
@@ -64,12 +116,12 @@ jobs:
# @link https://github.com/marketplace/actions/install-composer-dependencies
- name: Install Composer dependencies - normal
if: ${{ matrix.php < '8.0' }}
- uses: "ramsey/composer-install@v1"
+ uses: "ramsey/composer-install@v2"
# For PHP 8.0+, we need to install with ignore platform reqs as PHPUnit 7 is still used.
- name: Install Composer dependencies - with ignore platform
if: ${{ matrix.php >= '8.0' }}
- uses: "ramsey/composer-install@v1"
+ uses: "ramsey/composer-install@v2"
with:
composer-options: --ignore-platform-reqs
@@ -106,10 +158,12 @@ jobs:
if: ${{ matrix.custom_ini == false }}
run: composer validate --no-check-all --strict
- - name: Build the phar
- if: ${{ matrix.custom_ini == false }}
- run: php scripts/build-phar.php
+ - name: Download the PHPCS phar
+ uses: actions/download-artifact@v3
+ with:
+ name: phpcs-phar
+ # This test specifically tests that the Phar which will be released works correctly on all PHP versions.
- name: 'PHPCS: check code style using the Phar file'
if: ${{ matrix.custom_ini == false }}
run: php phpcs.phar
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 9fb5685d32..887b9c2b7b 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -22,10 +22,12 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install xmllint
- run: sudo apt-get install --no-install-recommends -y libxml2-utils
+ run: |
+ sudo apt-get update
+ sudo apt-get install --no-install-recommends -y libxml2-utils
- name: Retrieve XML Schema
run: curl -O https://www.w3.org/2012/04/XMLSchema.xsd
@@ -63,7 +65,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install PHP
uses: shivammathur/setup-php@v2
diff --git a/CodeSniffer.conf.dist b/CodeSniffer.conf.dist
index 62dc395cf8..f95058faa7 100644
--- a/CodeSniffer.conf.dist
+++ b/CodeSniffer.conf.dist
@@ -5,5 +5,5 @@
'show_warnings' => '0',
'show_progress' => '1',
'report_width' => '120',
-)
+);
?>
diff --git a/README.md b/README.md
index c0ea8e391a..859de432c6 100644
--- a/README.md
+++ b/README.md
@@ -33,9 +33,9 @@ php phpcbf.phar -h
### Composer
If you use Composer, you can install PHP_CodeSniffer system-wide with the following command:
-
- composer global require "squizlabs/php_codesniffer=*"
-
+```bash
+composer global require "squizlabs/php_codesniffer=*"
+```
Make sure you have the composer bin dir in your PATH. The default value is `~/.composer/vendor/bin/`, but you can check the value that you need to use by running `composer global config bin-dir --absolute`.
Or alternatively, include a dependency for `squizlabs/php_codesniffer` in your `composer.json` file. For example:
@@ -49,47 +49,48 @@ Or alternatively, include a dependency for `squizlabs/php_codesniffer` in your `
```
You will then be able to run PHP_CodeSniffer from the vendor bin directory:
-
- ./vendor/bin/phpcs -h
- ./vendor/bin/phpcbf -h
-
+```bash
+./vendor/bin/phpcs -h
+./vendor/bin/phpcbf -h
+```
### Phive
If you use Phive, you can install PHP_CodeSniffer as a project tool using the following commands:
-
- phive install phpcs
- phive install phpcbf
-
+```bash
+phive install phpcs
+phive install phpcbf
+```
You will then be able to run PHP_CodeSniffer from the tools directory:
-
- ./tools/phpcs -h
- ./tools/phpcbf -h
-
+```bash
+./tools/phpcs -h
+./tools/phpcbf -h
+```
### PEAR
If you use PEAR, you can install PHP_CodeSniffer using the PEAR installer. This will make the `phpcs` and `phpcbf` commands immediately available for use. To install PHP_CodeSniffer using the PEAR installer, first ensure you have [installed PEAR](http://pear.php.net/manual/en/installation.getting.php) and then run the following command:
-
- pear install PHP_CodeSniffer
-
+```bash
+pear install PHP_CodeSniffer
+```
### Git Clone
You can also download the PHP_CodeSniffer source and run the `phpcs` and `phpcbf` commands directly from the Git clone:
-
- git clone https://github.com/squizlabs/PHP_CodeSniffer.git
- cd PHP_CodeSniffer
- php bin/phpcs -h
- php bin/phpcbf -h
-
+```bash
+git clone https://github.com/squizlabs/PHP_CodeSniffer.git
+cd PHP_CodeSniffer
+php bin/phpcs -h
+php bin/phpcbf -h
+```
## Getting Started
The default coding standard used by PHP_CodeSniffer is the PEAR coding standard. To check a file against the PEAR coding standard, simply specify the file's location:
-
- $ phpcs /path/to/code/myfile.php
-
+```bash
+phpcs /path/to/code/myfile.php
+```
Or if you wish to check an entire directory you can specify the directory location instead of a file.
-
- $ phpcs /path/to/code-directory
-
+```bash
+phpcs /path/to/code-directory
+```
If you wish to check your code against the PSR-12 coding standard, use the `--standard` command line argument:
-
- $ phpcs --standard=PSR12 /path/to/code-directory
+```bash
+phpcs --standard=PSR12 /path/to/code-directory
+```
If PHP_CodeSniffer finds any coding standard errors, a report will be shown after running the command.
diff --git a/composer.json b/composer.json
index 7605a5df91..37f41a0b80 100644
--- a/composer.json
+++ b/composer.json
@@ -4,7 +4,8 @@
"type": "library",
"keywords": [
"phpcs",
- "standards"
+ "standards",
+ "static analysis"
],
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"license": "BSD-3-Clause",
diff --git a/package.xml b/package.xml
index 4cfe75f2da..9338b46e79 100644
--- a/package.xml
+++ b/package.xml
@@ -14,11 +14,11 @@ http://pear.php.net/dtd/package-2.0.xsd">
gsherwood@squiz.net
yes
- 2021-12-13
-
+ 2022-06-18
+
- 3.6.2
- 3.6.2
+ 3.7.2
+ 3.7.2
stable
@@ -26,28 +26,35 @@ http://pear.php.net/dtd/package-2.0.xsd">
BSD 3-Clause License
- - Processing large code bases that use tab indenting inside comments and strings will now be faster
- -- Thanks to Thiemo Kreuz for the patch
- - Fixed bug #3388 : phpcs does not work when run from WSL drives
- -- Thanks to Juliette Reinders Folmer and Graham Wharton for the patch
- - Fixed bug #3422 : Squiz.WhiteSpace.ScopeClosingBrace fixer removes HTML content when fixing closing brace alignment
- -- Thanks to Juliette Reinders Folmer for the patch
- - Fixed bug #3437 : PSR12 does not forbid blank lines at the start of the class body
- -- Added new PSR12.Classes.OpeningBraceSpace sniff to enforce this
- - Fixed bug #3440 : Squiz.WhiteSpace.MemberVarSpacing false positives when attributes used without docblock
- -- Thanks to Vadim Borodavko for the patch
- - Fixed bug #3448 : PHP 8.1 deprecation notice while generating running time value
- -- Thanks to Juliette Reinders Folmer and Andy Postnikov for the patch
- - Fixed bug #3456 : PSR12.Classes.ClassInstantiation.MissingParentheses false positive using attributes on anonymous class
- -- Thanks to Juliette Reinders Folmer for the patch
- - Fixed bug #3460 : Generic.Formatting.MultipleStatementAlignment false positive on closure with parameters
- -- Thanks to Juliette Reinders Folmer for the patch
- - Fixed bug #3468 : do/while loops are double-counted in Generic.Metrics.CyclomaticComplexity
- -- Thanks to Mark Baker for the patch
- - Fixed bug #3469 : Ternary Operator and Null Coalescing Operator are not counted in Generic.Metrics.CyclomaticComplexity
- -- Thanks to Mark Baker for the patch
- - Fixed bug #3472 : PHP 8 match() expression is not counted in Generic.Metrics.CyclomaticComplexity
- -- Thanks to Mark Baker for the patch
+ - Newer versions of Composer will now suggest installing PHPCS using require-dev instead of require
+ -- Thanks to Gary Jones (@GaryJones) for the patch
+ - A custom Out Of Memory error will now be shown if PHPCS or PHPCBF run out of memory during a run
+ -- Error message provides actionable information about how to fix the problem and ensures the error is not silent
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) and Alain Schlesser (@schlessera) for the patch
+ - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Squiz.Formatting.OperatorBracket no longer reports false positives in match() structures
+ - Fixed bug #3616 : Squiz.PHP.DisallowComparisonAssignment false positive for PHP 8 match expression
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3618 : Generic.WhiteSpace.ArbitraryParenthesesSpacing false positive for return new parent()
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3632 : Short list not tokenized correctly in control structures without braces
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3639 : Tokenizer not applying tab replacement to heredoc/nowdoc closers
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3640 : Generic.WhiteSpace.DisallowTabIndent not reporting errors for PHP 7.3 flexible heredoc/nowdoc syntax
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3645 : PHPCS can show 0 exit code when running in parallel even if child process has fatal error
+ -- Thanks to Alex Panshin (@enl) for the patch
+ - Fixed bug #3653 : False positives for match() in OperatorSpacingSniff
+ -- Thanks to Jaroslav Hanslík (@kukulich) for the patch
+ - Fixed bug #3666 : PEAR.Functions.FunctionCallSignature incorrect indent fix when checking mixed HTML/PHP files
+ - Fixed bug #3668 : PSR12.Classes.ClassInstantiation.MissingParentheses false positive when instantiating parent classes
+ -- Similar issues also fixed in Generic.Functions.FunctionCallArgumentSpacing and Squiz.Formatting.OperatorBracket
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3672 : Incorrect ScopeIndent.IncorrectExact report for match inside array literal
+ - Fixed bug #3694 : Generic.WhiteSpace.SpreadOperatorSpacingAfter does not ignore spread operator in PHP 8.1 first class callables
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
@@ -135,6 +142,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
+
+
@@ -143,18 +154,30 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
+
+
+
+
+
+
+
+
+
+
@@ -163,6 +186,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
@@ -789,6 +814,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
@@ -984,6 +1011,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
@@ -1072,6 +1100,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
@@ -1623,6 +1652,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
@@ -1740,6 +1770,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
@@ -2090,6 +2121,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
+
+
@@ -2098,18 +2133,30 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2118,6 +2165,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
@@ -2180,6 +2229,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
+
+
@@ -2188,18 +2241,30 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2208,6 +2273,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
@@ -2217,6 +2284,134 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
+ 3.7.2
+ 3.7.2
+
+
+ stable
+ stable
+
+ 2023-02-23
+ BSD License
+
+ - Newer versions of Composer will now suggest installing PHPCS using require-dev instead of require
+ -- Thanks to Gary Jones (@GaryJones) for the patch
+ - A custom Out Of Memory error will now be shown if PHPCS or PHPCBF run out of memory during a run
+ -- Error message provides actionable information about how to fix the problem and ensures the error is not silent
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) and Alain Schlesser (@schlessera) for the patch
+ - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Squiz.Formatting.OperatorBracket no longer reports false positives in match() structures
+ - Fixed bug #3616 : Squiz.PHP.DisallowComparisonAssignment false positive for PHP 8 match expression
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3618 : Generic.WhiteSpace.ArbitraryParenthesesSpacing false positive for return new parent()
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3632 : Short list not tokenized correctly in control structures without braces
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3639 : Tokenizer not applying tab replacement to heredoc/nowdoc closers
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3640 : Generic.WhiteSpace.DisallowTabIndent not reporting errors for PHP 7.3 flexible heredoc/nowdoc syntax
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3645 : PHPCS can show 0 exit code when running in parallel even if child process has fatal error
+ -- Thanks to Alex Panshin (@enl) for the patch
+ - Fixed bug #3653 : False positives for match() in OperatorSpacingSniff
+ -- Thanks to Jaroslav Hanslík (@kukulich) for the patch
+ - Fixed bug #3666 : PEAR.Functions.FunctionCallSignature incorrect indent fix when checking mixed HTML/PHP files
+ - Fixed bug #3668 : PSR12.Classes.ClassInstantiation.MissingParentheses false positive when instantiating parent classes
+ -- Similar issues also fixed in Generic.Functions.FunctionCallArgumentSpacing and Squiz.Formatting.OperatorBracket
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+ - Fixed bug #3672 : Incorrect ScopeIndent.IncorrectExact report for match inside array literal
+ - Fixed bug #3694 : Generic.WhiteSpace.SpreadOperatorSpacingAfter does not ignore spread operator in PHP 8.1 first class callables
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+
+
+
+
+ 3.7.1
+ 3.7.1
+
+
+ stable
+ stable
+
+ 2022-06-18
+ BSD License
+
+ - Fixed bug #3609 : Methods/constants with name empty/isset/unset are always reported as error
+ -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch
+
+
+
+
+ 3.7.0
+ 3.7.0
+
+
+ stable
+ stable
+
+ 2022-06-13
+ BSD License
+
+ - Added support for PHP 8.1 explicit octal notation
+ -- This new syntax has been backfilled for PHP versions less than 8.1
+ -- Thanks to Mark Baker for the patch
+ -- Thanks to Juliette Reinders Folmer for additional fixes
+ - Added support for PHP 8.1 enums
+ -- This new syntax has been backfilled for PHP versions less than 8.1
+ -- Includes a new T_ENUM_CASE token to represent the case statements inside an enum
+ -- Thanks to Jaroslav Hanslík for the patch
+ -- Thanks to Juliette Reinders Folmer for additional core and sniff support
+ - Added support for the PHP 8.1 readonly token
+ -- Tokenzing of the readonly keyword has been backfilled for PHP versions less than 8.1
+ -- Thanks to Jaroslav Hanslík for the patch
+ - Added support for PHP 8.1 intersection types
+ -- Includes a new T_TYPE_INTERSECTION token to represent the ampersand character inside intersection types
+ -- Thanks to Jaroslav Hanslík for the patch
+ - File::getMethodParameters now supports the new PHP 8.1 readonly token
+ -- When constructor property promotion is used, a new property_readonly array index is included in the return value
+ --- This is a boolean value indicating if the property is readonly
+ -- If the readonly token is detected, a new readonly_token array index is included in the return value
+ --- This contains the token index of the readonly keyword
+ -- Thanks to Juliette Reinders Folmer for the patch
+ - Support for new PHP 8.1 readonly keyword has been added to the following sniffs:
+ -- Generic.PHP.LowerCaseKeyword
+ -- PSR2.Classes.PropertyDeclaration
+ -- Squiz.Commenting.BlockComment
+ -- Squiz.Commenting.DocCommentAlignment
+ -- Squiz.Commenting.VariableComment
+ -- Squiz.WhiteSpace.ScopeKeywordSpacing
+ -- Thanks to Juliette Reinders Folmer for the patches
+ - The parallel feature is now more efficient and runs faster in some situations due to improved process management
+ -- Thanks to Sergei Morozov for the patch
+ - The list of installed coding standards now has consistent ordering across all platforms
+ -- Thanks to Juliette Reinders Folmer for the patch
+ - Generic.PHP.UpperCaseConstant and Generic.PHP.LowerCaseConstant now ignore type declarations
+ -- These sniffs now only report errors for true/false/null when used as values
+ -- Thanks to Juliette Reinders Folmer for the patch
+ - Generic.PHP.LowerCaseType now supports the PHP 8.1 never type
+ -- Thanks to Jaroslav Hanslík for the patch
+ - Fixed bug #3502 : A match statement within an array produces Squiz.Arrays.ArrayDeclaration.NoKeySpecified
+ - Fixed bug #3503 : Squiz.Commenting.FunctionComment.ThrowsNoFullStop false positive when one line @throw
+ - Fixed bug #3505 : The nullsafe operator is not counted in Generic.Metrics.CyclomaticComplexity
+ -- Thanks to Mark Baker for the patch
+ - Fixed bug #3526 : PSR12.Properties.ConstantVisibility false positive when using public final const syntax
+ -- Thanks to Juliette Reinders Folmer for the patch
+ - Fixed bug #3530 : Line indented incorrectly false positive when using match-expression inside switch case
+ - Fixed bug #3534 : Name of typed enum tokenized as T_GOTO_LABEL
+ -- Thanks to Juliette Reinders Folmer for the patch
+ - Fixed bug #3546 : Tokenizer/PHP: bug fix - parent/static keywords in class instantiations
+ -- Thanks to Juliette Reinders Folmer for the patch
+ - Fixed bug #3550 : False positive from PSR2.ControlStructures.SwitchDeclaration.TerminatingComment when using trailing comment
+ -- Thanks to Juliette Reinders Folmer for the patch
+ - Fixed bug #3575: Squiz.Scope.MethodScope misses visibility keyword on previous line
+ -- Thanks to Juliette Reinders Folmer for the patch
+ - Fixed bug #3604: Tokenizer/PHP: bug fix for double quoted strings using ${
+ -- Thanks to Juliette Reinders Folmer for the patch
+
+
3.6.2
diff --git a/scripts/build-phar.php b/scripts/build-phar.php
index 6b2800c883..45e44bab2e 100644
--- a/scripts/build-phar.php
+++ b/scripts/build-phar.php
@@ -14,6 +14,12 @@
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
+use PHP_CodeSniffer\Config;
+use PHP_CodeSniffer\Exceptions\RuntimeException;
+use PHP_CodeSniffer\Exceptions\TokenizerException;
+use PHP_CodeSniffer\Tokenizers\PHP;
+use PHP_CodeSniffer\Util\Tokens;
+
error_reporting(E_ALL | E_STRICT);
if (ini_get('phar.readonly') === '1') {
@@ -21,6 +27,60 @@
exit(1);
}
+require_once dirname(__DIR__).'/autoload.php';
+require_once dirname(__DIR__).'/src/Util/Tokens.php';
+
+if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
+ define('PHP_CODESNIFFER_VERBOSITY', 0);
+}
+
+
+/**
+ * Replacement for the PHP native php_strip_whitespace() function,
+ * which doesn't handle attributes correctly for cross-version PHP.
+ *
+ * @param string $fullpath Path to file.
+ * @param \PHP_CodeSniffer\Config $config Perfunctory Config.
+ *
+ * @return string
+ *
+ * @throws \PHP_CodeSniffer\Exceptions\RuntimeException When tokenizer errors are encountered.
+ */
+function stripWhitespaceAndComments($fullpath, $config)
+{
+ $contents = file_get_contents($fullpath);
+
+ try {
+ $tokenizer = new PHP($contents, $config, "\n");
+ $tokens = $tokenizer->getTokens();
+ } catch (TokenizerException $e) {
+ throw new RuntimeException('Failed to tokenize file '.$fullpath);
+ }
+
+ $stripped = '';
+ foreach ($tokens as $token) {
+ if ($token['code'] === T_ATTRIBUTE_END || $token['code'] === T_OPEN_TAG) {
+ $stripped .= $token['content']."\n";
+ continue;
+ }
+
+ if (isset(Tokens::$emptyTokens[$token['code']]) === false) {
+ $stripped .= $token['content'];
+ continue;
+ }
+
+ if ($token['code'] === T_WHITESPACE) {
+ $stripped .= ' ';
+ }
+ }
+
+ return $stripped;
+
+}//end stripWhitespaceAndComments()
+
+
+$startTime = microtime(true);
+
$scripts = [
'phpcs',
'phpcbf',
@@ -51,6 +111,9 @@
$rdi = new \RecursiveDirectoryIterator($srcDir, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
$di = new \RecursiveIteratorIterator($rdi, 0, \RecursiveIteratorIterator::CATCH_GET_CHILD);
+ $config = new Config();
+ $fileCount = 0;
+
foreach ($di as $file) {
$filename = $file->getFilename();
@@ -60,22 +123,30 @@
}
$fullpath = $file->getPathname();
- if (strpos($fullpath, '/Tests/') !== false) {
+ if (strpos($fullpath, DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR) !== false) {
continue;
}
$path = 'src'.substr($fullpath, $srcDirLen);
- $phar->addFromString($path, php_strip_whitespace($fullpath));
- }
+ if (substr($filename, -4) === '.xml') {
+ $phar->addFile($fullpath, $path);
+ } else {
+ // PHP file.
+ $phar->addFromString($path, stripWhitespaceAndComments($fullpath, $config));
+ }
+
+ ++$fileCount;
+ }//end foreach
// Add autoloader.
- $phar->addFromString('autoload.php', php_strip_whitespace(realpath(__DIR__.'/../autoload.php')));
+ $phar->addFromString('autoload.php', stripWhitespaceAndComments(realpath(__DIR__.'/../autoload.php'), $config));
// Add licence file.
- $phar->addFromString('licence.txt', php_strip_whitespace(realpath(__DIR__.'/../licence.txt')));
+ $phar->addFile(realpath(__DIR__.'/../licence.txt'), 'licence.txt');
echo 'done'.PHP_EOL;
+ echo "\t Added ".$fileCount.' files'.PHP_EOL;
/*
Add the stub.
@@ -94,3 +165,16 @@
echo 'done'.PHP_EOL;
}//end foreach
+
+$timeTaken = ((microtime(true) - $startTime) * 1000);
+if ($timeTaken < 1000) {
+ $timeTaken = round($timeTaken);
+ echo "DONE in {$timeTaken}ms".PHP_EOL;
+} else {
+ $timeTaken = round(($timeTaken / 1000), 2);
+ echo "DONE in $timeTaken secs".PHP_EOL;
+}
+
+echo PHP_EOL;
+echo 'Filesize generated phpcs.phar file: '.filesize(dirname(__DIR__).'/phpcs.phar').' bytes'.PHP_EOL;
+echo 'Filesize generated phpcs.phar file: '.filesize(dirname(__DIR__).'/phpcbf.phar').' bytes'.PHP_EOL;
diff --git a/src/Config.php b/src/Config.php
index 6bb5b3298f..bfca9e1d18 100644
--- a/src/Config.php
+++ b/src/Config.php
@@ -80,7 +80,7 @@ class Config
*
* @var string
*/
- const VERSION = '3.6.2';
+ const VERSION = '3.7.2';
/**
* Package stability; either stable, beta or alpha.
@@ -368,7 +368,7 @@ public function __construct(array $cliArgs=[], $dieOnUnknownArg=true)
}//end if
if (defined('STDIN') === false
- || strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'
+ || stripos(PHP_OS, 'WIN') === 0
) {
return;
}
@@ -1517,7 +1517,7 @@ public static function getExecutablePath($name)
return self::$executablePaths[$name];
}
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ if (stripos(PHP_OS, 'WIN') === 0) {
$cmd = 'where '.escapeshellarg($name).' 2> nul';
} else {
$cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
@@ -1597,7 +1597,7 @@ public static function setConfigData($key, $value, $temp=false)
if ($temp === false) {
$output = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
$output .= var_export($phpCodeSnifferConfig, true);
- $output .= "\n?".'>';
+ $output .= ";\n?".'>';
if (file_put_contents($configFile, $output) === false) {
$error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
diff --git a/src/Files/File.php b/src/Files/File.php
index e551dcbbf1..ad1ca0b236 100644
--- a/src/Files/File.php
+++ b/src/Files/File.php
@@ -1223,7 +1223,7 @@ public function getFilename()
/**
- * Returns the declaration names for classes, interfaces, traits, and functions.
+ * Returns the declaration name for classes, interfaces, traits, enums, and functions.
*
* @param int $stackPtr The position of the declaration token which
* declared the class, interface, trait, or function.
@@ -1232,7 +1232,7 @@ public function getFilename()
* or NULL if the function or class is anonymous.
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type
* T_FUNCTION, T_CLASS, T_ANON_CLASS,
- * T_CLOSURE, T_TRAIT, or T_INTERFACE.
+ * T_CLOSURE, T_TRAIT, T_ENUM, or T_INTERFACE.
*/
public function getDeclarationName($stackPtr)
{
@@ -1246,8 +1246,9 @@ public function getDeclarationName($stackPtr)
&& $tokenCode !== T_CLASS
&& $tokenCode !== T_INTERFACE
&& $tokenCode !== T_TRAIT
+ && $tokenCode !== T_ENUM
) {
- throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT');
+ throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM');
}
if ($tokenCode === T_FUNCTION
@@ -1310,6 +1311,8 @@ public function getDeclarationName($stackPtr)
* Parameters declared using PHP 8 constructor property promotion, have these additional array indexes:
* 'property_visibility' => string, // The property visibility as declared.
* 'visibility_token' => integer, // The stack pointer to the visibility modifier token.
+ * 'property_readonly' => bool, // TRUE if the readonly keyword was found.
+ * 'readonly_token' => integer, // The stack pointer to the readonly modifier token.
*
* @param int $stackPtr The position in the stack of the function token
* to acquire the parameters for.
@@ -1366,6 +1369,7 @@ public function getMethodParameters($stackPtr)
$typeHintEndToken = false;
$nullableType = false;
$visibilityToken = null;
+ $readonlyToken = null;
for ($i = $paramStart; $i <= $closer; $i++) {
// Check to see if this token has a parenthesis or bracket opener. If it does
@@ -1465,6 +1469,7 @@ public function getMethodParameters($stackPtr)
case T_NAMESPACE:
case T_NS_SEPARATOR:
case T_TYPE_UNION:
+ case T_TYPE_INTERSECTION:
case T_FALSE:
case T_NULL:
// Part of a type hint or default value.
@@ -1491,6 +1496,11 @@ public function getMethodParameters($stackPtr)
$visibilityToken = $i;
}
break;
+ case T_READONLY:
+ if ($defaultStart === null) {
+ $readonlyToken = $i;
+ }
+ break;
case T_CLOSE_PARENTHESIS:
case T_COMMA:
// If it's null, then there must be no parameters for this
@@ -1523,6 +1533,12 @@ public function getMethodParameters($stackPtr)
if ($visibilityToken !== null) {
$vars[$paramCount]['property_visibility'] = $this->tokens[$visibilityToken]['content'];
$vars[$paramCount]['visibility_token'] = $visibilityToken;
+ $vars[$paramCount]['property_readonly'] = false;
+ }
+
+ if ($readonlyToken !== null) {
+ $vars[$paramCount]['property_readonly'] = true;
+ $vars[$paramCount]['readonly_token'] = $readonlyToken;
}
if ($this->tokens[$i]['code'] === T_COMMA) {
@@ -1546,6 +1562,7 @@ public function getMethodParameters($stackPtr)
$typeHintEndToken = false;
$nullableType = false;
$visibilityToken = null;
+ $readonlyToken = null;
$paramCount++;
break;
@@ -1669,16 +1686,17 @@ public function getMethodProperties($stackPtr)
}
$valid = [
- T_STRING => T_STRING,
- T_CALLABLE => T_CALLABLE,
- T_SELF => T_SELF,
- T_PARENT => T_PARENT,
- T_STATIC => T_STATIC,
- T_FALSE => T_FALSE,
- T_NULL => T_NULL,
- T_NAMESPACE => T_NAMESPACE,
- T_NS_SEPARATOR => T_NS_SEPARATOR,
- T_TYPE_UNION => T_TYPE_UNION,
+ T_STRING => T_STRING,
+ T_CALLABLE => T_CALLABLE,
+ T_SELF => T_SELF,
+ T_PARENT => T_PARENT,
+ T_STATIC => T_STATIC,
+ T_FALSE => T_FALSE,
+ T_NULL => T_NULL,
+ T_NAMESPACE => T_NAMESPACE,
+ T_NS_SEPARATOR => T_NS_SEPARATOR,
+ T_TYPE_UNION => T_TYPE_UNION,
+ T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
];
for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) {
@@ -1743,6 +1761,7 @@ public function getMethodProperties($stackPtr)
* 'scope' => string, // Public, private, or protected.
* 'scope_specified' => boolean, // TRUE if the scope was explicitly specified.
* 'is_static' => boolean, // TRUE if the static keyword was found.
+ * 'is_readonly' => boolean, // TRUE if the readonly keyword was found.
* 'type' => string, // The type of the var (empty if no type specified).
* 'type_token' => integer, // The stack pointer to the start of the type
* // or FALSE if there is no type.
@@ -1775,23 +1794,26 @@ public function getMemberProperties($stackPtr)
&& $this->tokens[$ptr]['code'] !== T_TRAIT)
) {
if (isset($this->tokens[$ptr]) === true
- && $this->tokens[$ptr]['code'] === T_INTERFACE
+ && ($this->tokens[$ptr]['code'] === T_INTERFACE
+ || $this->tokens[$ptr]['code'] === T_ENUM)
) {
- // T_VARIABLEs in interfaces can actually be method arguments
- // but they wont be seen as being inside the method because there
+ // T_VARIABLEs in interfaces/enums can actually be method arguments
+ // but they won't be seen as being inside the method because there
// are no scope openers and closers for abstract methods. If it is in
// parentheses, we can be pretty sure it is a method argument.
if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === false
|| empty($this->tokens[$stackPtr]['nested_parenthesis']) === true
) {
- $error = 'Possible parse error: interfaces may not include member vars';
- $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar');
+ $error = 'Possible parse error: %ss may not include member vars';
+ $code = sprintf('Internal.ParseError.%sHasMemberVar', ucfirst($this->tokens[$ptr]['content']));
+ $data = [strtolower($this->tokens[$ptr]['content'])];
+ $this->addWarning($error, $stackPtr, $code, $data);
return [];
}
} else {
throw new RuntimeException('$stackPtr is not a class member var');
}
- }
+ }//end if
// Make sure it's not a method parameter.
if (empty($this->tokens[$stackPtr]['nested_parenthesis']) === false) {
@@ -1811,6 +1833,7 @@ public function getMemberProperties($stackPtr)
T_PROTECTED => T_PROTECTED,
T_STATIC => T_STATIC,
T_VAR => T_VAR,
+ T_READONLY => T_READONLY,
];
$valid += Util\Tokens::$emptyTokens;
@@ -1818,6 +1841,7 @@ public function getMemberProperties($stackPtr)
$scope = 'public';
$scopeSpecified = false;
$isStatic = false;
+ $isReadonly = false;
$startOfStatement = $this->findPrevious(
[
@@ -1850,6 +1874,9 @@ public function getMemberProperties($stackPtr)
case T_STATIC:
$isStatic = true;
break;
+ case T_READONLY:
+ $isReadonly = true;
+ break;
}
}//end for
@@ -1861,15 +1888,16 @@ public function getMemberProperties($stackPtr)
if ($i < $stackPtr) {
// We've found a type.
$valid = [
- T_STRING => T_STRING,
- T_CALLABLE => T_CALLABLE,
- T_SELF => T_SELF,
- T_PARENT => T_PARENT,
- T_FALSE => T_FALSE,
- T_NULL => T_NULL,
- T_NAMESPACE => T_NAMESPACE,
- T_NS_SEPARATOR => T_NS_SEPARATOR,
- T_TYPE_UNION => T_TYPE_UNION,
+ T_STRING => T_STRING,
+ T_CALLABLE => T_CALLABLE,
+ T_SELF => T_SELF,
+ T_PARENT => T_PARENT,
+ T_FALSE => T_FALSE,
+ T_NULL => T_NULL,
+ T_NAMESPACE => T_NAMESPACE,
+ T_NS_SEPARATOR => T_NS_SEPARATOR,
+ T_TYPE_UNION => T_TYPE_UNION,
+ T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
];
for ($i; $i < $stackPtr; $i++) {
@@ -1901,6 +1929,7 @@ public function getMemberProperties($stackPtr)
'scope' => $scope,
'scope_specified' => $scopeSpecified,
'is_static' => $isStatic,
+ 'is_readonly' => $isReadonly,
'type' => $type,
'type_token' => $typeToken,
'type_end_token' => $typeEndToken,
@@ -2743,11 +2772,11 @@ public function findExtendedClassName($stackPtr)
/**
- * Returns the names of the interfaces that the specified class implements.
+ * Returns the names of the interfaces that the specified class or enum implements.
*
* Returns FALSE on error or if there are no implemented interface names.
*
- * @param int $stackPtr The stack position of the class.
+ * @param int $stackPtr The stack position of the class or enum token.
*
* @return array|false
*/
@@ -2760,6 +2789,7 @@ public function findImplementedInterfaceNames($stackPtr)
if ($this->tokens[$stackPtr]['code'] !== T_CLASS
&& $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
+ && $this->tokens[$stackPtr]['code'] !== T_ENUM
) {
return false;
}
diff --git a/src/Reports/Code.php b/src/Reports/Code.php
index c54c1e1a4e..47c5581e25 100644
--- a/src/Reports/Code.php
+++ b/src/Reports/Code.php
@@ -225,7 +225,7 @@ public function generateFileReport($report, File $phpcsFile, $showSources=false,
if (strpos($tokenContent, "\t") !== false) {
$token = $tokens[$i];
$token['content'] = $tokenContent;
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ if (stripos(PHP_OS, 'WIN') === 0) {
$tab = "\000";
} else {
$tab = "\033[30;1m»\033[0m";
diff --git a/src/Reports/Gitblame.php b/src/Reports/Gitblame.php
index 947f3d80b1..6427567f8d 100644
--- a/src/Reports/Gitblame.php
+++ b/src/Reports/Gitblame.php
@@ -78,7 +78,7 @@ protected function getBlameContent($filename)
}
$rawContent = stream_get_contents($handle);
- fclose($handle);
+ pclose($handle);
$blames = explode("\n", $rawContent);
chdir($cwd);
diff --git a/src/Reports/Hgblame.php b/src/Reports/Hgblame.php
index b0af6643fd..f88a06836b 100644
--- a/src/Reports/Hgblame.php
+++ b/src/Reports/Hgblame.php
@@ -97,7 +97,7 @@ protected function getBlameContent($filename)
}
$rawContent = stream_get_contents($handle);
- fclose($handle);
+ pclose($handle);
$blames = explode("\n", $rawContent);
chdir($cwd);
diff --git a/src/Reports/Svnblame.php b/src/Reports/Svnblame.php
index f4719fe5dc..a7f65e154b 100644
--- a/src/Reports/Svnblame.php
+++ b/src/Reports/Svnblame.php
@@ -61,7 +61,7 @@ protected function getBlameContent($filename)
}
$rawContent = stream_get_contents($handle);
- fclose($handle);
+ pclose($handle);
$blames = explode("\n", $rawContent);
diff --git a/src/Ruleset.php b/src/Ruleset.php
index 7e659706c8..f90e7b6d2b 100644
--- a/src/Ruleset.php
+++ b/src/Ruleset.php
@@ -249,7 +249,12 @@ public function explain()
// one last time and clear the output buffer.
$sniffs[] = '';
- echo PHP_EOL."The $this->name standard contains $sniffCount sniffs".PHP_EOL;
+ $summaryLine = PHP_EOL."The $this->name standard contains 1 sniff".PHP_EOL;
+ if ($sniffCount !== 1) {
+ $summaryLine = str_replace('1 sniff', "$sniffCount sniffs", $summaryLine);
+ }
+
+ echo $summaryLine;
ob_start();
diff --git a/src/Runner.php b/src/Runner.php
index 253ec91f9f..e0cbca7160 100644
--- a/src/Runner.php
+++ b/src/Runner.php
@@ -53,6 +53,8 @@ class Runner
*/
public function runPHPCS()
{
+ $this->registerOutOfMemoryShutdownMessage('phpcs');
+
try {
Util\Timing::startTiming();
Runner::checkRequirements();
@@ -153,6 +155,8 @@ public function runPHPCS()
*/
public function runPHPCBF()
{
+ $this->registerOutOfMemoryShutdownMessage('phpcbf');
+
if (defined('PHP_CODESNIFFER_CBF') === false) {
define('PHP_CODESNIFFER_CBF', true);
}
@@ -460,10 +464,7 @@ private function run()
if ($pid === -1) {
throw new RuntimeException('Failed to create child process');
} else if ($pid !== 0) {
- $childProcs[] = [
- 'pid' => $pid,
- 'out' => $childOutFilename,
- ];
+ $childProcs[$pid] = $childOutFilename;
} else {
// Move forward to the start of the batch.
$todo->rewind();
@@ -536,7 +537,7 @@ private function run()
$output .= ";\n?".'>';
file_put_contents($childOutFilename, $output);
- exit($pid);
+ exit();
}//end if
}//end for
@@ -719,54 +720,61 @@ private function processChildProcs($childProcs)
$success = true;
while (count($childProcs) > 0) {
- foreach ($childProcs as $key => $procData) {
- $res = pcntl_waitpid($procData['pid'], $status, WNOHANG);
- if ($res === $procData['pid']) {
- if (file_exists($procData['out']) === true) {
- include $procData['out'];
-
- unlink($procData['out']);
- unset($childProcs[$key]);
-
- $numProcessed++;
-
- if (isset($childOutput) === false) {
- // The child process died, so the run has failed.
- $file = new DummyFile('', $this->ruleset, $this->config);
- $file->setErrorCounts(1, 0, 0, 0);
- $this->printProgress($file, $totalBatches, $numProcessed);
- $success = false;
- continue;
- }
+ $pid = pcntl_waitpid(0, $status);
+ if ($pid <= 0) {
+ continue;
+ }
- $this->reporter->totalFiles += $childOutput['totalFiles'];
- $this->reporter->totalErrors += $childOutput['totalErrors'];
- $this->reporter->totalWarnings += $childOutput['totalWarnings'];
- $this->reporter->totalFixable += $childOutput['totalFixable'];
- $this->reporter->totalFixed += $childOutput['totalFixed'];
+ $childProcessStatus = pcntl_wexitstatus($status);
+ if ($childProcessStatus !== 0) {
+ $success = false;
+ }
- if (isset($debugOutput) === true) {
- echo $debugOutput;
- }
+ $out = $childProcs[$pid];
+ unset($childProcs[$pid]);
+ if (file_exists($out) === false) {
+ continue;
+ }
- if (isset($childCache) === true) {
- foreach ($childCache as $path => $cache) {
- Cache::set($path, $cache);
- }
- }
+ include $out;
+ unlink($out);
- // Fake a processed file so we can print progress output for the batch.
- $file = new DummyFile('', $this->ruleset, $this->config);
- $file->setErrorCounts(
- $childOutput['totalErrors'],
- $childOutput['totalWarnings'],
- $childOutput['totalFixable'],
- $childOutput['totalFixed']
- );
- $this->printProgress($file, $totalBatches, $numProcessed);
- }//end if
- }//end if
- }//end foreach
+ $numProcessed++;
+
+ if (isset($childOutput) === false) {
+ // The child process died, so the run has failed.
+ $file = new DummyFile('', $this->ruleset, $this->config);
+ $file->setErrorCounts(1, 0, 0, 0);
+ $this->printProgress($file, $totalBatches, $numProcessed);
+ $success = false;
+ continue;
+ }
+
+ $this->reporter->totalFiles += $childOutput['totalFiles'];
+ $this->reporter->totalErrors += $childOutput['totalErrors'];
+ $this->reporter->totalWarnings += $childOutput['totalWarnings'];
+ $this->reporter->totalFixable += $childOutput['totalFixable'];
+ $this->reporter->totalFixed += $childOutput['totalFixed'];
+
+ if (isset($debugOutput) === true) {
+ echo $debugOutput;
+ }
+
+ if (isset($childCache) === true) {
+ foreach ($childCache as $path => $cache) {
+ Cache::set($path, $cache);
+ }
+ }
+
+ // Fake a processed file so we can print progress output for the batch.
+ $file = new DummyFile('', $this->ruleset, $this->config);
+ $file->setErrorCounts(
+ $childOutput['totalErrors'],
+ $childOutput['totalWarnings'],
+ $childOutput['totalFixable'],
+ $childOutput['totalFixed']
+ );
+ $this->printProgress($file, $totalBatches, $numProcessed);
}//end while
return $success;
@@ -887,4 +895,42 @@ public function printProgress(File $file, $numFiles, $numProcessed)
}//end printProgress()
+ /**
+ * Registers a PHP shutdown function to provide a more informative out of memory error.
+ *
+ * @param string $command The command which was used to initiate the PHPCS run.
+ *
+ * @return void
+ */
+ private function registerOutOfMemoryShutdownMessage($command)
+ {
+ // Allocate all needed memory beforehand as much as possible.
+ $errorMsg = PHP_EOL.'The PHP_CodeSniffer "%1$s" command ran out of memory.'.PHP_EOL;
+ $errorMsg .= 'Either raise the "memory_limit" of PHP in the php.ini file or raise the memory limit at runtime'.PHP_EOL;
+ $errorMsg .= 'using `%1$s -d memory_limit=512M` (replace 512M with the desired memory limit).'.PHP_EOL;
+ $errorMsg = sprintf($errorMsg, $command);
+ $memoryError = 'Allowed memory size of';
+ $errorArray = [
+ 'type' => 42,
+ 'message' => 'Some random dummy string to take up memory and take up some more memory and some more',
+ 'file' => 'Another random string, which would be a filename this time. Should be relatively long to allow for deeply nested files',
+ 'line' => 31427,
+ ];
+
+ register_shutdown_function(
+ static function () use (
+ $errorMsg,
+ $memoryError,
+ $errorArray
+ ) {
+ $errorArray = error_get_last();
+ if (is_array($errorArray) === true && strpos($errorArray['message'], $memoryError) !== false) {
+ echo $errorMsg;
+ }
+ }
+ );
+
+ }//end registerOutOfMemoryShutdownMessage()
+
+
}//end class
diff --git a/src/Sniffs/AbstractArraySniff.php b/src/Sniffs/AbstractArraySniff.php
index 141b9a133c..efe9969d8d 100644
--- a/src/Sniffs/AbstractArraySniff.php
+++ b/src/Sniffs/AbstractArraySniff.php
@@ -104,9 +104,9 @@ public function process(File $phpcsFile, $stackPtr)
/**
* Find next separator in array - either: comma or double arrow.
*
- * @param File $phpcsFile The current file being checked.
- * @param int $ptr The position of current token.
- * @param int $arrayEnd The token that ends the array definition.
+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked.
+ * @param int $ptr The position of current token.
+ * @param int $arrayEnd The token that ends the array definition.
*
* @return int
*/
diff --git a/src/Sniffs/Sniff.php b/src/Sniffs/Sniff.php
index c9d7daea82..3f0fb6a1cf 100644
--- a/src/Sniffs/Sniff.php
+++ b/src/Sniffs/Sniff.php
@@ -45,7 +45,7 @@ public function register();
* is found.
*
* The stackPtr variable indicates where in the stack the token was found.
- * A sniff can acquire information this token, along with all the other
+ * A sniff can acquire information about this token, along with all the other
* tokens within the stack by first acquiring the token stack:
*
*
diff --git a/src/Standards/Generic/Docs/Classes/OpeningBraceSameLineStandard.xml b/src/Standards/Generic/Docs/Classes/OpeningBraceSameLineStandard.xml
index 6226a3fff6..6fa08be7a4 100644
--- a/src/Standards/Generic/Docs/Classes/OpeningBraceSameLineStandard.xml
+++ b/src/Standards/Generic/Docs/Classes/OpeningBraceSameLineStandard.xml
@@ -15,6 +15,14 @@ class Foo {
{
+}
+ ]]>
+
+
+
+
+ {
}
]]>
diff --git a/src/Standards/Generic/Docs/Formatting/SpaceAfterNotStandard.xml b/src/Standards/Generic/Docs/Formatting/SpaceAfterNotStandard.xml
index dd3e773118..aea863695e 100644
--- a/src/Standards/Generic/Docs/Formatting/SpaceAfterNotStandard.xml
+++ b/src/Standards/Generic/Docs/Formatting/SpaceAfterNotStandard.xml
@@ -10,13 +10,10 @@
if (! $someVar || ! $x instanceOf stdClass) {};
]]>
-
+
$someVar || !$x instanceOf stdClass) {};
- ]]>
-
-
- $someVar || !
$x instanceOf stdClass) {};
]]>
diff --git a/src/Standards/Generic/Docs/WhiteSpace/ArbitraryParenthesesSpacingStandard.xml b/src/Standards/Generic/Docs/WhiteSpace/ArbitraryParenthesesSpacingStandard.xml
index 30e0def93c..338c838933 100644
--- a/src/Standards/Generic/Docs/WhiteSpace/ArbitraryParenthesesSpacingStandard.xml
+++ b/src/Standards/Generic/Docs/WhiteSpace/ArbitraryParenthesesSpacingStandard.xml
@@ -10,13 +10,10 @@
$a = (null !== $extra);
]]>
-
+
-
-
- findNext($this->register(), ($stackPtr + 1));
+ $tokens = $phpcsFile->getTokens();
+ $start = ($stackPtr + 1);
+ if (isset($tokens[$stackPtr]['scope_closer']) === true) {
+ $start = ($tokens[$stackPtr]['scope_closer'] + 1);
+ }
+
+ $nextClass = $phpcsFile->findNext($this->register(), $start);
if ($nextClass !== false) {
$error = 'Only one class is allowed in a file';
$phpcsFile->addError($error, $nextClass, 'MultipleFound');
diff --git a/src/Standards/Generic/Sniffs/Files/OneInterfacePerFileSniff.php b/src/Standards/Generic/Sniffs/Files/OneInterfacePerFileSniff.php
index f7cfa8d30b..9a6f5bccd5 100644
--- a/src/Standards/Generic/Sniffs/Files/OneInterfacePerFileSniff.php
+++ b/src/Standards/Generic/Sniffs/Files/OneInterfacePerFileSniff.php
@@ -39,7 +39,13 @@ public function register()
*/
public function process(File $phpcsFile, $stackPtr)
{
- $nextInterface = $phpcsFile->findNext($this->register(), ($stackPtr + 1));
+ $tokens = $phpcsFile->getTokens();
+ $start = ($stackPtr + 1);
+ if (isset($tokens[$stackPtr]['scope_closer']) === true) {
+ $start = ($tokens[$stackPtr]['scope_closer'] + 1);
+ }
+
+ $nextInterface = $phpcsFile->findNext($this->register(), $start);
if ($nextInterface !== false) {
$error = 'Only one interface is allowed in a file';
$phpcsFile->addError($error, $nextInterface, 'MultipleFound');
diff --git a/src/Standards/Generic/Sniffs/Files/OneObjectStructurePerFileSniff.php b/src/Standards/Generic/Sniffs/Files/OneObjectStructurePerFileSniff.php
index d9d71b6996..4d417e069e 100644
--- a/src/Standards/Generic/Sniffs/Files/OneObjectStructurePerFileSniff.php
+++ b/src/Standards/Generic/Sniffs/Files/OneObjectStructurePerFileSniff.php
@@ -27,6 +27,7 @@ public function register()
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
];
}//end register()
@@ -43,7 +44,13 @@ public function register()
*/
public function process(File $phpcsFile, $stackPtr)
{
- $nextClass = $phpcsFile->findNext($this->register(), ($stackPtr + 1));
+ $tokens = $phpcsFile->getTokens();
+ $start = ($stackPtr + 1);
+ if (isset($tokens[$stackPtr]['scope_closer']) === true) {
+ $start = ($tokens[$stackPtr]['scope_closer'] + 1);
+ }
+
+ $nextClass = $phpcsFile->findNext($this->register(), $start);
if ($nextClass !== false) {
$error = 'Only one object structure is allowed in a file';
$phpcsFile->addError($error, $nextClass, 'MultipleFound');
diff --git a/src/Standards/Generic/Sniffs/Files/OneTraitPerFileSniff.php b/src/Standards/Generic/Sniffs/Files/OneTraitPerFileSniff.php
index dd97da85b4..7ae523f70e 100644
--- a/src/Standards/Generic/Sniffs/Files/OneTraitPerFileSniff.php
+++ b/src/Standards/Generic/Sniffs/Files/OneTraitPerFileSniff.php
@@ -39,7 +39,13 @@ public function register()
*/
public function process(File $phpcsFile, $stackPtr)
{
- $nextClass = $phpcsFile->findNext($this->register(), ($stackPtr + 1));
+ $tokens = $phpcsFile->getTokens();
+ $start = ($stackPtr + 1);
+ if (isset($tokens[$stackPtr]['scope_closer']) === true) {
+ $start = ($tokens[$stackPtr]['scope_closer'] + 1);
+ }
+
+ $nextClass = $phpcsFile->findNext($this->register(), $start);
if ($nextClass !== false) {
$error = 'Only one trait is allowed in a file';
$phpcsFile->addError($error, $nextClass, 'MultipleFound');
diff --git a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php
index ca9223814b..136a1d4ae9 100644
--- a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php
+++ b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php
@@ -30,6 +30,7 @@ public function register()
T_UNSET,
T_SELF,
T_STATIC,
+ T_PARENT,
T_VARIABLE,
T_CLOSE_CURLY_BRACKET,
T_CLOSE_PARENTHESIS,
diff --git a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php
index 18100c2d67..9bd0dff3de 100644
--- a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php
+++ b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php
@@ -71,18 +71,19 @@ public function process(File $phpcsFile, $stackPtr)
// Predicate nodes for PHP.
$find = [
- T_CASE => true,
- T_DEFAULT => true,
- T_CATCH => true,
- T_IF => true,
- T_FOR => true,
- T_FOREACH => true,
- T_WHILE => true,
- T_ELSEIF => true,
- T_INLINE_THEN => true,
- T_COALESCE => true,
- T_COALESCE_EQUAL => true,
- T_MATCH_ARROW => true,
+ T_CASE => true,
+ T_DEFAULT => true,
+ T_CATCH => true,
+ T_IF => true,
+ T_FOR => true,
+ T_FOREACH => true,
+ T_WHILE => true,
+ T_ELSEIF => true,
+ T_INLINE_THEN => true,
+ T_COALESCE => true,
+ T_COALESCE_EQUAL => true,
+ T_MATCH_ARROW => true,
+ T_NULLSAFE_OBJECT_OPERATOR => true,
];
$complexity = 1;
diff --git a/src/Standards/Generic/Sniffs/PHP/DisallowRequestSuperglobalSniff.php b/src/Standards/Generic/Sniffs/PHP/DisallowRequestSuperglobalSniff.php
index d5c7ddb724..1c2b1aee93 100644
--- a/src/Standards/Generic/Sniffs/PHP/DisallowRequestSuperglobalSniff.php
+++ b/src/Standards/Generic/Sniffs/PHP/DisallowRequestSuperglobalSniff.php
@@ -31,8 +31,9 @@ public function register()
/**
* Processes this sniff, when one of its tokens is encountered.
*
- * @param File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the current token in the stack passed in $tokens.
+ * @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
*/
diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php
index 8b8c76e922..4376daa9dd 100644
--- a/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php
+++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php
@@ -11,6 +11,7 @@
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
+use PHP_CodeSniffer\Util\Tokens;
class LowerCaseConstantSniff implements Sniff
{
@@ -25,6 +26,17 @@ class LowerCaseConstantSniff implements Sniff
'JS',
];
+ /**
+ * The tokens this sniff is targetting.
+ *
+ * @var array
+ */
+ private $targets = [
+ T_TRUE => T_TRUE,
+ T_FALSE => T_FALSE,
+ T_NULL => T_NULL,
+ ];
+
/**
* Returns an array of tokens this test wants to listen for.
@@ -33,11 +45,14 @@ class LowerCaseConstantSniff implements Sniff
*/
public function register()
{
- return [
- T_TRUE,
- T_FALSE,
- T_NULL,
- ];
+ $targets = $this->targets;
+
+ // Register function keywords to filter out type declarations.
+ $targets[] = T_FUNCTION;
+ $targets[] = T_CLOSURE;
+ $targets[] = T_FN;
+
+ return $targets;
}//end register()
@@ -52,10 +67,89 @@ public function register()
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+
+ // Handle function declarations separately as they may contain the keywords in type declarations.
+ if ($tokens[$stackPtr]['code'] === T_FUNCTION
+ || $tokens[$stackPtr]['code'] === T_CLOSURE
+ || $tokens[$stackPtr]['code'] === T_FN
+ ) {
+ if (isset($tokens[$stackPtr]['parenthesis_closer']) === false) {
+ return;
+ }
+
+ $end = $tokens[$stackPtr]['parenthesis_closer'];
+ if (isset($tokens[$stackPtr]['scope_opener']) === true) {
+ $end = $tokens[$stackPtr]['scope_opener'];
+ }
+
+ // Do a quick check if any of the targets exist in the declaration.
+ $found = $phpcsFile->findNext($this->targets, $tokens[$stackPtr]['parenthesis_opener'], $end);
+ if ($found === false) {
+ // Skip forward, no need to examine these tokens again.
+ return $end;
+ }
+
+ // Handle the whole function declaration in one go.
+ $params = $phpcsFile->getMethodParameters($stackPtr);
+ foreach ($params as $param) {
+ if (isset($param['default_token']) === false) {
+ continue;
+ }
+
+ $paramEnd = $param['comma_token'];
+ if ($param['comma_token'] === false) {
+ $paramEnd = $tokens[$stackPtr]['parenthesis_closer'];
+ }
+
+ for ($i = $param['default_token']; $i < $paramEnd; $i++) {
+ if (isset($this->targets[$tokens[$i]['code']]) === true) {
+ $this->processConstant($phpcsFile, $i);
+ }
+ }
+ }
+
+ // Skip over return type declarations.
+ return $end;
+ }//end if
+
+ // Handle property declarations separately as they may contain the keywords in type declarations.
+ if (isset($tokens[$stackPtr]['conditions']) === true) {
+ $conditions = $tokens[$stackPtr]['conditions'];
+ $lastCondition = end($conditions);
+ if (isset(Tokens::$ooScopeTokens[$lastCondition]) === true) {
+ // This can only be an OO constant or property declaration as methods are handled above.
+ $equals = $phpcsFile->findPrevious(T_EQUAL, ($stackPtr - 1), null, false, null, true);
+ if ($equals !== false) {
+ $this->processConstant($phpcsFile, $stackPtr);
+ }
+
+ return;
+ }
+ }
+
+ // Handle everything else.
+ $this->processConstant($phpcsFile, $stackPtr);
+
+ }//end process()
+
+
+ /**
+ * Processes a non-type declaration constant.
+ *
+ * @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
+ */
+ protected function processConstant(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$keyword = $tokens[$stackPtr]['content'];
$expected = strtolower($keyword);
+
if ($keyword !== $expected) {
if ($keyword === strtoupper($keyword)) {
$phpcsFile->recordMetric($stackPtr, 'PHP constant case', 'upper');
@@ -77,7 +171,7 @@ public function process(File $phpcsFile, $stackPtr)
$phpcsFile->recordMetric($stackPtr, 'PHP constant case', 'lower');
}
- }//end process()
+ }//end processConstant()
}//end class
diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php
index e10047b64e..e6b4917834 100644
--- a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php
+++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php
@@ -11,7 +11,8 @@
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
-use PHP_CodeSniffer\Util;
+use PHP_CodeSniffer\Util\Common;
+use PHP_CodeSniffer\Util\Tokens;
class LowerCaseKeywordSniff implements Sniff
{
@@ -24,81 +25,21 @@ class LowerCaseKeywordSniff implements Sniff
*/
public function register()
{
- return [
- T_ABSTRACT,
- T_ARRAY,
- T_AS,
- T_BREAK,
- T_CALLABLE,
- T_CASE,
- T_CATCH,
- T_CLASS,
- T_CLONE,
- T_CLOSURE,
- T_CONST,
- T_CONTINUE,
- T_DECLARE,
- T_DEFAULT,
- T_DO,
- T_ECHO,
- T_ELSE,
- T_ELSEIF,
- T_EMPTY,
- T_ENDDECLARE,
- T_ENDFOR,
- T_ENDFOREACH,
- T_ENDIF,
- T_ENDSWITCH,
- T_ENDWHILE,
- T_EVAL,
- T_EXIT,
- T_EXTENDS,
- T_FINAL,
- T_FINALLY,
- T_FN,
- T_FOR,
- T_FOREACH,
- T_FUNCTION,
- T_GLOBAL,
- T_GOTO,
- T_IF,
- T_IMPLEMENTS,
- T_INCLUDE,
- T_INCLUDE_ONCE,
- T_INSTANCEOF,
- T_INSTEADOF,
- T_INTERFACE,
- T_ISSET,
- T_LIST,
- T_LOGICAL_AND,
- T_LOGICAL_OR,
- T_LOGICAL_XOR,
- T_MATCH,
- T_MATCH_DEFAULT,
- T_NAMESPACE,
- T_NEW,
- T_PARENT,
- T_PRINT,
- T_PRIVATE,
- T_PROTECTED,
- T_PUBLIC,
- T_REQUIRE,
- T_REQUIRE_ONCE,
- T_RETURN,
- T_SELF,
- T_STATIC,
- T_SWITCH,
- T_THROW,
- T_TRAIT,
- T_TRY,
- T_UNSET,
- T_USE,
- T_VAR,
- T_WHILE,
- T_YIELD,
- T_YIELD_FROM,
+ $targets = Tokens::$contextSensitiveKeywords;
+ $targets += [
+ T_CLOSURE => T_CLOSURE,
+ T_EMPTY => T_EMPTY,
+ T_ENUM_CASE => T_ENUM_CASE,
+ T_EVAL => T_EVAL,
+ T_ISSET => T_ISSET,
+ T_MATCH_DEFAULT => T_MATCH_DEFAULT,
+ T_PARENT => T_PARENT,
+ T_SELF => T_SELF,
+ T_UNSET => T_UNSET,
];
+ return $targets;
+
}//end register()
@@ -122,7 +63,7 @@ public function process(File $phpcsFile, $stackPtr)
$phpcsFile->recordMetric($stackPtr, 'PHP keyword case', 'mixed');
}
- $messageKeyword = Util\Common::prepareForOutput($keyword);
+ $messageKeyword = Common::prepareForOutput($keyword);
$error = 'PHP keywords must be lowercase; expected "%s" but found "%s"';
$data = [
diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php
index a738c30d66..4d463f28fb 100644
--- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php
+++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php
@@ -38,6 +38,7 @@ class LowerCaseTypeSniff implements Sniff
'static' => true,
'false' => true,
'null' => true,
+ 'never' => true,
];
@@ -51,6 +52,7 @@ public function register()
$tokens = Tokens::$castTokens;
$tokens[] = T_FUNCTION;
$tokens[] = T_CLOSURE;
+ $tokens[] = T_FN;
$tokens[] = T_VARIABLE;
return $tokens;
@@ -102,7 +104,9 @@ public function process(File $phpcsFile, $stackPtr)
$error = 'PHP property type declarations must be lowercase; expected "%s" but found "%s"';
$errorCode = 'PropertyTypeFound';
- if (strpos($type, '|') !== false) {
+ if ($props['type_token'] === T_TYPE_INTERSECTION) {
+ // Intersection types don't support simple types.
+ } else if (strpos($type, '|') !== false) {
$this->processUnionType(
$phpcsFile,
$props['type_token'],
@@ -131,7 +135,9 @@ public function process(File $phpcsFile, $stackPtr)
$error = 'PHP return type declarations must be lowercase; expected "%s" but found "%s"';
$errorCode = 'ReturnTypeFound';
- if (strpos($returnType, '|') !== false) {
+ if ($props['return_type_token'] === T_TYPE_INTERSECTION) {
+ // Intersection types don't support simple types.
+ } else if (strpos($returnType, '|') !== false) {
$this->processUnionType(
$phpcsFile,
$props['return_type_token'],
@@ -161,7 +167,9 @@ public function process(File $phpcsFile, $stackPtr)
$error = 'PHP parameter type declarations must be lowercase; expected "%s" but found "%s"';
$errorCode = 'ParamTypeFound';
- if (strpos($typeHint, '|') !== false) {
+ if ($param['type_hint_token'] === T_TYPE_INTERSECTION) {
+ // Intersection types don't support simple types.
+ } else if (strpos($typeHint, '|') !== false) {
$this->processUnionType(
$phpcsFile,
$param['type_hint_token'],
diff --git a/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php b/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php
index 54aa07f22a..2740884bb4 100644
--- a/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php
+++ b/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php
@@ -10,30 +10,13 @@
namespace PHP_CodeSniffer\Standards\Generic\Sniffs\PHP;
use PHP_CodeSniffer\Files\File;
-use PHP_CodeSniffer\Sniffs\Sniff;
-class UpperCaseConstantSniff implements Sniff
+class UpperCaseConstantSniff extends LowerCaseConstantSniff
{
/**
- * Returns an array of tokens this test wants to listen for.
- *
- * @return array
- */
- public function register()
- {
- return [
- T_TRUE,
- T_FALSE,
- T_NULL,
- ];
-
- }//end register()
-
-
- /**
- * Processes this sniff, when one of its tokens is encountered.
+ * Processes a non-type declaration constant.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
@@ -41,11 +24,12 @@ public function register()
*
* @return void
*/
- public function process(File $phpcsFile, $stackPtr)
+ protected function processConstant(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$keyword = $tokens[$stackPtr]['content'];
$expected = strtoupper($keyword);
+
if ($keyword !== $expected) {
if ($keyword === strtolower($keyword)) {
$phpcsFile->recordMetric($stackPtr, 'PHP constant case', 'lower');
@@ -67,7 +51,7 @@ public function process(File $phpcsFile, $stackPtr)
$phpcsFile->recordMetric($stackPtr, 'PHP constant case', 'upper');
}
- }//end process()
+ }//end processConstant()
}//end class
diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php
index 3e20cf4964..2140e55ef5 100644
--- a/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php
+++ b/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php
@@ -76,6 +76,8 @@ public function process(File $phpcsFile, $stackPtr)
T_DOC_COMMENT_WHITESPACE => true,
T_DOC_COMMENT_STRING => true,
T_COMMENT => true,
+ T_END_HEREDOC => true,
+ T_END_NOWDOC => true,
];
for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php
index a01449197a..7fd604849d 100644
--- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php
+++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php
@@ -142,12 +142,13 @@ public function process(File $phpcsFile, $stackPtr)
}
}
- $lastOpenTag = $stackPtr;
- $lastCloseTag = null;
- $openScopes = [];
- $adjustments = [];
- $setIndents = [];
- $disableExactEnd = 0;
+ $lastOpenTag = $stackPtr;
+ $lastCloseTag = null;
+ $openScopes = [];
+ $adjustments = [];
+ $setIndents = [];
+ $disableExactStack = [];
+ $disableExactEnd = 0;
$tokens = $phpcsFile->getTokens();
$first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr);
@@ -232,6 +233,7 @@ public function process(File $phpcsFile, $stackPtr)
if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS
&& isset($tokens[$i]['parenthesis_closer']) === true
) {
+ $disableExactStack[$tokens[$i]['parenthesis_closer']] = $tokens[$i]['parenthesis_closer'];
$disableExactEnd = max($disableExactEnd, $tokens[$i]['parenthesis_closer']);
if ($this->debug === true) {
$line = $tokens[$i]['line'];
@@ -616,11 +618,11 @@ public function process(File $phpcsFile, $stackPtr)
// Scope closers reset the required indent to the same level as the opening condition.
if (($checkToken !== null
- && isset($openScopes[$checkToken]) === true
+ && (isset($openScopes[$checkToken]) === true
|| (isset($tokens[$checkToken]['scope_condition']) === true
&& isset($tokens[$checkToken]['scope_closer']) === true
&& $tokens[$checkToken]['scope_closer'] === $checkToken
- && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['scope_opener']]['line']))
+ && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['scope_opener']]['line'])))
|| ($checkToken === null
&& isset($openScopes[$i]) === true)
) {
@@ -802,9 +804,17 @@ public function process(File $phpcsFile, $stackPtr)
&& isset($tokens[$checkToken]['scope_opener']) === true
) {
$exact = true;
+
if ($disableExactEnd > $checkToken) {
- if ($tokens[$checkToken]['conditions'] === $tokens[$disableExactEnd]['conditions']) {
- $exact = false;
+ foreach ($disableExactStack as $disableExactStackEnd) {
+ if ($disableExactStackEnd < $checkToken) {
+ continue;
+ }
+
+ if ($tokens[$checkToken]['conditions'] === $tokens[$disableExactStackEnd]['conditions']) {
+ $exact = false;
+ break;
+ }
}
}
@@ -1035,6 +1045,7 @@ public function process(File $phpcsFile, $stackPtr)
// Don't check indents exactly between arrays as they tend to have custom rules.
if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
+ $disableExactStack[$tokens[$i]['bracket_closer']] = $tokens[$i]['bracket_closer'];
$disableExactEnd = max($disableExactEnd, $tokens[$i]['bracket_closer']);
if ($this->debug === true) {
$line = $tokens[$i]['line'];
@@ -1056,7 +1067,6 @@ public function process(File $phpcsFile, $stackPtr)
) {
if ($this->debug === true) {
$line = $tokens[$i]['line'];
- $type = $tokens[$disableExactEnd]['type'];
echo "Here/nowdoc found on line $line".PHP_EOL;
}
@@ -1325,11 +1335,14 @@ public function process(File $phpcsFile, $stackPtr)
continue;
}//end if
- // Closing an anon class or function.
+ // Closing an anon class, closure, or match.
+ // Each may be returned, which can confuse control structures that
+ // use return as a closer, like CASE statements.
if (isset($tokens[$i]['scope_condition']) === true
&& $tokens[$i]['scope_closer'] === $i
&& ($tokens[$tokens[$i]['scope_condition']]['code'] === T_CLOSURE
- || $tokens[$tokens[$i]['scope_condition']]['code'] === T_ANON_CLASS)
+ || $tokens[$tokens[$i]['scope_condition']]['code'] === T_ANON_CLASS
+ || $tokens[$tokens[$i]['scope_condition']]['code'] === T_MATCH)
) {
if ($this->debug === true) {
$type = str_replace('_', ' ', strtolower(substr($tokens[$tokens[$i]['scope_condition']]['type'], 2)));
diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/SpreadOperatorSpacingAfterSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/SpreadOperatorSpacingAfterSniff.php
index 3146717e84..070bbcd1fb 100644
--- a/src/Standards/Generic/Sniffs/WhiteSpace/SpreadOperatorSpacingAfterSniff.php
+++ b/src/Standards/Generic/Sniffs/WhiteSpace/SpreadOperatorSpacingAfterSniff.php
@@ -62,6 +62,11 @@ public function process(File $phpcsFile, $stackPtr)
return;
}
+ if ($tokens[$nextNonEmpty]['code'] === T_CLOSE_PARENTHESIS) {
+ // Ignore PHP 8.1 first class callable syntax.
+ return;
+ }
+
if ($this->ignoreNewlines === true
&& $tokens[$stackPtr]['line'] !== $tokens[$nextNonEmpty]['line']
) {
diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc
index af56fd497d..075fc34c5b 100644
--- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc
+++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc
@@ -98,3 +98,16 @@ $array = [
match ($test) { 1 => 'a', 2 => 'b' }
=> 'dynamic keys, woho!',
];
+
+// Ensure that PHP 8.0 named parameters don't affect the sniff.
+$array = [
+ functionCall(
+ name: $value
+ ),
+];
+
+$array = [
+ functionCall(
+ name: $value
+ ),
+];
diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed
index 495145956b..505de5f780 100644
--- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed
@@ -99,3 +99,16 @@ $array = [
match ($test) { 1 => 'a', 2 => 'b' }
=> 'dynamic keys, woho!',
];
+
+// Ensure that PHP 8.0 named parameters don't affect the sniff.
+$array = [
+ functionCall(
+ name: $value
+ ),
+];
+
+$array = [
+ functionCall(
+ name: $value
+ ),
+];
diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php
index 0b99f2d68f..4861041f62 100644
--- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php
+++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php
@@ -26,24 +26,25 @@ class ArrayIndentUnitTest extends AbstractSniffUnitTest
public function getErrorList()
{
return [
- 14 => 1,
- 15 => 1,
- 17 => 1,
- 30 => 1,
- 31 => 1,
- 33 => 1,
- 41 => 1,
- 62 => 1,
- 63 => 1,
- 69 => 1,
- 77 => 1,
- 78 => 1,
- 79 => 1,
- 85 => 1,
- 86 => 1,
- 87 => 1,
- 88 => 1,
- 98 => 1,
+ 14 => 1,
+ 15 => 1,
+ 17 => 1,
+ 30 => 1,
+ 31 => 1,
+ 33 => 1,
+ 41 => 1,
+ 62 => 1,
+ 63 => 1,
+ 69 => 1,
+ 77 => 1,
+ 78 => 1,
+ 79 => 1,
+ 85 => 1,
+ 86 => 1,
+ 87 => 1,
+ 88 => 1,
+ 98 => 1,
+ 110 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc
index d0c136cbfa..91ab9d3975 100644
--- a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc
+++ b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc
@@ -5,7 +5,10 @@ interface MyInterface {}
interface YourInterface {}
trait MyTrait {}
trait YourTrait {}
+enum MyEnum {}
+enum YourEnum {}
class MyClass {}
interface MyInterface {}
trait MyTrait {}
-?>
\ No newline at end of file
+enum MyEnum {}
+?>
diff --git a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.2.inc b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.2.inc
index e6f92eb130..6829748a5f 100644
--- a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.2.inc
+++ b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.2.inc
@@ -2,4 +2,5 @@
class MyClass {}
interface MyInterface {}
trait MyTrait {}
+enum MyEnum {}
?>
\ No newline at end of file
diff --git a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php
index b3b3edb502..68982f8686 100644
--- a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php
+++ b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php
@@ -45,9 +45,10 @@ public function getWarningList($testFile='')
switch ($testFile) {
case 'DuplicateClassNameUnitTest.1.inc':
return [
- 8 => 1,
- 9 => 1,
10 => 1,
+ 11 => 1,
+ 12 => 1,
+ 13 => 1,
];
break;
case 'DuplicateClassNameUnitTest.2.inc':
@@ -55,6 +56,7 @@ public function getWarningList($testFile='')
2 => 1,
3 => 1,
4 => 1,
+ 5 => 1,
];
break;
case 'DuplicateClassNameUnitTest.5.inc':
diff --git a/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc b/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc
index 8147b4c73e..fd3abc030e 100644
--- a/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc
+++ b/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc
@@ -89,3 +89,7 @@ class Test_Class_Bad_G
/*some comment*/
{
}
+
+enum Test_Enum
+{
+}
diff --git a/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc.fixed b/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc.fixed
index 406eb230fd..a755ae4725 100644
--- a/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc.fixed
@@ -89,3 +89,7 @@ class Test_Class_Bad_G
/*some comment*/ {
}
+
+enum Test_Enum {
+
+}
diff --git a/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.php b/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.php
index 61cd4c9e8f..c357afc927 100644
--- a/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.php
+++ b/src/Standards/Generic/Tests/Classes/OpeningBraceSameLineUnitTest.php
@@ -38,6 +38,7 @@ public function getErrorList()
70 => 1,
79 => 1,
90 => 1,
+ 94 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php b/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php
index b613748414..269102dd3e 100644
--- a/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php
+++ b/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php
@@ -25,7 +25,7 @@ protected function shouldSkipTest()
// PEAR doesn't preserve the executable flag, so skip
// tests when running in a PEAR install.
// Also skip on Windows which doesn't have the concept of executable files.
- return ($GLOBALS['PHP_CODESNIFFER_PEAR'] || (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'));
+ return ($GLOBALS['PHP_CODESNIFFER_PEAR'] || stripos(PHP_OS, 'WIN') === 0);
}//end shouldSkipTest()
diff --git a/src/Standards/Generic/Tests/Files/OneObjectStructurePerFileUnitTest.inc b/src/Standards/Generic/Tests/Files/OneObjectStructurePerFileUnitTest.inc
index 94cebb4ee2..9f76d5d8d0 100644
--- a/src/Standards/Generic/Tests/Files/OneObjectStructurePerFileUnitTest.inc
+++ b/src/Standards/Generic/Tests/Files/OneObjectStructurePerFileUnitTest.inc
@@ -12,10 +12,15 @@ class baz {
}
trait barTrait {
-
+
}
interface barInterface {
}
-?>
\ No newline at end of file
+
+enum barEnum {
+
+}
+
+?>
diff --git a/src/Standards/Generic/Tests/Files/OneObjectStructurePerFileUnitTest.php b/src/Standards/Generic/Tests/Files/OneObjectStructurePerFileUnitTest.php
index f429f98095..36a4bb9385 100644
--- a/src/Standards/Generic/Tests/Files/OneObjectStructurePerFileUnitTest.php
+++ b/src/Standards/Generic/Tests/Files/OneObjectStructurePerFileUnitTest.php
@@ -30,6 +30,7 @@ public function getErrorList()
10 => 1,
14 => 1,
18 => 1,
+ 22 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc
index c46517df04..7c86a89f01 100644
--- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc
+++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc
@@ -153,3 +153,12 @@ $foobar = functionCallAnonClassParam(
$result = myFunction(param1: $arg1, param2: $arg2);
$result = myFunction(param1: $arg1 , param2:$arg2);
$result = myFunction(param1: $arg1, param2:$arg2, param3: $arg3,param4:$arg4, param5:$arg5);
+
+class Testing extends Bar
+{
+ public static function baz($foo, $bar)
+ {
+ $a = new parent($foo, $bar);
+ $a = new parent($foo ,$bar);
+ }
+}
diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed
index 1b51933eb9..8c9800725a 100644
--- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed
@@ -153,3 +153,12 @@ $foobar = functionCallAnonClassParam(
$result = myFunction(param1: $arg1, param2: $arg2);
$result = myFunction(param1: $arg1, param2:$arg2);
$result = myFunction(param1: $arg1, param2:$arg2, param3: $arg3, param4:$arg4, param5:$arg5);
+
+class Testing extends Bar
+{
+ public static function baz($foo, $bar)
+ {
+ $a = new parent($foo, $bar);
+ $a = new parent($foo, $bar);
+ }
+}
diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php
index 9f83fc9b77..789b60b37c 100644
--- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php
+++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php
@@ -54,6 +54,7 @@ public function getErrorList()
134 => 1,
154 => 2,
155 => 1,
+ 162 => 2,
];
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc
index f6c6bb757b..494dcc7694 100644
--- a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc
+++ b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc
@@ -433,4 +433,22 @@ function complexityFourteenWithMatch()
};
}
+
+function complexitySevenWithNullSafeOperator()
+{
+ $foo = $object1->getX()?->getY()?->getZ();
+ $bar = $object2->getX()?->getY()?->getZ();
+ $baz = $object3->getX()?->getY()?->getZ();
+}
+
+
+function complexityElevenWithNullSafeOperator()
+{
+ $foo = $object1->getX()?->getY()?->getZ();
+ $bar = $object2->getX()?->getY()?->getZ();
+ $baz = $object3->getX()?->getY()?->getZ();
+ $bacon = $object4->getX()?->getY()?->getZ();
+ $bits = $object5->getX()?->getY()?->getZ();
+}
+
?>
diff --git a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php
index 92635fc44d..d3860dff9e 100644
--- a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php
+++ b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php
@@ -49,6 +49,7 @@ public function getWarningList()
333 => 1,
381 => 1,
417 => 1,
+ 445 => 1,
];
}//end getWarningList()
diff --git a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc
index 9bda11472e..8441060d27 100644
--- a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc
+++ b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc
@@ -165,3 +165,21 @@ abstract class My_Class {
public function my_class() {}
public function _MY_CLASS() {}
}
+
+enum Suit: string implements Colorful, CardGame {
+ // Magic methods.
+ function __call($name, $args) {}
+ static function __callStatic($name, $args) {}
+ function __invoke() {}
+
+ // Valid Method Name.
+ public function getSomeValue() {}
+
+ // Double underscore non-magic methods not allowed.
+ function __myFunction() {}
+ function __my_function() {}
+
+ // Non-camelcase.
+ public function parseMyDSN() {}
+ public function get_some_value() {}
+}
diff --git a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php
index 502498d8bc..3d0320c3ae 100644
--- a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php
+++ b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php
@@ -63,6 +63,10 @@ public function getErrorList()
147 => 2,
158 => 1,
159 => 1,
+ 179 => 1,
+ 180 => 2,
+ 183 => 1,
+ 184 => 1,
];
return $errors;
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc
index 63aea51872..0307a0559d 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc
@@ -81,3 +81,20 @@ var_dump(MyClass::TRUE);
function tRUE() {}
$input->getFilterChain()->attachByName('Null', ['type' => Null::TYPE_STRING]);
+
+// Issue #3332 - ignore type declarations, but not default values.
+class TypedThings {
+ const MYCONST = FALSE;
+
+ public int|FALSE $int = FALSE;
+ public Type|NULL $int = new MyObj(NULL);
+
+ private function typed(int|FALSE $param = NULL, Type|NULL $obj = new MyObj(FALSE)) : string|FALSE|NULL
+ {
+ if (TRUE === FALSE) {
+ return NULL;
+ }
+ }
+}
+
+$cl = function (int|FALSE $param = NULL, Type|NULL $obj = new MyObj(FALSE)) : string|FALSE|NULL {};
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc.fixed
index b3c3d8a136..3a6b094c86 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.inc.fixed
@@ -81,3 +81,20 @@ var_dump(MyClass::TRUE);
function tRUE() {}
$input->getFilterChain()->attachByName('Null', ['type' => Null::TYPE_STRING]);
+
+// Issue #3332 - ignore type declarations, but not default values.
+class TypedThings {
+ const MYCONST = false;
+
+ public int|FALSE $int = false;
+ public Type|NULL $int = new MyObj(null);
+
+ private function typed(int|FALSE $param = null, Type|NULL $obj = new MyObj(false)) : string|FALSE|NULL
+ {
+ if (true === false) {
+ return null;
+ }
+ }
+}
+
+$cl = function (int|FALSE $param = null, Type|NULL $obj = new MyObj(false)) : string|FALSE|NULL {};
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php
index 85e8f7011d..2fb2d6f6dc 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php
@@ -30,20 +30,27 @@ public function getErrorList($testFile='LowerCaseConstantUnitTest.inc')
switch ($testFile) {
case 'LowerCaseConstantUnitTest.inc':
return [
- 7 => 1,
- 10 => 1,
- 15 => 1,
- 16 => 1,
- 23 => 1,
- 26 => 1,
- 31 => 1,
- 32 => 1,
- 39 => 1,
- 42 => 1,
- 47 => 1,
- 48 => 1,
- 70 => 1,
- 71 => 1,
+ 7 => 1,
+ 10 => 1,
+ 15 => 1,
+ 16 => 1,
+ 23 => 1,
+ 26 => 1,
+ 31 => 1,
+ 32 => 1,
+ 39 => 1,
+ 42 => 1,
+ 47 => 1,
+ 48 => 1,
+ 70 => 1,
+ 71 => 1,
+ 87 => 1,
+ 89 => 1,
+ 90 => 1,
+ 92 => 2,
+ 94 => 2,
+ 95 => 1,
+ 100 => 2,
];
break;
case 'LowerCaseConstantUnitTest.js':
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc
index 8d003c3bcc..37579d3217 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc
@@ -35,5 +35,14 @@ $r = Match ($x) {
DEFAULT, => 3,
};
+class Reading {
+ Public READOnly int $var;
+}
+
+EnuM ENUM: string
+{
+ Case HEARTS;
+}
+
__HALT_COMPILER(); // An exception due to phar support.
function
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed
index bbe76b9e18..7063327ae8 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed
@@ -35,5 +35,14 @@ $r = match ($x) {
default, => 3,
};
+class Reading {
+ public readonly int $var;
+}
+
+enum ENUM: string
+{
+ case HEARTS;
+}
+
__HALT_COMPILER(); // An exception due to phar support.
function
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php
index b272196b8d..6d08e12751 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php
@@ -40,6 +40,9 @@ public function getErrorList()
31 => 1,
32 => 1,
35 => 1,
+ 39 => 2,
+ 42 => 1,
+ 44 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc
index 8e2dca17e0..011adcd530 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc
@@ -81,3 +81,14 @@ class ConstructorPropertyPromotionWithTypes {
class ConstructorPropertyPromotionAndNormalParams {
public function __construct(public Int $promotedProp, ?Int $normalArg) {}
}
+
+function (): NeVeR {
+ exit;
+};
+
+function intersectionParamTypes (\Package\ClassName&\Package\Other_Class $var) {}
+
+function intersectionReturnTypes ($var): \Package\ClassName&\Package\Other_Class {}
+
+$arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : int => $a * $b;
+$arrow = fn (Int $a, String $b, BOOL $c, Array $d, Foo\Bar $e) : Float => $a * $b;
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed
index 6e03f7c2d5..d866101b6f 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed
@@ -81,3 +81,14 @@ class ConstructorPropertyPromotionWithTypes {
class ConstructorPropertyPromotionAndNormalParams {
public function __construct(public int $promotedProp, ?int $normalArg) {}
}
+
+function (): never {
+ exit;
+};
+
+function intersectionParamTypes (\Package\ClassName&\Package\Other_Class $var) {}
+
+function intersectionReturnTypes ($var): \Package\ClassName&\Package\Other_Class {}
+
+$arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : int => $a * $b;
+$arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : float => $a * $b;
diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php
index d447e8f47e..fa05aba6a3 100644
--- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php
+++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php
@@ -65,6 +65,8 @@ public function getErrorList()
74 => 3,
78 => 3,
82 => 2,
+ 85 => 1,
+ 94 => 5,
];
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc
index 965bf3b511..30c6d2980d 100644
--- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc
+++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc
@@ -78,4 +78,21 @@ class MyClass
var_dump(MyClass::true);
-function true() {}
\ No newline at end of file
+function true() {}
+
+// Issue #3332 - ignore type declarations, but not default values.
+class TypedThings {
+ const MYCONST = false;
+
+ public int|false $int = false;
+ public Type|null $int = new MyObj(null);
+
+ private function typed(int|false $param = null, Type|null $obj = new MyObj(false)) : string|false|null
+ {
+ if (true === false) {
+ return null;
+ }
+ }
+}
+
+$cl = function (int|false $param = null, Type|null $obj = new MyObj(false)) : string|false|null {};
diff --git a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed
index ae83dc91de..7705198c81 100644
--- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc.fixed
@@ -78,4 +78,21 @@ class MyClass
var_dump(MyClass::true);
-function true() {}
\ No newline at end of file
+function true() {}
+
+// Issue #3332 - ignore type declarations, but not default values.
+class TypedThings {
+ const MYCONST = FALSE;
+
+ public int|false $int = FALSE;
+ public Type|null $int = new MyObj(NULL);
+
+ private function typed(int|false $param = NULL, Type|null $obj = new MyObj(FALSE)) : string|false|null
+ {
+ if (TRUE === FALSE) {
+ return NULL;
+ }
+ }
+}
+
+$cl = function (int|false $param = NULL, Type|null $obj = new MyObj(FALSE)) : string|false|null {};
diff --git a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php
index 0bdafe6a43..30e5776337 100644
--- a/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php
+++ b/src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php
@@ -40,6 +40,13 @@ public function getErrorList()
48 => 1,
70 => 1,
71 => 1,
+ 85 => 1,
+ 87 => 1,
+ 88 => 1,
+ 90 => 2,
+ 92 => 2,
+ 93 => 1,
+ 98 => 2,
];
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc b/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc
index bad3998f97..2399a387e1 100644
--- a/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc
+++ b/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc
@@ -163,3 +163,15 @@ $a = (
if (true) {} ( 1+2) === 3 ? $a = 1 : $a = 2;
class A {} ( 1+2) === 3 ? $a = 1 : $a = 2;
function foo() {} ( 1+2) === 3 ? $a = 1 : $a = 2;
+
+// Issue #3618.
+class NonArbitraryParenthesesWithKeywords {
+ public static function baz( $foo, $bar ) {
+ $a = new self();
+ $b = new parent();
+ $c = new static();
+
+ // self/static are already tested above, round line 45.
+ $d = new parent( $foo,$bar );
+ }
+}
diff --git a/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc.fixed
index 08fcd6244a..9162728e17 100644
--- a/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc.fixed
@@ -151,3 +151,15 @@ $a = (
if (true) {} (1+2) === 3 ? $a = 1 : $a = 2;
class A {} (1+2) === 3 ? $a = 1 : $a = 2;
function foo() {} (1+2) === 3 ? $a = 1 : $a = 2;
+
+// Issue #3618.
+class NonArbitraryParenthesesWithKeywords {
+ public static function baz( $foo, $bar ) {
+ $a = new self();
+ $b = new parent();
+ $c = new static();
+
+ // self/static are already tested above, round line 45.
+ $d = new parent( $foo,$bar );
+ }
+}
diff --git a/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.3.inc b/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.3.inc
new file mode 100644
index 0000000000..68b18938d0
--- /dev/null
+++ b/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.3.inc
@@ -0,0 +1,13 @@
+ 1,
93 => 1,
];
- break;
+
case 'DisallowTabIndentUnitTest.2.inc':
return [
6 => 1,
@@ -96,23 +96,33 @@ public function getErrorList($testFile='')
13 => 1,
19 => 1,
];
- break;
+
+ case 'DisallowTabIndentUnitTest.3.inc':
+ if (\PHP_VERSION_ID >= 70300) {
+ return [
+ 7 => 1,
+ 13 => 1,
+ ];
+ }
+
+ // PHP 7.2 or lower: PHP version which doesn't support flexible heredocs/nowdocs yet.
+ return [];
+
case 'DisallowTabIndentUnitTest.js':
return [
3 => 1,
5 => 1,
6 => 1,
];
- break;
+
case 'DisallowTabIndentUnitTest.css':
return [
1 => 1,
2 => 1,
];
- break;
+
default:
return [];
- break;
}//end switch
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc
index 6aadcc2095..4061aff567 100644
--- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc
+++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc
@@ -1556,6 +1556,29 @@ $a = [
]
];
+switch ($foo) {
+ case 'a':
+ $foo = match ($foo) {
+ 'bar' => 'custom_1',
+ default => 'a'
+ };
+ return $foo;
+ case 'b':
+ return match ($foo) {
+ 'bar' => 'custom_1',
+ default => 'b'
+ };
+ default:
+ return 'default';
+}
+
+foo(function ($foo) {
+ return [
+ match ($foo) {
+ }
+ ];
+});
+
/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */
?>
@@ -1564,7 +1587,7 @@ $a = [
'custom_1',
+ default => 'a'
+ };
+ return $foo;
+ case 'b':
+ return match ($foo) {
+ 'bar' => 'custom_1',
+ default => 'b'
+ };
+ default:
+ return 'default';
+}
+
+foo(function ($foo) {
+ return [
+ match ($foo) {
+ }
+ ];
+});
+
/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */
?>
@@ -1564,7 +1587,7 @@ $a = [
'custom_1',
+ default => 'a'
+ };
+ return $foo;
+ case 'b':
+ return match ($foo) {
+ 'bar' => 'custom_1',
+ default => 'b'
+ };
+ default:
+ return 'default';
+}
+
+foo(function ($foo) {
+ return [
+ match ($foo) {
+ }
+ ];
+});
+
/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */
?>
@@ -1564,7 +1587,7 @@ $a = [
'custom_1',
+ default => 'a'
+ };
+ return $foo;
+ case 'b':
+ return match ($foo) {
+ 'bar' => 'custom_1',
+ default => 'b'
+ };
+ default:
+ return 'default';
+}
+
+foo(function ($foo) {
+ return [
+ match ($foo) {
+ }
+ ];
+});
+
/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */
?>
@@ -1564,7 +1587,7 @@ $a = [
1,
1529 => 1,
1530 => 1,
- 1567 => 1,
- 1568 => 1,
- 1569 => 1,
- 1570 => 1,
+ 1590 => 1,
+ 1591 => 1,
+ 1592 => 1,
+ 1593 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Generic/Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc b/src/Standards/Generic/Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc
index edce4dcc69..fb5c181429 100644
--- a/src/Standards/Generic/Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc
+++ b/src/Standards/Generic/Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc
@@ -67,7 +67,13 @@ function bar( & ... $spread ) {
);
}
+// Ignore PHP 8.1 first class callable declarations.
+$map = array_map(strtolower(...), $map);
+
// phpcs:set Generic.WhiteSpace.SpreadOperatorSpacingAfter spacing 0
+// Ignore PHP 8.1 first class callable declarations.
+$map = array_map(strtolower( ... ), $map);
+
// Intentional parse error. This has to be the last test in the file.
function bar( ...
diff --git a/src/Standards/Generic/Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc.fixed
index efec7ac15c..9388acfc63 100644
--- a/src/Standards/Generic/Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc.fixed
+++ b/src/Standards/Generic/Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc.fixed
@@ -62,7 +62,13 @@ function bar( & ... $spread ) {
);
}
+// Ignore PHP 8.1 first class callable declarations.
+$map = array_map(strtolower(...), $map);
+
// phpcs:set Generic.WhiteSpace.SpreadOperatorSpacingAfter spacing 0
+// Ignore PHP 8.1 first class callable declarations.
+$map = array_map(strtolower( ... ), $map);
+
// Intentional parse error. This has to be the last test in the file.
function bar( ...
diff --git a/src/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php b/src/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php
index a5b07a9e47..dbd61f3d23 100644
--- a/src/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php
+++ b/src/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php
@@ -27,6 +27,7 @@ public function register()
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
];
}//end register()
diff --git a/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php
index ac3351e02b..a01ea2cc7e 100644
--- a/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php
+++ b/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php
@@ -27,6 +27,7 @@ public function register()
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
];
}//end register()
diff --git a/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php
index e0672ffe3a..6a44584686 100644
--- a/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php
+++ b/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php
@@ -161,6 +161,7 @@ public function process(File $phpcsFile, $stackPtr)
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
T_FUNCTION,
T_CLOSURE,
T_PUBLIC,
diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php
index 3a339abe4c..b5e8695c15 100644
--- a/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php
+++ b/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php
@@ -390,11 +390,14 @@ public function processMultiLineCall(File $phpcsFile, $stackPtr, $openBracket, $
$padding = str_repeat(' ', $functionIndent);
if ($foundFunctionIndent === 0) {
$phpcsFile->fixer->addContentBefore($first, $padding);
+ } else if ($tokens[$first]['code'] === T_INLINE_HTML) {
+ $newContent = $padding.ltrim($tokens[$first]['content']);
+ $phpcsFile->fixer->replaceToken($first, $newContent);
} else {
$phpcsFile->fixer->replaceToken(($first - 1), $padding);
}
}
- }
+ }//end if
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($openBracket + 1), null, true);
if ($tokens[$next]['line'] === $tokens[$openBracket]['line']) {
diff --git a/src/Standards/PEAR/Sniffs/NamingConventions/ValidClassNameSniff.php b/src/Standards/PEAR/Sniffs/NamingConventions/ValidClassNameSniff.php
index 34ca2830b5..00e68bfec5 100644
--- a/src/Standards/PEAR/Sniffs/NamingConventions/ValidClassNameSniff.php
+++ b/src/Standards/PEAR/Sniffs/NamingConventions/ValidClassNameSniff.php
@@ -27,6 +27,7 @@ public function register()
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
];
}//end register()
diff --git a/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc b/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc
index d97ef4d29d..6942944b5c 100644
--- a/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc
+++ b/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc
@@ -110,3 +110,5 @@ if (!class_exists('ClassOpeningBraceTooMuchIndentation')) {
{
}
}
+
+enum IncorrectBracePlacement {}
diff --git a/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc.fixed b/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc.fixed
index 5b0a2f93fb..26688b1527 100644
--- a/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc.fixed
+++ b/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc.fixed
@@ -119,3 +119,7 @@ if (!class_exists('ClassOpeningBraceTooMuchIndentation')) {
{
}
}
+
+enum IncorrectBracePlacement
+{
+}
diff --git a/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.php b/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.php
index 1514a70378..4c1d28e733 100644
--- a/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.php
+++ b/src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.php
@@ -61,6 +61,7 @@ public function getErrorList($testFile='')
99 => 1,
104 => 1,
110 => 1,
+ 114 => 1,
];
default:
diff --git a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc
index 8414efbedb..da53b99e87 100644
--- a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc
+++ b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc
@@ -120,6 +120,16 @@ trait Empty_Trait_Doc
}//end trait
+/**
+ *
+ *
+ */
+enum Empty_Enum_Doc
+{
+
+}//end enum
+
+
/**
* Sample class comment
*
diff --git a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php
index 9a4bcf7dd6..004a064b35 100644
--- a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php
+++ b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php
@@ -44,6 +44,7 @@ public function getErrorList()
96 => 5,
106 => 5,
116 => 5,
+ 126 => 5,
];
}//end getErrorList()
diff --git a/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.3.inc b/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.3.inc
new file mode 100644
index 0000000000..c076fd4572
--- /dev/null
+++ b/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.3.inc
@@ -0,0 +1,8 @@
+ 1];
+ case 'FileCommentUnitTest.3.inc':
+ return [1 => 1];
+
default:
return [];
}//end switch
diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc
index ed3d2c43e1..612748fedf 100644
--- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc
+++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc
@@ -548,3 +548,22 @@ array_fill_keys(
), value: true,
);
// phpcs:set PEAR.Functions.FunctionCallSignature allowMultipleArguments true
+
+?>
+
+
+
+content
+
diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed
index 8d02e7467d..00226de562 100644
--- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed
+++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed
@@ -563,3 +563,22 @@ array_fill_keys(
value: true,
);
// phpcs:set PEAR.Functions.FunctionCallSignature allowMultipleArguments true
+
+?>
+
+
+
+content
+
diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.php b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.php
index 1dd59188f3..4984de2bf6 100644
--- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.php
+++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.php
@@ -131,6 +131,9 @@ public function getErrorList($testFile='FunctionCallSignatureUnitTest.inc')
546 => 1,
547 => 1,
548 => 1,
+ 559 => 1,
+ 567 => 1,
+ 568 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc b/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc
index c6d15df7f0..053a4fee2f 100644
--- a/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc
+++ b/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc
@@ -66,3 +66,25 @@ trait _Invalid_Name {}
trait ___ {}
trait Invalid__Name {}
+
+enum Valid_Name: string {}
+
+enum invalid_Name : String {}
+
+enum invalid_name {}
+
+enum Invalid_name: Int {}
+
+enum VALID_Name {}
+
+enum VALID_NAME {}
+
+enum VALID_Name : int {}
+
+enum ValidName {}
+
+enum _Invalid_Name {}
+
+enum ___ {}
+
+enum Invalid__Name {}
diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.php b/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.php
index 8ff9c674b2..54ee74aac1 100644
--- a/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.php
+++ b/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.php
@@ -44,6 +44,12 @@ public function getErrorList()
64 => 1,
66 => 2,
68 => 1,
+ 72 => 1,
+ 74 => 2,
+ 76 => 1,
+ 86 => 1,
+ 88 => 2,
+ 90 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc
index 78280cdd90..18b1a48176 100644
--- a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc
+++ b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc
@@ -220,3 +220,24 @@ abstract class My_Class {
public function my_class() {}
public function _MY_CLASS() {}
}
+
+enum Suit: string implements Colorful, CardGame {
+ // Magic methods.
+ function __call($name, $args) {}
+ static function __callStatic($name, $args) {}
+ function __invoke() {}
+
+ // Valid Method Name.
+ public function parseMyDSN() {}
+ private function _getAnotherValue() {}
+
+ // Double underscore non-magic methods not allowed.
+ function __myFunction() {}
+ function __my_function() {}
+
+ // Non-camelcase.
+ public function get_some_value() {}
+
+ // Private without underscore prefix.
+ private function getMe() {}
+}
diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php
index 9bb6de0d84..4639a1e2ab 100644
--- a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php
+++ b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php
@@ -122,6 +122,10 @@ public function getErrorList()
212 => 1,
213 => 1,
214 => 1,
+ 235 => 1,
+ 236 => 2,
+ 239 => 1,
+ 242 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc
index c52023781f..3f90067920 100644
--- a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc
+++ b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc
@@ -152,3 +152,13 @@ $match = match ($test) {
1 => 'a',
2 => 'b'
};
+
+enum Enum
+{
+}
+
+enum Suits {}
+
+enum Cards
+{
+ }
diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed
index 23156e4100..f369c2b2f6 100644
--- a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed
+++ b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed
@@ -157,3 +157,14 @@ $match = match ($test) {
1 => 'a',
2 => 'b'
};
+
+enum Enum
+{
+}
+
+enum Suits {
+}
+
+enum Cards
+{
+}
diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php
index 1b01ee9d4e..265932549a 100644
--- a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php
+++ b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php
@@ -42,6 +42,8 @@ public function getErrorList()
146 => 1,
149 => 1,
154 => 1,
+ 160 => 1,
+ 164 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php b/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php
index ac6407d6f1..3db26f6fd1 100644
--- a/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php
+++ b/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php
@@ -27,6 +27,7 @@ public function register()
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
];
}//end register()
@@ -50,7 +51,7 @@ public function process(File $phpcsFile, $stackPtr)
$errorData = [strtolower($tokens[$stackPtr]['content'])];
- $nextClass = $phpcsFile->findNext([T_CLASS, T_INTERFACE, T_TRAIT], ($tokens[$stackPtr]['scope_closer'] + 1));
+ $nextClass = $phpcsFile->findNext([T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], ($tokens[$stackPtr]['scope_closer'] + 1));
if ($nextClass !== false) {
$error = 'Each %s must be in a file by itself';
$phpcsFile->addError($error, $nextClass, 'MultipleClasses', $errorData);
@@ -59,7 +60,7 @@ public function process(File $phpcsFile, $stackPtr)
$phpcsFile->recordMetric($stackPtr, 'One class per file', 'yes');
}
- $namespace = $phpcsFile->findNext([T_NAMESPACE, T_CLASS, T_INTERFACE, T_TRAIT], 0);
+ $namespace = $phpcsFile->findNext([T_NAMESPACE, T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], 0);
if ($tokens[$namespace]['code'] !== T_NAMESPACE) {
$error = 'Each %s must be in a namespace of at least one level (a top-level vendor name)';
$phpcsFile->addError($error, $stackPtr, 'MissingNamespace', $errorData);
diff --git a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php
index 27454cc1d7..3f8c5e0d56 100644
--- a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php
+++ b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php
@@ -82,6 +82,7 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens)
T_CLASS => T_CLASS,
T_INTERFACE => T_INTERFACE,
T_TRAIT => T_TRAIT,
+ T_ENUM => T_ENUM,
T_FUNCTION => T_FUNCTION,
];
diff --git a/src/Standards/PSR1/Tests/Classes/ClassDeclarationUnitTest.3.inc b/src/Standards/PSR1/Tests/Classes/ClassDeclarationUnitTest.3.inc
new file mode 100644
index 0000000000..302908eb12
--- /dev/null
+++ b/src/Standards/PSR1/Tests/Classes/ClassDeclarationUnitTest.3.inc
@@ -0,0 +1,3 @@
+ T_NS_SEPARATOR,
T_SELF => T_SELF,
T_STATIC => T_STATIC,
+ T_PARENT => T_PARENT,
T_VARIABLE => T_VARIABLE,
T_DOLLAR => T_DOLLAR,
T_OBJECT_OPERATOR => T_OBJECT_OPERATOR,
diff --git a/src/Standards/PSR12/Sniffs/Classes/ClosingBraceSniff.php b/src/Standards/PSR12/Sniffs/Classes/ClosingBraceSniff.php
index 0f9752b1f4..fa1f8d60ed 100644
--- a/src/Standards/PSR12/Sniffs/Classes/ClosingBraceSniff.php
+++ b/src/Standards/PSR12/Sniffs/Classes/ClosingBraceSniff.php
@@ -27,6 +27,7 @@ public function register()
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
T_FUNCTION,
];
diff --git a/src/Standards/PSR12/Sniffs/Properties/ConstantVisibilitySniff.php b/src/Standards/PSR12/Sniffs/Properties/ConstantVisibilitySniff.php
index 8a1000f72f..7f63d1e87a 100644
--- a/src/Standards/PSR12/Sniffs/Properties/ConstantVisibilitySniff.php
+++ b/src/Standards/PSR12/Sniffs/Properties/ConstantVisibilitySniff.php
@@ -47,7 +47,10 @@ public function process(File $phpcsFile, $stackPtr)
return;
}
- $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
+ $ignore = Tokens::$emptyTokens;
+ $ignore[] = T_FINAL;
+
+ $prev = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true);
if (isset(Tokens::$scopeModifiers[$tokens[$prev]['code']]) === true) {
return;
}
diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc
index d933ee273e..9fd1548072 100644
--- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc
+++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc
@@ -42,3 +42,6 @@ $class = new ${$obj?->classname};
$anonWithAttribute = new #[SomeAttribute('summary')] class {
public const SOME_STUFF = 'foo';
};
+
+$foo = new parent();
+$foo = new parent;
diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed
index 02e3544fa6..aa9d0c7209 100644
--- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed
+++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed
@@ -42,3 +42,6 @@ $class = new ${$obj?->classname}();
$anonWithAttribute = new #[SomeAttribute('summary')] class {
public const SOME_STUFF = 'foo';
};
+
+$foo = new parent();
+$foo = new parent();
diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php
index 3fb1ab992b..0a16af8fc0 100644
--- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php
+++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php
@@ -43,6 +43,7 @@ public function getErrorList()
34 => 1,
37 => 1,
38 => 1,
+ 47 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.inc b/src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.inc
index 1d2e92c97e..2562d26c06 100644
--- a/src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.inc
+++ b/src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.inc
@@ -45,3 +45,8 @@ $instance = new class extends \Foo implements \HandleableInterface {
$app->get('/hello/{name}', function ($name) use ($app) {
return 'Hello ' . $app->escape($name);
});
+
+enum Foo4
+{
+
+}//end
diff --git a/src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.php b/src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.php
index 1deac1cdf5..d402f1bb94 100644
--- a/src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.php
+++ b/src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.php
@@ -31,6 +31,7 @@ public function getErrorList()
19 => 1,
24 => 1,
31 => 1,
+ 52 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc b/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc
index 509c48c316..2c41bde994 100644
--- a/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc
+++ b/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc
@@ -47,3 +47,11 @@ $instance = new class extends \Foo implements \HandleableInterface {
// Class content
};
+enum Valid
+{
+}
+
+enum Invalid
+{
+
+}
diff --git a/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc.fixed
index de3d4b49b0..ea58573392 100644
--- a/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc.fixed
+++ b/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc.fixed
@@ -39,3 +39,10 @@ $instance = new class extends \Foo implements \HandleableInterface {
// Class content
};
+enum Valid
+{
+}
+
+enum Invalid
+{
+}
diff --git a/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.php b/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.php
index 2ea4fd273e..71d9d6f48f 100644
--- a/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.php
+++ b/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.php
@@ -31,6 +31,7 @@ public function getErrorList()
24 => 1,
34 => 1,
41 => 1,
+ 55 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/PSR12/Tests/Files/ImportStatementUnitTest.inc b/src/Standards/PSR12/Tests/Files/ImportStatementUnitTest.inc
index 904e3f4080..6d024eaa95 100644
--- a/src/Standards/PSR12/Tests/Files/ImportStatementUnitTest.inc
+++ b/src/Standards/PSR12/Tests/Files/ImportStatementUnitTest.inc
@@ -17,3 +17,10 @@ class ClassName3
}
$foo = function() use($bar) {};
+
+enum SomeEnum
+{
+ use \FirstTrait;
+ use SecondTrait;
+ use ThirdTrait;
+}
diff --git a/src/Standards/PSR12/Tests/Files/ImportStatementUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Files/ImportStatementUnitTest.inc.fixed
index d5b3f67eed..dfed46fc17 100644
--- a/src/Standards/PSR12/Tests/Files/ImportStatementUnitTest.inc.fixed
+++ b/src/Standards/PSR12/Tests/Files/ImportStatementUnitTest.inc.fixed
@@ -17,3 +17,10 @@ class ClassName3
}
$foo = function() use($bar) {};
+
+enum SomeEnum
+{
+ use \FirstTrait;
+ use SecondTrait;
+ use ThirdTrait;
+}
diff --git a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc
index c07b1b91eb..84ea24b2e8 100644
--- a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc
+++ b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc
@@ -5,3 +5,18 @@ class Foo {
}
const APPLICATION_ENV = 'development';
+
+// Issue 3526, PHP 8.1 final constants.
+class SampleEnum
+{
+ final const FOO = 'SAMPLE';
+
+ public final const BAR = 'SAMPLE';
+
+ final private const BAZ = 'SAMPLE';
+}
+
+enum SomeEnum {
+ public const BAR = 'bar';
+ const BAZ = 'baz';
+}
diff --git a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php
index 3917eb6821..b738706da2 100644
--- a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php
+++ b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php
@@ -40,7 +40,11 @@ public function getErrorList()
*/
public function getWarningList()
{
- return [4 => 1];
+ return [
+ 4 => 1,
+ 12 => 1,
+ 21 => 1,
+ ];
}//end getWarningList()
diff --git a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc
index e62489fe03..c8ad746a73 100644
--- a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc
+++ b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc
@@ -207,3 +207,15 @@ class Foo implements Bar
*/
use Baz;
}
+
+enum SomeEnum1
+{
+ use FirstTrait;
+}
+
+enum SomeEnum2
+{
+
+ use FirstTrait;
+
+}
diff --git a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc.fixed
index 0090b4e931..1c5d8185c0 100644
--- a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc.fixed
+++ b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc.fixed
@@ -201,3 +201,13 @@ class Foo implements Bar
*/
use Baz;
}
+
+enum SomeEnum1
+{
+ use FirstTrait;
+}
+
+enum SomeEnum2
+{
+ use FirstTrait;
+}
diff --git a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php
index c406805a9b..797a2912e7 100644
--- a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php
+++ b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php
@@ -47,6 +47,7 @@ public function getErrorList()
165 => 1,
170 => 1,
208 => 1,
+ 219 => 3,
];
}//end getErrorList()
diff --git a/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php
index 8a158d966d..efdbb43827 100644
--- a/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php
+++ b/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php
@@ -41,6 +41,7 @@ protected function processMemberVar(File $phpcsFile, $stackPtr)
$find = Tokens::$scopeModifiers;
$find[] = T_VARIABLE;
$find[] = T_VAR;
+ $find[] = T_READONLY;
$find[] = T_SEMICOLON;
$find[] = T_OPEN_CURLY_BRACKET;
diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php
index e36b134247..de81c530ee 100644
--- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php
+++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php
@@ -247,7 +247,7 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end)
{
$tokens = $phpcsFile->getTokens();
- $lastToken = $phpcsFile->findPrevious(T_WHITESPACE, ($end - 1), $stackPtr, true);
+ $lastToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($end - 1), $stackPtr, true);
if ($lastToken === false) {
return false;
}
diff --git a/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php
index 21a9dbe9a3..aba9caa717 100644
--- a/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php
+++ b/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php
@@ -285,7 +285,7 @@ private function shouldIgnoreUse($phpcsFile, $stackPtr)
}
// Ignore USE keywords for traits.
- if ($phpcsFile->hasCondition($stackPtr, [T_CLASS, T_TRAIT]) === true) {
+ if ($phpcsFile->hasCondition($stackPtr, [T_CLASS, T_TRAIT, T_ENUM]) === true) {
return true;
}
diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc
index 031d2a8378..33bec44e70 100644
--- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc
+++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc
@@ -71,3 +71,13 @@ class MyClass
public int $var = null;
public static int/*comment*/$var = null;
}
+
+class ReadOnlyProp {
+ public readonly int $foo,
+ $bar,
+ $var = null;
+
+ protected readonly ?string $foo;
+
+ readonly array $foo;
+}
diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed
index aca7c2fcc3..df83112af2 100644
--- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed
+++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed
@@ -68,3 +68,13 @@ class MyClass
public int $var = null;
public static int /*comment*/$var = null;
}
+
+class ReadOnlyProp {
+ public readonly int $foo,
+ $bar,
+ $var = null;
+
+ protected readonly ?string $foo;
+
+ readonly array $foo;
+}
diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php
index 20da24d976..f1dd0194d2 100644
--- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php
+++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php
@@ -46,6 +46,9 @@ public function getErrorList()
69 => 1,
71 => 1,
72 => 1,
+ 76 => 1,
+ 80 => 1,
+ 82 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc
index c425a1f20d..2ca60a93e8 100644
--- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc
+++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc
@@ -511,7 +511,7 @@ switch ( $a ) {
case 1:
if ($a) {
try {
- return true;
+ return true; // Comment.
} catch (MyException $e) {
throw new Exception($e->getMessage());
}
@@ -584,3 +584,15 @@ switch ( $a ) {
$other = $code;
break;
}
+
+// Issue 3550 - comment after terminating statement.
+switch (rand()) {
+ case 1:
+ if (rand() === 1) {
+ break;
+ } else {
+ break; // comment
+ }
+ default:
+ break;
+}
diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed
index 47ae8d3de3..bbc8b7c48e 100644
--- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed
+++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed
@@ -506,7 +506,7 @@ switch ( $a ) {
case 1:
if ($a) {
try {
- return true;
+ return true; // Comment.
} catch (MyException $e) {
throw new Exception($e->getMessage());
}
@@ -579,3 +579,15 @@ switch ( $a ) {
$other = $code;
break;
}
+
+// Issue 3550 - comment after terminating statement.
+switch (rand()) {
+ case 1:
+ if (rand() === 1) {
+ break;
+ } else {
+ break; // comment
+ }
+ default:
+ break;
+}
diff --git a/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc
index 21c03119dd..096b44bc8a 100644
--- a/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc
+++ b/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc
@@ -64,3 +64,12 @@ class Nested_Function {
};
}
}
+
+enum MyEnum
+{
+ function _myFunction() {}
+ function __myFunction() {}
+ public static function myFunction() {}
+ static public function myFunction() {}
+ public function _() {}
+}
diff --git a/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc.fixed
index 5fb88613ed..eae8d28f5c 100644
--- a/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc.fixed
+++ b/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc.fixed
@@ -64,3 +64,12 @@ class Nested_Function {
};
}
}
+
+enum MyEnum
+{
+ function _myFunction() {}
+ function __myFunction() {}
+ public static function myFunction() {}
+ public static function myFunction() {}
+ public function _() {}
+}
diff --git a/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.php b/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.php
index 2d508d4c62..a8dcdfea58 100644
--- a/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.php
+++ b/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.php
@@ -40,6 +40,7 @@ public function getErrorList()
54 => 1,
56 => 3,
63 => 2,
+ 73 => 1,
];
}//end getErrorList()
@@ -61,6 +62,7 @@ public function getWarningList()
30 => 1,
46 => 1,
63 => 1,
+ 70 => 1,
];
}//end getWarningList()
diff --git a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc
index c4e83da440..61befc9e73 100644
--- a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc
+++ b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc
@@ -30,9 +30,14 @@ trait HelloWorld
use Hello, World;
}
+enum SomeEnum
+{
+ use Hello, World;
+}
+
$x = $foo ? function ($foo) use /* comment */ ($bar): int {
return 1;
} : $bar;
// Testcase must be on last line in the file.
-use
\ No newline at end of file
+use
diff --git a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php
index 17991bd79e..b45a4709de 100644
--- a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php
+++ b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php
@@ -374,6 +374,7 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array
|| $tokens[$nextToken]['code'] === T_OPEN_SHORT_ARRAY
|| $tokens[$nextToken]['code'] === T_CLOSURE
|| $tokens[$nextToken]['code'] === T_FN
+ || $tokens[$nextToken]['code'] === T_MATCH
) {
// Let subsequent calls of this test handle nested arrays.
if ($tokens[$lastToken]['code'] !== T_DOUBLE_ARROW) {
diff --git a/src/Standards/Squiz/Sniffs/Classes/ClassFileNameSniff.php b/src/Standards/Squiz/Sniffs/Classes/ClassFileNameSniff.php
index afbec4fa7e..88a7e0daae 100644
--- a/src/Standards/Squiz/Sniffs/Classes/ClassFileNameSniff.php
+++ b/src/Standards/Squiz/Sniffs/Classes/ClassFileNameSniff.php
@@ -27,6 +27,7 @@ public function register()
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
];
}//end register()
diff --git a/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php b/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php
index 10de719dca..ffddd2cde7 100644
--- a/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php
+++ b/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php
@@ -28,6 +28,7 @@ public function register()
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
];
}//end register()
@@ -58,7 +59,7 @@ public function process(File $phpcsFile, $stackPtr)
// starting with the number will be multiple tokens.
$opener = $tokens[$stackPtr]['scope_opener'];
$nameStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), $opener, true);
- $nameEnd = $phpcsFile->findNext(T_WHITESPACE, $nameStart, $opener);
+ $nameEnd = $phpcsFile->findNext([T_WHITESPACE, T_COLON], $nameStart, $opener);
if ($nameEnd === false) {
$name = $tokens[$nameStart]['content'];
} else {
diff --git a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php
index 6a12bb7368..eb647f5fe7 100644
--- a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php
+++ b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php
@@ -84,6 +84,7 @@ public function process(File $phpcsFile, $stackPtr)
T_CLASS => true,
T_INTERFACE => true,
T_TRAIT => true,
+ T_ENUM => true,
T_FUNCTION => true,
T_PUBLIC => true,
T_PRIVATE => true,
@@ -93,6 +94,7 @@ public function process(File $phpcsFile, $stackPtr)
T_ABSTRACT => true,
T_CONST => true,
T_VAR => true,
+ T_READONLY => true,
];
if (isset($ignore[$tokens[$nextToken]['code']]) === true) {
return;
diff --git a/src/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php
index 6ab6280f60..cd509d0c89 100644
--- a/src/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php
+++ b/src/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php
@@ -27,6 +27,7 @@ public function register()
T_FUNCTION,
T_CLASS,
T_INTERFACE,
+ T_ENUM,
];
}//end register()
@@ -69,8 +70,10 @@ public function process(File $phpcsFile, $stackPtr)
$comment = '//end '.$decName.'()';
} else if ($tokens[$stackPtr]['code'] === T_CLASS) {
$comment = '//end class';
- } else {
+ } else if ($tokens[$stackPtr]['code'] === T_INTERFACE) {
$comment = '//end interface';
+ } else {
+ $comment = '//end enum';
}//end if
if (isset($tokens[$stackPtr]['scope_closer']) === false) {
diff --git a/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php
index 2624bc2246..1f49d2c0c9 100644
--- a/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php
+++ b/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php
@@ -64,6 +64,8 @@ public function process(File $phpcsFile, $stackPtr)
$ignore = [
T_CLASS => true,
T_INTERFACE => true,
+ T_ENUM => true,
+ T_ENUM_CASE => true,
T_FUNCTION => true,
T_PUBLIC => true,
T_PRIVATE => true,
@@ -74,6 +76,7 @@ public function process(File $phpcsFile, $stackPtr)
T_OBJECT => true,
T_PROTOTYPE => true,
T_VAR => true,
+ T_READONLY => true,
];
if ($nextToken === false || isset($ignore[$tokens[$nextToken]['code']]) === false) {
diff --git a/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php
index 2685854769..08aaae29a6 100644
--- a/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php
+++ b/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php
@@ -95,6 +95,7 @@ public function process(File $phpcsFile, $stackPtr)
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
T_FUNCTION,
T_CLOSURE,
T_PUBLIC,
diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php
index a23032e280..ba3e1710f0 100644
--- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php
+++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php
@@ -245,6 +245,8 @@ protected function processThrows(File $phpcsFile, $stackPtr, $commentStart)
}
}
+ $comment = trim($comment);
+
// Starts with a capital letter and ends with a fullstop.
$firstChar = $comment[0];
if (strtoupper($firstChar) !== $firstChar) {
diff --git a/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php
index 7d7ee40e96..8ce950414a 100644
--- a/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php
+++ b/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php
@@ -74,6 +74,7 @@ public function process(File $phpcsFile, $stackPtr)
T_CLASS,
T_INTERFACE,
T_TRAIT,
+ T_ENUM,
T_FUNCTION,
T_CLOSURE,
T_PUBLIC,
diff --git a/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php
index a7731bb661..32e89789a7 100644
--- a/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php
+++ b/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php
@@ -35,6 +35,7 @@ public function processMemberVar(File $phpcsFile, $stackPtr)
T_PROTECTED => T_PROTECTED,
T_VAR => T_VAR,
T_STATIC => T_STATIC,
+ T_READONLY => T_READONLY,
T_WHITESPACE => T_WHITESPACE,
T_STRING => T_STRING,
T_NS_SEPARATOR => T_NS_SEPARATOR,
diff --git a/src/Standards/Squiz/Sniffs/ControlStructures/ForLoopDeclarationSniff.php b/src/Standards/Squiz/Sniffs/ControlStructures/ForLoopDeclarationSniff.php
index 6803f9abb4..370eab1890 100644
--- a/src/Standards/Squiz/Sniffs/ControlStructures/ForLoopDeclarationSniff.php
+++ b/src/Standards/Squiz/Sniffs/ControlStructures/ForLoopDeclarationSniff.php
@@ -222,7 +222,7 @@ public function process(File $phpcsFile, $stackPtr)
$semicolon = $openingBracket;
$targetNestinglevel = 0;
if (isset($tokens[$openingBracket]['conditions']) === true) {
- $targetNestinglevel += count($tokens[$openingBracket]['conditions']);
+ $targetNestinglevel = count($tokens[$openingBracket]['conditions']);
}
do {
diff --git a/src/Standards/Squiz/Sniffs/Files/FileExtensionSniff.php b/src/Standards/Squiz/Sniffs/Files/FileExtensionSniff.php
index bae882146d..6277b809e6 100644
--- a/src/Standards/Squiz/Sniffs/Files/FileExtensionSniff.php
+++ b/src/Standards/Squiz/Sniffs/Files/FileExtensionSniff.php
@@ -42,7 +42,7 @@ public function process(File $phpcsFile, $stackPtr)
$tokens = $phpcsFile->getTokens();
$fileName = $phpcsFile->getFilename();
$extension = substr($fileName, strrpos($fileName, '.'));
- $nextClass = $phpcsFile->findNext([T_CLASS, T_INTERFACE, T_TRAIT], $stackPtr);
+ $nextClass = $phpcsFile->findNext([T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], $stackPtr);
if ($nextClass !== false) {
$phpcsFile->recordMetric($stackPtr, 'File extension for class files', $extension);
diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php
index 60b113d3a9..8becb74a4e 100644
--- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php
+++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php
@@ -109,6 +109,7 @@ public function process(File $phpcsFile, $stackPtr)
T_OPEN_SHORT_ARRAY => true,
T_CASE => true,
T_EXIT => true,
+ T_MATCH_ARROW => true,
];
if (isset($invalidTokens[$tokens[$previousToken]['code']]) === true) {
@@ -141,6 +142,7 @@ public function process(File $phpcsFile, $stackPtr)
T_THIS,
T_SELF,
T_STATIC,
+ T_PARENT,
T_OBJECT_OPERATOR,
T_NULLSAFE_OBJECT_OPERATOR,
T_DOUBLE_COLON,
diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php
index 7c7e2246f5..9eb2124233 100644
--- a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php
+++ b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php
@@ -52,17 +52,18 @@ public function process(File $phpcsFile, $stackPtr)
}
}
- // Ignore values in array definitions.
- $array = $phpcsFile->findNext(
- T_ARRAY,
+ // Ignore values in array definitions or match structures.
+ $nextNonEmpty = $phpcsFile->findNext(
+ Tokens::$emptyTokens,
($stackPtr + 1),
null,
- false,
- null,
true
);
- if ($array !== false) {
+ if ($nextNonEmpty !== false
+ && ($tokens[$nextNonEmpty]['code'] === T_ARRAY
+ || $tokens[$nextNonEmpty]['code'] === T_MATCH)
+ ) {
return;
}
diff --git a/src/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php b/src/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php
index 8c60208c6c..8a34a4f29f 100644
--- a/src/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php
+++ b/src/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php
@@ -54,17 +54,8 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop
return;
}
- $modifier = null;
- for ($i = ($stackPtr - 1); $i > 0; $i--) {
- if ($tokens[$i]['line'] < $tokens[$stackPtr]['line']) {
- break;
- } else if (isset(Tokens::$scopeModifiers[$tokens[$i]['code']]) === true) {
- $modifier = $i;
- break;
- }
- }
-
- if ($modifier === null) {
+ $properties = $phpcsFile->getMethodProperties($stackPtr);
+ if ($properties['scope_specified'] === false) {
$error = 'Visibility must be declared on method "%s"';
$data = [$methodName];
$phpcsFile->addError($error, $stackPtr, 'Missing', $data);
diff --git a/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php b/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php
index 0bafbf4db4..f3b5495d90 100644
--- a/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php
+++ b/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php
@@ -22,7 +22,7 @@ class StaticThisUsageSniff extends AbstractScopeSniff
*/
public function __construct()
{
- parent::__construct([T_CLASS, T_TRAIT, T_ANON_CLASS], [T_FUNCTION]);
+ parent::__construct([T_CLASS, T_TRAIT, T_ENUM, T_ANON_CLASS], [T_FUNCTION]);
}//end __construct()
@@ -76,9 +76,9 @@ public function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope)
/**
* Check for $this variable usage between $next and $end tokens.
*
- * @param File $phpcsFile The current file being scanned.
- * @param int $next The position of the next token to check.
- * @param int $end The position of the last token to check.
+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being scanned.
+ * @param int $next The position of the next token to check.
+ * @param int $end The position of the last token to check.
*
* @return void
*/
diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php
index f38fd0e89b..808888f404 100644
--- a/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php
+++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php
@@ -145,6 +145,7 @@ public function process(File $phpcsFile, $stackPtr)
T_CLASS => true,
T_INTERFACE => true,
T_TRAIT => true,
+ T_ENUM => true,
T_DOC_COMMENT_OPEN_TAG => true,
];
diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php
index 2627d10d98..f1e2fce692 100644
--- a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php
+++ b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php
@@ -77,12 +77,13 @@ public function register()
// Returning/printing a negative value; eg. (return -1).
$this->nonOperandTokens += [
- T_RETURN => T_RETURN,
- T_ECHO => T_ECHO,
- T_EXIT => T_EXIT,
- T_PRINT => T_PRINT,
- T_YIELD => T_YIELD,
- T_FN_ARROW => T_FN_ARROW,
+ T_RETURN => T_RETURN,
+ T_ECHO => T_ECHO,
+ T_EXIT => T_EXIT,
+ T_PRINT => T_PRINT,
+ T_YIELD => T_YIELD,
+ T_FN_ARROW => T_FN_ARROW,
+ T_MATCH_ARROW => T_MATCH_ARROW,
];
// Trying to use a negative value; eg. myFunction($var, -2).
diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php
index ad995dc45b..2d800f0520 100644
--- a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php
+++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php
@@ -26,6 +26,7 @@ public function register()
{
$register = Tokens::$scopeModifiers;
$register[] = T_STATIC;
+ $register[] = T_READONLY;
return $register;
}//end register()
diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc
index 750aaebcc0..2774660c0c 100644
--- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc
+++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc
@@ -475,6 +475,13 @@ yield array(
static fn () : string => '',
);
+$foo = [
+ 'foo' => match ($anything) {
+ 'foo' => 'bar',
+ default => null,
+ },
+ ];
+
// Intentional syntax error.
$a = array(
'a' =>
diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed
index 3ecc091da8..b452006488 100644
--- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed
+++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed
@@ -511,6 +511,13 @@ yield array(
static fn () : string => '',
);
+$foo = [
+ 'foo' => match ($anything) {
+ 'foo' => 'bar',
+ default => null,
+ },
+ ];
+
// Intentional syntax error.
$a = array(
'a' =>
diff --git a/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc b/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc
index a346a00f21..8b5a5aa708 100644
--- a/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc
@@ -5,7 +5,8 @@
class ClassFileNameUnitTest {}
interface ClassFileNameUnitTest {}
trait ClassFileNameUnitTest {}
-
+enum ClassFileNameUnitTest {}
+enum ClassFileNameUnitTest: int {}
// Invalid filename matching class name (case sensitive).
class classFileNameUnitTest {}
@@ -17,6 +18,9 @@ interface CLASSFILENAMEUNITTEST {}
trait classFileNameUnitTest {}
trait classfilenameunittest {}
trait CLASSFILENAMEUNITTEST {}
+enum classFileNameUnitTest {}
+enum classfilenameunittest {}
+enum CLASSFILENAMEUNITTEST {}
// Invalid non-filename matching class names.
@@ -32,6 +36,10 @@ trait CompletelyWrongClassName {}
trait ClassFileNameUnitTestExtra {}
trait ClassFileNameUnitTestInc {}
trait ExtraClassFileNameUnitTest {}
+enum CompletelyWrongClassName {}
+enum ClassFileNameUnitTestExtra {}
+enum ClassFileNameUnitTestInc {}
+enum ExtraClassFileNameUnitTest {}
-?>
\ No newline at end of file
+?>
diff --git a/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.php b/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.php
index b229a2fc3f..5964d2b1ec 100644
--- a/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.php
+++ b/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.php
@@ -26,7 +26,6 @@ class ClassFileNameUnitTest extends AbstractSniffUnitTest
public function getErrorList()
{
return [
- 11 => 1,
12 => 1,
13 => 1,
14 => 1,
@@ -35,10 +34,10 @@ public function getErrorList()
17 => 1,
18 => 1,
19 => 1,
+ 20 => 1,
+ 21 => 1,
+ 22 => 1,
23 => 1,
- 24 => 1,
- 25 => 1,
- 26 => 1,
27 => 1,
28 => 1,
29 => 1,
@@ -47,6 +46,14 @@ public function getErrorList()
32 => 1,
33 => 1,
34 => 1,
+ 35 => 1,
+ 36 => 1,
+ 37 => 1,
+ 38 => 1,
+ 39 => 1,
+ 40 => 1,
+ 41 => 1,
+ 42 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc
index 511bbe4710..ea8cd89ec5 100644
--- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc
@@ -3,6 +3,7 @@ Abstract Class MyClass Extends MyClass {}
Final Class MyClass Implements MyInterface {}
Interface MyInterface {}
Trait MyTrait {}
+Enum MyEnum IMPLEMENTS Colorful {}
class MyClass
{
diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed
index 859d0d2db2..f573905217 100644
--- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed
@@ -3,6 +3,7 @@ abstract class MyClass extends MyClass {}
final class MyClass implements MyInterface {}
interface MyInterface {}
trait MyTrait {}
+enum MyEnum implements Colorful {}
class MyClass
{
diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php
index f2fc20b438..8c4d10c79a 100644
--- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php
+++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php
@@ -30,9 +30,10 @@ public function getErrorList()
3 => 3,
4 => 1,
5 => 1,
- 9 => 1,
+ 6 => 2,
10 => 1,
- 13 => 1,
+ 11 => 1,
+ 14 => 1,
];
return $errors;
diff --git a/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc b/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc
index aadbab5b8e..3fe39435b4 100644
--- a/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc
@@ -137,6 +137,50 @@ trait Base
}
}
+// Valid enum name.
+enum ValidCamelCaseClass: string {}
+
+
+// Incorrect usage of camel case.
+enum invalidCamelCaseClass {}
+enum Invalid_Camel_Case_Class_With_Underscores {}
+
+
+// All lowercase.
+enum invalidlowercaseclass: INT {}
+enum invalid_lowercase_class_with_underscores {}
+
+
+// All uppercase.
+enum VALIDUPPERCASECLASS: int {}
+enum INVALID_UPPERCASE_CLASS_WITH_UNDERSCORES {}
+
+
+// Mix camel case with uppercase.
+enum ValidCamelCaseClassWithUPPERCASE : string {}
+
+
+// Usage of numeric characters.
+enum ValidCamelCaseClassWith1Number {}
+enum ValidCamelCaseClassWith12345Numbers : string {}
+enum ValidCamelCaseClassEndingWithNumber5 {}
+
+enum Testing{}
+
+enum Base
+{
+ public function __construct()
+ {
+ $this->anonymous = new class extends ArrayObject
+ {
+ public function __construct()
+ {
+ parent::__construct(['a' => 1, 'b' => 2]);
+ }
+ };
+ }
+}
+
if ( class_exists( Test :: class ) ) {}
if ( class_exists( Test2 ::class ) ) {}
diff --git a/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.php b/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.php
index 70777c541b..b7de260b65 100644
--- a/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.php
+++ b/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.php
@@ -47,6 +47,11 @@ public function getErrorList()
108 => 1,
118 => 1,
120 => 1,
+ 145 => 1,
+ 146 => 1,
+ 150 => 1,
+ 151 => 1,
+ 156 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc
index eed554b3f3..7cd04a2114 100644
--- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc
@@ -288,3 +288,22 @@ final class MyClass
#[AttributeB]
final public function test() {}
}
+
+/**
+ * Comment should be ignored.
+ */
+abstract class MyClass
+{
+ /**
+ * Comment should be ignored.
+ */
+ readonly public string $prop;
+}
+
+/**
+ * Comment should be ignored
+ *
+ */
+enum MyEnum {
+
+}
diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed
index 7471b582a9..2e97614e2c 100644
--- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed
@@ -290,3 +290,22 @@ final class MyClass
#[AttributeB]
final public function test() {}
}
+
+/**
+ * Comment should be ignored.
+ */
+abstract class MyClass
+{
+ /**
+ * Comment should be ignored.
+ */
+ readonly public string $prop;
+}
+
+/**
+ * Comment should be ignored
+ *
+ */
+enum MyEnum {
+
+}
diff --git a/src/Standards/Squiz/Tests/Commenting/ClosingDeclarationCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/ClosingDeclarationCommentUnitTest.inc
index 9c3255cf9b..1a57149b26 100644
--- a/src/Standards/Squiz/Tests/Commenting/ClosingDeclarationCommentUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Commenting/ClosingDeclarationCommentUnitTest.inc
@@ -79,4 +79,8 @@ class TestClass
}
//end class
-?>
\ No newline at end of file
+enum MissingClosingComment {
+}
+
+enum HasClosingComment {
+}//end enum
diff --git a/src/Standards/Squiz/Tests/Commenting/ClosingDeclarationCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/ClosingDeclarationCommentUnitTest.php
index cdc89ba670..6f5166ec6b 100644
--- a/src/Standards/Squiz/Tests/Commenting/ClosingDeclarationCommentUnitTest.php
+++ b/src/Standards/Squiz/Tests/Commenting/ClosingDeclarationCommentUnitTest.php
@@ -34,6 +34,7 @@ public function getErrorList()
63 => 1,
67 => 1,
79 => 1,
+ 83 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc
index e7d880d682..e42cf8ab27 100644
--- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc
@@ -77,6 +77,26 @@ class MyClass2
var $x;
}
+abstract class MyClass
+{
+ /**
+* Property comment
+ */
+ readonly public string $prop;
+}
+
+/**
+ * Some info about the enum here
+ *
+*/
+enum Suits: string
+{
+ /**
+ * Some info about the case here.
+ */
+ case HEARTS;
+}
+
/** ************************************************************************
* Example with no errors.
**************************************************************************/
diff --git a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed
index 4d8cb39273..6182b539cd 100644
--- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed
@@ -77,6 +77,26 @@ class MyClass2
var $x;
}
+abstract class MyClass
+{
+ /**
+ * Property comment
+ */
+ readonly public string $prop;
+}
+
+/**
+ * Some info about the enum here
+ *
+ */
+enum Suits: string
+{
+ /**
+ * Some info about the case here.
+ */
+ case HEARTS;
+}
+
/** ************************************************************************
* Example with no errors.
**************************************************************************/
diff --git a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php
index 974951ce42..acbf13e869 100644
--- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php
+++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php
@@ -45,6 +45,12 @@ public function getErrorList($testFile='DocCommentAlignmentUnitTest.inc')
if ($testFile === 'DocCommentAlignmentUnitTest.inc') {
$errors[75] = 1;
+ $errors[83] = 1;
+ $errors[84] = 1;
+ $errors[90] = 1;
+ $errors[91] = 1;
+ $errors[95] = 1;
+ $errors[96] = 1;
}
return $errors;
diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed
index 5cc4764205..3bcd945388 100644
--- a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed
+++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed
@@ -25,7 +25,7 @@
* @author
* @copyright 1997 Squiz Pty Ltd (ABN 77 084 670 600)
* @copyright 1994-1997 Squiz Pty Ltd (ABN 77 084 670 600)
-* @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600)
+* @copyright 2023 Squiz Pty Ltd (ABN 77 084 670 600)
* @license http://www.php.net/license/3_0.txt
* @summary An unknown summary tag
*
diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.js.fixed b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.js.fixed
index b2b071f485..56a392d985 100644
--- a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.js.fixed
+++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.js.fixed
@@ -25,7 +25,7 @@
* @author
* @copyright 1997 Squiz Pty Ltd (ABN 77 084 670 600)
* @copyright 1994-1997 Squiz Pty Ltd (ABN 77 084 670 600)
-* @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600)
+* @copyright 2023 Squiz Pty Ltd (ABN 77 084 670 600)
* @license http://www.php.net/license/3_0.txt
* @summary An unknown summary tag
*
diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.9.inc b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.9.inc
new file mode 100644
index 0000000000..f6c9d99682
--- /dev/null
+++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.9.inc
@@ -0,0 +1,12 @@
+
+ * @copyright 2010-2014 Squiz Pty Ltd (ABN 77 084 670 600)
+ */
+
+enum Foo {
+}
diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php
index 080df3aafc..ee81369ab7 100644
--- a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php
+++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php
@@ -45,6 +45,7 @@ public function getErrorList($testFile='FileCommentUnitTest.inc')
case 'FileCommentUnitTest.4.inc':
case 'FileCommentUnitTest.6.inc':
case 'FileCommentUnitTest.7.inc':
+ case 'FileCommentUnitTest.9.inc':
return [1 => 1];
case 'FileCommentUnitTest.5.inc':
diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc
index deaa966eae..4f59f60b71 100644
--- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc
@@ -1041,3 +1041,8 @@ public function ignored() {
}
// phpcs:set Squiz.Commenting.FunctionComment specialMethods[] __construct,__destruct
+
+/**
+ * @return void
+ * @throws Exception If any other error occurs. */
+function throwCommentOneLine() {}
diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed
index b46df26b54..21a4103eb5 100644
--- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed
@@ -1041,3 +1041,8 @@ public function ignored() {
}
// phpcs:set Squiz.Commenting.FunctionComment specialMethods[] __construct,__destruct
+
+/**
+ * @return void
+ * @throws Exception If any other error occurs. */
+function throwCommentOneLine() {}
diff --git a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc
index 1b97af0b92..10a0b4b4c6 100644
--- a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc
@@ -165,6 +165,14 @@ final class MyClass
final public function test() {}
}
+/**
+ * Comment should be ignored.
+ *
+ */
+enum MyEnum {
+
+}
+
/*
* N.B.: The below test line must be the last test in the file.
* Testing that a new line after an inline comment when it's the last non-whitespace
diff --git a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed
index 6b66624176..97ae01490d 100644
--- a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed
@@ -158,6 +158,14 @@ final class MyClass
final public function test() {}
}
+/**
+ * Comment should be ignored.
+ *
+ */
+enum MyEnum {
+
+}
+
/*
* N.B.: The below test line must be the last test in the file.
* Testing that a new line after an inline comment when it's the last non-whitespace
diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc
index c2046b179f..36efc443bf 100644
--- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc
@@ -383,3 +383,22 @@ class HasAttributes
#[ORM\Column(ORM\Column::T_INTEGER)]
protected $height;
}
+
+class ReadOnlyProps
+{
+ /**
+ * Short description of the member variable.
+ *
+ * @var array
+ */
+ public readonly array $variableName = array();
+
+ /**
+ * Short description of the member variable.
+ *
+ * @var
+ */
+ readonly protected ?int $variableName = 10;
+
+ private readonly string $variable;
+}
diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed
index 37ca1cebbf..5c652f5402 100644
--- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed
@@ -383,3 +383,22 @@ class HasAttributes
#[ORM\Column(ORM\Column::T_INTEGER)]
protected $height;
}
+
+class ReadOnlyProps
+{
+ /**
+ * Short description of the member variable.
+ *
+ * @var array
+ */
+ public readonly array $variableName = array();
+
+ /**
+ * Short description of the member variable.
+ *
+ * @var
+ */
+ readonly protected ?int $variableName = 10;
+
+ private readonly string $variable;
+}
diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php
index f3ee3c76dd..1af5e14845 100644
--- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php
+++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.php
@@ -58,6 +58,8 @@ public function getErrorList()
336 => 1,
361 => 1,
364 => 1,
+ 399 => 1,
+ 403 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Squiz/Tests/Files/FileExtensionUnitTest.5.inc b/src/Standards/Squiz/Tests/Files/FileExtensionUnitTest.5.inc
new file mode 100644
index 0000000000..d777aff6b1
--- /dev/null
+++ b/src/Standards/Squiz/Tests/Files/FileExtensionUnitTest.5.inc
@@ -0,0 +1,3 @@
+
diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc
index 2a480bb926..8e62896387 100644
--- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc
@@ -193,3 +193,11 @@ $expr = match (true) {
if ($pos === count(value: $this->tokens) - 1) {
$file = '...'.substr(string: $file, offset: $padding * -1 + 3);
}
+
+match ($a) {
+ 'a' => -1,
+ 'b', 'c', 'd' => -2,
+ default => -3,
+};
+
+$cntPages = ceil(count($items) / parent::ON_PAGE);
diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed
index 669b16b2bc..9fa0216cb6 100644
--- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed
@@ -193,3 +193,11 @@ $expr = match (true) {
if ($pos === (count(value: $this->tokens) - 1)) {
$file = '...'.substr(string: $file, offset: ($padding * -1 + 3));
}
+
+match ($a) {
+ 'a' => -1,
+ 'b', 'c', 'd' => -2,
+ default => -3,
+};
+
+$cntPages = ceil(count($items) / parent::ON_PAGE);
diff --git a/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.inc b/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.inc
index c7b8a2b6b2..87c3bdf2e7 100644
--- a/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.inc
+++ b/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.inc
@@ -146,3 +146,12 @@ echo $obj?->varName;
echo $obj?->var_name;
echo $obj?->varname;
echo $obj?->_varName;
+
+enum SomeEnum
+{
+ public function foo($foo, $_foo, $foo_bar) {
+ $bar = 1;
+ $_bar = 2;
+ $bar_foo = 3;
+ }
+}
diff --git a/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.php b/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.php
index aaa9d099a7..9acbe241a8 100644
--- a/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.php
+++ b/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.php
@@ -61,6 +61,8 @@ public function getErrorList()
138 => 1,
141 => 1,
146 => 1,
+ 152 => 1,
+ 155 => 1,
];
return $errors;
diff --git a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc
index 022aca739e..a07047b196 100644
--- a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc
+++ b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc
@@ -71,3 +71,13 @@ $callback = function ($value) {
return false;
}
};
+
+function issue3616() {
+ $food = 'cake';
+
+ $returnValue = match (true) {
+ $food === 'apple' => 'This food is an apple',
+ $food === 'bar' => 'This food is a bar',
+ $food === 'cake' => 'This food is a cake',
+ };
+}
diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc
index 407c4740b1..c9bf052f34 100644
--- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc
+++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc
@@ -45,6 +45,12 @@ trait Something {
}
}
+enum Something {
+ function getReturnType() {
+ echo 'no error';
+ }
+}
+
$a = new class {
public function log($msg)
{
diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php
index f66eb3f7fe..f5f90c0b92 100644
--- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php
+++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php
@@ -83,7 +83,7 @@ public function getWarningList($testFile='')
9 => 1,
10 => 2,
14 => 1,
- 48 => 2,
+ 54 => 2,
];
break;
default:
diff --git a/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc b/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc
index cec0355c43..3cc617d75b 100644
--- a/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc
@@ -40,3 +40,18 @@ class Nested {
};
}
}
+
+enum SomeEnum
+{
+ function func1() {}
+ public function func1() {}
+ private function func1() {}
+ protected function func1() {}
+}
+
+class UnconventionalSpacing {
+ public
+ static
+ function
+ myFunction() {}
+}
diff --git a/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.php b/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.php
index 7fdab23b77..4dc7177953 100644
--- a/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.php
+++ b/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.php
@@ -29,6 +29,7 @@ public function getErrorList()
6 => 1,
30 => 1,
39 => 1,
+ 46 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc
index 38b443f2fd..dd6530e802 100644
--- a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc
+++ b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc
@@ -115,3 +115,13 @@ $b = new class()
return $This;
}
}
+
+enum MyEnum {
+ private function notStatic () {
+ $this->doSomething();
+ }
+
+ public static function myFunc() {
+ $this->doSomething();
+ }
+}
diff --git a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.php b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.php
index 2935241b44..b1a5dd6afd 100644
--- a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.php
+++ b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.php
@@ -26,18 +26,19 @@ class StaticThisUsageUnitTest extends AbstractSniffUnitTest
public function getErrorList()
{
return [
- 7 => 1,
- 8 => 1,
- 9 => 1,
- 14 => 1,
- 20 => 1,
- 41 => 1,
- 61 => 1,
- 69 => 1,
- 76 => 1,
- 80 => 1,
- 84 => 1,
- 99 => 1,
+ 7 => 1,
+ 8 => 1,
+ 9 => 1,
+ 14 => 1,
+ 20 => 1,
+ 41 => 1,
+ 61 => 1,
+ 69 => 1,
+ 76 => 1,
+ 80 => 1,
+ 84 => 1,
+ 99 => 1,
+ 125 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc
index 0371ce49b4..70abae434d 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc
+++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc
@@ -261,3 +261,9 @@ $expr = match( $foo ){
};
echo $expr;
+
+if($true) {
+
+ enum SomeEnum {}
+
+}
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed
index ad4505c3e1..c64de25e16 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed
@@ -253,3 +253,9 @@ $expr = match($foo){
};
echo $expr;
+
+if($true) {
+
+ enum SomeEnum {}
+
+}
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc
index 038072dfe0..12b55176c4 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc
+++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc
@@ -365,3 +365,10 @@ class HasAttributes
#[ThirdAttribute]
protected $propertyWithoutSpacing;
}
+
+enum SomeEnum
+{
+ // Enum cannot have properties
+
+ case ONE = 'one';
+}
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed
index 3cb2ca3a48..d683eaadfb 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed
@@ -350,3 +350,10 @@ class HasAttributes
#[ThirdAttribute]
protected $propertyWithoutSpacing;
}
+
+enum SomeEnum
+{
+ // Enum cannot have properties
+
+ case ONE = 'one';
+}
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc
index f89cf08d5c..06462acc35 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc
+++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc
@@ -477,5 +477,11 @@ $a = 'a '.-$b;
$a = 'a '.- MY_CONSTANT;
$a = 'a '.- $b;
+match ($a) {
+ 'a' => -1,
+ 'b', 'c', 'd' => -2,
+ default => -3,
+};
+
/* Intentional parse error. This has to be the last test in the file. */
$a = 10 +
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed
index 138616e752..8b92a4875a 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed
@@ -471,5 +471,11 @@ $a = 'a '.-$b;
$a = 'a '.- MY_CONSTANT;
$a = 'a '.- $b;
+match ($a) {
+ 'a' => -1,
+ 'b', 'c', 'd' => -2,
+ default => -3,
+};
+
/* Intentional parse error. This has to be the last test in the file. */
$a = 10 +
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc
index ecd80b5504..ecae5c6d53 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc
+++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc
@@ -120,3 +120,15 @@ $match = match ($test) {
+
+
+
+ 1,
116 => 1,
122 => 1,
+ 130 => 1,
+ 134 => 1,
];
}//end getErrorList()
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc
index 2213817116..12685dc97d 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc
+++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc
@@ -126,3 +126,16 @@ class ConstructorPropertyPromotionTest {
class ConstructorPropertyPromotionWithTypesTest {
public function __construct(protected float|int $x, public?string &$y = 'test', private mixed $z) {}
}
+
+// PHP 8.1 readonly keywords.
+class ReadonlyTest {
+ public readonly int $publicReadonlyProperty;
+
+ protected readonly int $protectedReadonlyProperty;
+
+ readonly protected int $protectedReadonlyProperty;
+
+ readonly private int $privateReadonlyProperty;
+
+ public function __construct(readonly protected float|int $x, public readonly?string &$y = 'test') {}
+}
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed
index e642f0c7b3..d3b682ed75 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed
+++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed
@@ -120,3 +120,16 @@ class ConstructorPropertyPromotionTest {
class ConstructorPropertyPromotionWithTypesTest {
public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {}
}
+
+// PHP 8.1 readonly keywords.
+class ReadonlyTest {
+ public readonly int $publicReadonlyProperty;
+
+ protected readonly int $protectedReadonlyProperty;
+
+ readonly protected int $protectedReadonlyProperty;
+
+ readonly private int $privateReadonlyProperty;
+
+ public function __construct(readonly protected float|int $x, public readonly ?string &$y = 'test') {}
+}
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php
index de4697c0cb..30b66215c9 100644
--- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php
+++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php
@@ -44,6 +44,9 @@ public function getErrorList()
119 => 1,
121 => 1,
127 => 2,
+ 134 => 2,
+ 138 => 2,
+ 140 => 3,
];
}//end getErrorList()
diff --git a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc
index 8ed8509d2a..3325e1152d 100644
--- a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc
+++ b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc
@@ -120,3 +120,12 @@ $anonClass = new class() {
echo $obj?->varName;
echo $obj?->var_name;
echo $obj?->varName;
+
+enum SomeEnum
+{
+ public function foo($foo, $_foo, $foo_bar) {
+ $bar = 1;
+ $_bar = 2;
+ $bar_foo = 3;
+ }
+}
diff --git a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php
index 916b334f1c..e57c735646 100644
--- a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php
+++ b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php
@@ -54,6 +54,8 @@ public function getErrorList()
113 => 1,
116 => 1,
121 => 1,
+ 126 => 1,
+ 129 => 1,
];
}//end getErrorList()
diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php
index 1ba26363af..3fc67b0c81 100644
--- a/src/Tokenizers/PHP.php
+++ b/src/Tokenizers/PHP.php
@@ -152,6 +152,13 @@ class PHP extends Tokenizer
'shared' => false,
'with' => [],
],
+ T_ENUM => [
+ 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
+ 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
+ 'strict' => true,
+ 'shared' => false,
+ 'with' => [],
+ ],
T_USE => [
'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
@@ -339,6 +346,8 @@ class PHP extends Tokenizer
T_ENDIF => 5,
T_ENDSWITCH => 9,
T_ENDWHILE => 8,
+ T_ENUM => 4,
+ T_ENUM_CASE => 4,
T_EVAL => 4,
T_EXTENDS => 7,
T_FILE => 8,
@@ -393,6 +402,7 @@ class PHP extends Tokenizer
T_PRIVATE => 7,
T_PUBLIC => 6,
T_PROTECTED => 9,
+ T_READONLY => 8,
T_REQUIRE => 7,
T_REQUIRE_ONCE => 12,
T_RETURN => 6,
@@ -452,6 +462,7 @@ class PHP extends Tokenizer
T_OPEN_SHORT_ARRAY => 1,
T_CLOSE_SHORT_ARRAY => 1,
T_TYPE_UNION => 1,
+ T_TYPE_INTERSECTION => 1,
];
/**
@@ -466,6 +477,8 @@ class PHP extends Tokenizer
T_CLASS => true,
T_INTERFACE => true,
T_TRAIT => true,
+ T_ENUM => true,
+ T_ENUM_CASE => true,
T_EXTENDS => true,
T_IMPLEMENTS => true,
T_ATTRIBUTE => true,
@@ -501,7 +514,7 @@ protected function tokenize($string)
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t*** START PHP TOKENIZING ***".PHP_EOL;
$isWin = false;
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ if (stripos(PHP_OS, 'WIN') === 0) {
$isWin = true;
}
}
@@ -588,6 +601,86 @@ protected function tokenize($string)
echo PHP_EOL;
}
+ /*
+ Tokenize context sensitive keyword as string when it should be string.
+ */
+
+ if ($tokenIsArray === true
+ && isset(Util\Tokens::$contextSensitiveKeywords[$token[0]]) === true
+ && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
+ || $finalTokens[$lastNotEmptyToken]['content'] === '&')
+ ) {
+ if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
+ $preserveKeyword = false;
+
+ // `new class`, and `new static` should be preserved.
+ if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
+ && ($token[0] === T_CLASS
+ || $token[0] === T_STATIC)
+ ) {
+ $preserveKeyword = true;
+ }
+
+ // `new class extends` `new class implements` should be preserved
+ if (($token[0] === T_EXTENDS || $token[0] === T_IMPLEMENTS)
+ && $finalTokens[$lastNotEmptyToken]['code'] === T_CLASS
+ ) {
+ $preserveKeyword = true;
+ }
+
+ // `namespace\` should be preserved
+ if ($token[0] === T_NAMESPACE) {
+ for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
+ if (is_array($tokens[$i]) === false) {
+ break;
+ }
+
+ if (isset(Util\Tokens::$emptyTokens[$tokens[$i][0]]) === true) {
+ continue;
+ }
+
+ if ($tokens[$i][0] === T_NS_SEPARATOR) {
+ $preserveKeyword = true;
+ }
+
+ break;
+ }
+ }
+ }//end if
+
+ if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
+ $preserveKeyword = true;
+
+ for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
+ if (isset(Util\Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
+ continue;
+ }
+
+ if ($finalTokens[$i]['code'] === T_FUNCTION) {
+ $preserveKeyword = false;
+ }
+
+ break;
+ }
+ }
+
+ if ($preserveKeyword === false) {
+ if (PHP_CODESNIFFER_VERBOSITY > 1) {
+ $type = Util\Tokens::tokenName($token[0]);
+ echo "\t\t* token $stackPtr changed from $type to T_STRING".PHP_EOL;
+ }
+
+ $finalTokens[$newStackPtr] = [
+ 'code' => T_STRING,
+ 'type' => 'T_STRING',
+ 'content' => $token[1],
+ ];
+
+ $newStackPtr++;
+ continue;
+ }
+ }//end if
+
/*
Parse doc blocks into something that can be easily iterated over.
*/
@@ -646,6 +739,46 @@ protected function tokenize($string)
}//end if
}//end if
+ /*
+ For Explicit Octal Notation prior to PHP 8.1 we need to combine the
+ T_LNUMBER and T_STRING token values into a single token value, and
+ then ignore the T_STRING token.
+ */
+
+ if (PHP_VERSION_ID < 80100
+ && $tokenIsArray === true && $token[1] === '0'
+ && (isset($tokens[($stackPtr + 1)]) === true
+ && is_array($tokens[($stackPtr + 1)]) === true
+ && $tokens[($stackPtr + 1)][0] === T_STRING
+ && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o'
+ && $tokens[($stackPtr + 1)][1][1] !== '_')
+ && preg_match('`^(o[0-7]+(?:_[0-7]+)?)([0-9_]*)$`i', $tokens[($stackPtr + 1)][1], $matches) === 1
+ ) {
+ $finalTokens[$newStackPtr] = [
+ 'code' => T_LNUMBER,
+ 'type' => 'T_LNUMBER',
+ 'content' => $token[1] .= $matches[1],
+ ];
+ $newStackPtr++;
+
+ if (isset($matches[2]) === true && $matches[2] !== '') {
+ $type = 'T_LNUMBER';
+ if ($matches[2][0] === '_') {
+ $type = 'T_STRING';
+ }
+
+ $finalTokens[$newStackPtr] = [
+ 'code' => constant($type),
+ 'type' => $type,
+ 'content' => $matches[2],
+ ];
+ $newStackPtr++;
+ }
+
+ $stackPtr++;
+ continue;
+ }//end if
+
/*
PHP 8.1 introduced two dedicated tokens for the & character.
Retokenizing both of these to T_BITWISE_AND, which is the
@@ -691,7 +824,8 @@ protected function tokenize($string)
if ($subTokenIsArray === true) {
$tokenContent .= $subToken[1];
- if ($subToken[1] === '{'
+ if (($subToken[1] === '{'
+ || $subToken[1] === '${')
&& $subToken[0] !== T_ENCAPSED_AND_WHITESPACE
) {
$nestedVars[] = $i;
@@ -870,6 +1004,104 @@ protected function tokenize($string)
continue;
}//end if
+ /*
+ Enum keyword for PHP < 8.1
+ */
+
+ if ($tokenIsArray === true
+ && $token[0] === T_STRING
+ && strtolower($token[1]) === 'enum'
+ ) {
+ // Get the next non-empty token.
+ for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
+ if (is_array($tokens[$i]) === false
+ || isset(Util\Tokens::$emptyTokens[$tokens[$i][0]]) === false
+ ) {
+ break;
+ }
+ }
+
+ if (isset($tokens[$i]) === true
+ && is_array($tokens[$i]) === true
+ && $tokens[$i][0] === T_STRING
+ ) {
+ // Modify $tokens directly so we can use it later when converting enum "case".
+ $tokens[$stackPtr][0] = T_ENUM;
+
+ $newToken = [];
+ $newToken['code'] = T_ENUM;
+ $newToken['type'] = 'T_ENUM';
+ $newToken['content'] = $token[1];
+ $finalTokens[$newStackPtr] = $newToken;
+
+ if (PHP_CODESNIFFER_VERBOSITY > 1) {
+ echo "\t\t* token $stackPtr changed from T_STRING to T_ENUM".PHP_EOL;
+ }
+
+ $newStackPtr++;
+ continue;
+ }
+ }//end if
+
+ /*
+ Convert enum "case" to T_ENUM_CASE
+ */
+
+ if ($tokenIsArray === true
+ && $token[0] === T_CASE
+ && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
+ ) {
+ $isEnumCase = false;
+ $scope = 1;
+
+ for ($i = ($stackPtr - 1); $i > 0; $i--) {
+ if ($tokens[$i] === '}') {
+ $scope++;
+ continue;
+ }
+
+ if ($tokens[$i] === '{') {
+ $scope--;
+ continue;
+ }
+
+ if (is_array($tokens[$i]) === false) {
+ continue;
+ }
+
+ if ($scope !== 0) {
+ continue;
+ }
+
+ if ($tokens[$i][0] === T_SWITCH) {
+ break;
+ }
+
+ if ($tokens[$i][0] === T_ENUM || $tokens[$i][0] === T_ENUM_CASE) {
+ $isEnumCase = true;
+ break;
+ }
+ }//end for
+
+ if ($isEnumCase === true) {
+ // Modify $tokens directly so we can use it as optimisation for other enum "case".
+ $tokens[$stackPtr][0] = T_ENUM_CASE;
+
+ $newToken = [];
+ $newToken['code'] = T_ENUM_CASE;
+ $newToken['type'] = 'T_ENUM_CASE';
+ $newToken['content'] = $token[1];
+ $finalTokens[$newStackPtr] = $newToken;
+
+ if (PHP_CODESNIFFER_VERBOSITY > 1) {
+ echo "\t\t* token $stackPtr changed from T_CASE to T_ENUM_CASE".PHP_EOL;
+ }
+
+ $newStackPtr++;
+ continue;
+ }
+ }//end if
+
/*
As of PHP 8.0 fully qualified, partially qualified and namespace relative
identifier names are tokenized differently.
@@ -991,7 +1223,7 @@ protected function tokenize($string)
/*
Tokenize the parameter labels for PHP 8.0 named parameters as a special T_PARAM_NAME
- token and ensure that the colon after it is always T_COLON.
+ token and ensures that the colon after it is always T_COLON.
*/
if ($tokenIsArray === true
@@ -1046,6 +1278,38 @@ protected function tokenize($string)
}//end if
}//end if
+ /*
+ "readonly" keyword for PHP < 8.1
+ */
+
+ if (PHP_VERSION_ID < 80100
+ && $tokenIsArray === true
+ && strtolower($token[1]) === 'readonly'
+ && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
+ ) {
+ // Get the next non-whitespace token.
+ for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
+ if (is_array($tokens[$i]) === false
+ || $tokens[$i][0] !== T_WHITESPACE
+ ) {
+ break;
+ }
+ }
+
+ if (isset($tokens[$i]) === false
+ || $tokens[$i] !== '('
+ ) {
+ $finalTokens[$newStackPtr] = [
+ 'code' => T_READONLY,
+ 'type' => 'T_READONLY',
+ 'content' => $token[1],
+ ];
+ $newStackPtr++;
+
+ continue;
+ }
+ }//end if
+
/*
Before PHP 7.0, the "yield from" was tokenized as
T_YIELD, T_WHITESPACE and T_STRING. So look for
@@ -1089,6 +1353,7 @@ protected function tokenize($string)
&& $tokenIsArray === true
&& $token[0] === T_STRING
&& strtolower($token[1]) === 'yield'
+ && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
) {
if (isset($tokens[($stackPtr + 1)]) === true
&& isset($tokens[($stackPtr + 2)]) === true
@@ -1330,6 +1595,7 @@ protected function tokenize($string)
if ($newType === T_LNUMBER
&& ((stripos($newContent, '0x') === 0 && hexdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
|| (stripos($newContent, '0b') === 0 && bindec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
+ || (stripos($newContent, '0o') === 0 && octdec(str_replace('_', '', $newContent)) > PHP_INT_MAX)
|| (stripos($newContent, '0x') !== 0
&& stripos($newContent, 'e') !== false || strpos($newContent, '.') !== false)
|| (strpos($newContent, '0') === 0 && stripos($newContent, '0x') !== 0
@@ -1421,57 +1687,42 @@ protected function tokenize($string)
if ($tokenIsArray === true
&& $token[0] === T_DEFAULT
+ && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false
) {
- if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === false) {
- for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
- if ($tokens[$x] === ',') {
- // Skip over potential trailing comma (supported in PHP).
- continue;
- }
-
- if (is_array($tokens[$x]) === false
- || isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false
- ) {
- // Non-empty, non-comma content.
- break;
- }
+ for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
+ if ($tokens[$x] === ',') {
+ // Skip over potential trailing comma (supported in PHP).
+ continue;
}
- if (isset($tokens[$x]) === true
- && is_array($tokens[$x]) === true
- && $tokens[$x][0] === T_DOUBLE_ARROW
+ if (is_array($tokens[$x]) === false
+ || isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false
) {
- // Modify the original token stack for the double arrow so that
- // future checks can disregard the double arrow token more easily.
- // For match expression "case" statements, this is handled
- // in PHP::processAdditional().
- $tokens[$x][0] = T_MATCH_ARROW;
- if (PHP_CODESNIFFER_VERBOSITY > 1) {
- echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
- }
-
- $newToken = [];
- $newToken['code'] = T_MATCH_DEFAULT;
- $newToken['type'] = 'T_MATCH_DEFAULT';
- $newToken['content'] = $token[1];
+ // Non-empty, non-comma content.
+ break;
+ }
+ }
- if (PHP_CODESNIFFER_VERBOSITY > 1) {
- echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL;
- }
+ if (isset($tokens[$x]) === true
+ && is_array($tokens[$x]) === true
+ && $tokens[$x][0] === T_DOUBLE_ARROW
+ ) {
+ // Modify the original token stack for the double arrow so that
+ // future checks can disregard the double arrow token more easily.
+ // For match expression "case" statements, this is handled
+ // in PHP::processAdditional().
+ $tokens[$x][0] = T_MATCH_ARROW;
+ if (PHP_CODESNIFFER_VERBOSITY > 1) {
+ echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
+ }
- $finalTokens[$newStackPtr] = $newToken;
- $newStackPtr++;
- continue;
- }//end if
- } else {
- // Definitely not the "default" keyword.
$newToken = [];
- $newToken['code'] = T_STRING;
- $newToken['type'] = 'T_STRING';
+ $newToken['code'] = T_MATCH_DEFAULT;
+ $newToken['type'] = 'T_MATCH_DEFAULT';
$newToken['content'] = $token[1];
if (PHP_CODESNIFFER_VERBOSITY > 1) {
- echo "\t\t* token $stackPtr changed from T_DEFAULT to T_STRING".PHP_EOL;
+ echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL;
}
$finalTokens[$newStackPtr] = $newToken;
@@ -1668,14 +1919,9 @@ protected function tokenize($string)
}
/*
- The string-like token after a function keyword should always be
- tokenized as T_STRING even if it appears to be a different token,
- such as when writing code like: function default(): foo
- so go forward and change the token type before it is processed.
-
- Note: this should not be done for `function Level\Name` within a
- group use statement for the PHP 8 identifier name tokens as it
- would interfere with the re-tokenization of those.
+ This is a special condition for T_ARRAY tokens used for
+ function return types. We want to keep the parenthesis map clean,
+ so let's tag these tokens as T_STRING.
*/
if ($tokenIsArray === true
@@ -1683,37 +1929,6 @@ protected function tokenize($string)
|| $token[0] === T_FN)
&& $finalTokens[$lastNotEmptyToken]['code'] !== T_USE
) {
- if ($token[0] === T_FUNCTION) {
- for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
- if (is_array($tokens[$x]) === false
- || (isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false
- && $tokens[$x][1] !== '&')
- ) {
- // Non-empty content.
- break;
- }
- }
-
- if ($x < $numTokens
- && is_array($tokens[$x]) === true
- && $tokens[$x][0] !== T_STRING
- && $tokens[$x][0] !== T_NAME_QUALIFIED
- ) {
- if (PHP_CODESNIFFER_VERBOSITY > 1) {
- $oldType = Util\Tokens::tokenName($tokens[$x][0]);
- echo "\t\t* token $x changed from $oldType to T_STRING".PHP_EOL;
- }
-
- $tokens[$x][0] = T_STRING;
- }
- }//end if
-
- /*
- This is a special condition for T_ARRAY tokens used for
- function return types. We want to keep the parenthesis map clean,
- so let's tag these tokens as T_STRING.
- */
-
// Go looking for the colon to start the return type hint.
// Start by finding the closing parenthesis of the function.
$parenthesisStack = [];
@@ -1753,22 +1968,6 @@ function return types. We want to keep the parenthesis map clean,
&& is_array($tokens[$x]) === false
&& $tokens[$x] === ':'
) {
- $allowed = [
- T_STRING => T_STRING,
- T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED,
- T_NAME_RELATIVE => T_NAME_RELATIVE,
- T_NAME_QUALIFIED => T_NAME_QUALIFIED,
- T_ARRAY => T_ARRAY,
- T_CALLABLE => T_CALLABLE,
- T_SELF => T_SELF,
- T_PARENT => T_PARENT,
- T_NAMESPACE => T_NAMESPACE,
- T_STATIC => T_STATIC,
- T_NS_SEPARATOR => T_NS_SEPARATOR,
- ];
-
- $allowed += Util\Tokens::$emptyTokens;
-
// Find the start of the return type.
for ($x += 1; $x < $numTokens; $x++) {
if (is_array($tokens[$x]) === true
@@ -1843,6 +2042,7 @@ function return types. We want to keep the parenthesis map clean,
T_OPEN_TAG => true,
T_OPEN_CURLY_BRACKET => true,
T_INLINE_THEN => true,
+ T_ENUM => true,
];
for ($x = ($newStackPtr - 1); $x > 0; $x--) {
@@ -1853,6 +2053,7 @@ function return types. We want to keep the parenthesis map clean,
if ($finalTokens[$x]['code'] !== T_CASE
&& $finalTokens[$x]['code'] !== T_INLINE_THEN
+ && $finalTokens[$x]['code'] !== T_ENUM
) {
$finalTokens[$newStackPtr] = [
'content' => $token[1].':',
@@ -1901,31 +2102,39 @@ function return types. We want to keep the parenthesis map clean,
$newStackPtr++;
}
} else {
- if ($tokenIsArray === true && $token[0] === T_STRING) {
- // Some T_STRING tokens should remain that way
- // due to their context.
- if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) {
- // Special case for syntax like: return new self
- // where self should not be a string.
- if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
- && strtolower($token[1]) === 'self'
- ) {
- $finalTokens[$newStackPtr] = [
- 'content' => $token[1],
- 'code' => T_SELF,
- 'type' => 'T_SELF',
- ];
- } else {
- $finalTokens[$newStackPtr] = [
- 'content' => $token[1],
- 'code' => T_STRING,
- 'type' => 'T_STRING',
- ];
+ // Some T_STRING tokens should remain that way due to their context.
+ if ($tokenIsArray === true
+ && $token[0] === T_STRING
+ && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
+ ) {
+ // Special case for syntax like: return new self/new parent
+ // where self/parent should not be a string.
+ $tokenContentLower = strtolower($token[1]);
+ if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
+ && ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
+ ) {
+ $finalTokens[$newStackPtr] = [
+ 'content' => $token[1],
+ ];
+ if ($tokenContentLower === 'self') {
+ $finalTokens[$newStackPtr]['code'] = T_SELF;
+ $finalTokens[$newStackPtr]['type'] = 'T_SELF';
}
- $newStackPtr++;
- continue;
- }//end if
+ if ($tokenContentLower === 'parent') {
+ $finalTokens[$newStackPtr]['code'] = T_PARENT;
+ $finalTokens[$newStackPtr]['type'] = 'T_PARENT';
+ }
+ } else {
+ $finalTokens[$newStackPtr] = [
+ 'content' => $token[1],
+ 'code' => T_STRING,
+ 'type' => 'T_STRING',
+ ];
+ }
+
+ $newStackPtr++;
+ continue;
}//end if
$newToken = null;
@@ -2073,7 +2282,7 @@ function return types. We want to keep the parenthesis map clean,
}
}
- if ($tokens[$i] !== '(' && $i !== $numTokens) {
+ if ($i !== $numTokens && $tokens[$i] !== '(') {
$newToken['code'] = T_STRING;
$newToken['type'] = 'T_STRING';
}
@@ -2089,16 +2298,6 @@ function return types. We want to keep the parenthesis map clean,
$newToken['type'] = 'T_FINALLY';
}
- // This is a special case for the PHP 5.5 classname::class syntax
- // where "class" should be T_STRING instead of T_CLASS.
- if (($newToken['code'] === T_CLASS
- || $newToken['code'] === T_FUNCTION)
- && $finalTokens[$lastNotEmptyToken]['code'] === T_DOUBLE_COLON
- ) {
- $newToken['code'] = T_STRING;
- $newToken['type'] = 'T_STRING';
- }
-
// This is a special case for PHP 5.6 use function and use const
// where "function" and "const" should be T_STRING instead of T_FUNCTION
// and T_CONST.
@@ -2276,18 +2475,19 @@ protected function processAdditional()
if (isset($this->tokens[$x]) === true && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
$ignore = Util\Tokens::$emptyTokens;
$ignore += [
- T_ARRAY => T_ARRAY,
- T_CALLABLE => T_CALLABLE,
- T_COLON => T_COLON,
- T_NAMESPACE => T_NAMESPACE,
- T_NS_SEPARATOR => T_NS_SEPARATOR,
- T_NULL => T_NULL,
- T_NULLABLE => T_NULLABLE,
- T_PARENT => T_PARENT,
- T_SELF => T_SELF,
- T_STATIC => T_STATIC,
- T_STRING => T_STRING,
- T_TYPE_UNION => T_TYPE_UNION,
+ T_ARRAY => T_ARRAY,
+ T_CALLABLE => T_CALLABLE,
+ T_COLON => T_COLON,
+ T_NAMESPACE => T_NAMESPACE,
+ T_NS_SEPARATOR => T_NS_SEPARATOR,
+ T_NULL => T_NULL,
+ T_NULLABLE => T_NULLABLE,
+ T_PARENT => T_PARENT,
+ T_SELF => T_SELF,
+ T_STATIC => T_STATIC,
+ T_STRING => T_STRING,
+ T_TYPE_UNION => T_TYPE_UNION,
+ T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
];
$closer = $this->tokens[$x]['parenthesis_closer'];
@@ -2488,13 +2688,18 @@ protected function processAdditional()
}
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
- if (isset($allowed[$this->tokens[$x]['code']]) === false) {
+ // Allow for control structures without braces.
+ if (($this->tokens[$x]['code'] === T_CLOSE_PARENTHESIS
+ && isset($this->tokens[$x]['parenthesis_owner']) === true
+ && isset(Util\Tokens::$scopeOpeners[$this->tokens[$this->tokens[$x]['parenthesis_owner']]['code']]) === true)
+ || isset($allowed[$this->tokens[$x]['code']]) === false
+ ) {
$isShortArray = true;
}
break;
}
- }
+ }//end for
if ($isShortArray === true) {
$this->tokens[$i]['code'] = T_OPEN_SHORT_ARRAY;
@@ -2583,9 +2788,12 @@ protected function processAdditional()
}//end if
continue;
- } else if ($this->tokens[$i]['code'] === T_BITWISE_OR) {
+ } else if ($this->tokens[$i]['code'] === T_BITWISE_OR
+ || $this->tokens[$i]['code'] === T_BITWISE_AND
+ ) {
/*
Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
+ Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
*/
$allowed = [
@@ -2650,12 +2858,12 @@ protected function processAdditional()
}//end for
if ($typeTokenCount === 0 || isset($suspectedType) === false) {
- // Definitely not a union type, move on.
+ // Definitely not a union or intersection type, move on.
continue;
}
$typeTokenCount = 0;
- $unionOperators = [$i];
+ $typeOperators = [$i];
$confirmed = false;
for ($x = ($i - 1); $x >= 0; $x--) {
@@ -2668,13 +2876,13 @@ protected function processAdditional()
continue;
}
- // Union types can't use the nullable operator, but be tolerant to parse errors.
+ // Union and intersection types can't use the nullable operator, but be tolerant to parse errors.
if ($typeTokenCount > 0 && $this->tokens[$x]['code'] === T_NULLABLE) {
continue;
}
- if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
- $unionOperators[] = $x;
+ if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
+ $typeOperators[] = $x;
continue;
}
@@ -2685,7 +2893,8 @@ protected function processAdditional()
if ($suspectedType === 'property or parameter'
&& (isset(Util\Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true
- || $this->tokens[$x]['code'] === T_VAR)
+ || $this->tokens[$x]['code'] === T_VAR
+ || $this->tokens[$x]['code'] === T_READONLY)
) {
// This will also confirm constructor property promotion parameters, but that's fine.
$confirmed = true;
@@ -2739,17 +2948,27 @@ protected function processAdditional()
}//end if
if ($confirmed === false) {
- // Not a union type after all, move on.
+ // Not a union or intersection type after all, move on.
continue;
}
- foreach ($unionOperators as $x) {
- $this->tokens[$x]['code'] = T_TYPE_UNION;
- $this->tokens[$x]['type'] = 'T_TYPE_UNION';
+ foreach ($typeOperators as $x) {
+ if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
+ $this->tokens[$x]['code'] = T_TYPE_UNION;
+ $this->tokens[$x]['type'] = 'T_TYPE_UNION';
- if (PHP_CODESNIFFER_VERBOSITY > 1) {
- $line = $this->tokens[$x]['line'];
- echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL;
+ if (PHP_CODESNIFFER_VERBOSITY > 1) {
+ $line = $this->tokens[$x]['line'];
+ echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL;
+ }
+ } else {
+ $this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
+ $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';
+
+ if (PHP_CODESNIFFER_VERBOSITY > 1) {
+ $line = $this->tokens[$x]['line'];
+ echo "\t* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION".PHP_EOL;
+ }
}
}
@@ -2793,25 +3012,6 @@ protected function processAdditional()
$this->tokens[$i]['code'] = T_STRING;
$this->tokens[$i]['type'] = 'T_STRING';
}
- } else if ($this->tokens[$i]['code'] === T_CONST) {
- // Context sensitive keywords support.
- for ($x = ($i + 1); $i < $numTokens; $x++) {
- if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
- // Non-whitespace content.
- break;
- }
- }
-
- if ($this->tokens[$x]['code'] !== T_STRING) {
- if (PHP_CODESNIFFER_VERBOSITY > 1) {
- $line = $this->tokens[$x]['line'];
- $type = $this->tokens[$x]['type'];
- echo "\t* token $x on line $line changed from $type to T_STRING".PHP_EOL;
- }
-
- $this->tokens[$x]['code'] = T_STRING;
- $this->tokens[$x]['type'] = 'T_STRING';
- }
}//end if
if (($this->tokens[$i]['code'] !== T_CASE
diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php
index c79323ccd3..0e00bf7f22 100644
--- a/src/Tokenizers/Tokenizer.php
+++ b/src/Tokenizers/Tokenizer.php
@@ -194,6 +194,8 @@ private function createPositionMap()
T_DOUBLE_QUOTED_STRING => true,
T_HEREDOC => true,
T_NOWDOC => true,
+ T_END_HEREDOC => true,
+ T_END_NOWDOC => true,
T_INLINE_HTML => true,
];
diff --git a/src/Util/Common.php b/src/Util/Common.php
index 204f44de14..ce7967cc33 100644
--- a/src/Util/Common.php
+++ b/src/Util/Common.php
@@ -250,7 +250,7 @@ public static function escapeshellcmd($cmd)
{
$cmd = escapeshellcmd($cmd);
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ if (stripos(PHP_OS, 'WIN') === 0) {
// Spaces are not escaped by escapeshellcmd on Windows, but need to be
// for the command to be able to execute.
$cmd = preg_replace('`(?= 48 && $ascii <= 57) {
- // The character is a number, so it cant be a capital.
+ // The character is a number, so it can't be a capital.
$isCaps = false;
} else {
if (strtoupper($string[$i]) === $string[$i]) {
diff --git a/src/Util/Standards.php b/src/Util/Standards.php
index 50f58f0294..65e5d6cb3c 100644
--- a/src/Util/Standards.php
+++ b/src/Util/Standards.php
@@ -180,7 +180,8 @@ public static function getInstalledStandards(
// Check if the installed dir is actually a standard itself.
$csFile = $standardsDir.'/ruleset.xml';
if (is_file($csFile) === true) {
- $installedStandards[] = basename($standardsDir);
+ $basename = basename($standardsDir);
+ $installedStandards[$basename] = $basename;
continue;
}
@@ -190,6 +191,7 @@ public static function getInstalledStandards(
}
$di = new \DirectoryIterator($standardsDir);
+ $standardsInDir = [];
foreach ($di as $file) {
if ($file->isDir() === true && $file->isDot() === false) {
$filename = $file->getFilename();
@@ -202,10 +204,13 @@ public static function getInstalledStandards(
// Valid coding standard dirs include a ruleset.
$csFile = $file->getPathname().'/ruleset.xml';
if (is_file($csFile) === true) {
- $installedStandards[] = $filename;
+ $standardsInDir[$filename] = $filename;
}
}
}
+
+ natsort($standardsInDir);
+ $installedStandards += $standardsInDir;
}//end foreach
return $installedStandards;
diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php
index f501c7f0a4..bb1fb2ca99 100644
--- a/src/Util/Tokens.php
+++ b/src/Util/Tokens.php
@@ -80,6 +80,8 @@
define('T_MATCH_ARROW', 'PHPCS_T_MATCH_ARROW');
define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT');
define('T_ATTRIBUTE_END', 'PHPCS_T_ATTRIBUTE_END');
+define('T_ENUM_CASE', 'PHPCS_T_ENUM_CASE');
+define('T_TYPE_INTERSECTION', 'PHPCS_T_TYPE_INTERSECTION');
// Some PHP 5.5 tokens, replicated for lower versions.
if (defined('T_FINALLY') === false) {
@@ -163,6 +165,14 @@
define('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', 'PHPCS_T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG');
}
+if (defined('T_READONLY') === false) {
+ define('T_READONLY', 'PHPCS_T_READONLY');
+}
+
+if (defined('T_ENUM') === false) {
+ define('T_ENUM', 'PHPCS_T_ENUM');
+}
+
// Tokens used for parsing doc blocks.
define('T_DOC_COMMENT_STAR', 'PHPCS_T_DOC_COMMENT_STAR');
define('T_DOC_COMMENT_WHITESPACE', 'PHPCS_T_DOC_COMMENT_WHITESPACE');
@@ -190,6 +200,7 @@ final class Tokens
T_CLASS => 1000,
T_INTERFACE => 1000,
T_TRAIT => 1000,
+ T_ENUM => 1000,
T_NAMESPACE => 1000,
T_FUNCTION => 100,
T_CLOSURE => 100,
@@ -415,6 +426,7 @@ final class Tokens
T_ANON_CLASS => T_ANON_CLASS,
T_INTERFACE => T_INTERFACE,
T_TRAIT => T_TRAIT,
+ T_ENUM => T_ENUM,
T_NAMESPACE => T_NAMESPACE,
T_FUNCTION => T_FUNCTION,
T_CLOSURE => T_CLOSURE,
@@ -616,6 +628,7 @@ final class Tokens
T_UNSET => T_UNSET,
T_EMPTY => T_EMPTY,
T_SELF => T_SELF,
+ T_PARENT => T_PARENT,
T_STATIC => T_STATIC,
];
@@ -629,6 +642,7 @@ final class Tokens
T_ANON_CLASS => T_ANON_CLASS,
T_INTERFACE => T_INTERFACE,
T_TRAIT => T_TRAIT,
+ T_ENUM => T_ENUM,
];
/**
@@ -649,6 +663,86 @@ final class Tokens
T_TRAIT_C => T_TRAIT_C,
];
+ /**
+ * Tokens representing context sensitive keywords in PHP.
+ *
+ * @var array
+ *
+ * https://wiki.php.net/rfc/context_sensitive_lexer
+ */
+ public static $contextSensitiveKeywords = [
+ T_ABSTRACT => T_ABSTRACT,
+ T_ARRAY => T_ARRAY,
+ T_AS => T_AS,
+ T_BREAK => T_BREAK,
+ T_CALLABLE => T_CALLABLE,
+ T_CASE => T_CASE,
+ T_CATCH => T_CATCH,
+ T_CLASS => T_CLASS,
+ T_CLONE => T_CLONE,
+ T_CONST => T_CONST,
+ T_CONTINUE => T_CONTINUE,
+ T_DECLARE => T_DECLARE,
+ T_DEFAULT => T_DEFAULT,
+ T_DO => T_DO,
+ T_ECHO => T_ECHO,
+ T_ELSE => T_ELSE,
+ T_ELSEIF => T_ELSEIF,
+ T_EMPTY => T_EMPTY,
+ T_ENDDECLARE => T_ENDDECLARE,
+ T_ENDFOR => T_ENDFOR,
+ T_ENDFOREACH => T_ENDFOREACH,
+ T_ENDIF => T_ENDIF,
+ T_ENDSWITCH => T_ENDSWITCH,
+ T_ENDWHILE => T_ENDWHILE,
+ T_ENUM => T_ENUM,
+ T_EVAL => T_EVAL,
+ T_EXIT => T_EXIT,
+ T_EXTENDS => T_EXTENDS,
+ T_FINAL => T_FINAL,
+ T_FINALLY => T_FINALLY,
+ T_FN => T_FN,
+ T_FOR => T_FOR,
+ T_FOREACH => T_FOREACH,
+ T_FUNCTION => T_FUNCTION,
+ T_GLOBAL => T_GLOBAL,
+ T_GOTO => T_GOTO,
+ T_IF => T_IF,
+ T_IMPLEMENTS => T_IMPLEMENTS,
+ T_INCLUDE => T_INCLUDE,
+ T_INCLUDE_ONCE => T_INCLUDE_ONCE,
+ T_INSTANCEOF => T_INSTANCEOF,
+ T_INSTEADOF => T_INSTEADOF,
+ T_INTERFACE => T_INTERFACE,
+ T_ISSET => T_ISSET,
+ T_LIST => T_LIST,
+ T_LOGICAL_AND => T_LOGICAL_AND,
+ T_LOGICAL_OR => T_LOGICAL_OR,
+ T_LOGICAL_XOR => T_LOGICAL_XOR,
+ T_MATCH => T_MATCH,
+ T_NAMESPACE => T_NAMESPACE,
+ T_NEW => T_NEW,
+ T_PRINT => T_PRINT,
+ T_PRIVATE => T_PRIVATE,
+ T_PROTECTED => T_PROTECTED,
+ T_PUBLIC => T_PUBLIC,
+ T_READONLY => T_READONLY,
+ T_REQUIRE => T_REQUIRE,
+ T_REQUIRE_ONCE => T_REQUIRE_ONCE,
+ T_RETURN => T_RETURN,
+ T_STATIC => T_STATIC,
+ T_SWITCH => T_SWITCH,
+ T_THROW => T_THROW,
+ T_TRAIT => T_TRAIT,
+ T_TRY => T_TRY,
+ T_UNSET => T_UNSET,
+ T_USE => T_USE,
+ T_VAR => T_VAR,
+ T_WHILE => T_WHILE,
+ T_YIELD => T_YIELD,
+ T_YIELD_FROM => T_YIELD_FROM,
+ ];
+
/**
* Given a token, returns the name of the token.
diff --git a/tests/Core/Autoloader/DetermineLoadedClassTest.php b/tests/Core/Autoloader/DetermineLoadedClassTest.php
index 65542b670b..c0f38fa6f1 100644
--- a/tests/Core/Autoloader/DetermineLoadedClassTest.php
+++ b/tests/Core/Autoloader/DetermineLoadedClassTest.php
@@ -1,6 +1,6 @@
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
@@ -9,6 +9,7 @@
namespace PHP_CodeSniffer\Tests\Core\Autoloader;
+use PHP_CodeSniffer\Autoload;
use PHPUnit\Framework\TestCase;
class DetermineLoadedClassTest extends TestCase
@@ -51,7 +52,7 @@ public function testOrdered()
'traits' => [],
];
- $className = \PHP_CodeSniffer\Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
+ $className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
}//end testOrdered()
@@ -81,7 +82,7 @@ public function testUnordered()
'traits' => [],
];
- $className = \PHP_CodeSniffer\Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
+ $className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
$classesAfterLoad = [
@@ -95,7 +96,7 @@ public function testUnordered()
'traits' => [],
];
- $className = \PHP_CodeSniffer\Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
+ $className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
$classesAfterLoad = [
@@ -109,7 +110,7 @@ public function testUnordered()
'traits' => [],
];
- $className = \PHP_CodeSniffer\Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
+ $className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
}//end testUnordered()
diff --git a/tests/Core/File/FindEndOfStatementTest.inc b/tests/Core/File/FindEndOfStatementTest.inc
index 6dfd0a2807..8351679891 100644
--- a/tests/Core/File/FindEndOfStatementTest.inc
+++ b/tests/Core/File/FindEndOfStatementTest.inc
@@ -39,6 +39,8 @@ $a = [
/* testStaticArrowFunction */
static fn ($a) => $a;
+return 0;
+
/* testArrowFunctionReturnValue */
fn(): array => [a($a, $b)];
@@ -101,5 +103,3 @@ $result = match ($key) {
2 => 'one',
},
};
-
-return 0;
diff --git a/tests/Core/File/FindImplementedInterfaceNamesTest.inc b/tests/Core/File/FindImplementedInterfaceNamesTest.inc
index 3885b27e1d..44c0f64321 100644
--- a/tests/Core/File/FindImplementedInterfaceNamesTest.inc
+++ b/tests/Core/File/FindImplementedInterfaceNamesTest.inc
@@ -24,3 +24,12 @@ class testFECNClassThatExtendsAndImplements extends testFECNClass implements Int
/* testClassThatImplementsAndExtends */
class testFECNClassThatImplementsAndExtends implements \InterfaceA, InterfaceB extends testFECNClass {}
+
+/* testBackedEnumWithoutImplements */
+enum Suit:string {}
+
+/* testEnumImplements */
+enum Suit implements Colorful {}
+
+/* testBackedEnumImplements */
+enum Suit: string implements Colorful, \Deck {}
diff --git a/tests/Core/File/FindImplementedInterfaceNamesTest.php b/tests/Core/File/FindImplementedInterfaceNamesTest.php
index 834e083202..2395032897 100644
--- a/tests/Core/File/FindImplementedInterfaceNamesTest.php
+++ b/tests/Core/File/FindImplementedInterfaceNamesTest.php
@@ -27,7 +27,7 @@ class FindImplementedInterfaceNamesTest extends AbstractMethodUnitTest
*/
public function testFindImplementedInterfaceNames($identifier, $expected)
{
- $OOToken = $this->getTargetToken($identifier, [T_CLASS, T_ANON_CLASS, T_INTERFACE]);
+ $OOToken = $this->getTargetToken($identifier, [T_CLASS, T_ANON_CLASS, T_INTERFACE, T_ENUM]);
$result = self::$phpcsFile->findImplementedInterfaceNames($OOToken);
$this->assertSame($expected, $result);
@@ -81,6 +81,21 @@ public function dataImplementedInterface()
'InterfaceB',
],
],
+ [
+ '/* testBackedEnumWithoutImplements */',
+ false,
+ ],
+ [
+ '/* testEnumImplements */',
+ ['Colorful'],
+ ],
+ [
+ '/* testBackedEnumImplements */',
+ [
+ 'Colorful',
+ '\Deck',
+ ],
+ ],
];
}//end dataImplementedInterface()
diff --git a/tests/Core/File/FindStartOfStatementTest.php b/tests/Core/File/FindStartOfStatementTest.php
index 464021f6fc..ff859eca1c 100644
--- a/tests/Core/File/FindStartOfStatementTest.php
+++ b/tests/Core/File/FindStartOfStatementTest.php
@@ -469,7 +469,7 @@ public function testNestedMatch()
/**
- * Test nested match expressions.
+ * Test PHP open tag.
*
* @return void
*/
@@ -485,7 +485,7 @@ public function testOpenTag()
/**
- * Test nested match expressions.
+ * Test PHP short open echo tag.
*
* @return void
*/
diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc
index ea47e9fc57..f40b6021f4 100644
--- a/tests/Core/File/GetMemberPropertiesTest.inc
+++ b/tests/Core/File/GetMemberPropertiesTest.inc
@@ -239,6 +239,21 @@ $anon = class() {
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
public int |string| /*comment*/ INT $duplicateTypeInUnion;
+
+ /* testPHP81Readonly */
+ public readonly int $readonly;
+
+ /* testPHP81ReadonlyWithNullableType */
+ public readonly ?array $array;
+
+ /* testPHP81ReadonlyWithUnionType */
+ public readonly string|int $readonlyWithUnionType;
+
+ /* testPHP81ReadonlyWithUnionTypeWithNull */
+ protected ReadOnly string|null $readonlyWithUnionTypeWithNull;
+
+ /* testPHP81OnlyReadonlyWithUnionType */
+ readonly string|int $onlyReadonly;
};
$anon = class {
@@ -256,3 +271,34 @@ $anon = class {
]
private mixed $baz;
};
+
+enum Suit
+{
+ /* testEnumProperty */
+ protected $anonymous;
+}
+
+enum Direction implements ArrayAccess
+{
+ case Up;
+ case Down;
+
+ /* testEnumMethodParamNotProperty */
+ public function offsetGet($val) { ... }
+}
+
+$anon = class() {
+ /* testPHP81IntersectionTypes */
+ public Foo&Bar $intersectionType;
+
+ /* testPHP81MoreIntersectionTypes */
+ public Foo&Bar&Baz $moreIntersectionTypes;
+
+ /* testPHP81IllegalIntersectionTypes */
+ // Intentional fatal error - types which are not allowed for intersection type, but that's not the concern of the method.
+ public int&string $illegalIntersectionType;
+
+ /* testPHP81NulltableIntersectionType */
+ // Intentional fatal error - nullability is not allowed with intersection type, but that's not the concern of the method.
+ public ?Foo&Bar $nullableIntersectionType;
+};
diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php
index 0af0437932..934d8e2890 100644
--- a/tests/Core/File/GetMemberPropertiesTest.php
+++ b/tests/Core/File/GetMemberPropertiesTest.php
@@ -51,6 +51,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -61,6 +62,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '?int',
'nullable_type' => true,
],
@@ -71,6 +73,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -81,6 +84,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'string',
'nullable_type' => false,
],
@@ -91,6 +95,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -101,6 +106,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'bool',
'nullable_type' => false,
],
@@ -111,6 +117,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -121,6 +128,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'array',
'nullable_type' => false,
],
@@ -131,6 +139,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -141,6 +150,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '?string',
'nullable_type' => true,
],
@@ -151,6 +161,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -161,6 +172,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -171,6 +183,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -181,6 +194,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -191,6 +205,18 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
+ 'type' => '',
+ 'nullable_type' => false,
+ ],
+ ],
+ [
+ '/* testNoPrefix */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -201,6 +227,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -211,6 +238,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -221,6 +249,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -231,6 +260,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'float',
'nullable_type' => false,
],
@@ -241,6 +271,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'float',
'nullable_type' => false,
],
@@ -251,6 +282,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '?string',
'nullable_type' => true,
],
@@ -261,26 +293,18 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '?string',
'nullable_type' => true,
],
],
- [
- '/* testNoPrefix */',
- [
- 'scope' => 'public',
- 'scope_specified' => false,
- 'is_static' => false,
- 'type' => '',
- 'nullable_type' => false,
- ],
- ],
[
'/* testGroupProtectedStatic 1 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -291,6 +315,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -301,6 +326,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -311,6 +337,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -321,6 +348,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -331,6 +359,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -341,6 +370,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -351,6 +381,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -361,6 +392,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -371,6 +403,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -381,6 +414,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '?array',
'nullable_type' => true,
],
@@ -391,6 +425,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '\MyNamespace\MyClass',
'nullable_type' => false,
],
@@ -401,6 +436,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '?ClassName',
'nullable_type' => true,
],
@@ -411,6 +447,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '?Folder\ClassName',
'nullable_type' => true,
],
@@ -421,6 +458,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '\MyNamespace\MyClass\Foo',
'nullable_type' => false,
],
@@ -431,6 +469,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -445,6 +484,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -455,6 +495,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
@@ -465,6 +506,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
+ 'is_readonly' => false,
'type' => 'miXed',
'nullable_type' => false,
],
@@ -475,6 +517,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '?mixed',
'nullable_type' => true,
],
@@ -485,6 +528,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '?namespace\Name',
'nullable_type' => true,
],
@@ -495,6 +539,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'int|float',
'nullable_type' => false,
],
@@ -505,6 +550,7 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'MyClassA|\Package\MyClassB',
'nullable_type' => false,
],
@@ -515,6 +561,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'array|bool|int|float|NULL|object|string',
'nullable_type' => false,
],
@@ -525,6 +572,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'false|mixed|self|parent|iterable|Resource',
'nullable_type' => false,
],
@@ -535,6 +583,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
// Missing static, but that's OK as not an allowed syntax.
'type' => 'callable||void',
'nullable_type' => false,
@@ -546,6 +595,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '?int|float',
'nullable_type' => true,
],
@@ -556,6 +606,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'null',
'nullable_type' => false,
],
@@ -566,6 +617,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'false',
'nullable_type' => false,
],
@@ -576,6 +628,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'bool|FALSE',
'nullable_type' => false,
],
@@ -586,6 +639,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'object|ClassName',
'nullable_type' => false,
],
@@ -596,6 +650,7 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'iterable|array|Traversable',
'nullable_type' => false,
],
@@ -606,16 +661,73 @@ public function dataGetMemberProperties()
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'int|string|INT',
'nullable_type' => false,
],
],
+ [
+ '/* testPHP81Readonly */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => true,
+ 'is_static' => false,
+ 'is_readonly' => true,
+ 'type' => 'int',
+ 'nullable_type' => false,
+ ],
+ ],
+ [
+ '/* testPHP81ReadonlyWithNullableType */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => true,
+ 'is_static' => false,
+ 'is_readonly' => true,
+ 'type' => '?array',
+ 'nullable_type' => true,
+ ],
+ ],
+ [
+ '/* testPHP81ReadonlyWithUnionType */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => true,
+ 'is_static' => false,
+ 'is_readonly' => true,
+ 'type' => 'string|int',
+ 'nullable_type' => false,
+ ],
+ ],
+ [
+ '/* testPHP81ReadonlyWithUnionTypeWithNull */',
+ [
+ 'scope' => 'protected',
+ 'scope_specified' => true,
+ 'is_static' => false,
+ 'is_readonly' => true,
+ 'type' => 'string|null',
+ 'nullable_type' => false,
+ ],
+ ],
+ [
+ '/* testPHP81OnlyReadonlyWithUnionType */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'is_static' => false,
+ 'is_readonly' => true,
+ 'type' => 'string|int',
+ 'nullable_type' => false,
+ ],
+ ],
[
'/* testPHP8PropertySingleAttribute */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'string',
'nullable_type' => false,
],
@@ -626,6 +738,7 @@ public function dataGetMemberProperties()
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => '?int|float',
'nullable_type' => true,
],
@@ -636,10 +749,55 @@ public function dataGetMemberProperties()
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
+ 'is_readonly' => false,
'type' => 'mixed',
'nullable_type' => false,
],
],
+ [
+ '/* testEnumProperty */',
+ [],
+ ],
+ [
+ '/* testPHP81IntersectionTypes */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => true,
+ 'is_static' => false,
+ 'type' => 'Foo&Bar',
+ 'nullable_type' => false,
+ ],
+ ],
+ [
+ '/* testPHP81MoreIntersectionTypes */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => true,
+ 'is_static' => false,
+ 'type' => 'Foo&Bar&Baz',
+ 'nullable_type' => false,
+ ],
+ ],
+ [
+ '/* testPHP81IllegalIntersectionTypes */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => true,
+ 'is_static' => false,
+ 'type' => 'int&string',
+ 'nullable_type' => false,
+ ],
+ ],
+ [
+ '/* testPHP81NulltableIntersectionType */',
+ [
+ 'scope' => 'public',
+ 'scope_specified' => true,
+ 'is_static' => false,
+ 'type' => '?Foo&Bar',
+ 'nullable_type' => true,
+ ],
+ ],
];
}//end dataGetMemberProperties()
@@ -681,6 +839,7 @@ public function dataNotClassProperty()
['/* testGlobalVariable */'],
['/* testNestedMethodParam 1 */'],
['/* testNestedMethodParam 2 */'],
+ ['/* testEnumMethodParamNotProperty */'],
];
}//end dataNotClassProperty()
diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc
index ed1762e7d2..dc46549140 100644
--- a/tests/Core/File/GetMethodParametersTest.inc
+++ b/tests/Core/File/GetMethodParametersTest.inc
@@ -108,6 +108,11 @@ class ConstructorPropertyPromotionAndNormalParams {
public function __construct(public int $promotedProp, ?int $normalArg) {}
}
+class ConstructorPropertyPromotionWithReadOnly {
+ /* testPHP81ConstructorPropertyPromotionWithReadOnly */
+ public function __construct(public readonly ?int $promotedProp, readonly private string|bool &$promotedToo) {}
+}
+
/* testPHP8ConstructorPropertyPromotionGlobalFunction */
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
function globalFunction(private $x) {}
@@ -140,3 +145,20 @@ class ParametersWithAttributes(
&...$otherParam,
) {}
}
+
+/* testPHP8IntersectionTypes */
+function intersectionTypes(Foo&Bar $obj1, Boo&Bar $obj2) {}
+
+/* testPHP81IntersectionTypesWithSpreadOperatorAndReference */
+function globalFunctionWithSpreadAndReference(Boo&Bar &$paramA, Foo&Bar ...$paramB) {}
+
+/* testPHP81MoreIntersectionTypes */
+function moreIntersectionTypes(MyClassA&\Package\MyClassB&\Package\MyClassC $var) {}
+
+/* testPHP81IllegalIntersectionTypes */
+// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method.
+$closure = function (string&int $numeric_string) {};
+
+/* testPHP81NullableIntersectionTypes */
+// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method.
+$closure = function (?Foo&Bar $object) {};
diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php
index e7692a7153..ba4d754485 100644
--- a/tests/Core/File/GetMethodParametersTest.php
+++ b/tests/Core/File/GetMethodParametersTest.php
@@ -62,173 +62,173 @@ public function testArrayHint()
/**
- * Verify type hint parsing.
+ * Verify variable.
*
* @return void
*/
- public function testTypeHint()
+ public function testVariable()
{
$expected = [];
$expected[0] = [
- 'name' => '$var1',
- 'content' => 'foo $var1',
- 'has_attributes' => false,
- 'pass_by_reference' => false,
- 'variable_length' => false,
- 'type_hint' => 'foo',
- 'nullable_type' => false,
- ];
-
- $expected[1] = [
- 'name' => '$var2',
- 'content' => 'bar $var2',
+ 'name' => '$var',
+ 'content' => '$var',
'has_attributes' => false,
'pass_by_reference' => false,
'variable_length' => false,
- 'type_hint' => 'bar',
+ 'type_hint' => '',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
- }//end testTypeHint()
+ }//end testVariable()
/**
- * Verify self type hint parsing.
+ * Verify default value parsing with a single function param.
*
* @return void
*/
- public function testSelfTypeHint()
+ public function testSingleDefaultValue()
{
$expected = [];
$expected[0] = [
- 'name' => '$var',
- 'content' => 'self $var',
+ 'name' => '$var1',
+ 'content' => '$var1=self::CONSTANT',
'has_attributes' => false,
+ 'default' => 'self::CONSTANT',
'pass_by_reference' => false,
'variable_length' => false,
- 'type_hint' => 'self',
+ 'type_hint' => '',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
- }//end testSelfTypeHint()
+ }//end testSingleDefaultValue()
/**
- * Verify nullable type hint parsing.
+ * Verify default value parsing.
*
* @return void
*/
- public function testNullableTypeHint()
+ public function testDefaultValues()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
- 'content' => '?int $var1',
+ 'content' => '$var1=1',
'has_attributes' => false,
+ 'default' => '1',
'pass_by_reference' => false,
'variable_length' => false,
- 'type_hint' => '?int',
- 'nullable_type' => true,
+ 'type_hint' => '',
+ 'nullable_type' => false,
];
-
$expected[1] = [
'name' => '$var2',
- 'content' => '?\bar $var2',
+ 'content' => "\$var2='value'",
'has_attributes' => false,
+ 'default' => "'value'",
'pass_by_reference' => false,
'variable_length' => false,
- 'type_hint' => '?\bar',
- 'nullable_type' => true,
+ 'type_hint' => '',
+ 'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
- }//end testNullableTypeHint()
+ }//end testDefaultValues()
/**
- * Verify variable.
+ * Verify type hint parsing.
*
* @return void
*/
- public function testVariable()
+ public function testTypeHint()
{
$expected = [];
$expected[0] = [
- 'name' => '$var',
- 'content' => '$var',
+ 'name' => '$var1',
+ 'content' => 'foo $var1',
'has_attributes' => false,
'pass_by_reference' => false,
'variable_length' => false,
- 'type_hint' => '',
+ 'type_hint' => 'foo',
+ 'nullable_type' => false,
+ ];
+
+ $expected[1] = [
+ 'name' => '$var2',
+ 'content' => 'bar $var2',
+ 'has_attributes' => false,
+ 'pass_by_reference' => false,
+ 'variable_length' => false,
+ 'type_hint' => 'bar',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
- }//end testVariable()
+ }//end testTypeHint()
/**
- * Verify default value parsing with a single function param.
+ * Verify self type hint parsing.
*
* @return void
*/
- public function testSingleDefaultValue()
+ public function testSelfTypeHint()
{
$expected = [];
$expected[0] = [
- 'name' => '$var1',
- 'content' => '$var1=self::CONSTANT',
+ 'name' => '$var',
+ 'content' => 'self $var',
'has_attributes' => false,
- 'default' => 'self::CONSTANT',
'pass_by_reference' => false,
'variable_length' => false,
- 'type_hint' => '',
+ 'type_hint' => 'self',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
- }//end testSingleDefaultValue()
+ }//end testSelfTypeHint()
/**
- * Verify default value parsing.
+ * Verify nullable type hint parsing.
*
* @return void
*/
- public function testDefaultValues()
+ public function testNullableTypeHint()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
- 'content' => '$var1=1',
+ 'content' => '?int $var1',
'has_attributes' => false,
- 'default' => '1',
'pass_by_reference' => false,
'variable_length' => false,
- 'type_hint' => '',
- 'nullable_type' => false,
+ 'type_hint' => '?int',
+ 'nullable_type' => true,
];
+
$expected[1] = [
'name' => '$var2',
- 'content' => "\$var2='value'",
+ 'content' => '?\bar $var2',
'has_attributes' => false,
- 'default' => "'value'",
'pass_by_reference' => false,
'variable_length' => false,
- 'type_hint' => '',
- 'nullable_type' => false,
+ 'type_hint' => '?\bar',
+ 'nullable_type' => true,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
- }//end testDefaultValues()
+ }//end testNullableTypeHint()
/**
@@ -696,6 +696,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes()
'type_hint' => '',
'nullable_type' => false,
'property_visibility' => 'public',
+ 'property_readonly' => false,
];
$expected[1] = [
'name' => '$y',
@@ -707,6 +708,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes()
'type_hint' => '',
'nullable_type' => false,
'property_visibility' => 'protected',
+ 'property_readonly' => false,
];
$expected[2] = [
'name' => '$z',
@@ -718,6 +720,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes()
'type_hint' => '',
'nullable_type' => false,
'property_visibility' => 'private',
+ 'property_readonly' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
@@ -742,6 +745,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes()
'type_hint' => 'float|int',
'nullable_type' => false,
'property_visibility' => 'protected',
+ 'property_readonly' => false,
];
$expected[1] = [
'name' => '$y',
@@ -753,6 +757,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes()
'type_hint' => '?string',
'nullable_type' => true,
'property_visibility' => 'public',
+ 'property_readonly' => false,
];
$expected[2] = [
'name' => '$z',
@@ -763,6 +768,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes()
'type_hint' => 'mixed',
'nullable_type' => false,
'property_visibility' => 'private',
+ 'property_readonly' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
@@ -787,6 +793,7 @@ public function testPHP8ConstructorPropertyPromotionAndNormalParam()
'type_hint' => 'int',
'nullable_type' => false,
'property_visibility' => 'public',
+ 'property_readonly' => false,
];
$expected[1] = [
'name' => '$normalArg',
@@ -803,6 +810,42 @@ public function testPHP8ConstructorPropertyPromotionAndNormalParam()
}//end testPHP8ConstructorPropertyPromotionAndNormalParam()
+ /**
+ * Verify recognition of PHP8 constructor with property promotion using PHP 8.1 readonly keyword.
+ *
+ * @return void
+ */
+ public function testPHP81ConstructorPropertyPromotionWithReadOnly()
+ {
+ $expected = [];
+ $expected[0] = [
+ 'name' => '$promotedProp',
+ 'content' => 'public readonly ?int $promotedProp',
+ 'has_attributes' => false,
+ 'pass_by_reference' => false,
+ 'variable_length' => false,
+ 'type_hint' => '?int',
+ 'nullable_type' => true,
+ 'property_visibility' => 'public',
+ 'property_readonly' => true,
+ ];
+ $expected[1] = [
+ 'name' => '$promotedToo',
+ 'content' => 'readonly private string|bool &$promotedToo',
+ 'has_attributes' => false,
+ 'pass_by_reference' => true,
+ 'variable_length' => false,
+ 'type_hint' => 'string|bool',
+ 'nullable_type' => false,
+ 'property_visibility' => 'private',
+ 'property_readonly' => true,
+ ];
+
+ $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81ConstructorPropertyPromotionWithReadOnly()
+
+
/**
* Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax.
*
@@ -948,6 +991,139 @@ public function testParameterAttributesInFunctionDeclaration()
}//end testParameterAttributesInFunctionDeclaration()
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration.
+ *
+ * @return void
+ */
+ public function testPHP8IntersectionTypes()
+ {
+ $expected = [];
+ $expected[0] = [
+ 'name' => '$obj1',
+ 'content' => 'Foo&Bar $obj1',
+ 'has_attributes' => false,
+ 'pass_by_reference' => false,
+ 'variable_length' => false,
+ 'type_hint' => 'Foo&Bar',
+ 'nullable_type' => false,
+ ];
+ $expected[1] = [
+ 'name' => '$obj2',
+ 'content' => 'Boo&Bar $obj2',
+ 'has_attributes' => false,
+ 'pass_by_reference' => false,
+ 'variable_length' => false,
+ 'type_hint' => 'Boo&Bar',
+ 'nullable_type' => false,
+ ];
+
+ $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP8IntersectionTypes()
+
+
+ /**
+ * Verify recognition of PHP8 intersection type declaration when the variable has either a spread operator or a reference.
+ *
+ * @return void
+ */
+ public function testPHP81IntersectionTypesWithSpreadOperatorAndReference()
+ {
+ $expected = [];
+ $expected[0] = [
+ 'name' => '$paramA',
+ 'content' => 'Boo&Bar &$paramA',
+ 'has_attributes' => false,
+ 'pass_by_reference' => true,
+ 'variable_length' => false,
+ 'type_hint' => 'Boo&Bar',
+ 'nullable_type' => false,
+ ];
+ $expected[1] = [
+ 'name' => '$paramB',
+ 'content' => 'Foo&Bar ...$paramB',
+ 'has_attributes' => false,
+ 'pass_by_reference' => false,
+ 'variable_length' => true,
+ 'type_hint' => 'Foo&Bar',
+ 'nullable_type' => false,
+ ];
+
+ $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81IntersectionTypesWithSpreadOperatorAndReference()
+
+
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration with more types.
+ *
+ * @return void
+ */
+ public function testPHP81MoreIntersectionTypes()
+ {
+ $expected = [];
+ $expected[0] = [
+ 'name' => '$var',
+ 'content' => 'MyClassA&\Package\MyClassB&\Package\MyClassC $var',
+ 'has_attributes' => false,
+ 'pass_by_reference' => false,
+ 'variable_length' => false,
+ 'type_hint' => 'MyClassA&\Package\MyClassB&\Package\MyClassC',
+ 'nullable_type' => false,
+ ];
+
+ $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81MoreIntersectionTypes()
+
+
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration with illegal simple types.
+ *
+ * @return void
+ */
+ public function testPHP81IllegalIntersectionTypes()
+ {
+ $expected = [];
+ $expected[0] = [
+ 'name' => '$numeric_string',
+ 'content' => 'string&int $numeric_string',
+ 'has_attributes' => false,
+ 'pass_by_reference' => false,
+ 'variable_length' => false,
+ 'type_hint' => 'string&int',
+ 'nullable_type' => false,
+ ];
+
+ $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81IllegalIntersectionTypes()
+
+
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration with (illegal) nullability.
+ *
+ * @return void
+ */
+ public function testPHP81NullableIntersectionTypes()
+ {
+ $expected = [];
+ $expected[0] = [
+ 'name' => '$object',
+ 'content' => '?Foo&Bar $object',
+ 'has_attributes' => false,
+ 'pass_by_reference' => false,
+ 'variable_length' => false,
+ 'type_hint' => '?Foo&Bar',
+ 'nullable_type' => true,
+ ];
+
+ $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81NullableIntersectionTypes()
+
+
/**
* Test helper.
*
diff --git a/tests/Core/File/GetMethodPropertiesTest.inc b/tests/Core/File/GetMethodPropertiesTest.inc
index 3e9682df6e..0c592369a5 100644
--- a/tests/Core/File/GetMethodPropertiesTest.inc
+++ b/tests/Core/File/GetMethodPropertiesTest.inc
@@ -33,7 +33,7 @@ class MyClass {
/* testMessyNullableReturnMethod */
public function myFunction() /* comment
- */ :
+ */ :
/* comment */ ? //comment
array {}
@@ -126,3 +126,27 @@ interface FooBar {
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
function duplicateTypeInUnion(): int | /*comment*/ string | INT {}
+
+/* testPHP81NeverType */
+function never(): never {}
+
+/* testPHP81NullableNeverType */
+// Intentional fatal error - nullability is not allowed with never, but that's not the concern of the method.
+function nullableNever(): ?never {}
+
+/* testPHP8IntersectionTypes */
+function intersectionTypes(): Foo&Bar {}
+
+/* testPHP81MoreIntersectionTypes */
+function moreIntersectionTypes(): MyClassA&\Package\MyClassB&\Package\MyClassC {}
+
+/* testPHP81IntersectionArrowFunction */
+$fn = fn($var): MyClassA&\Package\MyClassB => $var;
+
+/* testPHP81IllegalIntersectionTypes */
+// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method.
+$closure = function (): string&int {};
+
+/* testPHP81NullableIntersectionTypes */
+// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method.
+$closure = function (): ?Foo&Bar {};
diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php
index d912d6b1ce..66f4eea3ea 100644
--- a/tests/Core/File/GetMethodPropertiesTest.php
+++ b/tests/Core/File/GetMethodPropertiesTest.php
@@ -728,6 +728,167 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
}//end testPHP8DuplicateTypeInUnionWhitespaceAndComment()
+ /**
+ * Verify recognition of PHP8.1 type "never".
+ *
+ * @return void
+ */
+ public function testPHP81NeverType()
+ {
+ $expected = [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'return_type' => 'never',
+ 'nullable_return_type' => false,
+ 'is_abstract' => false,
+ 'is_final' => false,
+ 'is_static' => false,
+ 'has_body' => true,
+ ];
+
+ $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81NeverType()
+
+
+ /**
+ * Verify recognition of PHP8.1 type "never" with (illegal) nullability.
+ *
+ * @return void
+ */
+ public function testPHP81NullableNeverType()
+ {
+ $expected = [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'return_type' => '?never',
+ 'nullable_return_type' => true,
+ 'is_abstract' => false,
+ 'is_final' => false,
+ 'is_static' => false,
+ 'has_body' => true,
+ ];
+
+ $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81NullableNeverType()
+
+
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration.
+ *
+ * @return void
+ */
+ public function testPHP8IntersectionTypes()
+ {
+ $expected = [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'return_type' => 'Foo&Bar',
+ 'nullable_return_type' => false,
+ 'is_abstract' => false,
+ 'is_final' => false,
+ 'is_static' => false,
+ 'has_body' => true,
+ ];
+
+ $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP8IntersectionTypes()
+
+
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration with more types.
+ *
+ * @return void
+ */
+ public function testPHP81MoreIntersectionTypes()
+ {
+ $expected = [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'return_type' => 'MyClassA&\Package\MyClassB&\Package\MyClassC',
+ 'nullable_return_type' => false,
+ 'is_abstract' => false,
+ 'is_final' => false,
+ 'is_static' => false,
+ 'has_body' => true,
+ ];
+
+ $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81MoreIntersectionTypes()
+
+
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration in arrow function.
+ *
+ * @return void
+ */
+ public function testPHP81IntersectionArrowFunction()
+ {
+ $expected = [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'return_type' => 'MyClassA&\Package\MyClassB',
+ 'nullable_return_type' => false,
+ 'is_abstract' => false,
+ 'is_final' => false,
+ 'is_static' => false,
+ 'has_body' => true,
+ ];
+
+ $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81IntersectionArrowFunction()
+
+
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration with illegal simple types.
+ *
+ * @return void
+ */
+ public function testPHP81IllegalIntersectionTypes()
+ {
+ $expected = [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'return_type' => 'string&int',
+ 'nullable_return_type' => false,
+ 'is_abstract' => false,
+ 'is_final' => false,
+ 'is_static' => false,
+ 'has_body' => true,
+ ];
+
+ $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81IllegalIntersectionTypes()
+
+
+ /**
+ * Verify recognition of PHP8.1 intersection type declaration with (illegal) nullability.
+ *
+ * @return void
+ */
+ public function testPHP81NullableIntersectionTypes()
+ {
+ $expected = [
+ 'scope' => 'public',
+ 'scope_specified' => false,
+ 'return_type' => '?Foo&Bar',
+ 'nullable_return_type' => true,
+ 'is_abstract' => false,
+ 'is_final' => false,
+ 'is_static' => false,
+ 'has_body' => true,
+ ];
+
+ $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
+
+ }//end testPHP81NullableIntersectionTypes()
+
+
/**
* Test helper.
*
diff --git a/tests/Core/File/IsReferenceTest.inc b/tests/Core/File/IsReferenceTest.inc
index cd40ed3ba7..f71e2639d8 100644
--- a/tests/Core/File/IsReferenceTest.inc
+++ b/tests/Core/File/IsReferenceTest.inc
@@ -12,7 +12,7 @@ $a = [ $something & $somethingElse ];
$a = [ $first, $something & self::$somethingElse ];
/* testBitwiseAndD */
-$a = array $first, $something & $somethingElse );
+$a = array( $first, $something & $somethingElse );
/* testBitwiseAndE */
$a = [ 'a' => $first, 'b' => $something & $somethingElse ];
@@ -51,7 +51,7 @@ function myFunction(array &$one) {}
$closure = function (\MyClass &$one) {};
/* testFunctionPassByReferenceG */
-$closure = function myFunc($param, &...$moreParams) {};
+$closure = function ($param, &...$moreParams) {};
/* testForeachValueByReference */
foreach( $array as $key => &$value ) {}
diff --git a/tests/Core/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php
index b10a1efb95..8ac826f2f6 100644
--- a/tests/Core/Tokenizer/AttributesTest.php
+++ b/tests/Core/Tokenizer/AttributesTest.php
@@ -10,7 +10,6 @@
namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
-use PHP_CodeSniffer\Util\Tokens;
class AttributesTest extends AbstractMethodUnitTest
{
@@ -289,7 +288,7 @@ public function testAttributeAndLineComment()
/**
- * Test that attribute followed by a line comment is parsed correctly.
+ * Test that attributes on function declaration parameters are parsed correctly.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int $position The token position (starting from T_FUNCTION) of T_ATTRIBUTE token.
diff --git a/tests/Core/Tokenizer/BackfillEnumTest.inc b/tests/Core/Tokenizer/BackfillEnumTest.inc
new file mode 100644
index 0000000000..82bfe24fd5
--- /dev/null
+++ b/tests/Core/Tokenizer/BackfillEnumTest.inc
@@ -0,0 +1,95 @@
+enum = 'foo';
+ }
+}
+
+/* testEnumUsedAsFunctionName */
+function enum()
+{
+}
+
+/* testDeclarationContainingComment */
+enum /* comment */ Name
+{
+ case SOME_CASE;
+}
+
+enum /* testEnumUsedAsEnumName */ Enum
+{
+}
+
+/* testEnumUsedAsNamespaceName */
+namespace Enum;
+/* testEnumUsedAsPartOfNamespaceName */
+namespace My\Enum\Collection;
+/* testEnumUsedInObjectInitialization */
+$obj = new Enum;
+/* testEnumAsFunctionCall */
+$var = enum($a, $b);
+/* testEnumAsFunctionCallWithNamespace */
+var = namespace\enum();
+/* testClassConstantFetchWithEnumAsClassName */
+echo Enum::CONSTANT;
+/* testClassConstantFetchWithEnumAsConstantName */
+echo ClassName::ENUM;
+
+/* testParseErrorMissingName */
+enum {
+ case SOME_CASE;
+}
+
+/* testParseErrorLiveCoding */
+// This must be the last test in the file.
+enum
diff --git a/tests/Core/Tokenizer/BackfillEnumTest.php b/tests/Core/Tokenizer/BackfillEnumTest.php
new file mode 100644
index 0000000000..33cff3a2c7
--- /dev/null
+++ b/tests/Core/Tokenizer/BackfillEnumTest.php
@@ -0,0 +1,229 @@
+
+ * @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+
+class BackfillEnumTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Test that the "enum" keyword is tokenized as such.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param string $testContent The token content to look for.
+ * @param int $openerOffset Offset to find expected scope opener.
+ * @param int $closerOffset Offset to find expected scope closer.
+ *
+ * @dataProvider dataEnums
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testEnums($testMarker, $testContent, $openerOffset, $closerOffset)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $enum = $this->getTargetToken($testMarker, [T_ENUM, T_STRING], $testContent);
+
+ $this->assertSame(T_ENUM, $tokens[$enum]['code']);
+ $this->assertSame('T_ENUM', $tokens[$enum]['type']);
+
+ $this->assertArrayHasKey('scope_condition', $tokens[$enum]);
+ $this->assertArrayHasKey('scope_opener', $tokens[$enum]);
+ $this->assertArrayHasKey('scope_closer', $tokens[$enum]);
+
+ $this->assertSame($enum, $tokens[$enum]['scope_condition']);
+
+ $scopeOpener = $tokens[$enum]['scope_opener'];
+ $scopeCloser = $tokens[$enum]['scope_closer'];
+
+ $expectedScopeOpener = ($enum + $openerOffset);
+ $expectedScopeCloser = ($enum + $closerOffset);
+
+ $this->assertSame($expectedScopeOpener, $scopeOpener);
+ $this->assertArrayHasKey('scope_condition', $tokens[$scopeOpener]);
+ $this->assertArrayHasKey('scope_opener', $tokens[$scopeOpener]);
+ $this->assertArrayHasKey('scope_closer', $tokens[$scopeOpener]);
+ $this->assertSame($enum, $tokens[$scopeOpener]['scope_condition']);
+ $this->assertSame($scopeOpener, $tokens[$scopeOpener]['scope_opener']);
+ $this->assertSame($scopeCloser, $tokens[$scopeOpener]['scope_closer']);
+
+ $this->assertSame($expectedScopeCloser, $scopeCloser);
+ $this->assertArrayHasKey('scope_condition', $tokens[$scopeCloser]);
+ $this->assertArrayHasKey('scope_opener', $tokens[$scopeCloser]);
+ $this->assertArrayHasKey('scope_closer', $tokens[$scopeCloser]);
+ $this->assertSame($enum, $tokens[$scopeCloser]['scope_condition']);
+ $this->assertSame($scopeOpener, $tokens[$scopeCloser]['scope_opener']);
+ $this->assertSame($scopeCloser, $tokens[$scopeCloser]['scope_closer']);
+
+ }//end testEnums()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testEnums()
+ *
+ * @return array
+ */
+ public function dataEnums()
+ {
+ return [
+ [
+ '/* testPureEnum */',
+ 'enum',
+ 4,
+ 12,
+ ],
+ [
+ '/* testBackedIntEnum */',
+ 'enum',
+ 7,
+ 29,
+ ],
+ [
+ '/* testBackedStringEnum */',
+ 'enum',
+ 8,
+ 30,
+ ],
+ [
+ '/* testComplexEnum */',
+ 'enum',
+ 11,
+ 72,
+ ],
+ [
+ '/* testEnumWithEnumAsClassName */',
+ 'enum',
+ 6,
+ 7,
+ ],
+ [
+ '/* testEnumIsCaseInsensitive */',
+ 'EnUm',
+ 4,
+ 5,
+ ],
+ [
+ '/* testDeclarationContainingComment */',
+ 'enum',
+ 6,
+ 14,
+ ],
+ ];
+
+ }//end dataEnums()
+
+
+ /**
+ * Test that "enum" when not used as the keyword is still tokenized as `T_STRING`.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param string $testContent The token content to look for.
+ *
+ * @dataProvider dataNotEnums
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testNotEnums($testMarker, $testContent)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $target = $this->getTargetToken($testMarker, [T_ENUM, T_STRING], $testContent);
+ $this->assertSame(T_STRING, $tokens[$target]['code']);
+ $this->assertSame('T_STRING', $tokens[$target]['type']);
+
+ }//end testNotEnums()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testNotEnums()
+ *
+ * @return array
+ */
+ public function dataNotEnums()
+ {
+ return [
+ [
+ '/* testEnumAsClassNameAfterEnumKeyword */',
+ 'Enum',
+ ],
+ [
+ '/* testEnumUsedAsClassName */',
+ 'Enum',
+ ],
+ [
+ '/* testEnumUsedAsClassConstantName */',
+ 'ENUM',
+ ],
+ [
+ '/* testEnumUsedAsMethodName */',
+ 'enum',
+ ],
+ [
+ '/* testEnumUsedAsPropertyName */',
+ 'enum',
+ ],
+ [
+ '/* testEnumUsedAsFunctionName */',
+ 'enum',
+ ],
+ [
+ '/* testEnumUsedAsEnumName */',
+ 'Enum',
+ ],
+ [
+ '/* testEnumUsedAsNamespaceName */',
+ 'Enum',
+ ],
+ [
+ '/* testEnumUsedAsPartOfNamespaceName */',
+ 'Enum',
+ ],
+ [
+ '/* testEnumUsedInObjectInitialization */',
+ 'Enum',
+ ],
+ [
+ '/* testEnumAsFunctionCall */',
+ 'enum',
+ ],
+ [
+ '/* testEnumAsFunctionCallWithNamespace */',
+ 'enum',
+ ],
+ [
+ '/* testClassConstantFetchWithEnumAsClassName */',
+ 'Enum',
+ ],
+ [
+ '/* testClassConstantFetchWithEnumAsConstantName */',
+ 'ENUM',
+ ],
+ [
+ '/* testParseErrorMissingName */',
+ 'enum',
+ ],
+ [
+ '/* testParseErrorLiveCoding */',
+ 'enum',
+ ],
+ ];
+
+ }//end dataNotEnums()
+
+
+}//end class
diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc
new file mode 100644
index 0000000000..154d489506
--- /dev/null
+++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc
@@ -0,0 +1,28 @@
+
+ * @copyright 2019 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+
+class BackfillExplicitOctalNotationTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Test that explicitly-defined octal values are tokenized as a single number and not as a number and a string.
+ *
+ * @param string $marker The comment which prefaces the target token in the test file.
+ * @param string $value The expected content of the token
+ * @param int|string $nextToken The expected next token.
+ * @param string $nextContent The expected content of the next token.
+ *
+ * @dataProvider dataExplicitOctalNotation
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testExplicitOctalNotation($marker, $value, $nextToken, $nextContent)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $number = $this->getTargetToken($marker, [T_LNUMBER]);
+
+ $this->assertSame($value, $tokens[$number]['content'], 'Content of integer token does not match expectation');
+
+ $this->assertSame($nextToken, $tokens[($number + 1)]['code'], 'Next token is not the expected type, but '.$tokens[($number + 1)]['type']);
+ $this->assertSame($nextContent, $tokens[($number + 1)]['content'], 'Next token did not have the expected contents');
+
+ }//end testExplicitOctalNotation()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testExplicitOctalNotation()
+ *
+ * @return array
+ */
+ public function dataExplicitOctalNotation()
+ {
+ return [
+ [
+ 'marker' => '/* testExplicitOctal */',
+ 'value' => '0o137041',
+ 'nextToken' => T_SEMICOLON,
+ 'nextContent' => ';',
+ ],
+ [
+ 'marker' => '/* testExplicitOctalCapitalised */',
+ 'value' => '0O137041',
+ 'nextToken' => T_SEMICOLON,
+ 'nextContent' => ';',
+ ],
+ [
+ 'marker' => '/* testExplicitOctalWithNumericSeparator */',
+ 'value' => '0o137_041',
+ 'nextToken' => T_SEMICOLON,
+ 'nextContent' => ';',
+ ],
+ [
+ 'marker' => '/* testInvalid1 */',
+ 'value' => '0',
+ 'nextToken' => T_STRING,
+ 'nextContent' => 'o_137',
+ ],
+ [
+ 'marker' => '/* testInvalid2 */',
+ 'value' => '0',
+ 'nextToken' => T_STRING,
+ 'nextContent' => 'O_41',
+ ],
+ [
+ 'marker' => '/* testInvalid3 */',
+ 'value' => '0',
+ 'nextToken' => T_STRING,
+ 'nextContent' => 'o91',
+ ],
+ [
+ 'marker' => '/* testInvalid4 */',
+ 'value' => '0O2',
+ 'nextToken' => T_LNUMBER,
+ 'nextContent' => '82',
+ ],
+ [
+ 'marker' => '/* testInvalid5 */',
+ 'value' => '0o2',
+ 'nextToken' => T_LNUMBER,
+ 'nextContent' => '8_2',
+ ],
+ [
+ 'marker' => '/* testInvalid6 */',
+ 'value' => '0o2',
+ 'nextToken' => T_STRING,
+ 'nextContent' => '_82',
+ ],
+ ];
+
+ }//end dataExplicitOctalNotation()
+
+
+}//end class
diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc
index eef53f593b..d8559705c3 100644
--- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc
+++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc
@@ -34,6 +34,12 @@ $foo = 0b0101_1111;
/* testOctal */
$foo = 0137_041;
+/* testExplicitOctal */
+$foo = 0o137_041;
+
+/* testExplicitOctalCapitalised */
+$foo = 0O137_041;
+
/* testIntMoreThanMax */
$foo = 10_223_372_036_854_775_807;
@@ -71,6 +77,12 @@ $testValue = 107_925_284 .88;
/* testInvalid10 */
$testValue = 107_925_284/*comment*/.88;
+/* testInvalid11 */
+$foo = 0o_137;
+
+/* testInvalid12 */
+$foo = 0O_41;
+
/*
* Ensure that legitimate calculations are not touched by the backfill.
*/
diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php
index ee4275a214..645088fd6c 100644
--- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php
+++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php
@@ -132,6 +132,20 @@ public function dataTestBackfill()
'value' => '0137_041',
],
],
+ [
+ [
+ 'marker' => '/* testExplicitOctal */',
+ 'type' => 'T_LNUMBER',
+ 'value' => '0o137_041',
+ ],
+ ],
+ [
+ [
+ 'marker' => '/* testExplicitOctalCapitalised */',
+ 'type' => 'T_LNUMBER',
+ 'value' => '0O137_041',
+ ],
+ ],
[
[
'marker' => '/* testIntMoreThanMax */',
@@ -322,6 +336,32 @@ public function dataNoBackfill()
],
],
],
+ [
+ '/* testInvalid11 */',
+ [
+ [
+ 'code' => T_LNUMBER,
+ 'content' => '0',
+ ],
+ [
+ 'code' => T_STRING,
+ 'content' => 'o_137',
+ ],
+ ],
+ ],
+ [
+ '/* testInvalid12 */',
+ [
+ [
+ 'code' => T_LNUMBER,
+ 'content' => '0',
+ ],
+ [
+ 'code' => T_STRING,
+ 'content' => 'O_41',
+ ],
+ ],
+ ],
[
'/* testCalc1 */',
[
diff --git a/tests/Core/Tokenizer/BackfillReadonlyTest.inc b/tests/Core/Tokenizer/BackfillReadonlyTest.inc
new file mode 100644
index 0000000000..eaf0b4b3cc
--- /dev/null
+++ b/tests/Core/Tokenizer/BackfillReadonlyTest.inc
@@ -0,0 +1,100 @@
+readonly = 'foo';
+
+ /* testReadonlyPropertyInTernaryOperator */
+ $isReadonly = $this->readonly ? true : false;
+ }
+}
+
+/* testReadonlyUsedAsFunctionName */
+function readonly()
+{
+}
+
+/* testReadonlyUsedAsNamespaceName */
+namespace Readonly;
+/* testReadonlyUsedAsPartOfNamespaceName */
+namespace My\Readonly\Collection;
+/* testReadonlyAsFunctionCall */
+$var = readonly($a, $b);
+/* testClassConstantFetchWithReadonlyAsConstantName */
+echo ClassName::READONLY;
+
+/* testReadonlyUsedAsFunctionCallWithSpaceBetweenKeywordAndParens */
+$var = readonly /* comment */ ();
+
+/* testParseErrorLiveCoding */
+// This must be the last test in the file.
+readonly
diff --git a/tests/Core/Tokenizer/BackfillReadonlyTest.php b/tests/Core/Tokenizer/BackfillReadonlyTest.php
new file mode 100644
index 0000000000..dddc18ebc2
--- /dev/null
+++ b/tests/Core/Tokenizer/BackfillReadonlyTest.php
@@ -0,0 +1,236 @@
+
+ * @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+
+class BackfillReadonlyTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Test that the "readonly" keyword is tokenized as such.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param string $testContent The token content to look for.
+ *
+ * @dataProvider dataReadonly
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
+ *
+ * @return void
+ */
+ public function testReadonly($testMarker, $testContent)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $target = $this->getTargetToken($testMarker, [T_READONLY, T_STRING], $testContent);
+ $this->assertSame(T_READONLY, $tokens[$target]['code']);
+ $this->assertSame('T_READONLY', $tokens[$target]['type']);
+
+ }//end testReadonly()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testReadonly()
+ *
+ * @return array
+ */
+ public function dataReadonly()
+ {
+ return [
+ [
+ '/* testReadonlyProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testVarReadonlyProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyVarProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testStaticReadonlyProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyStaticProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testConstReadonlyProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyPropertyWithoutType */',
+ 'readonly',
+ ],
+ [
+ '/* testPublicReadonlyProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testProtectedReadonlyProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testPrivateReadonlyProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testPublicReadonlyPropertyWithReadonlyFirst */',
+ 'readonly',
+ ],
+ [
+ '/* testProtectedReadonlyPropertyWithReadonlyFirst */',
+ 'readonly',
+ ],
+ [
+ '/* testPrivateReadonlyPropertyWithReadonlyFirst */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyWithCommentsInDeclaration */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyWithNullableProperty */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyNullablePropertyWithUnionTypeHintAndNullFirst */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyNullablePropertyWithUnionTypeHintAndNullLast */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyPropertyWithArrayTypeHint */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyPropertyWithSelfTypeHint */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyPropertyWithParentTypeHint */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyPropertyWithFullyQualifiedTypeHint */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyIsCaseInsensitive */',
+ 'ReAdOnLy',
+ ],
+ [
+ '/* testReadonlyConstructorPropertyPromotion */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyConstructorPropertyPromotionWithReference */',
+ 'ReadOnly',
+ ],
+ [
+ '/* testReadonlyPropertyInAnonymousClass */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyUsedAsFunctionCallWithSpaceBetweenKeywordAndParens */',
+ 'readonly',
+ ],
+ [
+ '/* testParseErrorLiveCoding */',
+ 'readonly',
+ ],
+ ];
+
+ }//end dataReadonly()
+
+
+ /**
+ * Test that "readonly" when not used as the keyword is still tokenized as `T_STRING`.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param string $testContent The token content to look for.
+ *
+ * @dataProvider dataNotReadonly
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
+ *
+ * @return void
+ */
+ public function testNotReadonly($testMarker, $testContent)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $target = $this->getTargetToken($testMarker, [T_READONLY, T_STRING], $testContent);
+ $this->assertSame(T_STRING, $tokens[$target]['code']);
+ $this->assertSame('T_STRING', $tokens[$target]['type']);
+
+ }//end testNotReadonly()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testNotReadonly()
+ *
+ * @return array
+ */
+ public function dataNotReadonly()
+ {
+ return [
+ [
+ '/* testReadonlyUsedAsClassConstantName */',
+ 'READONLY',
+ ],
+ [
+ '/* testReadonlyUsedAsMethodName */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyUsedAsPropertyName */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyPropertyInTernaryOperator */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyUsedAsFunctionName */',
+ 'readonly',
+ ],
+ [
+ '/* testReadonlyUsedAsNamespaceName */',
+ 'Readonly',
+ ],
+ [
+ '/* testReadonlyUsedAsPartOfNamespaceName */',
+ 'Readonly',
+ ],
+ [
+ '/* testReadonlyAsFunctionCall */',
+ 'readonly',
+ ],
+ [
+ '/* testClassConstantFetchWithReadonlyAsConstantName */',
+ 'READONLY',
+ ],
+ ];
+
+ }//end dataNotReadonly()
+
+
+}//end class
diff --git a/tests/Core/Tokenizer/BitwiseOrTest.inc b/tests/Core/Tokenizer/BitwiseOrTest.inc
index 2af0ecae98..bfdbdc18c1 100644
--- a/tests/Core/Tokenizer/BitwiseOrTest.inc
+++ b/tests/Core/Tokenizer/BitwiseOrTest.inc
@@ -33,6 +33,21 @@ class TypeUnion
/* testTypeUnionPropertyFullyQualified */
public \Fully\Qualified\NameA|\Fully\Qualified\NameB $fullyQual;
+ /* testTypeUnionPropertyWithReadOnlyKeyword */
+ protected readonly string|null $array;
+
+ /* testTypeUnionPropertyWithStaticAndReadOnlyKeywords */
+ static readonly string|null $array;
+
+ /* testTypeUnionPropertyWithVarAndReadOnlyKeywords */
+ var readonly string|null $array;
+
+ /* testTypeUnionPropertyWithReadOnlyKeywordFirst */
+ readonly protected string|null $array;
+
+ /* testTypeUnionPropertyWithOnlyReadOnlyKeyword */
+ readonly string|null $nullableString;
+
public function paramTypes(
/* testTypeUnionParam1 */
int|float $paramA /* testBitwiseOrParamDefaultValue */ = CONSTANT_A | CONSTANT_B,
diff --git a/tests/Core/Tokenizer/BitwiseOrTest.php b/tests/Core/Tokenizer/BitwiseOrTest.php
index d4a27bdc33..d56e7340aa 100644
--- a/tests/Core/Tokenizer/BitwiseOrTest.php
+++ b/tests/Core/Tokenizer/BitwiseOrTest.php
@@ -105,6 +105,11 @@ public function dataTypeUnion()
['/* testTypeUnionPropertyNamespaceRelative */'],
['/* testTypeUnionPropertyPartiallyQualified */'],
['/* testTypeUnionPropertyFullyQualified */'],
+ ['/* testTypeUnionPropertyWithReadOnlyKeyword */'],
+ ['/* testTypeUnionPropertyWithReadOnlyKeywordFirst */'],
+ ['/* testTypeUnionPropertyWithStaticAndReadOnlyKeywords */'],
+ ['/* testTypeUnionPropertyWithVarAndReadOnlyKeywords */'],
+ ['/* testTypeUnionPropertyWithOnlyReadOnlyKeyword */'],
['/* testTypeUnionParam1 */'],
['/* testTypeUnionParam2 */'],
['/* testTypeUnionParam3 */'],
diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc
new file mode 100644
index 0000000000..82fe564382
--- /dev/null
+++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc
@@ -0,0 +1,227 @@
+ 'a',
+ 2 => 'b',
+ /* testMatchDefaultIsKeyword */ default => 'default',
+};
+
+$closure = /* testFnIsKeyword */ fn () => 'string';
+
+function () {
+ /* testYieldIsKeyword */ yield $f;
+ /* testYieldFromIsKeyword */ yield from someFunction();
+};
+
+/* testDeclareIsKeyword */ declare(ticks=1):
+/* testEndDeclareIsKeyword */ enddeclare;
+
+if (true /* testAndIsKeyword */ and false /* testOrIsKeyword */ or null /* testXorIsKeyword */ xor 0) {
+
+}
+
+$anonymousClass = new /* testAnonymousClassIsKeyword */ class {};
+$anonymousClass2 = new class /* testExtendsInAnonymousClassIsKeyword */ extends SomeParent {};
+$anonymousClass3 = new class /* testImplementsInAnonymousClassIsKeyword */ implements SomeInterface {};
+
+$instantiated1 = new /* testClassInstantiationParentIsKeyword */ parent();
+$instantiated2 = new /* testClassInstantiationSelfIsKeyword */ SELF;
+$instantiated3 = new /* testClassInstantiationStaticIsKeyword */ static($param);
+
+class Foo extends /* testNamespaceInNameIsKeyword */ namespace\Exception
+{}
+
+function /* testKeywordAfterFunctionShouldBeString */ eval() {}
+function /* testKeywordAfterFunctionByRefShouldBeString */ &switch() {}
diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php
new file mode 100644
index 0000000000..a747e573c2
--- /dev/null
+++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php
@@ -0,0 +1,509 @@
+
+ * @copyright 2020 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+use PHP_CodeSniffer\Util\Tokens;
+
+class ContextSensitiveKeywordsTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Test that context sensitive keyword is tokenized as string when it should be string.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ *
+ * @dataProvider dataStrings
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testStrings($testMarker)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $token = $this->getTargetToken($testMarker, (Tokens::$contextSensitiveKeywords + [T_STRING]));
+
+ $this->assertSame(T_STRING, $tokens[$token]['code']);
+ $this->assertSame('T_STRING', $tokens[$token]['type']);
+
+ }//end testStrings()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testStrings()
+ *
+ * @return array
+ */
+ public function dataStrings()
+ {
+ return [
+ ['/* testAbstract */'],
+ ['/* testArray */'],
+ ['/* testAs */'],
+ ['/* testBreak */'],
+ ['/* testCallable */'],
+ ['/* testCase */'],
+ ['/* testCatch */'],
+ ['/* testClass */'],
+ ['/* testClone */'],
+ ['/* testConst */'],
+ ['/* testContinue */'],
+ ['/* testDeclare */'],
+ ['/* testDefault */'],
+ ['/* testDo */'],
+ ['/* testEcho */'],
+ ['/* testElse */'],
+ ['/* testElseIf */'],
+ ['/* testEmpty */'],
+ ['/* testEndDeclare */'],
+ ['/* testEndFor */'],
+ ['/* testEndForeach */'],
+ ['/* testEndIf */'],
+ ['/* testEndSwitch */'],
+ ['/* testEndWhile */'],
+ ['/* testEnum */'],
+ ['/* testEval */'],
+ ['/* testExit */'],
+ ['/* testExtends */'],
+ ['/* testFinal */'],
+ ['/* testFinally */'],
+ ['/* testFn */'],
+ ['/* testFor */'],
+ ['/* testForeach */'],
+ ['/* testFunction */'],
+ ['/* testGlobal */'],
+ ['/* testGoto */'],
+ ['/* testIf */'],
+ ['/* testImplements */'],
+ ['/* testInclude */'],
+ ['/* testIncludeOnce */'],
+ ['/* testInstanceOf */'],
+ ['/* testInsteadOf */'],
+ ['/* testInterface */'],
+ ['/* testIsset */'],
+ ['/* testList */'],
+ ['/* testMatch */'],
+ ['/* testNamespace */'],
+ ['/* testNew */'],
+ ['/* testParent */'],
+ ['/* testPrint */'],
+ ['/* testPrivate */'],
+ ['/* testProtected */'],
+ ['/* testPublic */'],
+ ['/* testReadonly */'],
+ ['/* testRequire */'],
+ ['/* testRequireOnce */'],
+ ['/* testReturn */'],
+ ['/* testSelf */'],
+ ['/* testStatic */'],
+ ['/* testSwitch */'],
+ ['/* testThrows */'],
+ ['/* testTrait */'],
+ ['/* testTry */'],
+ ['/* testUnset */'],
+ ['/* testUse */'],
+ ['/* testVar */'],
+ ['/* testWhile */'],
+ ['/* testYield */'],
+ ['/* testYieldFrom */'],
+ ['/* testAnd */'],
+ ['/* testOr */'],
+ ['/* testXor */'],
+
+ ['/* testKeywordAfterNamespaceShouldBeString */'],
+ ['/* testNamespaceNameIsString1 */'],
+ ['/* testNamespaceNameIsString2 */'],
+ ['/* testNamespaceNameIsString3 */'],
+
+ ['/* testKeywordAfterFunctionShouldBeString */'],
+ ['/* testKeywordAfterFunctionByRefShouldBeString */'],
+ ];
+
+ }//end dataStrings()
+
+
+ /**
+ * Test that context sensitive keyword is tokenized as keyword when it should be keyword.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param string $expectedTokenType The expected token type.
+ *
+ * @dataProvider dataKeywords
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testKeywords($testMarker, $expectedTokenType)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $token = $this->getTargetToken($testMarker, (Tokens::$contextSensitiveKeywords + [T_ANON_CLASS, T_MATCH_DEFAULT, T_PARENT, T_SELF, T_STRING]));
+
+ $this->assertSame(constant($expectedTokenType), $tokens[$token]['code']);
+ $this->assertSame($expectedTokenType, $tokens[$token]['type']);
+
+ }//end testKeywords()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testKeywords()
+ *
+ * @return array
+ */
+ public function dataKeywords()
+ {
+ return [
+ [
+ '/* testNamespaceIsKeyword */',
+ 'T_NAMESPACE',
+ ],
+ [
+ '/* testAbstractIsKeyword */',
+ 'T_ABSTRACT',
+ ],
+ [
+ '/* testClassIsKeyword */',
+ 'T_CLASS',
+ ],
+ [
+ '/* testExtendsIsKeyword */',
+ 'T_EXTENDS',
+ ],
+ [
+ '/* testImplementsIsKeyword */',
+ 'T_IMPLEMENTS',
+ ],
+ [
+ '/* testUseIsKeyword */',
+ 'T_USE',
+ ],
+ [
+ '/* testInsteadOfIsKeyword */',
+ 'T_INSTEADOF',
+ ],
+ [
+ '/* testAsIsKeyword */',
+ 'T_AS',
+ ],
+ [
+ '/* testConstIsKeyword */',
+ 'T_CONST',
+ ],
+ [
+ '/* testPrivateIsKeyword */',
+ 'T_PRIVATE',
+ ],
+ [
+ '/* testProtectedIsKeyword */',
+ 'T_PROTECTED',
+ ],
+ [
+ '/* testPublicIsKeyword */',
+ 'T_PUBLIC',
+ ],
+ [
+ '/* testVarIsKeyword */',
+ 'T_VAR',
+ ],
+ [
+ '/* testStaticIsKeyword */',
+ 'T_STATIC',
+ ],
+ [
+ '/* testReadonlyIsKeyword */',
+ 'T_READONLY',
+ ],
+ [
+ '/* testFinalIsKeyword */',
+ 'T_FINAL',
+ ],
+ [
+ '/* testFunctionIsKeyword */',
+ 'T_FUNCTION',
+ ],
+ [
+ '/* testCallableIsKeyword */',
+ 'T_CALLABLE',
+ ],
+ [
+ '/* testSelfIsKeyword */',
+ 'T_SELF',
+ ],
+ [
+ '/* testParentIsKeyword */',
+ 'T_PARENT',
+ ],
+ [
+ '/* testReturnIsKeyword */',
+ 'T_RETURN',
+ ],
+
+ [
+ '/* testInterfaceIsKeyword */',
+ 'T_INTERFACE',
+ ],
+ [
+ '/* testTraitIsKeyword */',
+ 'T_TRAIT',
+ ],
+ [
+ '/* testEnumIsKeyword */',
+ 'T_ENUM',
+ ],
+
+ [
+ '/* testNewIsKeyword */',
+ 'T_NEW',
+ ],
+ [
+ '/* testInstanceOfIsKeyword */',
+ 'T_INSTANCEOF',
+ ],
+ [
+ '/* testCloneIsKeyword */',
+ 'T_CLONE',
+ ],
+
+ [
+ '/* testIfIsKeyword */',
+ 'T_IF',
+ ],
+ [
+ '/* testEmptyIsKeyword */',
+ 'T_EMPTY',
+ ],
+ [
+ '/* testElseIfIsKeyword */',
+ 'T_ELSEIF',
+ ],
+ [
+ '/* testElseIsKeyword */',
+ 'T_ELSE',
+ ],
+ [
+ '/* testEndIfIsKeyword */',
+ 'T_ENDIF',
+ ],
+
+ [
+ '/* testForIsKeyword */',
+ 'T_FOR',
+ ],
+ [
+ '/* testEndForIsKeyword */',
+ 'T_ENDFOR',
+ ],
+
+ [
+ '/* testForeachIsKeyword */',
+ 'T_FOREACH',
+ ],
+ [
+ '/* testEndForeachIsKeyword */',
+ 'T_ENDFOREACH',
+ ],
+
+ [
+ '/* testSwitchIsKeyword */',
+ 'T_SWITCH',
+ ],
+ [
+ '/* testCaseIsKeyword */',
+ 'T_CASE',
+ ],
+ [
+ '/* testDefaultIsKeyword */',
+ 'T_DEFAULT',
+ ],
+ [
+ '/* testEndSwitchIsKeyword */',
+ 'T_ENDSWITCH',
+ ],
+ [
+ '/* testBreakIsKeyword */',
+ 'T_BREAK',
+ ],
+ [
+ '/* testContinueIsKeyword */',
+ 'T_CONTINUE',
+ ],
+
+ [
+ '/* testDoIsKeyword */',
+ 'T_DO',
+ ],
+ [
+ '/* testWhileIsKeyword */',
+ 'T_WHILE',
+ ],
+ [
+ '/* testEndWhileIsKeyword */',
+ 'T_ENDWHILE',
+ ],
+
+ [
+ '/* testTryIsKeyword */',
+ 'T_TRY',
+ ],
+ [
+ '/* testThrowIsKeyword */',
+ 'T_THROW',
+ ],
+ [
+ '/* testCatchIsKeyword */',
+ 'T_CATCH',
+ ],
+ [
+ '/* testFinallyIsKeyword */',
+ 'T_FINALLY',
+ ],
+
+ [
+ '/* testGlobalIsKeyword */',
+ 'T_GLOBAL',
+ ],
+ [
+ '/* testEchoIsKeyword */',
+ 'T_ECHO',
+ ],
+ [
+ '/* testPrintIsKeyword */',
+ 'T_PRINT',
+ ],
+ [
+ '/* testDieIsKeyword */',
+ 'T_EXIT',
+ ],
+ [
+ '/* testEvalIsKeyword */',
+ 'T_EVAL',
+ ],
+ [
+ '/* testExitIsKeyword */',
+ 'T_EXIT',
+ ],
+ [
+ '/* testIssetIsKeyword */',
+ 'T_ISSET',
+ ],
+ [
+ '/* testUnsetIsKeyword */',
+ 'T_UNSET',
+ ],
+
+ [
+ '/* testIncludeIsKeyword */',
+ 'T_INCLUDE',
+ ],
+ [
+ '/* testIncludeOnceIsKeyword */',
+ 'T_INCLUDE_ONCE',
+ ],
+ [
+ '/* testRequireIsKeyword */',
+ 'T_REQUIRE',
+ ],
+ [
+ '/* testRequireOnceIsKeyword */',
+ 'T_REQUIRE_ONCE',
+ ],
+
+ [
+ '/* testListIsKeyword */',
+ 'T_LIST',
+ ],
+ [
+ '/* testGotoIsKeyword */',
+ 'T_GOTO',
+ ],
+ [
+ '/* testMatchIsKeyword */',
+ 'T_MATCH',
+ ],
+ [
+ '/* testMatchDefaultIsKeyword */',
+ 'T_MATCH_DEFAULT',
+ ],
+ [
+ '/* testFnIsKeyword */',
+ 'T_FN',
+ ],
+
+ [
+ '/* testYieldIsKeyword */',
+ 'T_YIELD',
+ ],
+ [
+ '/* testYieldFromIsKeyword */',
+ 'T_YIELD_FROM',
+ ],
+
+ [
+ '/* testDeclareIsKeyword */',
+ 'T_DECLARE',
+ ],
+ [
+ '/* testEndDeclareIsKeyword */',
+ 'T_ENDDECLARE',
+ ],
+
+ [
+ '/* testAndIsKeyword */',
+ 'T_LOGICAL_AND',
+ ],
+ [
+ '/* testOrIsKeyword */',
+ 'T_LOGICAL_OR',
+ ],
+ [
+ '/* testXorIsKeyword */',
+ 'T_LOGICAL_XOR',
+ ],
+
+ [
+ '/* testAnonymousClassIsKeyword */',
+ 'T_ANON_CLASS',
+ ],
+ [
+ '/* testExtendsInAnonymousClassIsKeyword */',
+ 'T_EXTENDS',
+ ],
+ [
+ '/* testImplementsInAnonymousClassIsKeyword */',
+ 'T_IMPLEMENTS',
+ ],
+ [
+ '/* testClassInstantiationParentIsKeyword */',
+ 'T_PARENT',
+ ],
+ [
+ '/* testClassInstantiationSelfIsKeyword */',
+ 'T_SELF',
+ ],
+ [
+ '/* testClassInstantiationStaticIsKeyword */',
+ 'T_STATIC',
+ ],
+ [
+ '/* testNamespaceInNameIsKeyword */',
+ 'T_NAMESPACE',
+ ],
+ ];
+
+ }//end dataKeywords()
+
+
+}//end class
diff --git a/tests/Core/Tokenizer/DoubleQuotedStringTest.inc b/tests/Core/Tokenizer/DoubleQuotedStringTest.inc
new file mode 100644
index 0000000000..62535b1e41
--- /dev/null
+++ b/tests/Core/Tokenizer/DoubleQuotedStringTest.inc
@@ -0,0 +1,52 @@
+bar";
+/* testProperty2 */
+"{$foo->bar}";
+
+/* testMethod1 */
+"{$foo->bar()}";
+
+/* testClosure1 */
+"{$foo()}";
+
+/* testChain1 */
+"{$foo['bar']->baz()()}";
+
+/* testVariableVar1 */
+"${$bar}";
+/* testVariableVar2 */
+"${(foo)}";
+/* testVariableVar3 */
+"${foo->bar}";
+
+/* testNested1 */
+"${foo["${bar}"]}";
+/* testNested2 */
+"${foo["${bar['baz']}"]}";
+/* testNested3 */
+"${foo->{$baz}}";
+/* testNested4 */
+"${foo->{${'a'}}}";
+/* testNested5 */
+"${foo->{"${'a'}"}}";
+
+/* testParseError */
+"${foo["${bar
diff --git a/tests/Core/Tokenizer/DoubleQuotedStringTest.php b/tests/Core/Tokenizer/DoubleQuotedStringTest.php
new file mode 100644
index 0000000000..cc9fe49ec4
--- /dev/null
+++ b/tests/Core/Tokenizer/DoubleQuotedStringTest.php
@@ -0,0 +1,136 @@
+
+ * @copyright 2022 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+
+class DoubleQuotedStringTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Test that double quoted strings contain the complete string.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param string $expectedContent The expected content of the double quoted string.
+ *
+ * @dataProvider dataDoubleQuotedString
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testDoubleQuotedString($testMarker, $expectedContent)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $target = $this->getTargetToken($testMarker, T_DOUBLE_QUOTED_STRING);
+ $this->assertSame($expectedContent, $tokens[$target]['content']);
+
+ }//end testDoubleQuotedString()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testDoubleQuotedString()
+ *
+ * @return array
+ */
+ public function dataDoubleQuotedString()
+ {
+ return [
+ [
+ 'testMarker' => '/* testSimple1 */',
+ 'expectedContent' => '"$foo"',
+ ],
+ [
+ 'testMarker' => '/* testSimple2 */',
+ 'expectedContent' => '"{$foo}"',
+ ],
+ [
+ 'testMarker' => '/* testSimple3 */',
+ 'expectedContent' => '"${foo}"',
+ ],
+ [
+ 'testMarker' => '/* testDIM1 */',
+ 'expectedContent' => '"$foo[bar]"',
+ ],
+ [
+ 'testMarker' => '/* testDIM2 */',
+ 'expectedContent' => '"{$foo[\'bar\']}"',
+ ],
+ [
+ 'testMarker' => '/* testDIM3 */',
+ 'expectedContent' => '"${foo[\'bar\']}"',
+ ],
+ [
+ 'testMarker' => '/* testProperty1 */',
+ 'expectedContent' => '"$foo->bar"',
+ ],
+ [
+ 'testMarker' => '/* testProperty2 */',
+ 'expectedContent' => '"{$foo->bar}"',
+ ],
+ [
+ 'testMarker' => '/* testMethod1 */',
+ 'expectedContent' => '"{$foo->bar()}"',
+ ],
+ [
+ 'testMarker' => '/* testClosure1 */',
+ 'expectedContent' => '"{$foo()}"',
+ ],
+ [
+ 'testMarker' => '/* testChain1 */',
+ 'expectedContent' => '"{$foo[\'bar\']->baz()()}"',
+ ],
+ [
+ 'testMarker' => '/* testVariableVar1 */',
+ 'expectedContent' => '"${$bar}"',
+ ],
+ [
+ 'testMarker' => '/* testVariableVar2 */',
+ 'expectedContent' => '"${(foo)}"',
+ ],
+ [
+ 'testMarker' => '/* testVariableVar3 */',
+ 'expectedContent' => '"${foo->bar}"',
+ ],
+ [
+ 'testMarker' => '/* testNested1 */',
+ 'expectedContent' => '"${foo["${bar}"]}"',
+ ],
+ [
+ 'testMarker' => '/* testNested2 */',
+ 'expectedContent' => '"${foo["${bar[\'baz\']}"]}"',
+ ],
+ [
+ 'testMarker' => '/* testNested3 */',
+ 'expectedContent' => '"${foo->{$baz}}"',
+ ],
+ [
+ 'testMarker' => '/* testNested4 */',
+ 'expectedContent' => '"${foo->{${\'a\'}}}"',
+ ],
+ [
+ 'testMarker' => '/* testNested5 */',
+ 'expectedContent' => '"${foo->{"${\'a\'}"}}"',
+ ],
+ [
+ 'testMarker' => '/* testParseError */',
+ 'expectedContent' => '"${foo["${bar
+',
+ ],
+ ];
+
+ }//end dataDoubleQuotedString()
+
+
+}//end class
diff --git a/tests/Core/Tokenizer/EnumCaseTest.inc b/tests/Core/Tokenizer/EnumCaseTest.inc
new file mode 100644
index 0000000000..13b87242e1
--- /dev/null
+++ b/tests/Core/Tokenizer/EnumCaseTest.inc
@@ -0,0 +1,95 @@
+
+ * @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+
+class EnumCaseTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Test that the enum "case" is converted to T_ENUM_CASE.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ *
+ * @dataProvider dataEnumCases
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
+ *
+ * @return void
+ */
+ public function testEnumCases($testMarker)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $enumCase = $this->getTargetToken($testMarker, [T_ENUM_CASE, T_CASE]);
+
+ $this->assertSame(T_ENUM_CASE, $tokens[$enumCase]['code']);
+ $this->assertSame('T_ENUM_CASE', $tokens[$enumCase]['type']);
+
+ $this->assertArrayNotHasKey('scope_condition', $tokens[$enumCase], 'Scope condition is set');
+ $this->assertArrayNotHasKey('scope_opener', $tokens[$enumCase], 'Scope opener is set');
+ $this->assertArrayNotHasKey('scope_closer', $tokens[$enumCase], 'Scope closer is set');
+
+ }//end testEnumCases()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testEnumCases()
+ *
+ * @return array
+ */
+ public function dataEnumCases()
+ {
+ return [
+ ['/* testPureEnumCase */'],
+ ['/* testBackingIntegerEnumCase */'],
+ ['/* testBackingStringEnumCase */'],
+ ['/* testEnumCaseInComplexEnum */'],
+ ['/* testEnumCaseIsCaseInsensitive */'],
+ ['/* testEnumCaseAfterSwitch */'],
+ ['/* testEnumCaseAfterSwitchWithEndSwitch */'],
+ ];
+
+ }//end dataEnumCases()
+
+
+ /**
+ * Test that "case" that is not enum case is still tokenized as `T_CASE`.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ *
+ * @dataProvider dataNotEnumCases
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
+ *
+ * @return void
+ */
+ public function testNotEnumCases($testMarker)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $case = $this->getTargetToken($testMarker, [T_ENUM_CASE, T_CASE]);
+
+ $this->assertSame(T_CASE, $tokens[$case]['code']);
+ $this->assertSame('T_CASE', $tokens[$case]['type']);
+
+ $this->assertArrayHasKey('scope_condition', $tokens[$case], 'Scope condition is not set');
+ $this->assertArrayHasKey('scope_opener', $tokens[$case], 'Scope opener is not set');
+ $this->assertArrayHasKey('scope_closer', $tokens[$case], 'Scope closer is not set');
+
+ }//end testNotEnumCases()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testNotEnumCases()
+ *
+ * @return array
+ */
+ public function dataNotEnumCases()
+ {
+ return [
+ ['/* testCaseWithSemicolonIsNotEnumCase */'],
+ ['/* testCaseWithConstantIsNotEnumCase */'],
+ ['/* testCaseWithConstantAndIdenticalIsNotEnumCase */'],
+ ['/* testCaseWithAssigmentToConstantIsNotEnumCase */'],
+ ['/* testIsNotEnumCaseIsCaseInsensitive */'],
+ ['/* testCaseInSwitchWhenCreatingEnumInSwitch1 */'],
+ ['/* testCaseInSwitchWhenCreatingEnumInSwitch2 */'],
+ ];
+
+ }//end dataNotEnumCases()
+
+
+ /**
+ * Test that "case" that is not enum case is still tokenized as `T_CASE`.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ *
+ * @dataProvider dataKeywordAsEnumCaseNameShouldBeString
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testKeywordAsEnumCaseNameShouldBeString($testMarker)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $enumCaseName = $this->getTargetToken($testMarker, [T_STRING, T_INTERFACE, T_TRAIT, T_ENUM, T_FUNCTION, T_FALSE, T_DEFAULT, T_ARRAY]);
+
+ $this->assertSame(T_STRING, $tokens[$enumCaseName]['code']);
+ $this->assertSame('T_STRING', $tokens[$enumCaseName]['type']);
+
+ }//end testKeywordAsEnumCaseNameShouldBeString()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testKeywordAsEnumCaseNameShouldBeString()
+ *
+ * @return array
+ */
+ public function dataKeywordAsEnumCaseNameShouldBeString()
+ {
+ return [
+ ['/* testKeywordAsEnumCaseNameShouldBeString1 */'],
+ ['/* testKeywordAsEnumCaseNameShouldBeString2 */'],
+ ['/* testKeywordAsEnumCaseNameShouldBeString3 */'],
+ ['/* testKeywordAsEnumCaseNameShouldBeString4 */'],
+ ];
+
+ }//end dataKeywordAsEnumCaseNameShouldBeString()
+
+
+}//end class
diff --git a/tests/Core/Tokenizer/GotoLabelTest.inc b/tests/Core/Tokenizer/GotoLabelTest.inc
index f6920f54fa..12df5d296b 100644
--- a/tests/Core/Tokenizer/GotoLabelTest.inc
+++ b/tests/Core/Tokenizer/GotoLabelTest.inc
@@ -51,3 +51,6 @@ switch (true) {
// Do something.
break;
}
+
+/* testNotGotoDeclarationEnumWithType */
+enum Suit: string implements Colorful, CardGame {}
diff --git a/tests/Core/Tokenizer/GotoLabelTest.php b/tests/Core/Tokenizer/GotoLabelTest.php
index fa6f8cfb47..0f937cc8b0 100644
--- a/tests/Core/Tokenizer/GotoLabelTest.php
+++ b/tests/Core/Tokenizer/GotoLabelTest.php
@@ -163,6 +163,10 @@ public function dataNotAGotoDeclaration()
'/* testNotGotoDeclarationGlobalConstantInTernary */',
'CONST_B',
],
+ [
+ '/* testNotGotoDeclarationEnumWithType */',
+ 'Suit',
+ ],
];
}//end dataNotAGotoDeclaration()
diff --git a/tests/Core/Tokenizer/HeredocNowdocCloserTest.inc b/tests/Core/Tokenizer/HeredocNowdocCloserTest.inc
new file mode 100644
index 0000000000..a800980b8e
--- /dev/null
+++ b/tests/Core/Tokenizer/HeredocNowdocCloserTest.inc
@@ -0,0 +1,43 @@
+
+ * @copyright 2020 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Config;
+use PHP_CodeSniffer\Ruleset;
+use PHP_CodeSniffer\Files\DummyFile;
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+
+/**
+ * Heredoc/nowdoc closer token test.
+ *
+ * @requires PHP 7.3
+ */
+class HeredocNowdocCloserTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file.
+ *
+ * {@internal This is a near duplicate of the original method. Only difference is that
+ * tab replacement is enabled for this test.}
+ *
+ * @return void
+ */
+ public static function setUpBeforeClass()
+ {
+ $config = new Config();
+ $config->standards = ['PSR1'];
+ $config->tabWidth = 4;
+
+ $ruleset = new Ruleset($config);
+
+ // Default to a file with the same name as the test class. Extension is property based.
+ $relativeCN = str_replace(__NAMESPACE__, '', get_called_class());
+ $relativePath = str_replace('\\', DIRECTORY_SEPARATOR, $relativeCN);
+ $pathToTestFile = realpath(__DIR__).$relativePath.'.'.static::$fileExtension;
+
+ // Make sure the file gets parsed correctly based on the file type.
+ $contents = 'phpcs_input_file: '.$pathToTestFile.PHP_EOL;
+ $contents .= file_get_contents($pathToTestFile);
+
+ self::$phpcsFile = new DummyFile($contents, $ruleset, $config);
+ self::$phpcsFile->process();
+
+ }//end setUpBeforeClass()
+
+
+ /**
+ * Verify that leading (indent) whitespace in a heredoc/nowdoc closer token get the tab replacement treatment.
+ *
+ * @param string $testMarker The comment prefacing the target token.
+ * @param array $expected Expectations for the token array.
+ *
+ * @dataProvider dataHeredocNowdocCloserTabReplacement
+ * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap
+ *
+ * @return void
+ */
+ public function testHeredocNowdocCloserTabReplacement($testMarker, $expected)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $closer = $this->getTargetToken($testMarker, [T_END_HEREDOC, T_END_NOWDOC]);
+
+ foreach ($expected as $key => $value) {
+ if ($key === 'orig_content' && $value === null) {
+ $this->assertArrayNotHasKey($key, $tokens[$closer], "Unexpected 'orig_content' key found in the token array.");
+ continue;
+ }
+
+ $this->assertArrayHasKey($key, $tokens[$closer], "Key $key not found in the token array.");
+ $this->assertSame($value, $tokens[$closer][$key], "Value for key $key does not match expectation.");
+ }
+
+ }//end testHeredocNowdocCloserTabReplacement()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testHeredocNowdocCloserTabReplacement()
+ *
+ * @return array
+ */
+ public function dataHeredocNowdocCloserTabReplacement()
+ {
+ return [
+ [
+ 'testMarker' => '/* testHeredocCloserNoIndent */',
+ 'expected' => [
+ 'length' => 3,
+ 'content' => 'EOD',
+ 'orig_content' => null,
+ ],
+ ],
+ [
+ 'testMarker' => '/* testNowdocCloserNoIndent */',
+ 'expected' => [
+ 'length' => 3,
+ 'content' => 'EOD',
+ 'orig_content' => null,
+ ],
+ ],
+ [
+ 'testMarker' => '/* testHeredocCloserSpaceIndent */',
+ 'expected' => [
+ 'length' => 7,
+ 'content' => ' END',
+ 'orig_content' => null,
+ ],
+ ],
+ [
+ 'testMarker' => '/* testNowdocCloserSpaceIndent */',
+ 'expected' => [
+ 'length' => 8,
+ 'content' => ' END',
+ 'orig_content' => null,
+ ],
+ ],
+ [
+ 'testMarker' => '/* testHeredocCloserTabIndent */',
+ 'expected' => [
+ 'length' => 8,
+ 'content' => ' END',
+ 'orig_content' => ' END',
+ ],
+ ],
+ [
+ 'testMarker' => '/* testNowdocCloserTabIndent */',
+ 'expected' => [
+ 'length' => 7,
+ 'content' => ' END',
+ 'orig_content' => ' END',
+ ],
+ ],
+ ];
+
+ }//end dataHeredocNowdocCloserTabReplacement()
+
+
+}//end class
diff --git a/tests/Core/Tokenizer/HeredocStringTest.inc b/tests/Core/Tokenizer/HeredocStringTest.inc
new file mode 100644
index 0000000000..ae43e24a59
--- /dev/null
+++ b/tests/Core/Tokenizer/HeredocStringTest.inc
@@ -0,0 +1,193 @@
+bar
+EOD;
+
+/* testProperty2 */
+$heredoc = <<<"EOD"
+{$foo->bar}
+EOD;
+
+/* testMethod1 */
+$heredoc = <<bar()}
+EOD;
+
+/* testClosure1 */
+$heredoc = <<<"EOD"
+{$foo()}
+EOD;
+
+/* testChain1 */
+$heredoc = <<baz()()}
+EOD;
+
+/* testVariableVar1 */
+$heredoc = <<<"EOD"
+${$bar}
+EOD;
+
+/* testVariableVar2 */
+$heredoc = <<bar}
+EOD;
+
+/* testNested1 */
+$heredoc = <<{$baz}}
+EOD;
+
+/* testNested4 */
+$heredoc = <<<"EOD"
+${foo->{${'a'}}}
+EOD;
+
+/* testNested5 */
+$heredoc = <<{"${'a'}"}}
+EOD;
+
+/* testSimple1Wrapped */
+$heredoc = <<bar Something
+EOD;
+
+/* testProperty2Wrapped */
+$heredoc = <<<"EOD"
+Do {$foo->bar} Something
+EOD;
+
+/* testMethod1Wrapped */
+$heredoc = <<bar()} Something
+EOD;
+
+/* testClosure1Wrapped */
+$heredoc = <<<"EOD"
+Do {$foo()} Something
+EOD;
+
+/* testChain1Wrapped */
+$heredoc = <<baz()()} Something
+EOD;
+
+/* testVariableVar1Wrapped */
+$heredoc = <<<"EOD"
+Do ${$bar} Something
+EOD;
+
+/* testVariableVar2Wrapped */
+$heredoc = <<bar} Something
+EOD;
+
+/* testNested1Wrapped */
+$heredoc = <<{$baz}} Something
+EOD;
+
+/* testNested4Wrapped */
+$heredoc = <<<"EOD"
+Do ${foo->{${'a'}}} Something
+EOD;
+
+/* testNested5Wrapped */
+$heredoc = <<{"${'a'}"}} Something
+EOD;
diff --git a/tests/Core/Tokenizer/HeredocStringTest.php b/tests/Core/Tokenizer/HeredocStringTest.php
new file mode 100644
index 0000000000..2c808be906
--- /dev/null
+++ b/tests/Core/Tokenizer/HeredocStringTest.php
@@ -0,0 +1,153 @@
+
+ * @copyright 2022 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+
+class HeredocStringTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Test that heredoc strings contain the complete interpolated string.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param string $expectedContent The expected content of the heredoc string.
+ *
+ * @dataProvider dataHeredocString
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testHeredocString($testMarker, $expectedContent)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $target = $this->getTargetToken($testMarker, T_HEREDOC);
+ $this->assertSame($expectedContent."\n", $tokens[$target]['content']);
+
+ }//end testHeredocString()
+
+
+ /**
+ * Test that heredoc strings contain the complete interpolated string when combined with other texts.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ * @param string $expectedContent The expected content of the heredoc string.
+ *
+ * @dataProvider dataHeredocString
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
+ *
+ * @return void
+ */
+ public function testHeredocStringWrapped($testMarker, $expectedContent)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $testMarker = substr($testMarker, 0, -3).'Wrapped */';
+ $target = $this->getTargetToken($testMarker, T_HEREDOC);
+ $this->assertSame('Do '.$expectedContent." Something\n", $tokens[$target]['content']);
+
+ }//end testHeredocStringWrapped()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testHeredocString()
+ *
+ * @return array
+ */
+ public function dataHeredocString()
+ {
+ return [
+ [
+ 'testMarker' => '/* testSimple1 */',
+ 'expectedContent' => '$foo',
+ ],
+ [
+ 'testMarker' => '/* testSimple2 */',
+ 'expectedContent' => '{$foo}',
+ ],
+ [
+ 'testMarker' => '/* testSimple3 */',
+ 'expectedContent' => '${foo}',
+ ],
+ [
+ 'testMarker' => '/* testDIM1 */',
+ 'expectedContent' => '$foo[bar]',
+ ],
+ [
+ 'testMarker' => '/* testDIM2 */',
+ 'expectedContent' => '{$foo[\'bar\']}',
+ ],
+ [
+ 'testMarker' => '/* testDIM3 */',
+ 'expectedContent' => '${foo[\'bar\']}',
+ ],
+ [
+ 'testMarker' => '/* testProperty1 */',
+ 'expectedContent' => '$foo->bar',
+ ],
+ [
+ 'testMarker' => '/* testProperty2 */',
+ 'expectedContent' => '{$foo->bar}',
+ ],
+ [
+ 'testMarker' => '/* testMethod1 */',
+ 'expectedContent' => '{$foo->bar()}',
+ ],
+ [
+ 'testMarker' => '/* testClosure1 */',
+ 'expectedContent' => '{$foo()}',
+ ],
+ [
+ 'testMarker' => '/* testChain1 */',
+ 'expectedContent' => '{$foo[\'bar\']->baz()()}',
+ ],
+ [
+ 'testMarker' => '/* testVariableVar1 */',
+ 'expectedContent' => '${$bar}',
+ ],
+ [
+ 'testMarker' => '/* testVariableVar2 */',
+ 'expectedContent' => '${(foo)}',
+ ],
+ [
+ 'testMarker' => '/* testVariableVar3 */',
+ 'expectedContent' => '${foo->bar}',
+ ],
+ [
+ 'testMarker' => '/* testNested1 */',
+ 'expectedContent' => '${foo["${bar}"]}',
+ ],
+ [
+ 'testMarker' => '/* testNested2 */',
+ 'expectedContent' => '${foo["${bar[\'baz\']}"]}',
+ ],
+ [
+ 'testMarker' => '/* testNested3 */',
+ 'expectedContent' => '${foo->{$baz}}',
+ ],
+ [
+ 'testMarker' => '/* testNested4 */',
+ 'expectedContent' => '${foo->{${\'a\'}}}',
+ ],
+ [
+ 'testMarker' => '/* testNested5 */',
+ 'expectedContent' => '${foo->{"${\'a\'}"}}',
+ ],
+ ];
+
+ }//end dataHeredocString()
+
+
+}//end class
diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc
index d05d27d951..b9c0df24d4 100644
--- a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc
+++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc
@@ -208,6 +208,9 @@ foobar(elseif: $value, /* testReservedKeywordElseif2 */ elseif: $value);
/* testReservedKeywordEmpty1 */
foobar(empty: $value, /* testReservedKeywordEmpty2 */ empty: $value);
+/* testReservedKeywordEnum1 */
+foobar(enum: $value, /* testReservedKeywordEnum2 */ enum: $value);
+
/* testReservedKeywordEnddeclare1 */
foobar(enddeclare: $value, /* testReservedKeywordEnddeclare2 */ enddeclare: $value);
@@ -310,6 +313,9 @@ foobar(protected: $value, /* testReservedKeywordProtected2 */ protected: $value)
/* testReservedKeywordPublic1 */
foobar(public: $value, /* testReservedKeywordPublic2 */ public: $value);
+/* testReservedKeywordReadonly1 */
+foobar(readonly: $value, /* testReservedKeywordReadonly2 */ readonly: $value);
+
/* testReservedKeywordRequire1 */
foobar(require: $value, /* testReservedKeywordRequire2 */ require: $value);
@@ -396,3 +402,6 @@ foobar(parent: $value, /* testReservedKeywordParent2 */ parent: $value);
/* testReservedKeywordSelf1 */
foobar(self: $value, /* testReservedKeywordSelf2 */ self: $value);
+
+/* testReservedKeywordNever1 */
+foobar(never: $value, /* testReservedKeywordNever2 */ never: $value);
diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php
index 52845ac72c..cc57637c6f 100644
--- a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php
+++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php
@@ -776,6 +776,7 @@ public function dataReservedKeywordsAsName()
'endif',
'endswitch',
'endwhile',
+ 'enum',
'eval',
'exit',
'extends',
@@ -804,6 +805,7 @@ public function dataReservedKeywordsAsName()
'private',
'protected',
'public',
+ 'readonly',
'require',
'require_once',
'return',
@@ -831,6 +833,7 @@ public function dataReservedKeywordsAsName()
'resource',
'mixed',
'numeric',
+ 'never',
// Not reserved keyword, but do have their own token in PHPCS.
'parent',
diff --git a/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc b/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc
index e2d61bb664..38e5a47d53 100644
--- a/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc
+++ b/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc
@@ -13,7 +13,7 @@ interface FooBar extends namespace\BarFoo {}
function foo() : namespace\Baz {}
/* testClosureReturnType */
-$closure = function () : namespace\Baz {}
+$closure = function () : namespace\Baz {};
/* testArrowFunctionReturnType */
$fn = fn() : namespace\Baz => new namespace\Baz;
diff --git a/tests/Core/Tokenizer/ShortArrayTest.inc b/tests/Core/Tokenizer/ShortArrayTest.inc
index 54065f213d..60b23a51cc 100644
--- a/tests/Core/Tokenizer/ShortArrayTest.inc
+++ b/tests/Core/Tokenizer/ShortArrayTest.inc
@@ -92,6 +92,20 @@ echo [1, 2, 3][0];
/* testArrayWithinFunctionCall */
$var = functionCall([$x, $y]);
+if ( true ) {
+ /* testShortListDeclarationAfterBracedControlStructure */
+ [ $a ] = [ 'hi' ];
+}
+
+if ( true )
+ /* testShortListDeclarationAfterNonBracedControlStructure */
+ [ $a ] = [ 'hi' ];
+
+if ( true ) :
+ /* testShortListDeclarationAfterAlternativeControlStructure */
+ [ $a ] = [ 'hi' ];
+endif;
+
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$array = [
diff --git a/tests/Core/Tokenizer/ShortArrayTest.php b/tests/Core/Tokenizer/ShortArrayTest.php
index 0484d4fcb0..1d97894f59 100644
--- a/tests/Core/Tokenizer/ShortArrayTest.php
+++ b/tests/Core/Tokenizer/ShortArrayTest.php
@@ -52,29 +52,29 @@ public function testSquareBrackets($testMarker)
public function dataSquareBrackets()
{
return [
- ['/* testArrayAccess1 */'],
- ['/* testArrayAccess2 */'],
- ['/* testArrayAssignment */'],
- ['/* testFunctionCallDereferencing */'],
- ['/* testMethodCallDereferencing */'],
- ['/* testStaticMethodCallDereferencing */'],
- ['/* testPropertyDereferencing */'],
- ['/* testPropertyDereferencingWithInaccessibleName */'],
- ['/* testStaticPropertyDereferencing */'],
- ['/* testStringDereferencing */'],
- ['/* testStringDereferencingDoubleQuoted */'],
- ['/* testConstantDereferencing */'],
- ['/* testClassConstantDereferencing */'],
- ['/* testMagicConstantDereferencing */'],
- ['/* testArrayAccessCurlyBraces */'],
- ['/* testArrayLiteralDereferencing */'],
- ['/* testShortArrayLiteralDereferencing */'],
- ['/* testClassMemberDereferencingOnInstantiation1 */'],
- ['/* testClassMemberDereferencingOnInstantiation2 */'],
- ['/* testClassMemberDereferencingOnClone */'],
- ['/* testNullsafeMethodCallDereferencing */'],
- ['/* testInterpolatedStringDereferencing */'],
- ['/* testLiveCoding */'],
+ 'array access 1' => ['/* testArrayAccess1 */'],
+ 'array access 2' => ['/* testArrayAccess2 */'],
+ 'array assignment' => ['/* testArrayAssignment */'],
+ 'function call dereferencing' => ['/* testFunctionCallDereferencing */'],
+ 'method call dereferencing' => ['/* testMethodCallDereferencing */'],
+ 'static method call dereferencing' => ['/* testStaticMethodCallDereferencing */'],
+ 'property dereferencing' => ['/* testPropertyDereferencing */'],
+ 'property dereferencing with inaccessable name' => ['/* testPropertyDereferencingWithInaccessibleName */'],
+ 'static property dereferencing' => ['/* testStaticPropertyDereferencing */'],
+ 'string dereferencing single quotes' => ['/* testStringDereferencing */'],
+ 'string dereferencing double quotes' => ['/* testStringDereferencingDoubleQuoted */'],
+ 'global constant dereferencing' => ['/* testConstantDereferencing */'],
+ 'class constant dereferencing' => ['/* testClassConstantDereferencing */'],
+ 'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'],
+ 'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'],
+ 'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'],
+ 'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'],
+ 'class member dereferencing on instantiation 1' => ['/* testClassMemberDereferencingOnInstantiation1 */'],
+ 'class member dereferencing on instantiation 2' => ['/* testClassMemberDereferencingOnInstantiation2 */'],
+ 'class member dereferencing on clone' => ['/* testClassMemberDereferencingOnClone */'],
+ 'nullsafe method call dereferencing' => ['/* testNullsafeMethodCallDereferencing */'],
+ 'interpolated string dereferencing' => ['/* testInterpolatedStringDereferencing */'],
+ 'live coding' => ['/* testLiveCoding */'],
];
}//end dataSquareBrackets()
@@ -117,13 +117,16 @@ public function testShortArrays($testMarker)
public function dataShortArrays()
{
return [
- ['/* testShortArrayDeclarationEmpty */'],
- ['/* testShortArrayDeclarationWithOneValue */'],
- ['/* testShortArrayDeclarationWithMultipleValues */'],
- ['/* testShortArrayDeclarationWithDereferencing */'],
- ['/* testShortListDeclaration */'],
- ['/* testNestedListDeclaration */'],
- ['/* testArrayWithinFunctionCall */'],
+ 'short array empty' => ['/* testShortArrayDeclarationEmpty */'],
+ 'short array with value' => ['/* testShortArrayDeclarationWithOneValue */'],
+ 'short array with values' => ['/* testShortArrayDeclarationWithMultipleValues */'],
+ 'short array with dereferencing' => ['/* testShortArrayDeclarationWithDereferencing */'],
+ 'short list' => ['/* testShortListDeclaration */'],
+ 'short list nested' => ['/* testNestedListDeclaration */'],
+ 'short array within function call' => ['/* testArrayWithinFunctionCall */'],
+ 'short list after braced control structure' => ['/* testShortListDeclarationAfterBracedControlStructure */'],
+ 'short list after non-braced control structure' => ['/* testShortListDeclarationAfterNonBracedControlStructure */'],
+ 'short list after alternative control structure' => ['/* testShortListDeclarationAfterAlternativeControlStructure */'],
];
}//end dataShortArrays()
diff --git a/tests/Core/Tokenizer/TypeIntersectionTest.inc b/tests/Core/Tokenizer/TypeIntersectionTest.inc
new file mode 100644
index 0000000000..abf9b85ba8
--- /dev/null
+++ b/tests/Core/Tokenizer/TypeIntersectionTest.inc
@@ -0,0 +1,120 @@
+ $param & $int;
+
+/* testTypeIntersectionArrowReturnType */
+$arrowWithReturnType = fn ($param) : Foo&Bar => $param * 10;
+
+/* testBitwiseAndInArrayKey */
+$array = array(
+ A & B => /* testBitwiseAndInArrayValue */ B & C
+);
+
+/* testBitwiseAndInShortArrayKey */
+$array = [
+ A & B => /* testBitwiseAndInShortArrayValue */ B & C
+];
+
+/* testBitwiseAndNonArrowFnFunctionCall */
+$obj->fn($something & $else);
+
+/* testBitwiseAnd6 */
+function &fn(/* testTypeIntersectionNonArrowFunctionDeclaration */ Foo&Bar $something) {}
+
+/* testTypeIntersectionWithInvalidTypes */
+function (int&string $var) {};
+
+/* testLiveCoding */
+// Intentional parse error. This has to be the last test in the file.
+return function( Foo&
diff --git a/tests/Core/Tokenizer/TypeIntersectionTest.php b/tests/Core/Tokenizer/TypeIntersectionTest.php
new file mode 100644
index 0000000000..2170021933
--- /dev/null
+++ b/tests/Core/Tokenizer/TypeIntersectionTest.php
@@ -0,0 +1,138 @@
+
+ * @author Jaroslav Hanslík
+ * @copyright 2020 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
+
+use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
+
+class TypeIntersectionTest extends AbstractMethodUnitTest
+{
+
+
+ /**
+ * Test that non-intersection type bitwise and tokens are still tokenized as bitwise and.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ *
+ * @dataProvider dataBitwiseAnd
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
+ *
+ * @return void
+ */
+ public function testBitwiseAnd($testMarker)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $opener = $this->getTargetToken($testMarker, [T_BITWISE_AND, T_TYPE_INTERSECTION]);
+ $this->assertSame(T_BITWISE_AND, $tokens[$opener]['code']);
+ $this->assertSame('T_BITWISE_AND', $tokens[$opener]['type']);
+
+ }//end testBitwiseAnd()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testBitwiseAnd()
+ *
+ * @return array
+ */
+ public function dataBitwiseAnd()
+ {
+ return [
+ ['/* testBitwiseAnd1 */'],
+ ['/* testBitwiseAnd2 */'],
+ ['/* testBitwiseAndPropertyDefaultValue */'],
+ ['/* testBitwiseAndParamDefaultValue */'],
+ ['/* testBitwiseAnd3 */'],
+ ['/* testBitwiseAnd4 */'],
+ ['/* testBitwiseAnd5 */'],
+ ['/* testBitwiseAndClosureParamDefault */'],
+ ['/* testBitwiseAndArrowParamDefault */'],
+ ['/* testBitwiseAndArrowExpression */'],
+ ['/* testBitwiseAndInArrayKey */'],
+ ['/* testBitwiseAndInArrayValue */'],
+ ['/* testBitwiseAndInShortArrayKey */'],
+ ['/* testBitwiseAndInShortArrayValue */'],
+ ['/* testBitwiseAndNonArrowFnFunctionCall */'],
+ ['/* testBitwiseAnd6 */'],
+ ['/* testLiveCoding */'],
+ ];
+
+ }//end dataBitwiseAnd()
+
+
+ /**
+ * Test that bitwise and tokens when used as part of a intersection type are tokenized as `T_TYPE_INTERSECTION`.
+ *
+ * @param string $testMarker The comment which prefaces the target token in the test file.
+ *
+ * @dataProvider dataTypeIntersection
+ * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
+ *
+ * @return void
+ */
+ public function testTypeIntersection($testMarker)
+ {
+ $tokens = self::$phpcsFile->getTokens();
+
+ $opener = $this->getTargetToken($testMarker, [T_BITWISE_AND, T_TYPE_INTERSECTION]);
+ $this->assertSame(T_TYPE_INTERSECTION, $tokens[$opener]['code']);
+ $this->assertSame('T_TYPE_INTERSECTION', $tokens[$opener]['type']);
+
+ }//end testTypeIntersection()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testTypeIntersection()
+ *
+ * @return array
+ */
+ public function dataTypeIntersection()
+ {
+ return [
+ ['/* testTypeIntersectionPropertySimple */'],
+ ['/* testTypeIntersectionPropertyReverseModifierOrder */'],
+ ['/* testTypeIntersectionPropertyMulti1 */'],
+ ['/* testTypeIntersectionPropertyMulti2 */'],
+ ['/* testTypeIntersectionPropertyMulti3 */'],
+ ['/* testTypeIntersectionPropertyNamespaceRelative */'],
+ ['/* testTypeIntersectionPropertyPartiallyQualified */'],
+ ['/* testTypeIntersectionPropertyFullyQualified */'],
+ ['/* testTypeIntersectionPropertyWithReadOnlyKeyword */'],
+ ['/* testTypeIntersectionParam1 */'],
+ ['/* testTypeIntersectionParam2 */'],
+ ['/* testTypeIntersectionParam3 */'],
+ ['/* testTypeIntersectionParamNamespaceRelative */'],
+ ['/* testTypeIntersectionParamPartiallyQualified */'],
+ ['/* testTypeIntersectionParamFullyQualified */'],
+ ['/* testTypeIntersectionReturnType */'],
+ ['/* testTypeIntersectionConstructorPropertyPromotion */'],
+ ['/* testTypeIntersectionAbstractMethodReturnType1 */'],
+ ['/* testTypeIntersectionAbstractMethodReturnType2 */'],
+ ['/* testTypeIntersectionReturnTypeNamespaceRelative */'],
+ ['/* testTypeIntersectionReturnPartiallyQualified */'],
+ ['/* testTypeIntersectionReturnFullyQualified */'],
+ ['/* testTypeIntersectionClosureParamIllegalNullable */'],
+ ['/* testTypeIntersectionWithReference */'],
+ ['/* testTypeIntersectionWithSpreadOperator */'],
+ ['/* testTypeIntersectionClosureReturn */'],
+ ['/* testTypeIntersectionArrowParam */'],
+ ['/* testTypeIntersectionArrowReturnType */'],
+ ['/* testTypeIntersectionNonArrowFunctionDeclaration */'],
+ ['/* testTypeIntersectionWithInvalidTypes */'],
+ ];
+
+ }//end dataTypeIntersection()
+
+
+}//end class