From 8e77e7c960d56b0fb2bb8771d32f4071424d1ca2 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 12 Jan 2020 01:45:37 +0100 Subject: [PATCH 001/733] Add option to disable the check for protected/private function --- .../Commenting/FunctionCommentSniff.php | 29 ++++++++++++++++--- .../Commenting/FunctionCommentUnitTest.inc | 17 +++++++++++ .../FunctionCommentUnitTest.inc.fixed | 17 +++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php index a6a51f6b08..e2b77d63d7 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php @@ -16,6 +16,14 @@ class FunctionCommentSniff implements Sniff { + /** + * Disable the check for functions with a lower visibility than the value given. + * Allowed values are public, protected and private. + * + * @var string + */ + public $minimumVisibility = 'private'; + /** * Returns an array of tokens this test wants to listen for. @@ -41,16 +49,29 @@ public function register() public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $find = Tokens::$methodPrefixes; - $find[] = T_WHITESPACE; - $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); + $ignore = Tokens::$emptyTokens; + $ignore[] = T_STATIC; + + $scopeModifier = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); + if ($tokens[$scopeModifier]['code'] === T_PROTECTED + && $this->minimumVisibility === 'public' + || $tokens[$scopeModifier]['code'] === T_PRIVATE + && ($this->minimumVisibility === 'public' || $this->minimumVisibility === 'protected') + ) { + return; + } + + $ignore = Tokens::$methodPrefixes; + $ignore[] = T_WHITESPACE; + + $commentEnd = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); if ($tokens[$commentEnd]['code'] === T_COMMENT) { // Inline comments might just be closing comments for // control structures or functions instead of function comments // using the wrong comment type. If there is other code on the line, // assume they relate to that code. - $prev = $phpcsFile->findPrevious($find, ($commentEnd - 1), null, true); + $prev = $phpcsFile->findPrevious($ignore, ($commentEnd - 1), null, true); if ($prev !== false && $tokens[$prev]['line'] === $tokens[$commentEnd]['line']) { $commentEnd = $prev; } diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc index 461b490775..1d32837b6f 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc @@ -381,3 +381,20 @@ public function setTranslator($a, &$b): void { $this->translator = $translator; } + +// phpcs:set PEAR.Commenting.FunctionComment minimumVisibility protected +private function setTranslator2($a, &$b): void +{ + $this->translator = $translator; +} + +// phpcs:set PEAR.Commenting.FunctionComment minimumVisibility public +protected function setTranslator3($a, &$b): void +{ + $this->translator = $translator; +} + +private function setTranslator4($a, &$b): void +{ + $this->translator = $translator; +} diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 80bf63f7be..28e8b4bcd4 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -381,3 +381,20 @@ public function setTranslator($a, &$b): void { $this->translator = $translator; } + +// phpcs:set PEAR.Commenting.FunctionComment minimumVisibility protected +private function setTranslator2($a, &$b): void +{ + $this->translator = $translator; +} + +// phpcs:set PEAR.Commenting.FunctionComment minimumVisibility public +protected function setTranslator3($a, &$b): void +{ + $this->translator = $translator; +} + +private function setTranslator4($a, &$b): void +{ + $this->translator = $translator; +} From b6272552b75ff10573399b9d5b44e849528c6be3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 9 Mar 2020 10:28:11 +0100 Subject: [PATCH 002/733] Use getMethodProperties --- .../PEAR/Sniffs/Commenting/FunctionCommentSniff.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php index e2b77d63d7..a9a9d7b562 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php @@ -53,10 +53,10 @@ public function process(File $phpcsFile, $stackPtr) $ignore = Tokens::$emptyTokens; $ignore[] = T_STATIC; - $scopeModifier = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); - if ($tokens[$scopeModifier]['code'] === T_PROTECTED + $scopeModifier = $phpcsFile->getMethodProperties($stackPtr)['scope']; + if ($scopeModifier === 'protected' && $this->minimumVisibility === 'public' - || $tokens[$scopeModifier]['code'] === T_PRIVATE + || $scopeModifier === 'private' && ($this->minimumVisibility === 'public' || $this->minimumVisibility === 'protected') ) { return; From 4e1deb81cf6207a951f1b5ed09fe7d29f7c2dacf Mon Sep 17 00:00:00 2001 From: "John P. Bloch" Date: Mon, 9 Mar 2020 13:40:21 -0500 Subject: [PATCH 003/733] Add support for aligning multi-line assignments at the start of the assign token --- .../MultipleStatementAlignmentSniff.php | 21 ++++++++ .../MultipleStatementAlignmentUnitTest.inc | 54 +++++++++++++++++++ ...ltipleStatementAlignmentUnitTest.inc.fixed | 54 +++++++++++++++++++ .../MultipleStatementAlignmentUnitTest.php | 7 +++ 4 files changed, 136 insertions(+) diff --git a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php index ab7caa9d48..ddf6f3771e 100644 --- a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php +++ b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php @@ -47,6 +47,23 @@ class MultipleStatementAlignmentSniff implements Sniff */ public $maxPadding = 1000; + /** + * Controls which side of the assignment token is used for alignment + * + * The default is to use the end of the assignemnt token: + * + * $test = 'Hello'; + * $test .= ' World'; + * + * Setting to false reverses the alignment: + * + * $test = 'Hello'; + * $test .= 'World'; + * + * @var boolean + */ + public $alignAtEndOfAssignToken = true; + /** * Returns an array of tokens this test wants to listen for. @@ -253,6 +270,10 @@ public function checkAlignment($phpcsFile, $stackPtr, $end=null) // padding length if they aligned with us. $varEnd = $tokens[($var + 1)]['column']; $assignLen = $tokens[$assign]['length']; + if ($this->alignAtEndOfAssignToken !== true) { + $assignLen = 1; + } + if ($assign !== $stackPtr) { if ($prevAssign === null) { // Processing an inner block but no assignments found. diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc index 61beb4cefb..f1d3c5ee0f 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc @@ -406,3 +406,57 @@ $foofoo = new Foo([ $i = 0; echo "TEST: ".($i += 1)."\n"; + +// Valid +$foo = 'Hello'; +$variable = 12; +$foo .= ' World'; +$test = 1; +$test <<= 6; + +// Invalid +$foo = 'Hello'; +$variable = 12; +$foo .= ' World'; +$test = 1; +$test <<= 6; + +// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEndOfAssignToken false + +// Valid +$foo = 'Hello'; +$variable = 12; +$foo .= ' World'; +$test = 1; +$test <<= 6; + +// Invalid +$foo = 'Hello'; +$variable = 12; +$foo .= ' World'; +$test = 1; +$test <<= 6; + +// phpcs:set Generic.Formatting.MultipleStatementAlignment maxPadding 8 + +$one = 'one'; +$varonetwo = 'two'; +$varonetwothree = 'three'; +$varonetwothreefour = 'four'; + +$one = 'one'; +$varonetwo .= 'two'; +$varonetwo = 'two'; +$varonetwo .= 'two'; +$varonetwothree = 'three'; +$varonetwothreefour = 'four'; + +$one <<= 8; +$onetwothree = 3; + +// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEndOfAssignToken true + +$one <<= 8; +$onetwothree = 3; + +// phpcs:set Generic.Formatting.MultipleStatementAlignment maxPadding 1000 diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed index 3129397f57..cb7e8f6f02 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed @@ -406,3 +406,57 @@ $foofoo = new Foo([ $i = 0; echo "TEST: ".($i += 1)."\n"; + +// Valid +$foo = 'Hello'; +$variable = 12; +$foo .= ' World'; +$test = 1; +$test <<= 6; + +// Invalid +$foo = 'Hello'; +$variable = 12; +$foo .= ' World'; +$test = 1; +$test <<= 6; + +// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEndOfAssignToken false + +// Valid +$foo = 'Hello'; +$variable = 12; +$foo .= ' World'; +$test = 1; +$test <<= 6; + +// Invalid +$foo = 'Hello'; +$variable = 12; +$foo .= ' World'; +$test = 1; +$test <<= 6; + +// phpcs:set Generic.Formatting.MultipleStatementAlignment maxPadding 8 + +$one = 'one'; +$varonetwo = 'two'; +$varonetwothree = 'three'; +$varonetwothreefour = 'four'; + +$one = 'one'; +$varonetwo .= 'two'; +$varonetwo = 'two'; +$varonetwo .= 'two'; +$varonetwothree = 'three'; +$varonetwothreefour = 'four'; + +$one <<= 8; +$onetwothree = 3; + +// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEndOfAssignToken true + +$one <<= 8; +$onetwothree = 3; + +// phpcs:set Generic.Formatting.MultipleStatementAlignment maxPadding 1000 diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php index 4076515161..eef66a5d04 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php @@ -111,6 +111,13 @@ public function getWarningList($testFile='MultipleStatementAlignmentUnitTest.inc 398 => 1, 399 => 1, 401 => 1, + 420 => 1, + 422 => 1, + 436 => 1, + 438 => 1, + 442 => 1, + 443 => 1, + 454 => 1, ]; break; case 'MultipleStatementAlignmentUnitTest.js': From fac9db058be601dd0e86761919721180ec1b6520 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 11 Mar 2020 22:32:47 +0100 Subject: [PATCH 004/733] AbstractSniffUnitTest: bug fix - warnings not counted in total At the bottom of a test run a message along the lines of `4 sniff test files generated 2 unique error codes; 0 were fixable (0%)` is shown. The unique error codes, as well as the fixable count and percentage would only include `error` codes and would totally disregard the codes coming from `warning`s. Fixed now. --- tests/Standards/AbstractSniffUnitTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Standards/AbstractSniffUnitTest.php b/tests/Standards/AbstractSniffUnitTest.php index dc7c14cbe8..c050a0c2af 100644 --- a/tests/Standards/AbstractSniffUnitTest.php +++ b/tests/Standards/AbstractSniffUnitTest.php @@ -320,6 +320,17 @@ public function generateFailureMessages(LocalFile $file) $warningsTemp = []; foreach ($warnings as $warning) { $warningsTemp[] = $warning['message'].' ('.$warning['source'].')'; + + $source = $warning['source']; + if (in_array($source, $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'], true) === false) { + $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'][] = $source; + } + + if ($warning['fixable'] === true + && in_array($source, $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'], true) === false + ) { + $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'][] = $source; + } } $allProblems[$line]['found_warnings'] = array_merge($foundWarningsTemp, $warningsTemp); From 79ffe3fbb5b252b4a3b959deaf132f44a18453f8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 16 Mar 2019 18:47:17 +0100 Subject: [PATCH 005/733] Generic/ArbitraryParenthesesSpacing: minor clean up As `declare()` is recognized as a parentheses owner, there is no need for it to be listed in the "additional tokens indicating that parenthesis are not arbitrary" list. Tested via an existing test: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Standards/Generic/Tests/WhiteSpace/ArbitraryParenthesesSpacingUnitTest.inc#L29 --- .../Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php index 7a7c5d8eee..09aea5436e 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php @@ -58,7 +58,6 @@ public function register() $this->ignoreTokens[T_CLOSE_SHORT_ARRAY] = T_CLOSE_SHORT_ARRAY; $this->ignoreTokens[T_USE] = T_USE; - $this->ignoreTokens[T_DECLARE] = T_DECLARE; $this->ignoreTokens[T_THROW] = T_THROW; $this->ignoreTokens[T_YIELD] = T_YIELD; $this->ignoreTokens[T_YIELD_FROM] = T_YIELD_FROM; From ed0888ca659466db30ccbd9ba16a55264f86df59 Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Wed, 17 Jun 2020 08:34:55 +0200 Subject: [PATCH 006/733] Fix rare undefined offset errors in ConcatenationSpacingSniff This can happen when PHPCS runs on a file that is currently being worked on, but not yet completed. The file might end with a dot. We can not assume there are always 2 more tokens after a dot. --- .../Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php b/src/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php index 4125b63ad0..a5302d269a 100644 --- a/src/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php @@ -79,10 +79,14 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->recordMetric($stackPtr, 'Spacing before string concat', $before); } - if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + if (isset($tokens[($stackPtr + 1)]) === false + || $tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE + ) { $after = 0; } else { - if ($tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line']) { + if (isset($tokens[($stackPtr + 2)]) === true + && $tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line'] + ) { $after = 'newline'; } else { $after = $tokens[($stackPtr + 1)]['length']; From 898d911b988ac2adc196db8a4bd257683778a64e Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Wed, 17 Jun 2020 09:22:09 +0200 Subject: [PATCH 007/733] Fix InlineControlStructureSniff adding brackets when there is already a bracket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While live coding, there might not only be an incomplete `if ():` at the end of a file, it might as well be an incomplete `if () {`. The most minimal example I found is ` Date: Wed, 17 Jun 2020 11:00:05 +0200 Subject: [PATCH 008/733] Don't report anything if the code is incomplete --- .../Sniffs/Strings/ConcatenationSpacingSniff.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php b/src/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php index a5302d269a..9d32e54a0a 100644 --- a/src/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php @@ -55,6 +55,10 @@ public function register() public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); + if (isset($tokens[($stackPtr + 2)]) === false) { + // Syntax error or live coding, bow out. + return; + } $ignoreBefore = false; $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); @@ -79,14 +83,10 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->recordMetric($stackPtr, 'Spacing before string concat', $before); } - if (isset($tokens[($stackPtr + 1)]) === false - || $tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE - ) { + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { $after = 0; } else { - if (isset($tokens[($stackPtr + 2)]) === true - && $tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line'] - ) { + if ($tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line']) { $after = 'newline'; } else { $after = $tokens[($stackPtr + 1)]['length']; From 3b9282ab1acad6a9d3a1351507272de0c5c25625 Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Wed, 17 Jun 2020 12:54:16 +0200 Subject: [PATCH 009/733] Fix possible index error in NonExecutableCodeSniff I was running into this while working on another sniff. One trivial example I found to demonstrate the bug is ` Date: Wed, 17 Jun 2020 14:10:50 +0200 Subject: [PATCH 010/733] Add T_OPEN_CURLY_BRACKET check back --- src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 7919bd448a..9c3b1a63ad 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -216,7 +216,9 @@ public function process(File $phpcsFile, $stackPtr) continue; } - if (isset($tokens[$start]['bracket_closer']) === true) { + if (isset($tokens[$start]['bracket_closer']) === true + && $tokens[$start]['code'] === T_OPEN_CURLY_BRACKET + ) { $start = $tokens[$start]['bracket_closer']; continue; } From 5ae8918189fbcd3326add9d4803ce68793350246 Mon Sep 17 00:00:00 2001 From: vladyslavstartsev <17382248+vladyslavstartsev@users.noreply.github.com> Date: Fri, 19 Jun 2020 10:57:20 +0300 Subject: [PATCH 011/733] remove (from publishing) files that are not needed for end user --- .gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitattributes b/.gitattributes index 56dcc5dc74..d766e840b0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,11 @@ .travis.yml export-ignore +.cspell.json export-ignore +.gitattributes export-ignore +.gitignore export-ignore +phpcs.xml.dist export-ignore +phpstan.neon export-ignore package.xml export-ignore phpunit.xml.dist export-ignore php5-testingConfig.ini export-ignore php7-testingConfig.ini export-ignore +scripts/ export-ignore From 5ee753130f87213b7d3b9ba5948ab6f53f79cd98 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 21 Jun 2020 11:40:51 +0200 Subject: [PATCH 012/733] Result caching: minor optimization tweak [1] While debugging something else, I noticed that `.bak` files were being used for the "PHPCS native file hash". As non-php files in the PHPCS directories have no effect on the actual run output, we may as well skip them from being part of the hash. --- src/Util/Cache.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Util/Cache.php b/src/Util/Cache.php index b7819c91eb..48aa65aaa0 100644 --- a/src/Util/Cache.php +++ b/src/Util/Cache.php @@ -105,6 +105,11 @@ function ($file, $key, $iterator) { return false; } + // Skip non-php files. + if ($file->isFile() === true && substr($filename, -4) !== '.php') { + return false; + } + $filePath = Common::realpath($file->getPathname()); if ($filePath === false) { return false; From 15ddd84f3a16aadcd8927bc48c668b3e753b9414 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 21 Jun 2020 11:40:51 +0200 Subject: [PATCH 013/733] Result caching: minor optimization tweak [2] Optimize use of the iterators: 1. Skip dot files at the `RecursiveDirectoryIterator` level by setting the `FilesystemIterator::SKIP_DOTS` flag and remove the code which was doing the same in the callback. Note: the other two flags are the default flags used by the `RecursiveDirectoryIterator` constructor, so are needed to maintain the existing behaviour. 2. No need for the `$file->getPathname()` function call. The `$key` already contains that information, as per the default flags. 3. No need for a file-system `is_dir()` call. If it's a directory, the iterator will have children. --- src/Util/Cache.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Util/Cache.php b/src/Util/Cache.php index 48aa65aaa0..68abef59c4 100644 --- a/src/Util/Cache.php +++ b/src/Util/Cache.php @@ -95,27 +95,25 @@ public static function load(Ruleset $ruleset, Config $config) // hash. This ensures that core PHPCS changes will also invalidate the cache. // Note that we ignore sniffs here, and any files that don't affect // the outcome of the run. - $di = new \RecursiveDirectoryIterator($installDir); + $di = new \RecursiveDirectoryIterator( + $installDir, + (\FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS) + ); $filter = new \RecursiveCallbackFilterIterator( $di, function ($file, $key, $iterator) { - // Skip hidden files. - $filename = $file->getFilename(); - if (substr($filename, 0, 1) === '.') { - return false; - } - // Skip non-php files. + $filename = $file->getFilename(); if ($file->isFile() === true && substr($filename, -4) !== '.php') { return false; } - $filePath = Common::realpath($file->getPathname()); + $filePath = Common::realpath($key); if ($filePath === false) { return false; } - if (is_dir($filePath) === true + if ($iterator->hasChildren() === true && ($filename === 'Standards' || $filename === 'Exceptions' || $filename === 'Reports' From 5293f30ff93f5cb22ac68cf070667263115da88c Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Thu, 25 Jun 2020 12:25:17 +0200 Subject: [PATCH 014/733] Add T_OPEN_PARENTHESIS check back --- src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 9c3b1a63ad..343d9b29e3 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -211,7 +211,9 @@ public function process(File $phpcsFile, $stackPtr) break; } - if (isset($tokens[$start]['parenthesis_closer']) === true) { + if (isset($tokens[$start]['parenthesis_closer']) === true + && $tokens[$start]['code'] === T_OPEN_PARENTHESIS + ) { $start = $tokens[$start]['parenthesis_closer']; continue; } From 33af77177be5ae239349d3f8424e6a933f8d710f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Jul 2020 19:59:36 +0200 Subject: [PATCH 015/733] Tests: stabilize an expected global The global `$PHP_CODESNIFFER_PEAR` variable is used by the `Core` test and those test don't necessarily need to use the generic `AllTests` file as an entry point. They can also be run using `phpunit ./tests/Core/AllTests.php`. In that case, however, the `$PHP_CODESNIFFER_PEAR` variable is not defined leading to `Undefined index: PHP_CODESNIFFER_PEAR` errors. By setting the global in the bootstrap, which is always loaded, this is avoided. --- tests/AllTests.php | 5 +---- tests/bootstrap.php | 7 +++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/AllTests.php b/tests/AllTests.php index 4ed001dfed..9d099c1e3d 100644 --- a/tests/AllTests.php +++ b/tests/AllTests.php @@ -9,16 +9,13 @@ namespace PHP_CodeSniffer\Tests; -$GLOBALS['PHP_CODESNIFFER_PEAR'] = false; - -if (is_file(__DIR__.'/../autoload.php') === true) { +if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === false) { include_once 'Core/AllTests.php'; include_once 'Standards/AllSniffs.php'; } else { include_once 'CodeSniffer/Core/AllTests.php'; include_once 'CodeSniffer/Standards/AllSniffs.php'; include_once 'FileList.php'; - $GLOBALS['PHP_CODESNIFFER_PEAR'] = true; } // PHPUnit 7 made the TestSuite run() method incompatible with diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 560253c6a7..47084d1135 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -44,6 +44,13 @@ class_alias('PHPUnit_TextUI_TestRunner', 'PHPUnit'.'\TextUI\TestRunner'); class_alias('PHPUnit_Framework_TestResult', 'PHPUnit'.'\Framework\TestResult'); } +// Determine whether this is a PEAR install or not. +$GLOBALS['PHP_CODESNIFFER_PEAR'] = false; + +if (is_file(__DIR__.'/../autoload.php') === false) { + $GLOBALS['PHP_CODESNIFFER_PEAR'] = true; +} + /** * A global util function to help print unit test fixing data. From 209cc5f279e5615bd518e5320a83fd5fd5ec29de Mon Sep 17 00:00:00 2001 From: Steve Talbot Date: Mon, 6 Jul 2020 13:39:17 +0100 Subject: [PATCH 016/733] Fixed bug #3007: Directory exclude pattern improperly excludes directories with names that start the same --- src/Filters/Filter.php | 2 +- tests/Core/Filters/Filter/AcceptTest.php | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Filters/Filter.php b/src/Filters/Filter.php index fa7360f0cd..cc809f7afe 100644 --- a/src/Filters/Filter.php +++ b/src/Filters/Filter.php @@ -218,7 +218,7 @@ protected function shouldIgnorePath($path) // Need to check this pattern for dirs as well as individual file paths. $this->ignoreFilePatterns[$pattern] = $type; - $pattern = substr($pattern, 0, -2); + $pattern = substr($pattern, 0, -2) . '(?=/|$)'; $this->ignoreDirPatterns[$pattern] = $type; } else { // This is a file-specific pattern, so only need to check this diff --git a/tests/Core/Filters/Filter/AcceptTest.php b/tests/Core/Filters/Filter/AcceptTest.php index 5d928c38b8..2c9f57cd39 100644 --- a/tests/Core/Filters/Filter/AcceptTest.php +++ b/tests/Core/Filters/Filter/AcceptTest.php @@ -111,9 +111,13 @@ public function dataExcludePatterns() '/path/to/src/Main.php', '/path/to/src/Something/Main.php', '/path/to/src/Somethingelse/Main.php', + '/path/to/src/SomethingelseEvenLonger/Main.php', '/path/to/src/Other/Main.php', ], - ['/path/to/src/Main.php'], + [ + '/path/to/src/Main.php', + '/path/to/src/SomethingelseEvenLonger/Main.php', + ], ], // Test ignoring standard/sniff specific exclude patterns. From 367c60cb925c2741e67e3428019ff39268975cae Mon Sep 17 00:00:00 2001 From: Steve Talbot Date: Mon, 6 Jul 2020 14:25:55 +0100 Subject: [PATCH 017/733] Remove spaces around concat operator #3007 --- src/Filters/Filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Filters/Filter.php b/src/Filters/Filter.php index cc809f7afe..5bed499b25 100644 --- a/src/Filters/Filter.php +++ b/src/Filters/Filter.php @@ -218,7 +218,7 @@ protected function shouldIgnorePath($path) // Need to check this pattern for dirs as well as individual file paths. $this->ignoreFilePatterns[$pattern] = $type; - $pattern = substr($pattern, 0, -2) . '(?=/|$)'; + $pattern = substr($pattern, 0, -2).'(?=/|$)'; $this->ignoreDirPatterns[$pattern] = $type; } else { // This is a file-specific pattern, so only need to check this From a1a7e7018c67676609abb4f3212083ff2464e8f1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 10 Jul 2020 05:16:05 +0200 Subject: [PATCH 018/733] Tokenizer/PHP: support PHP8 magic constant dereferencing As of PHP 8, magic constants can be dereferenced, however, the square brackets are incorrectly tokenized as _short array_ brackets instead of as "normal" square brackets. Fixed by: * Adding a new `$magicConstants` array to the `Util\Tokens` calls. * Adding the `$magicConstants` to the allowed tokens for leaving the brackets alone in the PHP Tokenizer class. Includes adding unit tests for this section of the `PHP::processAdditional()` method. I've added as many relevant syntaxes as I could come up with, but won't claim that the unit test case file covers every possible scenario. Should be a good start though. Ref: https://wiki.php.net/rfc/variable_syntax_tweaks#constants_and_magic_constants --- package.xml | 6 ++ src/Tokenizers/PHP.php | 1 + src/Util/Tokens.php | 18 ++++ tests/Core/Tokenizer/ShortArrayTest.inc | 92 +++++++++++++++++ tests/Core/Tokenizer/ShortArrayTest.php | 130 ++++++++++++++++++++++++ 5 files changed, 247 insertions(+) create mode 100644 tests/Core/Tokenizer/ShortArrayTest.inc create mode 100644 tests/Core/Tokenizer/ShortArrayTest.php diff --git a/package.xml b/package.xml index 4d2105b94e..be71b05995 100644 --- a/package.xml +++ b/package.xml @@ -113,6 +113,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -1977,6 +1979,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2030,6 +2034,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 2fc86a86d6..84c06a072c 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1973,6 +1973,7 @@ protected function processAdditional() T_STRING => T_STRING, T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, ]; + $allowed += Util\Tokens::$magicConstants; for ($x = ($i - 1); $x >= 0; $x--) { // If we hit a scope opener, the statement has ended diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index d11b193629..bc3a32414a 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -589,6 +589,24 @@ final class Tokens T_TRAIT => T_TRAIT, ]; + /** + * Tokens representing PHP magic constants. + * + * @var array => + * + * @link https://www.php.net/language.constants.predefined PHP Manual on magic constants + */ + public static $magicConstants = [ + T_CLASS_C => T_CLASS_C, + T_DIR => T_DIR, + T_FILE => T_FILE, + T_FUNC_C => T_FUNC_C, + T_LINE => T_LINE, + T_METHOD_C => T_METHOD_C, + T_NS_C => T_NS_C, + T_TRAIT_C => T_TRAIT_C, + ]; + /** * Given a token, returns the name of the token. diff --git a/tests/Core/Tokenizer/ShortArrayTest.inc b/tests/Core/Tokenizer/ShortArrayTest.inc new file mode 100644 index 0000000000..a864869af9 --- /dev/null +++ b/tests/Core/Tokenizer/ShortArrayTest.inc @@ -0,0 +1,92 @@ +function_call()[$x]; + +/* testStaticMethodCallDereferencing */ +$var = ClassName::function_call()[$x]; + +/* testPropertyDereferencing */ +$var = $obj->property[2]; + +/* testPropertyDereferencingWithInaccessibleName */ +$var = $ref->{'ref-type'}[1]; + +/* testStaticPropertyDereferencing */ +$var ClassName::$property[2]; + +/* testStringDereferencing */ +$var = 'PHP'[1]; + +/* testStringDereferencingDoubleQuoted */ +$var = "PHP"[$y]; + +/* testConstantDereferencing */ +$var = MY_CONSTANT[1]; + +/* testClassConstantDereferencing */ +$var ClassName::CONSTANT_NAME[2]; + +/* testMagicConstantDereferencing */ +$var = __FILE__[0]; + +/* testArrayAccessCurlyBraces */ +$var = $array{'key'}['key']; + +/* testArrayLiteralDereferencing */ +echo array(1, 2, 3)[0]; + +echo [1, 2, 3]/* testShortArrayLiteralDereferencing */[0]; + +/* testClassMemberDereferencingOnInstantiation1 */ +(new foo)[0]; + +/* testClassMemberDereferencingOnInstantiation2 */ +$a = (new Foo( array(1, array(4, 5), 3) ))[1][0]; + +/* testClassMemberDereferencingOnClone */ +echo (clone $iterable)[20]; + + +/* + * Short array brackets. + */ + +/* testShortArrayDeclarationEmpty */ +$array = []; + +/* testShortArrayDeclarationWithOneValue */ +$array = [1]; + +/* testShortArrayDeclarationWithMultipleValues */ +$array = [1, 2, 3]; + +/* testShortArrayDeclarationWithDereferencing */ +echo [1, 2, 3][0]; + +/* testShortListDeclaration */ +[ $a, $b ] = $array; + +[ $a, $b, /* testNestedListDeclaration */, [$c, $d]] = $array; + +/* testArrayWithinFunctionCall */ +$var = functionCall([$x, $y]); + +/* 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 new file mode 100644 index 0000000000..04aaf3db26 --- /dev/null +++ b/tests/Core/Tokenizer/ShortArrayTest.php @@ -0,0 +1,130 @@ + + * @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 ShortArrayTest extends AbstractMethodUnitTest +{ + + + /** + * Test that real square brackets are still tokenized as square brackets. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataSquareBrackets + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testSquareBrackets($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $opener = $this->getTargetToken($testMarker, [T_OPEN_SQUARE_BRACKET, T_OPEN_SHORT_ARRAY]); + $this->assertSame(T_OPEN_SQUARE_BRACKET, $tokens[$opener]['code']); + $this->assertSame('T_OPEN_SQUARE_BRACKET', $tokens[$opener]['type']); + + if (isset($tokens[$opener]['bracket_closer']) === true) { + $closer = $tokens[$opener]['bracket_closer']; + $this->assertSame(T_CLOSE_SQUARE_BRACKET, $tokens[$closer]['code']); + $this->assertSame('T_CLOSE_SQUARE_BRACKET', $tokens[$closer]['type']); + } + + }//end testSquareBrackets() + + + /** + * Data provider. + * + * @see testSquareBrackets() + * + * @return array + */ + public function dataSquareBrackets() + { + return [ + ['/* testArrayAccess1 */'], + ['/* testArrayAccess2 */'], + ['/* testArrayAssignment */'], + ['/* testFunctionCallDereferencing */'], + ['/* testMethodCallDereferencing */'], + ['/* testStaticMethodCallDereferencing */'], + ['/* testPropertyDereferencing */'], + ['/* testPropertyDereferencingWithInaccessibleName */'], + ['/* testStaticPropertyDereferencing */'], + ['/* testStringDereferencing */'], + ['/* testStringDereferencingDoubleQuoted */'], + ['/* testConstantDereferencing */'], + ['/* testClassConstantDereferencing */'], + ['/* testMagicConstantDereferencing */'], + ['/* testArrayAccessCurlyBraces */'], + ['/* testArrayLiteralDereferencing */'], + ['/* testShortArrayLiteralDereferencing */'], + ['/* testClassMemberDereferencingOnInstantiation1 */'], + ['/* testClassMemberDereferencingOnInstantiation2 */'], + ['/* testClassMemberDereferencingOnClone */'], + ['/* testLiveCoding */'], + ]; + + }//end dataSquareBrackets() + + + /** + * Test that short arrays and short lists are still tokenized as short arrays. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataShortArrays + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testShortArrays($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $opener = $this->getTargetToken($testMarker, [T_OPEN_SQUARE_BRACKET, T_OPEN_SHORT_ARRAY]); + $this->assertSame(T_OPEN_SHORT_ARRAY, $tokens[$opener]['code']); + $this->assertSame('T_OPEN_SHORT_ARRAY', $tokens[$opener]['type']); + + if (isset($tokens[$opener]['bracket_closer']) === true) { + $closer = $tokens[$opener]['bracket_closer']; + $this->assertSame(T_CLOSE_SHORT_ARRAY, $tokens[$closer]['code']); + $this->assertSame('T_CLOSE_SHORT_ARRAY', $tokens[$closer]['type']); + } + + }//end testShortArrays() + + + /** + * Data provider. + * + * @see testShortArrays() + * + * @return array + */ + public function dataShortArrays() + { + return [ + ['/* testShortArrayDeclarationEmpty */'], + ['/* testShortArrayDeclarationWithOneValue */'], + ['/* testShortArrayDeclarationWithMultipleValues */'], + ['/* testShortArrayDeclarationWithDereferencing */'], + ['/* testShortListDeclaration */'], + ['/* testNestedListDeclaration */'], + ['/* testArrayWithinFunctionCall */'], + ]; + + }//end dataShortArrays() + + +}//end class From d8337c5560fac75cfe48590d94250c2b6496cd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Bugy=C3=ADk?= Date: Tue, 14 Jul 2020 23:12:45 +0200 Subject: [PATCH 019/733] Generic.CodeAnalysis.UnusedFunctionParameter has now option $ignoreTypeHints --- .../UnusedFunctionParameterSniff.php | 19 ++++++++++++++++++- .../UnusedFunctionParameterUnitTest.inc | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php index 427f509cae..cf1a5ebc96 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php @@ -23,6 +23,13 @@ class UnusedFunctionParameterSniff implements Sniff { + /** + * The list of class type hints which will be ignored. + * + * @var array + */ + public $ignoreTypeHints = []; + /** * Returns an array of tokens this test wants to listen for. @@ -191,6 +198,10 @@ public function process(File $phpcsFile, $stackPtr) // If there is only one parameter and it is unused, no need for additional errorcode toggling logic. if ($methodParamsCount === 1) { foreach ($params as $paramName => $position) { + if (in_array($methodParams[0]['type_hint'], $this->ignoreTypeHints, true) === true) { + continue; + } + $data = [$paramName]; $phpcsFile->addWarning($error, $position, $errorCode, $data); } @@ -207,6 +218,7 @@ public function process(File $phpcsFile, $stackPtr) $errorInfo[$methodParams[$i]['name']] = [ 'position' => $params[$methodParams[$i]['name']], 'errorcode' => $errorCode.'BeforeLastUsed', + 'typehint' => $methodParams[$i]['type_hint'], ]; } } else { @@ -216,14 +228,19 @@ public function process(File $phpcsFile, $stackPtr) $errorInfo[$methodParams[$i]['name']] = [ 'position' => $params[$methodParams[$i]['name']], 'errorcode' => $errorCode.'AfterLastUsed', + 'typehint' => $methodParams[$i]['type_hint'], ]; } } - } + }//end for if (count($errorInfo) > 0) { $errorInfo = array_reverse($errorInfo); foreach ($errorInfo as $paramName => $info) { + if (in_array($info['typehint'], $this->ignoreTypeHints, true) === true) { + continue; + } + $data = [$paramName]; $phpcsFile->addWarning($error, $info['position'], $info['errorcode'], $data); } diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc index 9997e787c5..0645ab4821 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc @@ -123,3 +123,17 @@ function myCallback($a, $b, $c, $d) { } fn ($a, $b, $c) => $b; + +// phpcs:set Generic.CodeAnalysis.UnusedFunctionParameter ignoreTypeHints[] Exception + +function oneParam(Exception $foo) { + return 'foobar'; +} + +function moreParamFirst(Exception $foo, LogicException $bar) { + return 'foobar' . $bar; +} + +function moreParamSecond(LogicException $bar, Exception $foo) { + return 'foobar' . $bar; +} From 80f031b9f5bcb3527266087f03bc1be135718128 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 4 Jul 2020 19:58:05 +0200 Subject: [PATCH 020/733] File::getMethodParameters(): add tests with PHP 8 "mixed" type No changes needed to the actual method, the parameter type is already handled correctly. --- tests/Core/File/GetMethodParametersTest.inc | 7 ++++ tests/Core/File/GetMethodParametersTest.php | 44 +++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index 86eff8dd4a..e4d5bfca74 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -31,3 +31,10 @@ function myFunction($a = 10 & 20) {} /* testArrowFunction */ fn(int $a, ...$b) => $b; + +/* testPHP8MixedTypeHint */ +function mixedTypeHint(mixed &...$var1) {} + +/* testPHP8MixedTypeHintNullable */ +// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method. +function mixedTypeHintNullable(?Mixed $var1) {} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index 1e8595da5c..f5d43c1a63 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -274,6 +274,50 @@ public function testArrowFunction() }//end testArrowFunction() + /** + * Verify recognition of PHP8 mixed type declaration. + * + * @return void + */ + public function testPHP8MixedTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => 'mixed &...$var1', + 'pass_by_reference' => true, + 'variable_length' => true, + 'type_hint' => 'mixed', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8MixedTypeHint() + + + /** + * Verify recognition of PHP8 mixed type declaration with nullability. + * + * @return void + */ + public function testPHP8MixedTypeHintNullable() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => '?Mixed $var1', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?Mixed', + 'nullable_type' => true, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8MixedTypeHintNullable() + + /** * Test helper. * From 17b2c664d112f67c9f51291ee29ea4b1c8cf981e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 4 Jul 2020 20:14:40 +0200 Subject: [PATCH 021/733] File::getMethodProperties(): add tests with PHP 8 "mixed" return type No changes needed to the actual method, the return type is already handled correctly. --- tests/Core/File/GetMethodPropertiesTest.inc | 7 ++++ tests/Core/File/GetMethodPropertiesTest.php | 46 +++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/tests/Core/File/GetMethodPropertiesTest.inc b/tests/Core/File/GetMethodPropertiesTest.inc index b04202fcd2..c268ece3a9 100644 --- a/tests/Core/File/GetMethodPropertiesTest.inc +++ b/tests/Core/File/GetMethodPropertiesTest.inc @@ -73,3 +73,10 @@ class ReturnMe { return $this; } } + +/* testPHP8MixedTypeHint */ +function mixedTypeHint() :mixed {} + +/* testPHP8MixedTypeHintNullable */ +// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method. +function mixedTypeHintNullable(): ?mixed {} diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php index b801962637..36d57e7301 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -406,6 +406,52 @@ public function testReturnTypeStatic() }//end testReturnTypeStatic() + /** + * Test a function with return type "mixed". + * + * @return void + */ + public function testPHP8MixedTypeHint() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'mixed', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8MixedTypeHint() + + + /** + * Test a function with return type "mixed" and nullability. + * + * @return void + */ + public function testPHP8MixedTypeHintNullable() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => '?mixed', + 'nullable_return_type' => true, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8MixedTypeHintNullable() + + /** * Test helper. * From 6d6b0eb614bf605fd08142147f11d11ff585781a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 4 Jul 2020 23:17:47 +0200 Subject: [PATCH 022/733] File::getMemberProperties(): add tests with PHP 8 "mixed" property type No changes needed to the actual method, the property type is already handled correctly. --- tests/Core/File/GetMemberPropertiesTest.inc | 9 +++++++++ tests/Core/File/GetMemberPropertiesTest.php | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index af52ff75df..8c87c64035 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -179,3 +179,12 @@ function_call( 'param', new class { /* testNestedMethodParam 2 */ public function __construct( $open, $post_id ) {} }, 10, 2 ); + +class PHP8Mixed { + /* testPHP8MixedTypeHint */ + public static miXed $mixed; + + /* testPHP8MixedTypeHintNullable */ + // Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method. + private ?mixed $nullableMixed; +} diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index a4bff22133..3e8d164dcd 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -459,6 +459,26 @@ public function dataGetMemberProperties() 'nullable_type' => false, ], ], + [ + '/* testPHP8MixedTypeHint */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => true, + 'type' => 'miXed', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8MixedTypeHintNullable */', + [ + 'scope' => 'private', + 'scope_specified' => true, + 'is_static' => false, + 'type' => '?mixed', + 'nullable_type' => true, + ], + ], ]; }//end dataGetMemberProperties() From 285f4651fdad878a4257a6082d30bd5051775fa9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Jul 2020 07:47:35 +0200 Subject: [PATCH 023/733] Tokenizer/PHP: add support for `static` return type to arrow function tokenization Includes unit test. --- src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/BackfillFnTokenTest.inc | 3 +++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 2fc86a86d6..7224fb27ec 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1829,6 +1829,7 @@ protected function processAdditional() T_CALLABLE => T_CALLABLE, T_PARENT => T_PARENT, T_SELF => T_SELF, + T_STATIC => T_STATIC, ]; $closer = $this->tokens[$x]['parenthesis_closer']; diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index 7714a78680..083fe6979c 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -75,6 +75,9 @@ fn(callable $a) : callable => $a; /* testArrayReturnType */ fn(array $a) : array => $a; +/* testStaticReturnType */ +fn(array $a) : static => $a; + /* testTernary */ $fn = fn($a) => $a ? /* testTernaryThen */ fn() : string => 'a' : /* testTernaryElse */ fn() : string => 'b'; diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 4dbb8b43c4..45bbbb4415 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -466,7 +466,7 @@ public function testNullableNamespace() /** - * Test arrow functions that use self/parent/callable return types. + * Test arrow functions that use self/parent/callable/array/static return types. * * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional * @@ -481,6 +481,7 @@ public function testKeywordReturnTypes() 'Parent', 'Callable', 'Array', + 'Static', ]; foreach ($testMarkers as $marker) { From 751ec21e69281c756bfbac0b18811d846691744f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 20 Jul 2020 20:46:19 +0200 Subject: [PATCH 024/733] Travis: change from "trusty" to "xenial" As the "trusty" environment is no longer officially supported by Travis, they decided in their wisdom to silently stop updating the PHP "nightly" image, which makes it next to useless as the last image apparently is from January.... This updates the Travis config to: * Use the `xenial` distro, which at this time is the default. * Sets the distro for low PHP versions explicitly to `trusty`. * Makes the expected OS explicit (linux). * Updates the `matrix` key to `jobs`, which is the canonical for which `matrix` is an alias. * Removed `sudo: false` - this hasn't been supported for over a year now. As the `xenial` images (again) appear to randomly be shipped with incompatible PHPUnit versions, I'm changing the setup to always run `composer install` and use the PHPUnit in the `vendor` directory. This will make the build less prone to error out on incompatible Travis image changes in the future and should hardly make a difference in build time. Note: the build against PHP nightly will now fail as it is run against a much more recent PHP build. This build failure will be fixed once the comment tokenizer change has been pulled & merged. To pull that PR, merging this one is a prerequisite. --- .travis.yml | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 66e4286d6a..0145ca1649 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,16 @@ -sudo: false +os: linux +dist: xenial language: php -dist: trusty -matrix: +jobs: fast_finish: true include: - php: 5.4 + dist: trusty - php: 5.5 + dist: trusty - php: 5.5 + dist: trusty env: CUSTOM_INI=1 XMLLINT=1 addons: apt: @@ -19,20 +22,16 @@ matrix: env: CUSTOM_INI=1 PEAR_VALIDATE=1 - php: 7.1 - php: 7.2 - env: PHPUNIT_INCOMPAT=1 - php: 7.3 - env: PHPUNIT_INCOMPAT=1 - php: 7.4 - env: PHPUNIT_INCOMPAT=1 - php: 7.4 - env: PHPSTAN=1 PHPUNIT_INCOMPAT=1 + env: PHPSTAN=1 addons: apt: packages: - libonig-dev # Nightly is PHP 8.0 since Feb 2019. - php: nightly - env: PHPUNIT_INCOMPAT=1 addons: apt: packages: @@ -40,7 +39,7 @@ matrix: allow_failures: - php: 7.4 - env: PHPSTAN=1 PHPUNIT_INCOMPAT=1 + env: PHPSTAN=1 - php: nightly before_install: @@ -49,9 +48,9 @@ before_install: - phpenv config-rm xdebug.ini || echo 'No xdebug config.' # PHPUnit 8.x is not (yet) supported, so prevent issues with Travis images using it. - | - if [[ $PHPUNIT_INCOMPAT == "1" && $TRAVIS_PHP_VERSION != "nightly" ]]; then + if [[ $TRAVIS_PHP_VERSION != "nightly" ]]; then composer install - elif [[ $PHPUNIT_INCOMPAT == "1" && $TRAVIS_PHP_VERSION == "nightly" ]]; then + elif [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then // Allow installing "incompatible" PHPUnit version on PHP 8/nightly. composer install --ignore-platform-reqs fi @@ -62,12 +61,7 @@ before_script: script: - php bin/phpcs --config-set php_path php - - | - if [[ $PHPUNIT_INCOMPAT != "1" ]]; then - phpunit tests/AllTests.php - else - vendor/bin/phpunit tests/AllTests.php - fi + - vendor/bin/phpunit tests/AllTests.php - if [[ $CUSTOM_INI != "1" ]]; then php bin/phpcs --no-cache --parallel=1; fi - if [[ $CUSTOM_INI != "1" && $TRAVIS_PHP_VERSION != "nightly" ]]; then pear package-validate package.xml; fi - if [[ $PEAR_VALIDATE == "1" ]]; then php scripts/validate-pear-package.php; fi From 7ffd6391fe9c5e84364dfd62ac148537970948ca Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 21 Jul 2020 08:26:27 +1000 Subject: [PATCH 025/733] Changelogs for #3013 and #3019 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index be71b05995..b4f36dc64c 100644 --- a/package.xml +++ b/package.xml @@ -26,8 +26,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - Added support for PHP 8.0 magic constant dereferencing + -- Thanks to Juliette Reinders Folmer for the patch - File::getMethodProperties() now detects the PHP 8.0 static return type -- Thanks to Juliette Reinders Folmer for the patch + - The PHP 8.0 static return type is now supported for arrow functions + -- Thanks to Juliette Reinders Folmer for the patch - The cache is no longer used if the list of loaded PHP extensions changes -- Thanks to Juliette Reinders Folmer for the patch - Squiz.Scope.StaticThisUsage now detects usage of $this inside closures and arrow Functions From f1ac8b36b7cdac414c4e1190ab21adf5046ee8c7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Jul 2020 07:44:42 +0200 Subject: [PATCH 026/733] BackfillFnTokenTest: minor test order tweak The test cases weren't listed in the same order as they were found in the test case file. --- tests/Core/Tokenizer/BackfillFnTokenTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 45bbbb4415..ea25f98ad5 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -653,14 +653,14 @@ public function dataNotAnArrowFunction() 'Fn', ], ['/* testNonArrowObjectMethodCall */'], - [ - '/* testNonArrowNamespacedFunctionCall */', - 'Fn', - ], [ '/* testNonArrowObjectMethodCallUpper */', 'FN', ], + [ + '/* testNonArrowNamespacedFunctionCall */', + 'Fn', + ], ['/* testNonArrowNamespaceOperatorFunctionCall */'], ['/* testLiveCoding */'], ]; From 0906e8c4fcd5abf86f865f2f7b571c34ac8d0b3b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Jul 2020 07:27:47 +0200 Subject: [PATCH 027/733] BackfillFnTokenTest: fix bug in the tests The scope closers weren't being tested properly as the local `$closer` variable was looking for the wrong array index. Once that was enabled, it exposed another issue in the tests: nested arrow functions with a scope closer being shared with the "outer" arrow function did not have the correct expectations set. Fixed now by: * Selectively skipping the 'scope_condition' check for the scope closer in the `backfillHelper()` method. * Checking that the `scope_opener` for such shared `scope_closer`s actually points to the "outer" arrow function. --- tests/Core/Tokenizer/BackfillFnTokenTest.php | 65 +++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index ea25f98ad5..55d378333f 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -37,7 +37,7 @@ public function testSimple() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer is not the semicolon token'); } @@ -66,7 +66,7 @@ public function testWhitespace() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 6), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 13), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 6), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 13), 'Closer scope closer is not the semicolon token'); @@ -94,7 +94,7 @@ public function testComment() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 15), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 15), 'Closer scope closer is not the semicolon token'); @@ -122,7 +122,7 @@ public function testHeredoc() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 4), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 4), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the semicolon token'); @@ -150,7 +150,7 @@ public function testNestedOuter() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 25), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 25), 'Closer scope closer is not the semicolon token'); @@ -169,7 +169,7 @@ public function testNestedInner() $tokens = self::$phpcsFile->getTokens(); $token = $this->getTargetToken('/* testNestedInner */', T_FN); - $this->backfillHelper($token); + $this->backfillHelper($token, true); $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); $this->assertSame($tokens[$token]['scope_closer'], ($token + 16), 'Scope closer is not the semicolon token'); @@ -178,8 +178,8 @@ public function testNestedInner() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 16), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); + $closer = $tokens[$token]['scope_closer']; + $this->assertSame($tokens[$closer]['scope_opener'], ($token - 4), 'Closer scope opener is not the arrow token of the "outer" arrow function (shared scope closer)'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 16), 'Closer scope closer is not the semicolon token'); }//end testNestedInner() @@ -206,7 +206,7 @@ public function testFunctionCall() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 17), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 17), 'Closer scope closer is not the semicolon token'); @@ -234,7 +234,7 @@ public function testChainedFunctionCall() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer is not the bracket token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer is not the bracket token'); @@ -262,7 +262,7 @@ public function testFunctionArgument() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 15), 'Opener scope closer is not the comma token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 15), 'Closer scope closer is not the comma token'); @@ -290,7 +290,7 @@ public function testClosure() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 60), 'Opener scope closer is not the comma token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 60), 'Closer scope closer is not the comma token'); @@ -318,7 +318,7 @@ public function testReturnType() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the comma token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the comma token'); @@ -346,7 +346,7 @@ public function testReference() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 6), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 6), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the semicolon token'); @@ -374,7 +374,7 @@ public function testGrouped() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 8), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 8), 'Closer scope closer is not the semicolon token'); @@ -402,7 +402,7 @@ public function testArrayValue() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 4), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the comma token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 4), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the comma token'); @@ -430,7 +430,7 @@ public function testYield() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 14), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 14), 'Closer scope closer is not the semicolon token'); @@ -458,7 +458,7 @@ public function testNullableNamespace() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 15), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 15), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the semicolon token'); @@ -495,7 +495,7 @@ public function testKeywordReturnTypes() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), "Opener scope opener is not the arrow token(for $marker)"); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 14), "Opener scope closer is not the semicolon token(for $marker)"); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), "Closer scope opener is not the arrow token(for $marker)"); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 14), "Closer scope closer is not the semicolon token(for $marker)"); } @@ -524,7 +524,7 @@ public function testTernary() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 40), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 40), 'Closer scope closer is not the semicolon token'); @@ -538,12 +538,12 @@ public function testTernary() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener for THEN is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer for THEN is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener for THEN is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer for THEN is not the semicolon token'); $token = $this->getTargetToken('/* testTernaryElse */', T_FN); - $this->backfillHelper($token); + $this->backfillHelper($token, true); $this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener for ELSE is not the arrow token'); $this->assertSame($tokens[$token]['scope_closer'], ($token + 11), 'Scope closer for ELSE is not the semicolon token'); @@ -552,8 +552,8 @@ public function testTernary() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener for ELSE is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 11), 'Opener scope closer for ELSE is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener for ELSE is not the arrow token'); + $closer = $tokens[$token]['scope_closer']; + $this->assertSame($tokens[$closer]['scope_opener'], ($token - 24), 'Closer scope opener for ELSE is not the arrow token of the "outer" arrow function (shared scope closer)'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 11), 'Closer scope closer for ELSE is not the semicolon token'); }//end testTernary() @@ -580,7 +580,7 @@ public function testNestedInMethod() $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); $this->assertSame($tokens[$opener]['scope_closer'], ($token + 17), 'Opener scope closer is not the semicolon token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); $this->assertSame($tokens[$closer]['scope_closer'], ($token + 17), 'Closer scope closer is not the semicolon token'); @@ -671,11 +671,16 @@ public function dataNotAnArrowFunction() /** * Helper function to check that all token keys are correctly set for T_FN tokens. * - * @param string $token The T_FN token to check. + * @param string $token The T_FN token to check. + * @param bool $skipScopeCloserCheck Whether to skip the scope closer check. + * This should be set to "true" when testing nested arrow functions, + * where the "inner" arrow function shares a scope closer with the + * "outer" arrow function, as the 'scope_condition' for the scope closer + * of the "inner" arrow function will point to the "outer" arrow function. * * @return void */ - private function backfillHelper($token) + private function backfillHelper($token, $skipScopeCloserCheck=false) { $tokens = self::$phpcsFile->getTokens(); @@ -694,11 +699,13 @@ private function backfillHelper($token) $this->assertTrue(array_key_exists('scope_closer', $tokens[$opener]), 'Opener scope closer is not set'); $this->assertSame($tokens[$opener]['scope_condition'], $token, 'Opener scope condition is not the T_FN token'); - $closer = $tokens[$token]['scope_opener']; + $closer = $tokens[$token]['scope_closer']; $this->assertTrue(array_key_exists('scope_condition', $tokens[$closer]), 'Closer scope condition is not set'); $this->assertTrue(array_key_exists('scope_opener', $tokens[$closer]), 'Closer scope opener is not set'); $this->assertTrue(array_key_exists('scope_closer', $tokens[$closer]), 'Closer scope closer is not set'); - $this->assertSame($tokens[$closer]['scope_condition'], $token, 'Closer scope condition is not the T_FN token'); + if ($skipScopeCloserCheck === false) { + $this->assertSame($tokens[$closer]['scope_condition'], $token, 'Closer scope condition is not the T_FN token'); + } $opener = $tokens[$token]['parenthesis_opener']; $this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]), 'Opening parenthesis owner is not set'); From ee91c1692959dd6df0b7d2e0fcccffba6bd0cf20 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 12 Jul 2020 23:46:39 +0200 Subject: [PATCH 028/733] PHP 8.0 | Tokenizer/PHP: stabilize comment tokenization As described in issue 3002, in PHP 8 a trailing new line is no longer included in a `T_COMMENT` token. This commit "forward-fills" the PHP 5/7 tokenization of `T_COMMENT` tokens to PHP 8. Includes extensive unit tests. I'm hoping to have caught everything affected :fingers_crossed: The initial set of unit tests `StableCommentWhitespaceTest` use Linux line endings `\n`. The secondary set of unit tests `StableCommentWhitespaceWinTest` use Windows line endings `\r\n` to test that the fix is stable for files using different line ending. For the tests with Windows line endings, both the test case file as well as the actual test file have been set up to use Windows line endings for all lines, not just the test data lines, to make it simpler to manage the line endings for the files. The test file has been excluded from the line endings CS check for that reason and a directive has been added to the `.gitattributes` file to safeguard that the line endings of those files will remain Windows line endings. Fixes 3002 --- .gitattributes | 4 + package.xml | 12 + phpcs.xml.dist | 7 +- src/Tokenizers/PHP.php | 41 + .../Tokenizer/StableCommentWhitespaceTest.inc | 119 +++ .../Tokenizer/StableCommentWhitespaceTest.php | 954 ++++++++++++++++++ .../StableCommentWhitespaceWinTest.inc | 43 + .../StableCommentWhitespaceWinTest.php | 265 +++++ 8 files changed, 1444 insertions(+), 1 deletion(-) create mode 100644 tests/Core/Tokenizer/StableCommentWhitespaceTest.inc create mode 100644 tests/Core/Tokenizer/StableCommentWhitespaceTest.php create mode 100644 tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc create mode 100644 tests/Core/Tokenizer/StableCommentWhitespaceWinTest.php diff --git a/.gitattributes b/.gitattributes index 56dcc5dc74..5456688d96 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,7 @@ package.xml export-ignore phpunit.xml.dist export-ignore php5-testingConfig.ini export-ignore php7-testingConfig.ini export-ignore + +# Declare files that should always have CRLF line endings on checkout. +*WinTest.inc text eol=crlf +*WinTest.php text eol=crlf diff --git a/package.xml b/package.xml index b4f36dc64c..2d8c9f19f2 100644 --- a/package.xml +++ b/package.xml @@ -119,6 +119,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + @@ -1985,6 +1989,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + @@ -2040,6 +2048,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 48cbdd8ff7..f7f32e6be4 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -143,7 +143,12 @@ - tests/bootstrap.php + tests/bootstrap\.php + + + + + tests/Core/Tokenizer/StableCommentWhitespaceWinTest\.php diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 1c30b15179..61f9c2f798 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -566,6 +566,47 @@ protected function tokenize($string) continue; } + /* + PHP 8 tokenizes a new line after a slash comment to the next whitespace token. + */ + + if (PHP_VERSION_ID >= 80000 + && $tokenIsArray === true + && ($token[0] === T_COMMENT && strpos($token[1], '//') === 0) + && isset($tokens[($stackPtr + 1)]) === true + && is_array($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)][0] === T_WHITESPACE + ) { + $nextToken = $tokens[($stackPtr + 1)]; + + // If the next token is a single new line, merge it into the comment token + // and set to it up to be skipped. + if ($nextToken[1] === "\n" || $nextToken[1] === "\r\n" || $nextToken[1] === "\n\r") { + $token[1] .= $nextToken[1]; + $tokens[($stackPtr + 1)] = null; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* merged newline after comment into comment token $stackPtr".PHP_EOL; + } + } else { + // This may be a whitespace token consisting of multiple new lines. + if (strpos($nextToken[1], "\r\n") === 0) { + $token[1] .= "\r\n"; + $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2); + } else if (strpos($nextToken[1], "\n\r") === 0) { + $token[1] .= "\n\r"; + $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 2); + } else if (strpos($nextToken[1], "\n") === 0) { + $token[1] .= "\n"; + $tokens[($stackPtr + 1)][1] = substr($nextToken[1], 1); + } + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* stripped first newline after comment and added it to comment token $stackPtr".PHP_EOL; + } + }//end if + }//end if + /* If this is a double quoted string, PHP will tokenize the whole thing which causes problems with the scope map when braces are diff --git a/tests/Core/Tokenizer/StableCommentWhitespaceTest.inc b/tests/Core/Tokenizer/StableCommentWhitespaceTest.inc new file mode 100644 index 0000000000..6d92b9cfd3 --- /dev/null +++ b/tests/Core/Tokenizer/StableCommentWhitespaceTest.inc @@ -0,0 +1,119 @@ + + + * @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 StableCommentWhitespaceTest extends AbstractMethodUnitTest +{ + + + /** + * Test that comment tokenization with new lines at the end of the comment is stable. + * + * @param string $testMarker The comment prefacing the test. + * @param array $expectedTokens The tokenization expected. + * + * @dataProvider dataCommentTokenization + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testCommentTokenization($testMarker, $expectedTokens) + { + $tokens = self::$phpcsFile->getTokens(); + $comment = $this->getTargetToken($testMarker, Tokens::$commentTokens); + + foreach ($expectedTokens as $key => $tokenInfo) { + $this->assertSame(constant($tokenInfo['type']), $tokens[$comment]['code']); + $this->assertSame($tokenInfo['type'], $tokens[$comment]['type']); + $this->assertSame($tokenInfo['content'], $tokens[$comment]['content']); + + ++$comment; + } + + }//end testCommentTokenization() + + + /** + * Data provider. + * + * @see testCommentTokenization() + * + * @return array + */ + public function dataCommentTokenization() + { + return [ + [ + '/* testSingleLineSlashComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineSlashCommentTrailing */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineSlashAnnotation */', + [ + [ + 'type' => 'T_PHPCS_DISABLE', + 'content' => '// phpcs:disable Stnd.Cat +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashCommentWithIndent */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment1 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment2 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashCommentWithAnnotationStart */', + [ + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '// phpcs:ignore Stnd.Cat +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashCommentWithAnnotationMiddle */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment1 +', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '// @phpcs:ignore Stnd.Cat +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashCommentWithAnnotationEnd */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment2 +', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '// phpcs:ignore Stnd.Cat +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineStarComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '/* Single line star comment */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineStarCommentTrailing */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '/* Comment */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineStarAnnotation */', + [ + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '/* phpcs:ignore Stnd.Cat */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineStarComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '/* Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => ' * Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => ' * Comment3 */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineStarCommentWithIndent */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '/* Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => ' * Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => ' * Comment3 */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineStarCommentWithAnnotationStart */', + [ + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '/* @phpcs:ignore Stnd.Cat +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => ' * Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => ' * Comment3 */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineStarCommentWithAnnotationMiddle */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '/* Comment1 +', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => ' * phpcs:ignore Stnd.Cat +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => ' * Comment3 */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineStarCommentWithAnnotationEnd */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '/* Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => ' * Comment2 +', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => ' * phpcs:ignore Stnd.Cat */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + + [ + '/* testSingleLineDocblockComment */', + [ + [ + 'type' => 'T_DOC_COMMENT_OPEN_TAG', + 'content' => '/**', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment ', + ], + [ + 'type' => 'T_DOC_COMMENT_CLOSE_TAG', + 'content' => '*/', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineDocblockCommentTrailing */', + [ + [ + 'type' => 'T_DOC_COMMENT_OPEN_TAG', + 'content' => '/**', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment ', + ], + [ + 'type' => 'T_DOC_COMMENT_CLOSE_TAG', + 'content' => '*/', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineDocblockAnnotation */', + [ + [ + 'type' => 'T_DOC_COMMENT_OPEN_TAG', + 'content' => '/**', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => 'phpcs:ignore Stnd.Cat.Sniff ', + ], + [ + 'type' => 'T_DOC_COMMENT_CLOSE_TAG', + 'content' => '*/', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + + [ + '/* testMultiLineDocblockComment */', + [ + [ + 'type' => 'T_DOC_COMMENT_OPEN_TAG', + 'content' => '/**', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment1', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment2', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_TAG', + 'content' => '@tag', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_CLOSE_TAG', + 'content' => '*/', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineDocblockCommentWithIndent */', + [ + [ + 'type' => 'T_DOC_COMMENT_OPEN_TAG', + 'content' => '/**', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment1', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment2', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_TAG', + 'content' => '@tag', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_CLOSE_TAG', + 'content' => '*/', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineDocblockCommentWithAnnotation */', + [ + [ + 'type' => 'T_DOC_COMMENT_OPEN_TAG', + 'content' => '/**', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => 'phpcs:ignore Stnd.Cat', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_TAG', + 'content' => '@tag', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_CLOSE_TAG', + 'content' => '*/', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineDocblockCommentWithTagAnnotation */', + [ + [ + 'type' => 'T_DOC_COMMENT_OPEN_TAG', + 'content' => '/**', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '@phpcs:ignore Stnd.Cat', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STAR', + 'content' => '*', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_TAG', + 'content' => '@tag', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_STRING', + 'content' => 'Comment', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_DOC_COMMENT_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_DOC_COMMENT_CLOSE_TAG', + 'content' => '*/', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineSlashCommentNoNewLineAtEnd */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Slash ', + ], + [ + 'type' => 'T_CLOSE_TAG', + 'content' => '?> +', + ], + ], + ], + [ + '/* testCommentAtEndOfFile */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '/* Comment', + ], + ], + ], + ]; + + }//end dataCommentTokenization() + + +}//end class diff --git a/tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc b/tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc new file mode 100644 index 0000000000..8ca0003924 --- /dev/null +++ b/tests/Core/Tokenizer/StableCommentWhitespaceWinTest.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\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Tokens; + +class StableCommentWhitespaceWinTest extends AbstractMethodUnitTest +{ + + + /** + * Test that comment tokenization with new lines at the end of the comment is stable. + * + * @param string $testMarker The comment prefacing the test. + * @param array $expectedTokens The tokenization expected. + * + * @dataProvider dataCommentTokenization + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testCommentTokenization($testMarker, $expectedTokens) + { + $tokens = self::$phpcsFile->getTokens(); + $comment = $this->getTargetToken($testMarker, Tokens::$commentTokens); + + foreach ($expectedTokens as $key => $tokenInfo) { + $this->assertSame(constant($tokenInfo['type']), $tokens[$comment]['code']); + $this->assertSame($tokenInfo['type'], $tokens[$comment]['type']); + $this->assertSame($tokenInfo['content'], $tokens[$comment]['content']); + + ++$comment; + } + + }//end testCommentTokenization() + + + /** + * Data provider. + * + * @see testCommentTokenization() + * + * @return array + */ + public function dataCommentTokenization() + { + return [ + [ + '/* testSingleLineSlashComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineSlashCommentTrailing */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineSlashAnnotation */', + [ + [ + 'type' => 'T_PHPCS_DISABLE', + 'content' => '// phpcs:disable Stnd.Cat +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashCommentWithIndent */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment1 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment2 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashCommentWithAnnotationStart */', + [ + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '// phpcs:ignore Stnd.Cat +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashCommentWithAnnotationMiddle */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment1 +', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '// @phpcs:ignore Stnd.Cat +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineSlashCommentWithAnnotationEnd */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '// Comment2 +', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '// phpcs:ignore Stnd.Cat +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineSlashCommentNoNewLineAtEnd */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '// Slash ', + ], + [ + 'type' => 'T_CLOSE_TAG', + 'content' => '?> +', + ], + ], + ], + [ + '/* testCommentAtEndOfFile */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '/* Comment', + ], + ], + ], + ]; + + }//end dataCommentTokenization() + + +}//end class From e3eecbb8b0ac88472c5c21723b995ac3269f2fbd Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 21 Jul 2020 09:06:10 +1000 Subject: [PATCH 029/733] Added bug report template --- .github/ISSUE_TEMPLATE/bug_report.md | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..00f6f43f12 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,45 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Code sample** +```php +echo "A short code snippet that can be used to reproduce the bug"; +``` + +**Custom ruleset** +```xml + + + If you are using a custom ruleset, please enter it here. + +``` + +**To reproduce** +Steps to reproduce the behavior: +1. Create a file called `test.php` with the code sample above... +2. Run `phpcs test.php ...` +3. See error message displayed +``` +PHPCS output here +``` + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Versions (please complete the following information):** + - OS: [e.g., Windows 10, MacOS 10.15] + - PHP: [e.g., 7.2, 7.4] + - PHPCS: [e.g., 3.5.5, master] + - Standard: [e.g., PSR2, PSR12, Squiz] + +**Additional context** +Add any other context about the problem here. From fc5fd628b25dbf87ab3424a4bdf1cfad0cfc0bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=A0?= Date: Thu, 23 Jul 2020 00:01:12 +0200 Subject: [PATCH 030/733] Add serialize and unserialize to list of known magic methods --- .../CamelCapsFunctionNameSniff.php | 32 ++++++++++--------- .../ValidFunctionNameSniff.php | 32 ++++++++++--------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php index 64e12689a9..195504ae31 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php @@ -23,21 +23,23 @@ class CamelCapsFunctionNameSniff extends AbstractScopeSniff * @var array */ protected $magicMethods = [ - 'construct' => true, - 'destruct' => true, - 'call' => true, - 'callstatic' => true, - 'get' => true, - 'set' => true, - 'isset' => true, - 'unset' => true, - 'sleep' => true, - 'wakeup' => true, - 'tostring' => true, - 'set_state' => true, - 'clone' => true, - 'invoke' => true, - 'debuginfo' => true, + 'construct' => true, + 'destruct' => true, + 'call' => true, + 'callstatic ' => true, + 'get' => true, + 'set' => true, + 'isset' => true, + 'unset' => true, + 'sleep' => true, + 'wakeup' => true, + 'serialize' => true, + 'unserialize' => true, + 'tostring' => true, + 'invoke' => true, + 'set_state' => true, + 'clone' => true, + 'debuginfo' => true, ]; /** diff --git a/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php index 49486a54a3..c2ae1be50a 100644 --- a/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php +++ b/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -23,21 +23,23 @@ class ValidFunctionNameSniff extends AbstractScopeSniff * @var array */ protected $magicMethods = [ - 'construct' => true, - 'destruct' => true, - 'call' => true, - 'callstatic' => true, - 'get' => true, - 'set' => true, - 'isset' => true, - 'unset' => true, - 'sleep' => true, - 'wakeup' => true, - 'tostring' => true, - 'set_state' => true, - 'clone' => true, - 'invoke' => true, - 'debuginfo' => true, + 'construct' => true, + 'destruct' => true, + 'call' => true, + 'callstatic ' => true, + 'get' => true, + 'set' => true, + 'isset' => true, + 'unset' => true, + 'sleep' => true, + 'wakeup' => true, + 'serialize' => true, + 'unserialize' => true, + 'tostring' => true, + 'invoke' => true, + 'set_state' => true, + 'clone' => true, + 'debuginfo' => true, ]; /** From ea2cf3ff00b49c2b74d0a85e5f2d7d6b69b5cd48 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 23 Jul 2020 09:02:38 +1000 Subject: [PATCH 031/733] Fixed bug #2994 : Generic.Formatting.DisallowMultipleStatements false positive for FOR loop with no body --- package.xml | 1 + .../DisallowMultipleStatementsSniff.php | 22 ++++++++++--------- .../DisallowMultipleStatementsUnitTest.inc | 4 ++++ ...sallowMultipleStatementsUnitTest.inc.fixed | 4 ++++ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/package.xml b/package.xml index b4f36dc64c..fcdb419187 100644 --- a/package.xml +++ b/package.xml @@ -43,6 +43,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2943 : Redundant semicolon added to a file when fixing PSR2.Files.ClosingTag.NotAllowed - Fixed bug #2977 : File::isReference() does not detect return by reference for closures -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #2994 : Generic.Formatting.DisallowMultipleStatements false positive for FOR loop with no body diff --git a/src/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php b/src/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php index d67bb761e4..6f9ff9f917 100644 --- a/src/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php +++ b/src/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php @@ -59,16 +59,18 @@ public function process(File $phpcsFile, $stackPtr) } while ($tokens[$prev]['code'] === T_PHPCS_IGNORE); // Ignore multiple statements in a FOR condition. - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - foreach ($tokens[$stackPtr]['nested_parenthesis'] as $bracket) { - if (isset($tokens[$bracket]['parenthesis_owner']) === false) { - // Probably a closure sitting inside a function call. - continue; - } - - $owner = $tokens[$bracket]['parenthesis_owner']; - if ($tokens[$owner]['code'] === T_FOR) { - return; + foreach ([$stackPtr, $prev] as $checkToken) { + if (isset($tokens[$checkToken]['nested_parenthesis']) === true) { + foreach ($tokens[$checkToken]['nested_parenthesis'] as $bracket) { + if (isset($tokens[$bracket]['parenthesis_owner']) === false) { + // Probably a closure sitting inside a function call. + continue; + } + + $owner = $tokens[$bracket]['parenthesis_owner']; + if ($tokens[$owner]['code'] === T_FOR) { + return; + } } } } diff --git a/src/Standards/Generic/Tests/Formatting/DisallowMultipleStatementsUnitTest.inc b/src/Standards/Generic/Tests/Formatting/DisallowMultipleStatementsUnitTest.inc index 5dd11cfdb7..ed316cc219 100644 --- a/src/Standards/Generic/Tests/Formatting/DisallowMultipleStatementsUnitTest.inc +++ b/src/Standards/Generic/Tests/Formatting/DisallowMultipleStatementsUnitTest.inc @@ -14,3 +14,7 @@ $this->wizardid = 10; $this->paint(); echo 'x'; Date: Thu, 23 Jul 2020 13:10:13 +1000 Subject: [PATCH 032/733] Changelog for #3027 --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index a80b1b59e2..fc3dde3350 100644 --- a/package.xml +++ b/package.xml @@ -28,6 +28,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Added support for PHP 8.0 magic constant dereferencing -- Thanks to Juliette Reinders Folmer for the patch + - Added support for changes to the way PHP 8.0 tokenizes comments + -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required + -- Thanks to Juliette Reinders Folmer for the patch - File::getMethodProperties() now detects the PHP 8.0 static return type -- Thanks to Juliette Reinders Folmer for the patch - The PHP 8.0 static return type is now supported for arrow functions From c9bbba6d47c7d506a806ee2120d97241208334dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Bugy=C3=ADk?= Date: Fri, 22 May 2020 15:53:54 +0200 Subject: [PATCH 033/733] Generator/Markdown: Workaround for better rendering --- src/Generators/Markdown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Generators/Markdown.php b/src/Generators/Markdown.php index b51b267bcb..9756bcf140 100644 --- a/src/Generators/Markdown.php +++ b/src/Generators/Markdown.php @@ -84,7 +84,7 @@ protected function printFooter() protected function processSniff(\DOMNode $doc) { $title = $this->getTitle($doc); - echo "## $title".PHP_EOL; + echo PHP_EOL."## $title".PHP_EOL; foreach ($doc->childNodes as $node) { if ($node->nodeName === 'standard') { From 1e8a69c8c105d84ec75f3c05d8a341178589ea26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=A0?= Date: Thu, 23 Jul 2020 11:10:36 +0200 Subject: [PATCH 034/733] Fix misaligned space --- .../Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php | 2 +- .../PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php index 195504ae31..b45112034a 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php @@ -26,7 +26,7 @@ class CamelCapsFunctionNameSniff extends AbstractScopeSniff 'construct' => true, 'destruct' => true, 'call' => true, - 'callstatic ' => true, + 'callstatic' => true, 'get' => true, 'set' => true, 'isset' => true, diff --git a/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php index c2ae1be50a..e7f87d44dd 100644 --- a/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php +++ b/src/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -26,7 +26,7 @@ class ValidFunctionNameSniff extends AbstractScopeSniff 'construct' => true, 'destruct' => true, 'call' => true, - 'callstatic ' => true, + 'callstatic' => true, 'get' => true, 'set' => true, 'isset' => true, From ec236a59927b0ce41880d247677ab9855e71c486 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 23 Jul 2020 11:29:20 +0200 Subject: [PATCH 035/733] PHP 8.0 | Tokenizer/PHP: bug fix PHP 8.0 elevate a number of notices/warnings to errors, which now exposes a bug in the `goto` tokenizer logic. Error: `Trying to access array offset on value of type null` Fixed by making sure that the token being accessed is an array before trying to access it. --- src/Tokenizers/PHP.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 61f9c2f798..5d5b3c95b6 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1441,7 +1441,8 @@ function return types. We want to keep the parenthesis map clean, && $token[0] === T_STRING && isset($tokens[($stackPtr + 1)]) === true && $tokens[($stackPtr + 1)] === ':' - && $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM + && (is_array($tokens[($stackPtr - 1)]) === false + || $tokens[($stackPtr - 1)][0] !== T_PAAMAYIM_NEKUDOTAYIM) ) { $stopTokens = [ T_CASE => true, From c296ba809c984dd651c55c66cace247cee5eeee0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 03:09:50 +0200 Subject: [PATCH 036/733] Tokenizer/PHP: add some missing tokens to the $knownLengths property Not sure if it really makes a difference, but I noticed some tokens were missing from this array which could safely be added. --- src/Tokenizers/PHP.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 61f9c2f798..061be8a03c 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -320,6 +320,7 @@ class PHP extends Tokenizer T_DOUBLE_ARROW => 2, T_DOUBLE_COLON => 2, T_ECHO => 4, + T_ELLIPSIS => 3, T_ELSE => 4, T_ELSEIF => 6, T_EMPTY => 5, @@ -334,6 +335,7 @@ class PHP extends Tokenizer T_FILE => 8, T_FINAL => 5, T_FINALLY => 7, + T_FN => 2, T_FOR => 3, T_FOREACH => 7, T_FUNCTION => 8, From 5aa9f1a74be6c608a8a414fcc7f2f26fe7a891fd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 00:03:34 +0200 Subject: [PATCH 037/733] PHP 8.0 | Tokenizer/PHP: add support for nullsafe object operator PHP 8 introduces a new object chaining operator `?->` which short-circuits moving to the next expression if the left-hand side evaluates to `null`. This operator can not be used in write-context, but that is not the concern of the PHPCS `Tokenizers\PHP` class. This commit: * Defines the token constant for PHP < 8.0. * Adds a backfill for the nullsafe object operator for PHP < 8.0 to the PHP tokenizer. * Adds the token to applicable token lists in the PHP and base tokenizer class, like the one used in the short array re-tokenization. * Adds perfunctory unit tests for the nullsafe object operator backfill. * Adds a unit test using the operator to the tokenizer tests for the short array re-tokenization. Refs: * https://wiki.php.net/rfc/nullsafe_operator * https://github.com/php/php-src/commit/9bf119832dbf625174794834c71b1e793450d87f --- package.xml | 6 + src/Tokenizers/PHP.php | 55 +++++-- src/Tokenizers/Tokenizer.php | 11 +- src/Util/Tokens.php | 5 + .../Tokenizer/NullsafeObjectOperatorTest.inc | 29 ++++ .../Tokenizer/NullsafeObjectOperatorTest.php | 140 ++++++++++++++++++ tests/Core/Tokenizer/ShortArrayTest.inc | 2 + tests/Core/Tokenizer/ShortArrayTest.php | 1 + 8 files changed, 230 insertions(+), 19 deletions(-) create mode 100644 tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc create mode 100644 tests/Core/Tokenizer/NullsafeObjectOperatorTest.php diff --git a/package.xml b/package.xml index fc3dde3350..0192b57f5f 100644 --- a/package.xml +++ b/package.xml @@ -121,6 +121,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -1991,6 +1993,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2050,6 +2054,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 61f9c2f798..de1fe98236 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -370,6 +370,7 @@ class PHP extends Tokenizer T_NS_C => 13, T_NS_SEPARATOR => 1, T_NEW => 3, + T_NULLSAFE_OBJECT_OPERATOR => 3, T_OBJECT_OPERATOR => 2, T_OPEN_TAG_WITH_ECHO => 3, T_OR_EQUAL => 2, @@ -1015,6 +1016,29 @@ protected function tokenize($string) continue; } + /* + Before PHP 8, the ?-> operator was tokenized as + T_INLINE_THEN followed by T_OBJECT_OPERATOR. + So look for and combine these tokens in earlier versions. + */ + + if ($tokenIsArray === false + && $token[0] === '?' + && isset($tokens[($stackPtr + 1)]) === true + && is_array($tokens[($stackPtr + 1)]) === true + && $tokens[($stackPtr + 1)][0] === T_OBJECT_OPERATOR + ) { + $newToken = []; + $newToken['code'] = T_NULLSAFE_OBJECT_OPERATOR; + $newToken['type'] = 'T_NULLSAFE_OBJECT_OPERATOR'; + $newToken['content'] = '?->'; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + $stackPtr++; + continue; + } + /* Before PHP 7.4, underscores inside T_LNUMBER and T_DNUMBER tokens split the token with a T_STRING. So look for @@ -1510,17 +1534,18 @@ function return types. We want to keep the parenthesis map clean, // Some T_STRING tokens should remain that way // due to their context. $context = [ - T_OBJECT_OPERATOR => true, - T_FUNCTION => true, - T_CLASS => true, - T_EXTENDS => true, - T_IMPLEMENTS => true, - T_NEW => true, - T_CONST => true, - T_NS_SEPARATOR => true, - T_USE => true, - T_NAMESPACE => true, - T_PAAMAYIM_NEKUDOTAYIM => true, + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_FUNCTION => true, + T_CLASS => true, + T_EXTENDS => true, + T_IMPLEMENTS => true, + T_NEW => true, + T_CONST => true, + T_NS_SEPARATOR => true, + T_USE => true, + T_NAMESPACE => true, + T_PAAMAYIM_NEKUDOTAYIM => true, ]; if (isset($context[$finalTokens[$lastNotEmptyToken]['code']]) === true) { @@ -2012,6 +2037,7 @@ protected function processAdditional() T_CLOSE_PARENTHESIS => T_CLOSE_PARENTHESIS, T_VARIABLE => T_VARIABLE, T_OBJECT_OPERATOR => T_OBJECT_OPERATOR, + T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR, T_STRING => T_STRING, T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, ]; @@ -2081,9 +2107,10 @@ protected function processAdditional() } $context = [ - T_OBJECT_OPERATOR => true, - T_NS_SEPARATOR => true, - T_PAAMAYIM_NEKUDOTAYIM => true, + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_NS_SEPARATOR => true, + T_PAAMAYIM_NEKUDOTAYIM => true, ]; if (isset($context[$this->tokens[$x]['code']]) === true) { if (PHP_CODESNIFFER_VERBOSITY > 1) { diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 82b2b9cc78..24f11d2505 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -1291,11 +1291,12 @@ private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0) // a new statement, it isn't a scope opener. $disallowed = Util\Tokens::$assignmentTokens; $disallowed += [ - T_DOLLAR => true, - T_VARIABLE => true, - T_OBJECT_OPERATOR => true, - T_COMMA => true, - T_OPEN_PARENTHESIS => true, + T_DOLLAR => true, + T_VARIABLE => true, + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_COMMA => true, + T_OPEN_PARENTHESIS => true, ]; if (isset($disallowed[$this->tokens[$x]['code']]) === true) { diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index bc3a32414a..3994264067 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -124,6 +124,11 @@ define('T_FN', 'PHPCS_T_FN'); } +// Some PHP 8.0 tokens, replicated for lower versions. +if (defined('T_NULLSAFE_OBJECT_OPERATOR') === false) { + define('T_NULLSAFE_OBJECT_OPERATOR', 'PHPCS_T_NULLSAFE_OBJECT_OPERATOR'); +} + // 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'); diff --git a/tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc b/tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc new file mode 100644 index 0000000000..982841eac4 --- /dev/null +++ b/tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc @@ -0,0 +1,29 @@ +foo; + +/* testNullsafeObjectOperator */ +echo $obj?->foo; + +/* testNullsafeObjectOperatorWriteContext */ +// Intentional parse error, but not the concern of the tokenizer. +$foo?->bar->baz = 'baz'; + +/* testTernaryThen */ +echo $obj ? $obj->prop : $other->prop; + +/* testParseErrorWhitespaceNotAllowed */ +echo $obj ? + -> foo; + +/* testParseErrorCommentNotAllowed */ +echo $obj ?/*comment*/-> foo; + +/* testLiveCoding */ +// Intentional parse error. This has to be the last test in the file. +echo $obj? diff --git a/tests/Core/Tokenizer/NullsafeObjectOperatorTest.php b/tests/Core/Tokenizer/NullsafeObjectOperatorTest.php new file mode 100644 index 0000000000..8e465a3be1 --- /dev/null +++ b/tests/Core/Tokenizer/NullsafeObjectOperatorTest.php @@ -0,0 +1,140 @@ += 8.0 nullsafe object operator. + * + * @author Juliette Reinders Folmer + * @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 NullsafeObjectOperatorTest extends AbstractMethodUnitTest +{ + + /** + * Tokens to search for. + * + * @var array + */ + protected $find = [ + T_NULLSAFE_OBJECT_OPERATOR, + T_OBJECT_OPERATOR, + T_INLINE_THEN, + ]; + + + /** + * Test that a normal object operator is still tokenized as such. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testObjectOperator() + { + $tokens = self::$phpcsFile->getTokens(); + + $operator = $this->getTargetToken('/* testObjectOperator */', $this->find); + $this->assertSame(T_OBJECT_OPERATOR, $tokens[$operator]['code'], 'Failed asserting code is object operator'); + $this->assertSame('T_OBJECT_OPERATOR', $tokens[$operator]['type'], 'Failed asserting type is object operator'); + + }//end testObjectOperator() + + + /** + * Test that a nullsafe object operator is tokenized as such. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataNullsafeObjectOperator + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testNullsafeObjectOperator($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $operator = $this->getTargetToken($testMarker, $this->find); + $this->assertSame(T_NULLSAFE_OBJECT_OPERATOR, $tokens[$operator]['code'], 'Failed asserting code is nullsafe object operator'); + $this->assertSame('T_NULLSAFE_OBJECT_OPERATOR', $tokens[$operator]['type'], 'Failed asserting type is nullsafe object operator'); + + }//end testNullsafeObjectOperator() + + + /** + * Data provider. + * + * @see testNullsafeObjectOperator() + * + * @return array + */ + public function dataNullsafeObjectOperator() + { + return [ + ['/* testNullsafeObjectOperator */'], + ['/* testNullsafeObjectOperatorWriteContext */'], + ]; + + }//end dataNullsafeObjectOperator() + + + /** + * Test that a question mark not followed by an object operator is tokenized as T_TERNARY_THEN. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $testObjectOperator Whether to test for the next non-empty token being tokenized + * as an object operator. + * + * @dataProvider dataTernaryThen + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testTernaryThen($testMarker, $testObjectOperator=false) + { + $tokens = self::$phpcsFile->getTokens(); + + $operator = $this->getTargetToken($testMarker, $this->find); + $this->assertSame(T_INLINE_THEN, $tokens[$operator]['code'], 'Failed asserting code is inline then'); + $this->assertSame('T_INLINE_THEN', $tokens[$operator]['type'], 'Failed asserting type is inline then'); + + if ($testObjectOperator === true) { + $next = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($operator + 1), null, true); + $this->assertSame(T_OBJECT_OPERATOR, $tokens[$next]['code'], 'Failed asserting code is object operator'); + $this->assertSame('T_OBJECT_OPERATOR', $tokens[$next]['type'], 'Failed asserting type is object operator'); + } + + }//end testTernaryThen() + + + /** + * Data provider. + * + * @see testTernaryThen() + * + * @return array + */ + public function dataTernaryThen() + { + return [ + ['/* testTernaryThen */'], + [ + '/* testParseErrorWhitespaceNotAllowed */', + true, + ], + [ + '/* testParseErrorCommentNotAllowed */', + true, + ], + ['/* testLiveCoding */'], + ]; + + }//end dataTernaryThen() + + +}//end class diff --git a/tests/Core/Tokenizer/ShortArrayTest.inc b/tests/Core/Tokenizer/ShortArrayTest.inc index a864869af9..b052ee5681 100644 --- a/tests/Core/Tokenizer/ShortArrayTest.inc +++ b/tests/Core/Tokenizer/ShortArrayTest.inc @@ -62,6 +62,8 @@ $a = (new Foo( array(1, array(4, 5), 3) ))[1][0]; /* testClassMemberDereferencingOnClone */ echo (clone $iterable)[20]; +/* testNullsafeMethodCallDereferencing */ +$var = $obj?->function_call()[$x]; /* * Short array brackets. diff --git a/tests/Core/Tokenizer/ShortArrayTest.php b/tests/Core/Tokenizer/ShortArrayTest.php index 04aaf3db26..45f6c4cf88 100644 --- a/tests/Core/Tokenizer/ShortArrayTest.php +++ b/tests/Core/Tokenizer/ShortArrayTest.php @@ -72,6 +72,7 @@ public function dataSquareBrackets() ['/* testClassMemberDereferencingOnInstantiation1 */'], ['/* testClassMemberDereferencingOnInstantiation2 */'], ['/* testClassMemberDereferencingOnClone */'], + ['/* testNullsafeMethodCallDereferencing */'], ['/* testLiveCoding */'], ]; From 6cc330134e1c9876fa8c916bc5fa996ceef0d6e8 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 10 Aug 2020 10:24:01 +1000 Subject: [PATCH 038/733] Changelog for #2967 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index fc3dde3350..fe003de384 100644 --- a/package.xml +++ b/package.xml @@ -44,6 +44,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2888 : PSR12.Files.FileHeader blank line error with multiple namespaces in one file - Fixed bug #2926 : phpcs hangs when using arrow functions that return heredoc - Fixed bug #2943 : Redundant semicolon added to a file when fixing PSR2.Files.ClosingTag.NotAllowed + - Fixed bug #2967 : Markdown generator does not output headings correctly + -- Thanks to Petr Bugyík for the patch - Fixed bug #2977 : File::isReference() does not detect return by reference for closures -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #2994 : Generic.Formatting.DisallowMultipleStatements false positive for FOR loop with no body From 953a7b02ed899aaa9c180bc867db8962db7a8b11 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 10 Aug 2020 10:30:06 +1000 Subject: [PATCH 039/733] Changelog for #3033 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index fe003de384..a4baf22747 100644 --- a/package.xml +++ b/package.xml @@ -49,6 +49,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2977 : File::isReference() does not detect return by reference for closures -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #2994 : Generic.Formatting.DisallowMultipleStatements false positive for FOR loop with no body + - Fixed bug #3033 : Error generated during tokenizing of goto statements on PHP 8 + -- Thanks to Juliette Reinders Folmer for the patch From cbbe4a4c8d4f6922e6bce778ffa5f5bc79686637 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 10 Aug 2020 10:35:58 +1000 Subject: [PATCH 040/733] Changelog for #3031 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index a4baf22747..439d036bb3 100644 --- a/package.xml +++ b/package.xml @@ -37,6 +37,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - The cache is no longer used if the list of loaded PHP extensions changes -- Thanks to Juliette Reinders Folmer for the patch + - Generic.NamingConventions.CamelCapsFunctionName no longer reports __serialize and __unserialize as invalid names + -- Thanks to Filip Š for the patch + - PEAR.NamingConventions.ValidFunctionName no longer reports __serialize and __unserialize as invalid names + -- Thanks to Filip Š for the patch - Squiz.Scope.StaticThisUsage now detects usage of $this inside closures and arrow Functions -- Thanks to Michał Bundyra for the patch - Fixed bug #2877 : PEAR.Functions.FunctionCallSignature false positive for array of functions From e97627871a7eab2f70e59166072a6b767d5834e0 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 10 Aug 2020 14:50:15 +1000 Subject: [PATCH 041/733] Prepare for 3.5.6 release --- package.xml | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 439d036bb3..2ff80cbbc0 100644 --- a/package.xml +++ b/package.xml @@ -14,8 +14,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> gsherwood@squiz.net yes - 2020-04-17 - + 2020-08-10 + 3.5.6 3.5.6 @@ -2071,6 +2071,49 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 3.5.6 + 3.5.6 + + + stable + stable + + 2020-08-10 + BSD License + + - Added support for PHP 8.0 magic constant dereferencing + -- Thanks to Juliette Reinders Folmer for the patch + - Added support for changes to the way PHP 8.0 tokenizes comments + -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required + -- Thanks to Juliette Reinders Folmer for the patch + - File::getMethodProperties() now detects the PHP 8.0 static return type + -- Thanks to Juliette Reinders Folmer for the patch + - The PHP 8.0 static return type is now supported for arrow functions + -- Thanks to Juliette Reinders Folmer for the patch + - The cache is no longer used if the list of loaded PHP extensions changes + -- Thanks to Juliette Reinders Folmer for the patch + - Generic.NamingConventions.CamelCapsFunctionName no longer reports __serialize and __unserialize as invalid names + -- Thanks to Filip Š for the patch + - PEAR.NamingConventions.ValidFunctionName no longer reports __serialize and __unserialize as invalid names + -- Thanks to Filip Š for the patch + - Squiz.Scope.StaticThisUsage now detects usage of $this inside closures and arrow Functions + -- Thanks to Michał Bundyra for the patch + - Fixed bug #2877 : PEAR.Functions.FunctionCallSignature false positive for array of functions + -- Thanks to Vincent Langlet for the patch + - Fixed bug #2888 : PSR12.Files.FileHeader blank line error with multiple namespaces in one file + - Fixed bug #2926 : phpcs hangs when using arrow functions that return heredoc + - Fixed bug #2943 : Redundant semicolon added to a file when fixing PSR2.Files.ClosingTag.NotAllowed + - Fixed bug #2967 : Markdown generator does not output headings correctly + -- Thanks to Petr Bugyík for the patch + - Fixed bug #2977 : File::isReference() does not detect return by reference for closures + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #2994 : Generic.Formatting.DisallowMultipleStatements false positive for FOR loop with no body + - Fixed bug #3033 : Error generated during tokenizing of goto statements on PHP 8 + -- Thanks to Juliette Reinders Folmer for the patch + + 3.5.5 From 3b14b9d317e31ea1896edf5552f2c8f356e3c983 Mon Sep 17 00:00:00 2001 From: xjm Date: Wed, 12 Aug 2020 16:50:54 -0500 Subject: [PATCH 042/733] Add a property for skipping processing on {@inheritdoc}. --- .../Commenting/FunctionCommentSniff.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index 38fc4d0f94..00dad5517c 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -17,6 +17,13 @@ class FunctionCommentSniff extends PEARFunctionCommentSniff { + /** + * Whether to skip inheritdoc comments. + * + * @var bool + */ + public $skipIfInheritdoc = false; + /** * The current PHP version. * @@ -40,6 +47,14 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) $tokens = $phpcsFile->getTokens(); $return = null; + if ($this->skipIfInheritdoc) { + for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { + $trimmedContent = strtolower(trim($tokens[$i]['content'])); + if ($trimmedContent === '{@inheritdoc}') { + return; + } + } + } foreach ($tokens[$commentStart]['comment_tags'] as $tag) { if ($tokens[$tag]['content'] === '@return') { if ($return !== null) { @@ -189,6 +204,15 @@ protected function processThrows(File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); + if ($this->skipIfInheritdoc) { + for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { + $trimmedContent = strtolower(trim($tokens[$i]['content'])); + if ($trimmedContent === '{@inheritdoc}') { + return; + } + } + } + foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { if ($tokens[$tag]['content'] !== '@throws') { continue; @@ -264,6 +288,15 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $tokens = $phpcsFile->getTokens(); + if ($this->skipIfInheritdoc) { + for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { + $trimmedContent = strtolower(trim($tokens[$i]['content'])); + if ($trimmedContent === '{@inheritdoc}') { + return; + } + } + } + $params = []; $maxType = 0; $maxVar = 0; From 201f6631f80643f89634e9b049497d78bcf5fe5a Mon Sep 17 00:00:00 2001 From: xjm Date: Wed, 12 Aug 2020 16:53:23 -0500 Subject: [PATCH 043/733] Ironically, fix coding standards. --- .../Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index 00dad5517c..8901f6a0fc 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -20,7 +20,7 @@ class FunctionCommentSniff extends PEARFunctionCommentSniff /** * Whether to skip inheritdoc comments. * - * @var bool + * @var boolean */ public $skipIfInheritdoc = false; @@ -47,7 +47,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) $tokens = $phpcsFile->getTokens(); $return = null; - if ($this->skipIfInheritdoc) { + if ($this->skipIfInheritdoc === true) { for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { $trimmedContent = strtolower(trim($tokens[$i]['content'])); if ($trimmedContent === '{@inheritdoc}') { @@ -55,6 +55,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) } } } + foreach ($tokens[$commentStart]['comment_tags'] as $tag) { if ($tokens[$tag]['content'] === '@return') { if ($return !== null) { @@ -204,7 +205,7 @@ protected function processThrows(File $phpcsFile, $stackPtr, $commentStart) { $tokens = $phpcsFile->getTokens(); - if ($this->skipIfInheritdoc) { + if ($this->skipIfInheritdoc === true) { for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { $trimmedContent = strtolower(trim($tokens[$i]['content'])); if ($trimmedContent === '{@inheritdoc}') { @@ -288,7 +289,7 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $tokens = $phpcsFile->getTokens(); - if ($this->skipIfInheritdoc) { + if ($this->skipIfInheritdoc === true) { for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { $trimmedContent = strtolower(trim($tokens[$i]['content'])); if ($trimmedContent === '{@inheritdoc}') { From 826f7e83a45b00e1c8ee9d60034c2b5c8573900b Mon Sep 17 00:00:00 2001 From: xjm Date: Wed, 12 Aug 2020 19:03:15 -0500 Subject: [PATCH 044/733] Factor out a helper for a stricter check that the comment is ONLY {@inheritdoc} (and nothing else). --- .../Commenting/FunctionCommentSniff.php | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index 8901f6a0fc..62123c8340 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -48,11 +48,8 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) $return = null; if ($this->skipIfInheritdoc === true) { - for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { - $trimmedContent = strtolower(trim($tokens[$i]['content'])); - if ($trimmedContent === '{@inheritdoc}') { - return; - } + if ($this->checkInheritdoc($phpcsFile, $stackPtr, $commentStart) === true) { + return; } } @@ -206,11 +203,8 @@ protected function processThrows(File $phpcsFile, $stackPtr, $commentStart) $tokens = $phpcsFile->getTokens(); if ($this->skipIfInheritdoc === true) { - for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { - $trimmedContent = strtolower(trim($tokens[$i]['content'])); - if ($trimmedContent === '{@inheritdoc}') { - return; - } + if ($this->checkInheritdoc($phpcsFile, $stackPtr, $commentStart) === true) { + return; } } @@ -290,11 +284,8 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $tokens = $phpcsFile->getTokens(); if ($this->skipIfInheritdoc === true) { - for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { - $trimmedContent = strtolower(trim($tokens[$i]['content'])); - if ($trimmedContent === '{@inheritdoc}') { - return; - } + if ($this->checkInheritdoc($phpcsFile, $stackPtr, $commentStart) === true) { + return; } } @@ -729,4 +720,38 @@ protected function checkSpacingAfterParamName(File $phpcsFile, $param, $maxVar, }//end checkSpacingAfterParamName() + /** + * Determines whether the whole comment is an inheritdoc comment. + * + * @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. + * @param int $commentStart The position in the stack where the comment started. + * + * @return void + */ + protected function checkInheritdoc(File $phpcsFile, $stackPtr, $commentStart) + { + $tokens = $phpcsFile->getTokens(); + + $allowedTokens = [ + T_DOC_COMMENT_OPEN_TAG, + T_DOC_COMMENT_WHITESPACE, + T_DOC_COMMENT_STAR, + ]; + for ($i = $commentStart; $i <= $tokens[$commentStart]['comment_closer']; $i++) { + if (in_array($tokens[$i]['code'], $allowedTokens) === false) { + $trimmedContent = strtolower(trim($tokens[$i]['content'])); + + if ($trimmedContent === '{@inheritdoc}') { + return true; + } else { + return false; + } + } + } + + }//end checkInheritdoc() + + }//end class From b5981126daa6890256a1c29912765e7574213bea Mon Sep 17 00:00:00 2001 From: xjm Date: Thu, 13 Aug 2020 12:02:18 -0500 Subject: [PATCH 045/733] The helper returns a Boolean, not void. --- src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index 62123c8340..a5403337a0 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -728,7 +728,7 @@ protected function checkSpacingAfterParamName(File $phpcsFile, $param, $maxVar, * in the stack passed in $tokens. * @param int $commentStart The position in the stack where the comment started. * - * @return void + * @return boolean TRUE if the docblock contains only {@inheritdoc} (case-insensitive). */ protected function checkInheritdoc(File $phpcsFile, $stackPtr, $commentStart) { From e866f6d600643d963b00a9c0eaaef5ad8513f95a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 19 Aug 2020 13:02:26 +1000 Subject: [PATCH 046/733] Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary --- .../Tests/Functions/ReturnTypeDeclarationUnitTest.inc | 2 ++ .../Functions/ReturnTypeDeclarationUnitTest.inc.fixed | 2 ++ src/Tokenizers/PHP.php | 11 +++++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Standards/PSR12/Tests/Functions/ReturnTypeDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Functions/ReturnTypeDeclarationUnitTest.inc index 2a68862c32..59ab1aa785 100644 --- a/src/Standards/PSR12/Tests/Functions/ReturnTypeDeclarationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Functions/ReturnTypeDeclarationUnitTest.inc @@ -62,3 +62,5 @@ function functionName(?string $arg1, ?int &$arg2): ?string {} fn (?\DateTime $arg) : ?\DateTime => $arg; + +return (!$a ? [ new class { public function b(): c {} } ] : []); diff --git a/src/Standards/PSR12/Tests/Functions/ReturnTypeDeclarationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Functions/ReturnTypeDeclarationUnitTest.inc.fixed index 827e03c7b4..cd79f78163 100644 --- a/src/Standards/PSR12/Tests/Functions/ReturnTypeDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Functions/ReturnTypeDeclarationUnitTest.inc.fixed @@ -58,3 +58,5 @@ function functionName(?string $arg1, ?int &$arg2): ?string {} function functionName(?string $arg1, ?int &$arg2): ?string {} fn (?\DateTime $arg): ?\DateTime => $arg; + +return (!$a ? [ new class { public function b(): c {} } ] : []); diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5d5b3c95b6..6135e957cf 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1574,7 +1574,7 @@ function return types. We want to keep the parenthesis map clean, // Convert colons that are actually the ELSE component of an // inline IF statement. if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) { - // Make sure this isn't the return type separator of a closure. + // Make sure this isn't a return type separator. $isInlineIf = true; for ($i = ($stackPtr - 1); $i > 0; $i--) { if (is_array($tokens[$i]) === false @@ -1600,12 +1600,15 @@ function return types. We want to keep the parenthesis map clean, } // We've found the open parenthesis, so if the previous - // non-empty token is FUNCTION or USE, this is a closure. + // non-empty token is FUNCTION or USE, this is a return type. + // Note that we need to skip T_STRING tokens here as these + // can be function names. for ($i--; $i > 0; $i--) { if (is_array($tokens[$i]) === false || ($tokens[$i][0] !== T_DOC_COMMENT && $tokens[$i][0] !== T_COMMENT - && $tokens[$i][0] !== T_WHITESPACE) + && $tokens[$i][0] !== T_WHITESPACE + && $tokens[$i][0] !== T_STRING) ) { break; } @@ -1614,7 +1617,7 @@ function return types. We want to keep the parenthesis map clean, if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) { $isInlineIf = false; if (PHP_CODESNIFFER_VERBOSITY > 1) { - echo "\t\t* token is function return type, not T_INLINE_ELSE".PHP_EOL; + echo "\t\t* token is return type, not T_INLINE_ELSE".PHP_EOL; } } }//end if From cee37ff6378dfe9812a61a9d6f9d97be45b66dcc Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 19 Aug 2020 13:02:44 +1000 Subject: [PATCH 047/733] Changelog for #2975 + version bump --- package.xml | 34 +++------------------------------- src/Config.php | 2 +- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/package.xml b/package.xml index 2ff80cbbc0..a35e012978 100644 --- a/package.xml +++ b/package.xml @@ -17,8 +17,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2020-08-10 - 3.5.6 - 3.5.6 + 3.5.7 + 3.5.7 stable @@ -26,35 +26,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - - Added support for PHP 8.0 magic constant dereferencing - -- Thanks to Juliette Reinders Folmer for the patch - - Added support for changes to the way PHP 8.0 tokenizes comments - -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required - -- Thanks to Juliette Reinders Folmer for the patch - - File::getMethodProperties() now detects the PHP 8.0 static return type - -- Thanks to Juliette Reinders Folmer for the patch - - The PHP 8.0 static return type is now supported for arrow functions - -- Thanks to Juliette Reinders Folmer for the patch - - The cache is no longer used if the list of loaded PHP extensions changes - -- Thanks to Juliette Reinders Folmer for the patch - - Generic.NamingConventions.CamelCapsFunctionName no longer reports __serialize and __unserialize as invalid names - -- Thanks to Filip Š for the patch - - PEAR.NamingConventions.ValidFunctionName no longer reports __serialize and __unserialize as invalid names - -- Thanks to Filip Š for the patch - - Squiz.Scope.StaticThisUsage now detects usage of $this inside closures and arrow Functions - -- Thanks to Michał Bundyra for the patch - - Fixed bug #2877 : PEAR.Functions.FunctionCallSignature false positive for array of functions - -- Thanks to Vincent Langlet for the patch - - Fixed bug #2888 : PSR12.Files.FileHeader blank line error with multiple namespaces in one file - - Fixed bug #2926 : phpcs hangs when using arrow functions that return heredoc - - Fixed bug #2943 : Redundant semicolon added to a file when fixing PSR2.Files.ClosingTag.NotAllowed - - Fixed bug #2967 : Markdown generator does not output headings correctly - -- Thanks to Petr Bugyík for the patch - - Fixed bug #2977 : File::isReference() does not detect return by reference for closures - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #2994 : Generic.Formatting.DisallowMultipleStatements false positive for FOR loop with no body - - Fixed bug #3033 : Error generated during tokenizing of goto statements on PHP 8 - -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary diff --git a/src/Config.php b/src/Config.php index 74198153cd..f77eaef473 100644 --- a/src/Config.php +++ b/src/Config.php @@ -79,7 +79,7 @@ class Config * * @var string */ - const VERSION = '3.5.6'; + const VERSION = '3.5.7'; /** * Package stability; either stable, beta or alpha. From 04253420ca203acde87d06ff122f3c5e08b043f9 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 20 Aug 2020 09:18:16 +1000 Subject: [PATCH 048/733] Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level --- .../Sniffs/Arrays/ArrayIndentSniff.php | 37 ++++++++++++++++++- .../Tests/Arrays/ArrayIndentUnitTest.inc | 7 ++++ .../Arrays/ArrayIndentUnitTest.inc.fixed | 7 ++++ .../Tests/Arrays/ArrayIndentUnitTest.php | 7 ++-- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Arrays/ArrayIndentSniff.php b/src/Standards/Generic/Sniffs/Arrays/ArrayIndentSniff.php index cffea8033c..22258ad598 100644 --- a/src/Standards/Generic/Sniffs/Arrays/ArrayIndentSniff.php +++ b/src/Standards/Generic/Sniffs/Arrays/ArrayIndentSniff.php @@ -59,8 +59,41 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array { $tokens = $phpcsFile->getTokens(); - $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr, true); - $expectedIndent = ($tokens[$first]['column'] - 1 + $this->indent); + // Determine how far indented the entire array declaration should be. + $ignore = Tokens::$emptyTokens; + $ignore[] = T_DOUBLE_ARROW; + $prev = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); + $start = $phpcsFile->findStartOfStatement($prev); + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $start, true); + $baseIndent = ($tokens[$first]['column'] - 1); + + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr, true); + $startIndent = ($tokens[$first]['column'] - 1); + + // If the open brace is not indented to at least to the level of the start + // of the statement, the sniff will conflict with other sniffs trying to + // check indent levels because it's not valid. But we don't enforce exactly + // how far indented it should be. + if ($startIndent < $baseIndent) { + $error = 'Array open brace not indented correctly; expected at least %s spaces but found %s'; + $data = [ + $baseIndent, + $startIndent, + ]; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'OpenBraceIncorrect', $data); + if ($fix === true) { + $padding = str_repeat(' ', $baseIndent); + if ($startIndent === 0) { + $phpcsFile->fixer->addContentBefore($first, $padding); + } else { + $phpcsFile->fixer->replaceToken(($first - 1), $padding); + } + } + + return; + }//end if + + $expectedIndent = ($startIndent + $this->indent); foreach ($indices as $index) { if (isset($index['index_start']) === true) { diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc index 044e4a1727..77d967745f 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc @@ -63,6 +63,13 @@ $array = [ $c ? $d : $e, ]; +$foo = +[ + 'bar' => + [ + ], +]; + // phpcs:set Generic.Arrays.ArrayIndent indent 2 $var = [ diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed index 404313a4d1..ac06a68158 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed @@ -64,6 +64,13 @@ $array = [ $c ? $d : $e, ]; +$foo = +[ + 'bar' => + [ + ], +]; + // phpcs:set Generic.Arrays.ArrayIndent indent 2 $var = [ diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php index 6c94bd1a8f..42dff594af 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php @@ -36,9 +36,10 @@ public function getErrorList() 62 => 1, 63 => 1, 69 => 1, - 70 => 1, - 71 => 1, - 72 => 1, + 76 => 1, + 77 => 1, + 78 => 1, + 79 => 1, ]; }//end getErrorList() From 913acfe4264dc459e78e7cbaf681f2597f48364c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 20 Aug 2020 09:18:32 +1000 Subject: [PATCH 049/733] Changelog for #2882 --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index a35e012978..3e0c16b477 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary From ce1cd52c49023fa120f3810e7e6347af1f5aba80 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 21 Aug 2020 09:02:42 +1000 Subject: [PATCH 050/733] Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC Stopping at the end of a here/nowdoc when finding the start of a statement isn't correct. These token have openers/closers to help sniff developers, but they are still strings and should be skipped over in the same way. --- src/Files/File.php | 2 ++ .../Tests/WhiteSpace/ScopeIndentUnitTest.1.inc | 16 ++++++++++++++++ .../WhiteSpace/ScopeIndentUnitTest.1.inc.fixed | 16 ++++++++++++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.2.inc | 16 ++++++++++++++++ .../WhiteSpace/ScopeIndentUnitTest.2.inc.fixed | 16 ++++++++++++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 ++++---- 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index b18d4d445e..9a34bd0f43 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2278,6 +2278,8 @@ public function findStartOfStatement($start, $ignore=null) if (isset($this->tokens[$i]['scope_opener']) === true && $i === $this->tokens[$i]['scope_closer'] && $this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS + && $this->tokens[$i]['code'] !== T_END_NOWDOC + && $this->tokens[$i]['code'] !== T_END_HEREDOC ) { // Found the end of the previous scope block. return $lastNotEmpty; diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index bcce855e31..fdb8398c08 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1454,6 +1454,22 @@ return [ ]), ]; +return [ + 'lor' => + <<<'INTRO' + lorem ipsum + INTRO, + 'em' => [ + [ + '', + ], + ], + 'abc' => [ + 'a' => 'wop wop', + 'b' => 'ola ola.', + ], +]; + ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 3ad7b793b8..2956221acf 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1454,6 +1454,22 @@ return [ ]), ]; +return [ + 'lor' => + <<<'INTRO' + lorem ipsum + INTRO, + 'em' => [ + [ + '', + ], + ], + 'abc' => [ + 'a' => 'wop wop', + 'b' => 'ola ola.', + ], +]; + ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index d128111de0..10eab4608a 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1454,6 +1454,22 @@ return [ ]), ]; +return [ + 'lor' => + <<<'INTRO' + lorem ipsum + INTRO, + 'em' => [ + [ + '', + ], + ], + 'abc' => [ + 'a' => 'wop wop', + 'b' => 'ola ola.', + ], +]; + ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index d6505e0ced..20910c874b 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1454,6 +1454,22 @@ return [ ]), ]; +return [ + 'lor' => + <<<'INTRO' + lorem ipsum + INTRO, + 'em' => [ + [ + '', + ], + ], + 'abc' => [ + 'a' => 'wop wop', + 'b' => 'ola ola.', + ], +]; + ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index f347678cac..3d1afb70e2 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -178,10 +178,10 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1340 => 1, 1342 => 1, 1345 => 1, - 1464 => 1, - 1465 => 1, - 1466 => 1, - 1467 => 1, + 1480 => 1, + 1481 => 1, + 1482 => 1, + 1483 => 1, ]; }//end getErrorList() From 26557405068d222eda1d2c15eb7019a54b0d8a95 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 21 Aug 2020 09:17:06 +1000 Subject: [PATCH 051/733] Changelog for #2883 --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index 3e0c16b477..4932f5df71 100644 --- a/package.xml +++ b/package.xml @@ -27,6 +27,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level + - Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary From d33a6a99ae2f165f1f835ebf1645e471280cbcbd Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 21 Aug 2020 10:01:49 +1000 Subject: [PATCH 052/733] Moved around tests due to PHP < 7.3 syntax error in tests --- .../WhiteSpace/ScopeIndentUnitTest.1.inc | 32 +++++++++---------- .../ScopeIndentUnitTest.1.inc.fixed | 32 +++++++++---------- .../WhiteSpace/ScopeIndentUnitTest.2.inc | 32 +++++++++---------- .../ScopeIndentUnitTest.2.inc.fixed | 32 +++++++++---------- .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 ++--- 5 files changed, 68 insertions(+), 68 deletions(-) diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index fdb8398c08..c81c887aa4 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1454,22 +1454,6 @@ return [ ]), ]; -return [ - 'lor' => - <<<'INTRO' - lorem ipsum - INTRO, - 'em' => [ - [ - '', - ], - ], - 'abc' => [ - 'a' => 'wop wop', - 'b' => 'ola ola.', - ], -]; - ?> @@ -1489,4 +1473,20 @@ $a = array( */ ); +return [ + 'lor' => + <<<'INTRO' + lorem ipsum + INTRO, + 'em' => [ + [ + '', + ], + ], + 'abc' => [ + 'a' => 'wop wop', + 'b' => 'ola ola.', + ], +]; + echo "" diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 2956221acf..7c697357ee 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1454,22 +1454,6 @@ return [ ]), ]; -return [ - 'lor' => - <<<'INTRO' - lorem ipsum - INTRO, - 'em' => [ - [ - '', - ], - ], - 'abc' => [ - 'a' => 'wop wop', - 'b' => 'ola ola.', - ], -]; - ?> @@ -1489,4 +1473,20 @@ $a = array( */ ); +return [ + 'lor' => + <<<'INTRO' + lorem ipsum + INTRO, + 'em' => [ + [ + '', + ], + ], + 'abc' => [ + 'a' => 'wop wop', + 'b' => 'ola ola.', + ], +]; + echo "" diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index 10eab4608a..275953a883 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1454,22 +1454,6 @@ return [ ]), ]; -return [ - 'lor' => - <<<'INTRO' - lorem ipsum - INTRO, - 'em' => [ - [ - '', - ], - ], - 'abc' => [ - 'a' => 'wop wop', - 'b' => 'ola ola.', - ], -]; - ?> @@ -1489,4 +1473,20 @@ $a = array( */ ); +return [ + 'lor' => + <<<'INTRO' + lorem ipsum + INTRO, + 'em' => [ + [ + '', + ], + ], + 'abc' => [ + 'a' => 'wop wop', + 'b' => 'ola ola.', + ], +]; + echo "" diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index 20910c874b..5de521a1f2 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1454,22 +1454,6 @@ return [ ]), ]; -return [ - 'lor' => - <<<'INTRO' - lorem ipsum - INTRO, - 'em' => [ - [ - '', - ], - ], - 'abc' => [ - 'a' => 'wop wop', - 'b' => 'ola ola.', - ], -]; - ?> @@ -1489,4 +1473,20 @@ $a = array( */ ); +return [ + 'lor' => + <<<'INTRO' + lorem ipsum + INTRO, + 'em' => [ + [ + '', + ], + ], + 'abc' => [ + 'a' => 'wop wop', + 'b' => 'ola ola.', + ], +]; + echo "" diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index 3d1afb70e2..f347678cac 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -178,10 +178,10 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1340 => 1, 1342 => 1, 1345 => 1, - 1480 => 1, - 1481 => 1, - 1482 => 1, - 1483 => 1, + 1464 => 1, + 1465 => 1, + 1466 => 1, + 1467 => 1, ]; }//end getErrorList() From 2fae0e8f54b2efeb3b519fcb5d6cef391cd6410e Mon Sep 17 00:00:00 2001 From: Anna Borzenko Date: Mon, 24 Aug 2020 12:05:50 +0200 Subject: [PATCH 053/733] Added AbstractPrefixRequiredForAbstractClass, InterfaceSuffixRequiredForInterface, TraitSuffixRequiredForTrait sniffs to Generic.NamingConventions --- package.xml | 12 ++++ ...PrefixRequiredForAbstractClassStandard.xml | 23 +++++++ ...faceSuffixRequiredForInterfaceStandard.xml | 23 +++++++ .../TraitSuffixRequiredForTraitStandard.xml | 23 +++++++ ...actPrefixRequiredForAbstractClassSniff.php | 61 +++++++++++++++++++ ...terfaceSuffixRequiredForInterfaceSniff.php | 55 +++++++++++++++++ .../TraitSuffixRequiredForTraitSniff.php | 55 +++++++++++++++++ ...PrefixRequiredForAbstractClassUnitTest.inc | 54 ++++++++++++++++ ...PrefixRequiredForAbstractClassUnitTest.php | 53 ++++++++++++++++ ...faceSuffixRequiredForInterfaceUnitTest.inc | 27 ++++++++ ...faceSuffixRequiredForInterfaceUnitTest.php | 50 +++++++++++++++ .../TraitSuffixRequiredForTraitUnitTest.inc | 13 ++++ .../TraitSuffixRequiredForTraitUnitTest.php | 51 ++++++++++++++++ 13 files changed, 500 insertions(+) create mode 100644 src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml create mode 100644 src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml create mode 100644 src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml create mode 100644 src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php create mode 100644 src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php create mode 100644 src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php create mode 100644 src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc create mode 100644 src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.php create mode 100644 src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc create mode 100644 src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.php create mode 100644 src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc create mode 100644 src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.php diff --git a/package.xml b/package.xml index 4932f5df71..be984e9a02 100644 --- a/package.xml +++ b/package.xml @@ -253,8 +253,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + @@ -358,8 +361,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + @@ -603,10 +609,16 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + diff --git a/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml b/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml new file mode 100644 index 0000000000..0d8b7c6c63 --- /dev/null +++ b/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml b/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml new file mode 100644 index 0000000000..1a11acef77 --- /dev/null +++ b/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml b/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml new file mode 100644 index 0000000000..f754775d7d --- /dev/null +++ b/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php new file mode 100644 index 0000000000..aca7bfde63 --- /dev/null +++ b/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php @@ -0,0 +1,61 @@ + + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; + +class AbstractPrefixRequiredForAbstractClassSniff implements Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return int[] + */ + public function register() + { + return [T_CLASS]; + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($prev === false || $phpcsFile->getTokens()[$prev]['code'] !== T_ABSTRACT) { + // This class is not abstract so we don't need to check it. + return; + } + + $className = $phpcsFile->getDeclarationName($stackPtr); + if ($className === null) { + // We are not interested in anonymous classes. + return; + } + + $prefix = substr($className, 0, 8); + if ($prefix !== 'Abstract') { + $phpcsFile->addError('Abstract classes MUST be prefixed by Abstract: e.g. Psr\Foo\AbstractBar.', $stackPtr, 'RequiredAbstractPrefix'); + } + + }//end process() + + +}//end class diff --git a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php new file mode 100644 index 0000000000..214f8475de --- /dev/null +++ b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php @@ -0,0 +1,55 @@ + + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; + +class InterfaceSuffixRequiredForInterfaceSniff implements Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return int[] + */ + public function register() + { + return [T_INTERFACE]; + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $interfaceName = $phpcsFile->getDeclarationName($stackPtr); + if ($interfaceName === null) { + return; + } + + $interfaceNameLength = strlen($interfaceName); + $suffix = substr($interfaceName, ($interfaceNameLength - 9), $interfaceNameLength); + if ($suffix !== 'Interface') { + $phpcsFile->addError('Interfaces MUST be suffixed by Interface: e.g. Psr\Foo\BarInterface.', $stackPtr, 'RequiredInterfaceSuffix'); + } + + }//end process() + + +}//end class diff --git a/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php new file mode 100644 index 0000000000..ecf26ea14c --- /dev/null +++ b/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php @@ -0,0 +1,55 @@ + + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; + +class TraitSuffixRequiredForTraitSniff implements Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return int[] + */ + public function register() + { + return [T_TRAIT]; + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $traitName = $phpcsFile->getDeclarationName($stackPtr); + if ($traitName === null) { + return; + } + + $traitNameLength = strlen($traitName); + $suffix = substr($traitName, ($traitNameLength - 5), $traitNameLength); + if ($suffix !== 'Trait') { + $phpcsFile->addError('Traits MUST be suffixed by Trait: e.g. Psr\Foo\BarTrait.', $stackPtr, 'RequiredTraitSuffix'); + } + + }//end process() + + +}//end class diff --git a/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc new file mode 100644 index 0000000000..f47131918e --- /dev/null +++ b/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc @@ -0,0 +1,54 @@ + + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Tests\NamingConventions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class AbstractPrefixRequiredForAbstractClassUnitTest extends AbstractSniffUnitTest +{ + + + /** + * Returns the lines where errors should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of errors that should occur on that line. + * + * @return array + */ + public function getErrorList() + { + return [ + 3 => 1, + 13 => 1, + 18 => 1, + 23 => 1, + 42 => 1, + ]; + + }//end getErrorList() + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + + }//end getWarningList() + + +}//end class diff --git a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc new file mode 100644 index 0000000000..aea665ddda --- /dev/null +++ b/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc @@ -0,0 +1,27 @@ + + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Tests\NamingConventions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class InterfaceSuffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest +{ + + + /** + * Returns the lines where errors should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of errors that should occur on that line. + * + * @return array + */ + public function getErrorList() + { + return [ + 8 => 1, + 19 => 1, + ]; + + }//end getErrorList() + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + + }//end getWarningList() + + +}//end class diff --git a/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc new file mode 100644 index 0000000000..9cea6f2a3f --- /dev/null +++ b/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc @@ -0,0 +1,13 @@ + + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Tests\NamingConventions; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class TraitSuffixRequiredForTraitUnitTest extends AbstractSniffUnitTest +{ + + + /** + * Returns the lines where errors should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of errors that should occur on that line. + * + * @return array + */ + public function getErrorList() + { + return [ + 3 => 1, + 9 => 1, + 11 => 1, + ]; + + }//end getErrorList() + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + + }//end getWarningList() + + +}//end class From 798e7aa0462eeb69013e74b6645d127172b3afc7 Mon Sep 17 00:00:00 2001 From: Anna Borzenko Date: Tue, 25 Aug 2020 18:23:29 +0200 Subject: [PATCH 054/733] Check class, trait, interface naming in lowercase, change error codes to Missing, add Found info --- .../AbstractPrefixRequiredForAbstractClassSniff.php | 7 +++---- .../InterfaceSuffixRequiredForInterfaceSniff.php | 7 +++---- .../NamingConventions/TraitSuffixRequiredForTraitSniff.php | 7 +++---- .../AbstractPrefixRequiredForAbstractClassUnitTest.inc | 7 ++++++- .../InterfaceSuffixRequiredForInterfaceUnitTest.inc | 2 +- .../InterfaceSuffixRequiredForInterfaceUnitTest.php | 5 +---- .../TraitSuffixRequiredForTraitUnitTest.inc | 2 +- .../TraitSuffixRequiredForTraitUnitTest.php | 5 ++--- 8 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php index aca7bfde63..7892e111fc 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php @@ -38,8 +38,7 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); - if ($prev === false || $phpcsFile->getTokens()[$prev]['code'] !== T_ABSTRACT) { + if ($phpcsFile->getClassProperties($stackPtr)['is_abstract'] === false) { // This class is not abstract so we don't need to check it. return; } @@ -51,8 +50,8 @@ public function process(File $phpcsFile, $stackPtr) } $prefix = substr($className, 0, 8); - if ($prefix !== 'Abstract') { - $phpcsFile->addError('Abstract classes MUST be prefixed by Abstract: e.g. Psr\Foo\AbstractBar.', $stackPtr, 'RequiredAbstractPrefix'); + if (strtolower($prefix) !== 'abstract') { + $phpcsFile->addError('Abstract classes MUST be prefixed by Abstract: e.g. AbstractBar. Found: %s', $stackPtr, 'Missing', [$className]); } }//end process() diff --git a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php index 214f8475de..643e3e7289 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php @@ -43,10 +43,9 @@ public function process(File $phpcsFile, $stackPtr) return; } - $interfaceNameLength = strlen($interfaceName); - $suffix = substr($interfaceName, ($interfaceNameLength - 9), $interfaceNameLength); - if ($suffix !== 'Interface') { - $phpcsFile->addError('Interfaces MUST be suffixed by Interface: e.g. Psr\Foo\BarInterface.', $stackPtr, 'RequiredInterfaceSuffix'); + $suffix = substr($interfaceName, - 9); + if (strtolower($suffix) !== 'interface') { + $phpcsFile->addError('Interfaces MUST be suffixed by Interface: e.g. BarInterface. Found: %s', $stackPtr, 'Missing', [$interfaceName]); } }//end process() diff --git a/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php index ecf26ea14c..17179badb2 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php @@ -43,10 +43,9 @@ public function process(File $phpcsFile, $stackPtr) return; } - $traitNameLength = strlen($traitName); - $suffix = substr($traitName, ($traitNameLength - 5), $traitNameLength); - if ($suffix !== 'Trait') { - $phpcsFile->addError('Traits MUST be suffixed by Trait: e.g. Psr\Foo\BarTrait.', $stackPtr, 'RequiredTraitSuffix'); + $suffix = substr($traitName, - 5); + if (strtolower($suffix) !== 'trait') { + $phpcsFile->addError('Traits MUST be suffixed by Trait: e.g. BarTrait. Found: %s', $stackPtr, 'Missing', [$traitName]); } }//end process() diff --git a/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc index f47131918e..27b9d818da 100644 --- a/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc +++ b/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc @@ -51,4 +51,9 @@ $var = 'abstract class IncorrectNameButOk'; $abstracVar = ''; -class NameAbstractBar {} \ No newline at end of file +class NameAbstractBar {} + +abstract class abstractOkName +{ + +} \ No newline at end of file diff --git a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc index aea665ddda..d562d3eaeb 100644 --- a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc +++ b/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc @@ -16,7 +16,7 @@ interface ISomeNameInterface } -interface ISomeOtherNameinterface // error +interface ISomeOtherNameinterface { } diff --git a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.php index f614d50638..ccbf634893 100644 --- a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.php @@ -24,10 +24,7 @@ class InterfaceSuffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest */ public function getErrorList() { - return [ - 8 => 1, - 19 => 1, - ]; + return [8 => 1]; }//end getErrorList() diff --git a/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc index 9cea6f2a3f..e807697185 100644 --- a/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc +++ b/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc @@ -8,6 +8,6 @@ trait GoodTraitTrait {} trait BadTraitName {} // error -trait BadTraitNametrait {} // error +trait BadTraitNametrait {} trait NormalTraitNameTrait {} \ No newline at end of file diff --git a/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.php index ddf74af43d..07f1b4c843 100644 --- a/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.php @@ -25,9 +25,8 @@ class TraitSuffixRequiredForTraitUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 3 => 1, - 9 => 1, - 11 => 1, + 3 => 1, + 9 => 1, ]; }//end getErrorList() From b3633601cb2826a3ef8f07decb615a54a9e91fa3 Mon Sep 17 00:00:00 2001 From: Anna Borzenko Date: Tue, 25 Aug 2020 18:32:38 +0200 Subject: [PATCH 055/733] Deleted extra whitespace --- .../InterfaceSuffixRequiredForInterfaceSniff.php | 2 +- .../NamingConventions/TraitSuffixRequiredForTraitSniff.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php index 643e3e7289..0f388a178b 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php @@ -43,7 +43,7 @@ public function process(File $phpcsFile, $stackPtr) return; } - $suffix = substr($interfaceName, - 9); + $suffix = substr($interfaceName, -9); if (strtolower($suffix) !== 'interface') { $phpcsFile->addError('Interfaces MUST be suffixed by Interface: e.g. BarInterface. Found: %s', $stackPtr, 'Missing', [$interfaceName]); } diff --git a/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php index 17179badb2..d886669cb1 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php @@ -43,7 +43,7 @@ public function process(File $phpcsFile, $stackPtr) return; } - $suffix = substr($traitName, - 5); + $suffix = substr($traitName, -5); if (strtolower($suffix) !== 'trait') { $phpcsFile->addError('Traits MUST be suffixed by Trait: e.g. BarTrait. Found: %s', $stackPtr, 'Missing', [$traitName]); } From 4b5aa4f1b8219b32bb908aeaf0306ba494e4434a Mon Sep 17 00:00:00 2001 From: Anna Borzenko Date: Tue, 25 Aug 2020 22:40:46 +0200 Subject: [PATCH 056/733] Added ability to set affix type, value, case sensitive while checking Interface name, changed docs --- package.xml | 8 +- ...PrefixRequiredForAbstractClassStandard.xml | 6 +- ... => AffixRequiredForInterfaceStandard.xml} | 6 +- .../TraitSuffixRequiredForTraitStandard.xml | 6 +- .../AffixRequiredForInterfaceSniff.php | 131 ++++++++++++++++++ ...terfaceSuffixRequiredForInterfaceSniff.php | 54 -------- .../AffixRequiredForInterfaceUnitTest.inc | 82 +++++++++++ ... => AffixRequiredForInterfaceUnitTest.php} | 12 +- ...faceSuffixRequiredForInterfaceUnitTest.inc | 27 ---- 9 files changed, 235 insertions(+), 97 deletions(-) rename src/Standards/Generic/Docs/NamingConventions/{InterfaceSuffixRequiredForInterfaceStandard.xml => AffixRequiredForInterfaceStandard.xml} (65%) create mode 100644 src/Standards/Generic/Sniffs/NamingConventions/AffixRequiredForInterfaceSniff.php delete mode 100644 src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php create mode 100644 src/Standards/Generic/Tests/NamingConventions/AffixRequiredForInterfaceUnitTest.inc rename src/Standards/Generic/Tests/NamingConventions/{InterfaceSuffixRequiredForInterfaceUnitTest.php => AffixRequiredForInterfaceUnitTest.php} (79%) delete mode 100644 src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc diff --git a/package.xml b/package.xml index be984e9a02..605b3c9657 100644 --- a/package.xml +++ b/package.xml @@ -256,7 +256,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + @@ -364,7 +364,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + @@ -615,8 +615,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - + + diff --git a/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml b/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml index 0d8b7c6c63..4f84fa4465 100644 --- a/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml +++ b/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml @@ -1,20 +1,20 @@ AbstractBar { } ]]> Bar { } ]]> diff --git a/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml b/src/Standards/Generic/Docs/NamingConventions/AffixRequiredForInterfaceStandard.xml similarity index 65% rename from src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml rename to src/Standards/Generic/Docs/NamingConventions/AffixRequiredForInterfaceStandard.xml index 1a11acef77..9c67b1d3f5 100644 --- a/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml +++ b/src/Standards/Generic/Docs/NamingConventions/AffixRequiredForInterfaceStandard.xml @@ -1,20 +1,20 @@ BarInterface { } ]]> Bar { } ]]> diff --git a/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml b/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml index f754775d7d..62c9b26d46 100644 --- a/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml +++ b/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml @@ -1,20 +1,20 @@ BarTrait { } ]]> Bar { } ]]> diff --git a/src/Standards/Generic/Sniffs/NamingConventions/AffixRequiredForInterfaceSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/AffixRequiredForInterfaceSniff.php new file mode 100644 index 0000000000..ab1cac7c6d --- /dev/null +++ b/src/Standards/Generic/Sniffs/NamingConventions/AffixRequiredForInterfaceSniff.php @@ -0,0 +1,131 @@ + + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; + +class AffixRequiredForInterfaceSniff implements Sniff +{ + + /** + * The affix type - either 'suffix' or 'prefix'. + * Default to 'suffix'. + * + * @var string + */ + public $affixType = 'suffix'; + + /** + * A prefix/suffix that must be added to interface name. + * Default to 'Interface'. + * + * @var string + */ + public $affixValue = 'Interface'; + + /** + * Whether to run case sensitive prefix/suffix comparison. + * Default to false. + * + * @var boolean + */ + public $isCaseSensitive = false; + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return int[] + */ + public function register() + { + return [T_INTERFACE]; + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $interfaceName = $phpcsFile->getDeclarationName($stackPtr); + if ($interfaceName === null) { + return; + } + + $this->affixValue = trim((string) $this->affixValue); + $affixLength = strlen($this->affixValue); + if ($affixLength === 0) { + // If affix is empty - then we think all names are valid. + return; + } + + $isSuffixRequired = $this->affixType === 'suffix'; + if ($isSuffixRequired === true) { + $affix = substr($interfaceName, -$affixLength); + } else { + $affix = substr($interfaceName, 0, $affixLength); + } + + if (strlen($interfaceName) < $affixLength || $this->checkAffix($affix) === false) { + $verb = 'prefixed'; + if ($isSuffixRequired === true) { + $verb = 'suffixed'; + } + + $affixErrorValue = $this->affixValue; + if ($this->isCaseSensitive === true) { + $affixErrorValue .= ' (case sensitive)'; + } + + $nameExample = $this->affixValue.'Bar'; + if ($isSuffixRequired === true) { + $nameExample = 'Bar'.$this->affixValue; + } + + $errorData = [ + $verb, + $affixErrorValue, + $nameExample, + $interfaceName, + ]; + $phpcsFile->addError('Interfaces MUST be %s by %s: e.g. %s. Found: %s', $stackPtr, 'Missing', $errorData); + }//end if + + }//end process() + + + /** + * Checks if affix from the interface name is right. + * + * @param string $affix Affix from the checking interface name. + * + * @return bool + */ + private function checkAffix($affix) + { + if ($this->isCaseSensitive === false) { + return strtolower($affix) === strtolower($this->affixValue); + } + + return $affix === $this->affixValue; + + }//end checkAffix() + + +}//end class diff --git a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php deleted file mode 100644 index 0f388a178b..0000000000 --- a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence - */ - -namespace PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions; - -use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Sniffs\Sniff; - -class InterfaceSuffixRequiredForInterfaceSniff implements Sniff -{ - - - /** - * Registers the tokens that this sniff wants to listen for. - * - * @return int[] - */ - public function register() - { - return [T_INTERFACE]; - - }//end register() - - - /** - * Processes this sniff, when one of its tokens is encountered. - * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void - */ - public function process(File $phpcsFile, $stackPtr) - { - $interfaceName = $phpcsFile->getDeclarationName($stackPtr); - if ($interfaceName === null) { - return; - } - - $suffix = substr($interfaceName, -9); - if (strtolower($suffix) !== 'interface') { - $phpcsFile->addError('Interfaces MUST be suffixed by Interface: e.g. BarInterface. Found: %s', $stackPtr, 'Missing', [$interfaceName]); - } - - }//end process() - - -}//end class diff --git a/src/Standards/Generic/Tests/NamingConventions/AffixRequiredForInterfaceUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/AffixRequiredForInterfaceUnitTest.inc new file mode 100644 index 0000000000..56a562bb86 --- /dev/null +++ b/src/Standards/Generic/Tests/NamingConventions/AffixRequiredForInterfaceUnitTest.inc @@ -0,0 +1,82 @@ + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence @@ -10,7 +10,7 @@ use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; -class InterfaceSuffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest +class AffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest { @@ -24,7 +24,13 @@ class InterfaceSuffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest */ public function getErrorList() { - return [8 => 1]; + return [ + 8 => 1, + 32 => 1, + 51 => 1, + 62 => 1, + 70 => 1, + ]; }//end getErrorList() diff --git a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc deleted file mode 100644 index d562d3eaeb..0000000000 --- a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc +++ /dev/null @@ -1,27 +0,0 @@ - Date: Fri, 28 Aug 2020 10:28:02 +0200 Subject: [PATCH 057/733] Revert "Added ability to set affix type, value, case sensitive while checking Interface name, changed docs" --- package.xml | 8 +- ...aceSuffixRequiredForInterfaceStandard.xml} | 2 +- .../AffixRequiredForInterfaceSniff.php | 131 ------------------ ...terfaceSuffixRequiredForInterfaceSniff.php | 54 ++++++++ .../AffixRequiredForInterfaceUnitTest.inc | 82 ----------- ...faceSuffixRequiredForInterfaceUnitTest.inc | 27 ++++ ...aceSuffixRequiredForInterfaceUnitTest.php} | 12 +- 7 files changed, 89 insertions(+), 227 deletions(-) rename src/Standards/Generic/Docs/NamingConventions/{AffixRequiredForInterfaceStandard.xml => InterfaceSuffixRequiredForInterfaceStandard.xml} (76%) delete mode 100644 src/Standards/Generic/Sniffs/NamingConventions/AffixRequiredForInterfaceSniff.php create mode 100644 src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php delete mode 100644 src/Standards/Generic/Tests/NamingConventions/AffixRequiredForInterfaceUnitTest.inc create mode 100644 src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc rename src/Standards/Generic/Tests/NamingConventions/{AffixRequiredForInterfaceUnitTest.php => InterfaceSuffixRequiredForInterfaceUnitTest.php} (79%) diff --git a/package.xml b/package.xml index 605b3c9657..be984e9a02 100644 --- a/package.xml +++ b/package.xml @@ -256,7 +256,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + @@ -364,7 +364,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + @@ -615,8 +615,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - + + diff --git a/src/Standards/Generic/Docs/NamingConventions/AffixRequiredForInterfaceStandard.xml b/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml similarity index 76% rename from src/Standards/Generic/Docs/NamingConventions/AffixRequiredForInterfaceStandard.xml rename to src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml index 9c67b1d3f5..d4f38447e5 100644 --- a/src/Standards/Generic/Docs/NamingConventions/AffixRequiredForInterfaceStandard.xml +++ b/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml @@ -1,7 +1,7 @@ diff --git a/src/Standards/Generic/Sniffs/NamingConventions/AffixRequiredForInterfaceSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/AffixRequiredForInterfaceSniff.php deleted file mode 100644 index ab1cac7c6d..0000000000 --- a/src/Standards/Generic/Sniffs/NamingConventions/AffixRequiredForInterfaceSniff.php +++ /dev/null @@ -1,131 +0,0 @@ - - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence - */ - -namespace PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions; - -use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Sniffs\Sniff; - -class AffixRequiredForInterfaceSniff implements Sniff -{ - - /** - * The affix type - either 'suffix' or 'prefix'. - * Default to 'suffix'. - * - * @var string - */ - public $affixType = 'suffix'; - - /** - * A prefix/suffix that must be added to interface name. - * Default to 'Interface'. - * - * @var string - */ - public $affixValue = 'Interface'; - - /** - * Whether to run case sensitive prefix/suffix comparison. - * Default to false. - * - * @var boolean - */ - public $isCaseSensitive = false; - - - /** - * Registers the tokens that this sniff wants to listen for. - * - * @return int[] - */ - public function register() - { - return [T_INTERFACE]; - - }//end register() - - - /** - * Processes this sniff, when one of its tokens is encountered. - * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void - */ - public function process(File $phpcsFile, $stackPtr) - { - $interfaceName = $phpcsFile->getDeclarationName($stackPtr); - if ($interfaceName === null) { - return; - } - - $this->affixValue = trim((string) $this->affixValue); - $affixLength = strlen($this->affixValue); - if ($affixLength === 0) { - // If affix is empty - then we think all names are valid. - return; - } - - $isSuffixRequired = $this->affixType === 'suffix'; - if ($isSuffixRequired === true) { - $affix = substr($interfaceName, -$affixLength); - } else { - $affix = substr($interfaceName, 0, $affixLength); - } - - if (strlen($interfaceName) < $affixLength || $this->checkAffix($affix) === false) { - $verb = 'prefixed'; - if ($isSuffixRequired === true) { - $verb = 'suffixed'; - } - - $affixErrorValue = $this->affixValue; - if ($this->isCaseSensitive === true) { - $affixErrorValue .= ' (case sensitive)'; - } - - $nameExample = $this->affixValue.'Bar'; - if ($isSuffixRequired === true) { - $nameExample = 'Bar'.$this->affixValue; - } - - $errorData = [ - $verb, - $affixErrorValue, - $nameExample, - $interfaceName, - ]; - $phpcsFile->addError('Interfaces MUST be %s by %s: e.g. %s. Found: %s', $stackPtr, 'Missing', $errorData); - }//end if - - }//end process() - - - /** - * Checks if affix from the interface name is right. - * - * @param string $affix Affix from the checking interface name. - * - * @return bool - */ - private function checkAffix($affix) - { - if ($this->isCaseSensitive === false) { - return strtolower($affix) === strtolower($this->affixValue); - } - - return $affix === $this->affixValue; - - }//end checkAffix() - - -}//end class diff --git a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php new file mode 100644 index 0000000000..0f388a178b --- /dev/null +++ b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php @@ -0,0 +1,54 @@ + + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; + +class InterfaceSuffixRequiredForInterfaceSniff implements Sniff +{ + + + /** + * Registers the tokens that this sniff wants to listen for. + * + * @return int[] + */ + public function register() + { + return [T_INTERFACE]; + + }//end register() + + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $interfaceName = $phpcsFile->getDeclarationName($stackPtr); + if ($interfaceName === null) { + return; + } + + $suffix = substr($interfaceName, -9); + if (strtolower($suffix) !== 'interface') { + $phpcsFile->addError('Interfaces MUST be suffixed by Interface: e.g. BarInterface. Found: %s', $stackPtr, 'Missing', [$interfaceName]); + } + + }//end process() + + +}//end class diff --git a/src/Standards/Generic/Tests/NamingConventions/AffixRequiredForInterfaceUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/AffixRequiredForInterfaceUnitTest.inc deleted file mode 100644 index 56a562bb86..0000000000 --- a/src/Standards/Generic/Tests/NamingConventions/AffixRequiredForInterfaceUnitTest.inc +++ /dev/null @@ -1,82 +0,0 @@ - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence @@ -10,7 +10,7 @@ use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; -class AffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest +class InterfaceSuffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest { @@ -24,13 +24,7 @@ class AffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest */ public function getErrorList() { - return [ - 8 => 1, - 32 => 1, - 51 => 1, - 62 => 1, - 70 => 1, - ]; + return [8 => 1]; }//end getErrorList() From d24aeba3fb0d90057f24327437ef96d09ff047b3 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 29 Aug 2020 16:00:48 -0700 Subject: [PATCH 058/733] Ignore tokens that belong to array element declaration --- .../Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php | 11 ++++++++++- .../Tests/Arrays/ArrayDeclarationUnitTest.2.inc | 13 +++++++++++++ .../Arrays/ArrayDeclarationUnitTest.2.inc.fixed | 13 +++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php index de5ee8535d..53fddfd0d7 100644 --- a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -620,7 +620,16 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array $valuePointer = $value['value']; - $previous = $phpcsFile->findPrevious([T_WHITESPACE, T_COMMA], ($valuePointer - 1), ($arrayStart + 1), true); + $ignoreTokens = ([ + T_WHITESPACE => T_WHITESPACE, + T_COMMA => T_COMMA, + ] + Tokens::$castTokens); + + if ($tokens[$valuePointer]['code'] === T_CLOSURE) { + $ignoreTokens += [T_STATIC => T_STATIC]; + } + + $previous = $phpcsFile->findPrevious($ignoreTokens, ($valuePointer - 1), ($arrayStart + 1), true); if ($previous === false) { $previous = $stackPtr; } diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc index 60ff35d64c..7ee9033dfe 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc @@ -436,6 +436,19 @@ $c]; ['a' => $a, 'b' => $b, 'c' => $c]; +[ + static function() { + return null; + }, + (array) [], + (bool) [], + (double) [], + (int) [], + (object) [], + (string) [], + (unset) [], +]; + // Intentional syntax error. $a = [ 'a' => diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed index 1d17fa0c4d..969ceed6bb 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed @@ -470,6 +470,19 @@ $foo = [ 'c' => $c, ]; +[ + static function() { + return null; + }, + (array) [], + (bool) [], + (double) [], + (int) [], + (object) [], + (string) [], + (unset) [], +]; + // Intentional syntax error. $a = [ 'a' => From e33a2f208c6a49325f3d1d901b7c4a27fc0422bc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 01:42:09 +0200 Subject: [PATCH 059/733] Various minor documentation fixes ... picked up along the way. --- src/Files/File.php | 2 +- src/Fixer.php | 2 +- .../Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php | 2 +- src/Tokenizers/PHP.php | 6 +++--- tests/Core/File/IsReferenceTest.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 9a34bd0f43..1b36cbd4cf 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2496,7 +2496,7 @@ public function hasCondition($stackPtr, $types) * @param int $stackPtr The position of the token we are checking. * @param int|string $type The type of token to search for. * @param bool $first If TRUE, will return the matched condition - * furtherest away from the passed token. + * furthest away from the passed token. * If FALSE, will return the matched condition * closest to the passed token. * diff --git a/src/Fixer.php b/src/Fixer.php index 897e14771b..1e1f2561d4 100644 --- a/src/Fixer.php +++ b/src/Fixer.php @@ -743,7 +743,7 @@ public function addContentBefore($stackPtr, $content) * @param int $change The number of spaces to adjust the indent by * (positive or negative). * - * @return bool If the change was accepted. + * @return void */ public function changeCodeBlockIndent($start, $end, $change) { diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php index 94eab1f991..cc9958c586 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php @@ -63,7 +63,7 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); $token = $tokens[$stackPtr]; - // Skip for-loop without body. + // Skip if statement without body. if (isset($token['parenthesis_opener']) === false) { return; } diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 6135e957cf..b629d872c5 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -828,7 +828,7 @@ protected function tokenize($string) && $tokens[($stackPtr + 2)][0] === T_STRING && strtolower($tokens[($stackPtr + 2)][1]) === 'from' ) { - // Could be multi-line, so just the token stack. + // Could be multi-line, so adjust the token stack. $token[0] = T_YIELD_FROM; $token[1] .= $tokens[($stackPtr + 1)][1].$tokens[($stackPtr + 2)][1]; @@ -960,7 +960,7 @@ protected function tokenize($string) /* Before PHP 7, the ??= operator was tokenized as T_INLINE_THEN, T_INLINE_THEN, T_EQUAL. - Between PHP 7.0 and 7.2, the ??= operator was tokenized as + Between PHP 7.0 and 7.3, the ??= operator was tokenized as T_COALESCE, T_EQUAL. So look for and combine these tokens in earlier versions. */ @@ -1372,7 +1372,7 @@ function return types. We want to keep the parenthesis map clean, if (is_array($tokens[$x]) === false && $tokens[$x] === '?') { // Found a nullable operator, so skip it. - // But also covert the token to save the tokenizer + // But also convert the token to save the tokenizer // a bit of time later on. $tokens[$x] = [ T_NULLABLE, diff --git a/tests/Core/File/IsReferenceTest.php b/tests/Core/File/IsReferenceTest.php index d6c37c6c1f..1c624118c4 100644 --- a/tests/Core/File/IsReferenceTest.php +++ b/tests/Core/File/IsReferenceTest.php @@ -16,7 +16,7 @@ class IsReferenceTest extends AbstractMethodUnitTest /** - * Test a class that extends another. + * Test correctly identifying whether a "bitwise and" token is a reference or not. * * @param string $identifier Comment which precedes the test case. * @param bool $expected Expected function output. From db202d9d332297c32513132476cb93460b30ebbd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 01:16:19 +0200 Subject: [PATCH 060/733] Travis: simpify PHPStan build As there is already a PHP 7.4 build in place running exactly the same build as the PHP 7.4 PHPStan build, with the exception of the actual PHPStan run, we can skip the "normal" build checks for the PHPStan build and just and only run PHPStan. --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0145ca1649..432aa23377 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,10 @@ jobs: apt: packages: - libonig-dev + script: + - composer require --dev phpstan/phpstan + - php vendor/bin/phpstan analyse --configuration=phpstan.neon + # Nightly is PHP 8.0 since Feb 2019. - php: nightly addons: @@ -83,5 +87,3 @@ script: - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/PSR12/ruleset.xml <(xmllint --format "./src/Standards/PSR12/ruleset.xml"); fi - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/Squiz/ruleset.xml <(xmllint --format "./src/Standards/Squiz/ruleset.xml"); fi - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/Zend/ruleset.xml <(xmllint --format "./src/Standards/Zend/ruleset.xml"); fi - # Run PHPStan - - if [[ $PHPSTAN == "1" ]]; then composer require --dev phpstan/phpstan && php vendor/bin/phpstan analyse --configuration=phpstan.neon; fi From 0be1542de351c55cbdfdd2bd028366e4801520da Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 20:40:17 +0200 Subject: [PATCH 061/733] PHP 8.0 | Stabilize comment tokenization for hash comments Follow up on 3027 which handled this for slash-style comments. Hash-style comments were not addressed in the earlier change. This oversight has now been fixed. Includes additional unit tests. Fixes 3069 --- src/Tokenizers/PHP.php | 4 +- .../Tokenizer/StableCommentWhitespaceTest.inc | 20 ++++ .../Tokenizer/StableCommentWhitespaceTest.php | 102 ++++++++++++++++++ .../StableCommentWhitespaceWinTest.inc | 20 ++++ .../StableCommentWhitespaceWinTest.php | 102 ++++++++++++++++++ 5 files changed, 246 insertions(+), 2 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 6135e957cf..8f52ca8fc6 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -567,12 +567,12 @@ protected function tokenize($string) } /* - PHP 8 tokenizes a new line after a slash comment to the next whitespace token. + PHP 8 tokenizes a new line after a slash and hash comment to the next whitespace token. */ if (PHP_VERSION_ID >= 80000 && $tokenIsArray === true - && ($token[0] === T_COMMENT && strpos($token[1], '//') === 0) + && ($token[0] === T_COMMENT && (strpos($token[1], '//') === 0 || strpos($token[1], '#') === 0)) && isset($tokens[($stackPtr + 1)]) === true && is_array($tokens[($stackPtr + 1)]) === true && $tokens[($stackPtr + 1)][0] === T_WHITESPACE diff --git a/tests/Core/Tokenizer/StableCommentWhitespaceTest.inc b/tests/Core/Tokenizer/StableCommentWhitespaceTest.inc index 6d92b9cfd3..3bf013c66b 100644 --- a/tests/Core/Tokenizer/StableCommentWhitespaceTest.inc +++ b/tests/Core/Tokenizer/StableCommentWhitespaceTest.inc @@ -111,9 +111,29 @@ $prop = 123; /** Comment */ * @tag Comment */ +/* testSingleLineHashComment */ +# Comment + +/* testSingleLineHashCommentTrailing */ +echo 'a'; # Comment + +/* testMultiLineHashComment */ +# Comment1 +# Comment2 +# Comment3 + +/* testMultiLineHashCommentWithIndent */ + # Comment1 + # Comment2 + # Comment3 + /* testSingleLineSlashCommentNoNewLineAtEnd */ // Slash ?> + 'T_WHITESPACE', 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineHashComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineHashCommentTrailing */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineHashComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineHashCommentWithIndent */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment1 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment2 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', ], ], @@ -933,6 +1021,20 @@ public function dataCommentTokenization() [ 'type' => 'T_CLOSE_TAG', 'content' => '?> +', + ], + ], + ], + [ + '/* testSingleLineHashCommentNoNewLineAtEnd */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Hash ', + ], + [ + 'type' => 'T_CLOSE_TAG', + 'content' => '?> ', ], ], diff --git a/tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc b/tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc index 8ca0003924..6ca7026d4b 100644 --- a/tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc +++ b/tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc @@ -39,5 +39,25 @@ echo 'a'; // Comment // Slash ?> + 'T_CLOSE_TAG', 'content' => '?> +', + ], + ], + ], + [ + '/* testSingleLineHashComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineHashCommentTrailing */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineHashComment */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment1 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment2 +', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testMultiLineHashCommentWithIndent */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment1 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment2 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '# Comment3 +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testSingleLineHashCommentNoNewLineAtEnd */', + [ + [ + 'type' => 'T_COMMENT', + 'content' => '# Hash ', + ], + [ + 'type' => 'T_CLOSE_TAG', + 'content' => '?> ', ], ], From 945ea76ae4857e8230e44739eae6ea15225ac24a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 20:16:09 +0200 Subject: [PATCH 062/733] PSR12/NullableTypeDeclaration: allow for static return type PHP 8.0 introduces `static` as a valid return type. This adds support for handling this to the `PSR12.Functions.NullableTypeDeclaration` sniff. Includes unit test. --- .../PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php | 1 + .../PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc | 3 +++ .../Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed | 3 +++ .../PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php | 1 + 4 files changed, 8 insertions(+) diff --git a/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php b/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php index f4d63d4a36..8d90734311 100644 --- a/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php +++ b/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php @@ -26,6 +26,7 @@ class NullableTypeDeclarationSniff implements Sniff T_CALLABLE => true, T_SELF => true, T_PARENT => true, + T_STATIC => true, ]; diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc index 1f4500c5ea..e3a5a775f2 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc @@ -82,3 +82,6 @@ class testInstanceOf() { $bal = $value instanceof static ? CONSTANT_NAME : $value; } } + +// PHP 8.0: static return type. +function testStatic() : ? static {} diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed index 47c5174aa8..6225d1b337 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed @@ -80,3 +80,6 @@ class testInstanceOf() { $bal = $value instanceof static ? CONSTANT_NAME : $value; } } + +// PHP 8.0: static return type. +function testStatic() : ?static {} diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php index 3f217b422a..2344b6d97d 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php @@ -40,6 +40,7 @@ protected function getErrorList() 57 => 2, 58 => 2, 59 => 2, + 87 => 1, ]; }//end getErrorList() From 42052ffee3968a5e80247fb40f059401046e3f8a Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Mon, 31 Aug 2020 18:02:00 -0700 Subject: [PATCH 063/733] Do not attempt to fix ArrayDeclaration.SpaceBeforeComma if there is a comment between --- .../Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php | 11 ++++++++--- .../Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc | 7 +++++++ .../Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed | 7 +++++++ .../Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php | 1 + 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php index de5ee8535d..cb19db696c 100644 --- a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -450,9 +450,14 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array $error = 'Expected 0 spaces before comma; %s found'; $data = [$spaceLength]; - $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpaceBeforeComma', $data); - if ($fix === true) { - $phpcsFile->fixer->replaceToken(($nextToken - 1), ''); + // The error is only fixable if there is only whitespace between the tokens. + if ($prev === $phpcsFile->findPrevious(T_WHITESPACE, ($nextToken - 1), null, true)) { + $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpaceBeforeComma', $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken(($nextToken - 1), ''); + } + } else { + $phpcsFile->addError($error, $nextToken, 'SpaceBeforeComma', $data); } } }//end if diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc index 60ff35d64c..8e93bf47e1 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc @@ -436,6 +436,13 @@ $c]; ['a' => $a, 'b' => $b, 'c' => $c]; +[ + 'foo', + 'bar' + // This is a non-fixable error. + , +]; + // Intentional syntax error. $a = [ 'a' => diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed index 1d17fa0c4d..b81e1face0 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed @@ -470,6 +470,13 @@ $foo = [ 'c' => $c, ]; +[ + 'foo', + 'bar' + // This is a non-fixable error. + , +]; + // Intentional syntax error. $a = [ 'a' => diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php index 818b1b5562..00dd939b85 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php @@ -204,6 +204,7 @@ public function getErrorList($testFile='') 434 => 2, 436 => 2, 437 => 3, + 443 => 1, ]; default: return []; From db79f9fe4c0781564f56b4c0cd438245db79a8e5 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 1 Sep 2020 09:00:57 +1000 Subject: [PATCH 064/733] Changelog for #3070 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 4932f5df71..d0376f0d03 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - PSR12.Functions.NullableTypeDeclaration now supports the PHP8 static return type + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level - Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary From 1024264ce11d0b03511b261771dce3f1e35f1288 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 1 Sep 2020 09:04:25 +1000 Subject: [PATCH 065/733] Changelog for #3072 (ref #3069) --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index d0376f0d03..7830df4572 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - Added support for changes to the way PHP 8.0 tokenizes hash comments + -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required + -- Thanks to Juliette Reinders Folmer for the patch - PSR12.Functions.NullableTypeDeclaration now supports the PHP8 static return type -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level From e37326f6f40c6c3a086a7d135e33b68275b901d6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 1 Sep 2020 02:25:36 +0200 Subject: [PATCH 066/733] Travis: further tweaks for cleaner output This commit does a couple of things: 1. It adds a new build against PHP 7.4 which runs just and only the PEAR package file validation and the XML related checks. That build doesn't need to do a `composer install` and, as it has it's own script, will show just the output of those checks. 2. It adds a separate `script` section to the two builds being run with custom PHP `ini` files (and moves those down in the matrix). The end result of this is: * We can get rid of all the conditions in the `script` section. * Builds will show much cleaner output, so it is easier to find out what went wrong. For the "custom builds", the Travis script now has a separate "name" for these, so they can still be easily identified for what they are, without the environment variables (which is what used to show). --- .travis.yml | 109 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/.travis.yml b/.travis.yml index 432aa23377..e1ae55dc32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,41 +5,89 @@ language: php jobs: fast_finish: true include: + ############################################# + # Builds using the default test script. + ############################################# - php: 5.4 dist: trusty - php: 5.5 dist: trusty - - php: 5.5 - dist: trusty - env: CUSTOM_INI=1 XMLLINT=1 - addons: - apt: - packages: - - libxml2-utils - php: 5.6 - php: 7.0 - - php: 7.0 - env: CUSTOM_INI=1 PEAR_VALIDATE=1 - php: 7.1 - php: 7.2 - php: 7.3 - php: 7.4 - - php: 7.4 - env: PHPSTAN=1 + # Nightly is PHP 8.0 since Feb 2019. + - php: nightly addons: apt: packages: - libonig-dev + + ############################################# + # Builds which don't use the default test script. + ############################################# + # Builds running the basic tests with different PHP ini settings. + - php: 5.5 + name: "PHP: 5.5 | Unit tests with custom PHP ini" + dist: trusty + before_script: + - phpenv config-add php5-testingConfig.ini script: - - composer require --dev phpstan/phpstan - - php vendor/bin/phpstan analyse --configuration=phpstan.neon + - php bin/phpcs --config-set php_path php + - vendor/bin/phpunit tests/AllTests.php + - php: 7.0 + name: "PHP: 7.4 | Unit tests with custom PHP ini" + before_script: + - phpenv config-add php7-testingConfig.ini + script: + - php bin/phpcs --config-set php_path php + - vendor/bin/phpunit tests/AllTests.php - # Nightly is PHP 8.0 since Feb 2019. - - php: nightly + # Build running just the PEAR package file and XML file validation and code style check. + - php: 7.4 + name: "PHP: 7.4 | Pear + XML validate" + addons: + apt: + packages: + - libxml2-utils + before_install: + - export XMLLINT_INDENT=" " + - phpenv config-rm xdebug.ini || echo 'No xdebug config.' + install: + - curl -O https://www.w3.org/2012/04/XMLSchema.xsd + script: + # Validate the Pear Package file contents. + - php scripts/validate-pear-package.php + # Validate the xml ruleset files. + # @link http://xmlsoft.org/xmllint.html + - xmllint --noout --schema phpcs.xsd ./src/Standards/*/ruleset.xml + - xmllint --noout --schema ./XMLSchema.xsd ./phpcs.xsd + # Check the code-style consistency of the xml files. + - diff -B ./phpcs.xml.dist <(xmllint --format "./phpcs.xml.dist") + - diff -B ./src/Standards/Generic/ruleset.xml <(xmllint --format "./src/Standards/Generic/ruleset.xml") + - diff -B ./src/Standards/MySource/ruleset.xml <(xmllint --format "./src/Standards/MySource/ruleset.xml") + - diff -B ./src/Standards/PEAR/ruleset.xml <(xmllint --format "./src/Standards/PEAR/ruleset.xml") + - diff -B ./src/Standards/PSR1/ruleset.xml <(xmllint --format "./src/Standards/PSR1/ruleset.xml") + - diff -B ./src/Standards/PSR2/ruleset.xml <(xmllint --format "./src/Standards/PSR2/ruleset.xml") + - diff -B ./src/Standards/PSR12/ruleset.xml <(xmllint --format "./src/Standards/PSR12/ruleset.xml") + - diff -B ./src/Standards/Squiz/ruleset.xml <(xmllint --format "./src/Standards/Squiz/ruleset.xml") + - diff -B ./src/Standards/Zend/ruleset.xml <(xmllint --format "./src/Standards/Zend/ruleset.xml") + + # Build running just and only PHPStan. + - php: 7.4 + name: "PHP: 7.4 | PHPStan" + env: PHPSTAN=1 addons: apt: packages: - libonig-dev + before_install: + - phpenv config-rm xdebug.ini || echo 'No xdebug config.' + script: + - composer require --dev phpstan/phpstan + - php vendor/bin/phpstan analyse --configuration=phpstan.neon allow_failures: - php: 7.4 @@ -47,7 +95,6 @@ jobs: - php: nightly before_install: - - export XMLLINT_INDENT=" " # Speed up build time by disabling Xdebug when its not needed. - phpenv config-rm xdebug.ini || echo 'No xdebug config.' # PHPUnit 8.x is not (yet) supported, so prevent issues with Travis images using it. @@ -59,31 +106,11 @@ before_install: composer install --ignore-platform-reqs fi -before_script: - - if [[ $CUSTOM_INI == "1" && ${TRAVIS_PHP_VERSION:0:1} == "5" ]]; then phpenv config-add php5-testingConfig.ini; fi - - if [[ $CUSTOM_INI == "1" ]] && [[ ${TRAVIS_PHP_VERSION:0:1} == "7" || $TRAVIS_PHP_VERSION == "nightly" ]]; then phpenv config-add php7-testingConfig.ini; fi - script: - php bin/phpcs --config-set php_path php - vendor/bin/phpunit tests/AllTests.php - - if [[ $CUSTOM_INI != "1" ]]; then php bin/phpcs --no-cache --parallel=1; fi - - if [[ $CUSTOM_INI != "1" && $TRAVIS_PHP_VERSION != "nightly" ]]; then pear package-validate package.xml; fi - - if [[ $PEAR_VALIDATE == "1" ]]; then php scripts/validate-pear-package.php; fi - - if [[ $CUSTOM_INI != "1" ]]; then composer validate --no-check-all --strict; fi - - if [[ $CUSTOM_INI != "1" ]]; then php scripts/build-phar.php; fi - - if [[ $CUSTOM_INI != "1" ]]; then php phpcs.phar; fi - # Validate the xml ruleset files. - # @link http://xmlsoft.org/xmllint.html - - if [[ $XMLLINT == "1" ]]; then xmllint --noout --schema phpcs.xsd ./src/Standards/*/ruleset.xml; fi - - if [[ $XMLLINT == "1" ]]; then curl -O https://www.w3.org/2012/04/XMLSchema.xsd; fi - - if [[ $XMLLINT == "1" ]]; then xmllint --noout --schema ./XMLSchema.xsd ./phpcs.xsd; fi - # Check the code-style consistency of the xml files. - - if [[ $XMLLINT == "1" ]]; then diff -B ./phpcs.xml.dist <(xmllint --format "./phpcs.xml.dist"); fi - - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/Generic/ruleset.xml <(xmllint --format "./src/Standards/Generic/ruleset.xml"); fi - - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/MySource/ruleset.xml <(xmllint --format "./src/Standards/MySource/ruleset.xml"); fi - - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/PEAR/ruleset.xml <(xmllint --format "./src/Standards/PEAR/ruleset.xml"); fi - - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/PSR1/ruleset.xml <(xmllint --format "./src/Standards/PSR1/ruleset.xml"); fi - - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/PSR2/ruleset.xml <(xmllint --format "./src/Standards/PSR2/ruleset.xml"); fi - - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/PSR12/ruleset.xml <(xmllint --format "./src/Standards/PSR12/ruleset.xml"); fi - - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/Squiz/ruleset.xml <(xmllint --format "./src/Standards/Squiz/ruleset.xml"); fi - - if [[ $XMLLINT == "1" ]]; then diff -B ./src/Standards/Zend/ruleset.xml <(xmllint --format "./src/Standards/Zend/ruleset.xml"); fi + - php bin/phpcs --no-cache --parallel=1 + - pear package-validate package.xml + - composer validate --no-check-all --strict + - php scripts/build-phar.php + - php phpcs.phar From d2deeddbeb89c39e013d295d6a497af2ee68f848 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 2 Sep 2020 08:55:28 +1000 Subject: [PATCH 067/733] Changelog for #3046 (ref #3040) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 9f87d0da25..50368d146b 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - The PHP 8.0 T_NULLSAFE_OBJECT_OPERATOR token has been made available for older versions + -- Thanks to Juliette Reinders Folmer for the patch - Added support for changes to the way PHP 8.0 tokenizes hash comments -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required -- Thanks to Juliette Reinders Folmer for the patch From f378d2ac8e5dd042c36909c7fb92a2cdc3da82f4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 2 Sep 2020 09:09:24 +1000 Subject: [PATCH 068/733] Changelog for #3007 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 50368d146b..e88cfd2b11 100644 --- a/package.xml +++ b/package.xml @@ -36,6 +36,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level - Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary + - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same + -- Thanks to Steve Talbot for the patch From 8a17a80c0c7d0cee0dca54c284dc2a54cca534cf Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:48:24 +0200 Subject: [PATCH 069/733] PHP 8.0 | Generic/UpperCaseConstantName: allow for nullsafe object operator Includes unit test. --- .../Sniffs/NamingConventions/UpperCaseConstantNameSniff.php | 1 + .../Tests/NamingConventions/UpperCaseConstantNameUnitTest.inc | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php index e4173d98d0..db50eb56bb 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php @@ -83,6 +83,7 @@ public function process(File $phpcsFile, $stackPtr) $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR || $tokens[$prev]['code'] === T_DOUBLE_COLON + || $tokens[$prev]['code'] === T_NULLSAFE_OBJECT_OPERATOR ) { return; } diff --git a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.inc index 7c33bbf119..6bf96f251c 100644 --- a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.inc +++ b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.inc @@ -29,3 +29,5 @@ class ClassConstBowOutTest { const // phpcs:ignore Standard.Category.Sniff some_constant = 2; } + +$foo->getBar()?->define('foo'); From bf708e65cabf9d3b30e1542da58d11641a09f872 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:48:56 +0200 Subject: [PATCH 070/733] PHP 8.0 | Generic/ForbiddenFunctions: allow for nullsafe object operator Includes unit test. --- .../Sniffs/PHP/ForbiddenFunctionsSniff.php | 25 ++++++++++--------- .../Tests/PHP/ForbiddenFunctionsUnitTest.inc | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php b/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php index d8b8bcf498..1a10b15320 100644 --- a/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php @@ -120,18 +120,19 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); $ignore = [ - T_DOUBLE_COLON => true, - T_OBJECT_OPERATOR => true, - T_FUNCTION => true, - T_CONST => true, - T_PUBLIC => true, - T_PRIVATE => true, - T_PROTECTED => true, - T_AS => true, - T_NEW => true, - T_INSTEADOF => true, - T_NS_SEPARATOR => true, - T_IMPLEMENTS => true, + T_DOUBLE_COLON => true, + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_FUNCTION => true, + T_CONST => true, + T_PUBLIC => true, + T_PRIVATE => true, + T_PROTECTED => true, + T_AS => true, + T_NEW => true, + T_INSTEADOF => true, + T_NS_SEPARATOR => true, + T_IMPLEMENTS => true, ]; $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); diff --git a/src/Standards/Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc b/src/Standards/Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc index 4659b73289..b30f0dde94 100644 --- a/src/Standards/Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc @@ -54,4 +54,4 @@ class SizeOf implements Something {} function mymodule_form_callback(SizeOf $sizeof) { } -?> +$size = $class?->sizeof($array); From 1124ec8383d2c8b794d46c708cf1ded2625f5e25 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:49:27 +0200 Subject: [PATCH 071/733] PHP 8.0 | Generic/SAPIUsage: allow for nullsafe object operator Includes unit test. --- src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php | 9 +++++---- src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php b/src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php index b768c4b147..19d58572c6 100644 --- a/src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php @@ -42,10 +42,11 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); $ignore = [ - T_DOUBLE_COLON => true, - T_OBJECT_OPERATOR => true, - T_FUNCTION => true, - T_CONST => true, + T_DOUBLE_COLON => true, + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_FUNCTION => true, + T_CONST => true, ]; $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); diff --git a/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc b/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc index 8f378c8db1..f0f350f374 100644 --- a/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/SAPIUsageUnitTest.inc @@ -2,3 +2,4 @@ if (php_sapi_name() !== 'cli') {} if (PHP_SAPI !== 'cli') {} if ($object->php_sapi_name() === true) {} +if ($object?->php_sapi_name() === true) {} From 7e6daafc7688d072a4cdafec1f2e5e9349e7f8d2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 06:17:42 +0200 Subject: [PATCH 072/733] PHP 8.0 | PSR1/SideEffects: allow for nullsafe object operator Includes unit tests. Note: adding the token to the second condition (test 15/16) seems redundant and the original `T_OBJECT_OPERATOR` could be removed here too as, if an instantiated object is seen before the `defined()`, a side-effect would already have registered for the variable anyway. --- package.xml | 4 ++++ src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php | 5 ++++- src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.13.inc | 2 ++ src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.14.inc | 2 ++ src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.15.inc | 2 ++ src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc | 2 ++ src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.php | 2 ++ 7 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.13.inc create mode 100644 src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.14.inc create mode 100644 src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.15.inc create mode 100644 src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc diff --git a/package.xml b/package.xml index 50368d146b..bfbac68c3c 100644 --- a/package.xml +++ b/package.xml @@ -1005,6 +1005,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + diff --git a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php index 12508fad4c..985e645ed3 100644 --- a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php +++ b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php @@ -193,6 +193,7 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens) ) { $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), null, true); if ($tokens[$prev]['code'] !== T_OBJECT_OPERATOR + && $tokens[$prev]['code'] !== T_NULLSAFE_OBJECT_OPERATOR && $tokens[$prev]['code'] !== T_DOUBLE_COLON && $tokens[$prev]['code'] !== T_FUNCTION ) { @@ -216,11 +217,13 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens) && strtolower($tokens[$i]['content']) === 'defined' ) { $openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true); - if ($tokens[$openBracket]['code'] === T_OPEN_PARENTHESIS + if ($openBracket !== false + && $tokens[$openBracket]['code'] === T_OPEN_PARENTHESIS && isset($tokens[$openBracket]['parenthesis_closer']) === true ) { $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), null, true); if ($tokens[$prev]['code'] !== T_OBJECT_OPERATOR + && $tokens[$prev]['code'] !== T_NULLSAFE_OBJECT_OPERATOR && $tokens[$prev]['code'] !== T_DOUBLE_COLON && $tokens[$prev]['code'] !== T_FUNCTION ) { diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.13.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.13.inc new file mode 100644 index 0000000000..9c1de6267b --- /dev/null +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.13.inc @@ -0,0 +1,2 @@ +define("MAXSIZE", 100); diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.14.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.14.inc new file mode 100644 index 0000000000..9499885b69 --- /dev/null +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.14.inc @@ -0,0 +1,2 @@ +define("MAXSIZE", 100); diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.15.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.15.inc new file mode 100644 index 0000000000..0500d10e6b --- /dev/null +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.15.inc @@ -0,0 +1,2 @@ +defined('MINSIZE') or define("MAXSIZE", 100); diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc new file mode 100644 index 0000000000..588ece5840 --- /dev/null +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.16.inc @@ -0,0 +1,2 @@ +defined('MINSIZE') or define("MAXSIZE", 100); diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.php b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.php index b7728d1074..013bf7ce5a 100644 --- a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.php +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.php @@ -67,6 +67,8 @@ public function getWarningList($testFile='') case 'SideEffectsUnitTest.5.inc': case 'SideEffectsUnitTest.10.inc': case 'SideEffectsUnitTest.12.inc': + case 'SideEffectsUnitTest.15.inc': + case 'SideEffectsUnitTest.16.inc': return [1 => 1]; default: return []; From 877e89f5f1db8d43f4044d6da32aad33e7c06b39 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:51:35 +0200 Subject: [PATCH 073/733] PHP 8.0 | PSR12/ClassInstantiation: allow for nullsafe object operator Includes unit tests. --- .../Sniffs/Classes/ClassInstantiationSniff.php | 17 +++++++++-------- .../Classes/ClassInstantiationUnitTest.inc | 4 ++++ .../ClassInstantiationUnitTest.inc.fixed | 4 ++++ .../Classes/ClassInstantiationUnitTest.php | 2 ++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php index f474a26b8d..804ecfe1c7 100644 --- a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php +++ b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php @@ -44,14 +44,15 @@ public function process(File $phpcsFile, $stackPtr) // Find the class name. $allowed = [ - T_STRING => T_STRING, - T_NS_SEPARATOR => T_NS_SEPARATOR, - T_SELF => T_SELF, - T_STATIC => T_STATIC, - T_VARIABLE => T_VARIABLE, - T_DOLLAR => T_DOLLAR, - T_OBJECT_OPERATOR => T_OBJECT_OPERATOR, - T_DOUBLE_COLON => T_DOUBLE_COLON, + T_STRING => T_STRING, + T_NS_SEPARATOR => T_NS_SEPARATOR, + T_SELF => T_SELF, + T_STATIC => T_STATIC, + T_VARIABLE => T_VARIABLE, + T_DOLLAR => T_DOLLAR, + T_OBJECT_OPERATOR => T_OBJECT_OPERATOR, + T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR, + T_DOUBLE_COLON => T_DOUBLE_COLON, ]; $allowed += Tokens::$emptyTokens; diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc index dcd849e6bd..ca0dc3614a 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc @@ -32,3 +32,7 @@ $foo = new $bar['a'] [$baz['a']/* comment */ ['b']]['b']; $a = new self::$transport[$cap_string]; $renderer = new $this->inline_diff_renderer; $a = new ${$varHoldingClassName}; + +$class = new $obj?->classname(); +$class = new $obj?->classname; +$class = new ${$obj?->classname}; diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed index d8438dac13..a4d209cc16 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed @@ -32,3 +32,7 @@ $foo = new $bar['a'] [$baz['a']/* comment */ ['b']]['b'](); $a = new self::$transport[$cap_string](); $renderer = new $this->inline_diff_renderer(); $a = new ${$varHoldingClassName}(); + +$class = new $obj?->classname(); +$class = new $obj?->classname(); +$class = new ${$obj?->classname}(); diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php index 40c90c8306..3fb1ab992b 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.php @@ -41,6 +41,8 @@ public function getErrorList() 32 => 1, 33 => 1, 34 => 1, + 37 => 1, + 38 => 1, ]; }//end getErrorList() From 9d58172b38c7041d6a8f0de59c187083bc871504 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:52:26 +0200 Subject: [PATCH 074/733] PHP 8.0 | Squiz/OperatorBracket: allow for nullsafe object operator Includes unit test. --- src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php | 2 ++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc | 2 ++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed | 2 ++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.php | 1 + 4 files changed, 7 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index 3d67f9b78f..f19d9a275d 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -140,6 +140,7 @@ public function process(File $phpcsFile, $stackPtr) T_SELF, T_STATIC, T_OBJECT_OPERATOR, + T_NULLSAFE_OBJECT_OPERATOR, T_DOUBLE_COLON, T_OPEN_SQUARE_BRACKET, T_CLOSE_SQUARE_BRACKET, @@ -283,6 +284,7 @@ public function addMissingBracketsError($phpcsFile, $stackPtr) T_SELF => true, T_STATIC => true, T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, T_DOUBLE_COLON => true, T_MODULUS => true, T_ISSET => true, diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc index e9c8061bfd..d4e1666931 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc @@ -170,3 +170,5 @@ $value = (binary) $blah + b"binary $foo"; $test = (1 * static::TEST); $test = myfunc(1 * static::TEST); + +$errorPos = $params[$x]?->getLine() + $commentStart; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed index a0e0a895b3..7776cd5f2c 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed @@ -170,3 +170,5 @@ $value = ((binary) $blah + b"binary $foo"); $test = (1 * static::TEST); $test = myfunc(1 * static::TEST); + +$errorPos = ($params[$x]?->getLine() + $commentStart); diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php index 651319b877..925eca14c2 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php @@ -66,6 +66,7 @@ public function getErrorList($testFile='OperatorBracketUnitTest.inc') 163 => 2, 165 => 2, 169 => 1, + 174 => 1, ]; break; case 'OperatorBracketUnitTest.js': From ef289d92a54f45b29ff858d669487603642e6309 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:53:07 +0200 Subject: [PATCH 075/733] PHP 8.0 | Squiz/ValidVariableName: allow for nullsafe object operator Includes unit test. --- .../Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php | 4 +++- .../Tests/NamingConventions/ValidVariableNameUnitTest.inc | 4 ++++ .../Tests/NamingConventions/ValidVariableNameUnitTest.php | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php b/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php index ed2505817b..5f17a08b53 100644 --- a/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php +++ b/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -38,7 +38,9 @@ protected function processVariable(File $phpcsFile, $stackPtr) } $objOperator = $phpcsFile->findNext([T_WHITESPACE], ($stackPtr + 1), null, true); - if ($tokens[$objOperator]['code'] === T_OBJECT_OPERATOR) { + if ($tokens[$objOperator]['code'] === T_OBJECT_OPERATOR + || $tokens[$objOperator]['code'] === T_NULLSAFE_OBJECT_OPERATOR + ) { // Check to see if we are using a variable from an object. $var = $phpcsFile->findNext([T_WHITESPACE], ($objOperator + 1), null, true); if ($tokens[$var]['code'] === T_STRING) { diff --git a/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.inc b/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.inc index 5692d6b33b..c7b8a2b6b2 100644 --- a/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.inc +++ b/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.inc @@ -142,3 +142,7 @@ $anonClass = new class() { } }; +echo $obj?->varName; +echo $obj?->var_name; +echo $obj?->varname; +echo $obj?->_varName; diff --git a/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.php b/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.php index 4ec65e5e1a..aaa9d099a7 100644 --- a/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.php +++ b/src/Standards/Squiz/Tests/NamingConventions/ValidVariableNameUnitTest.php @@ -60,6 +60,7 @@ public function getErrorList() 123 => 1, 138 => 1, 141 => 1, + 146 => 1, ]; return $errors; From 616eb38061602fea3d33257a5aa3a1af55076ced Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:53:37 +0200 Subject: [PATCH 076/733] PHP 8.0 | Squiz/IncrementDecrementUsage: allow for nullsafe object operator Includes unit test. Note: there is quite a lot more which can do with improvement in this sniff, as the sniff is not code style independent, nor does it take all allowed syntaxes into account, such as array in/decrementing `$a[0]++` or nested property in/decrementing `--$obj->prop->nested` and more along those lines, but that's outside the scope of this PR. --- .../Squiz/Sniffs/Operators/IncrementDecrementUsageSniff.php | 3 ++- .../Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.inc | 4 +++- .../Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.php | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Operators/IncrementDecrementUsageSniff.php b/src/Standards/Squiz/Sniffs/Operators/IncrementDecrementUsageSniff.php index c3a8b3915e..cb3778453a 100644 --- a/src/Standards/Squiz/Sniffs/Operators/IncrementDecrementUsageSniff.php +++ b/src/Standards/Squiz/Sniffs/Operators/IncrementDecrementUsageSniff.php @@ -74,7 +74,8 @@ protected function processIncDec($phpcsFile, $stackPtr) // start looking for other operators. if ($tokens[($stackPtr - 1)]['code'] === T_VARIABLE || ($tokens[($stackPtr - 1)]['code'] === T_STRING - && $tokens[($stackPtr - 2)]['code'] === T_OBJECT_OPERATOR) + && ($tokens[($stackPtr - 2)]['code'] === T_OBJECT_OPERATOR + || $tokens[($stackPtr - 2)]['code'] === T_NULLSAFE_OBJECT_OPERATOR)) ) { $start = ($stackPtr + 1); } else { diff --git a/src/Standards/Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.inc b/src/Standards/Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.inc index e8d3beb94c..a4f82d1a8c 100644 --- a/src/Standards/Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.inc +++ b/src/Standards/Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.inc @@ -37,4 +37,6 @@ $var = (1 + $var); $expected[$i]['sort_order'] = ($i + 1); $expected[($i + 1)]['sort_order'] = ($i + 1); -$id = $id.($this->i++).$id; +$id = $id.($obj->i++).$id; +$id = $obj?->i++.$id; +$id = $obj?->i++*10; diff --git a/src/Standards/Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.php b/src/Standards/Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.php index 768911b508..3846905da0 100644 --- a/src/Standards/Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.php +++ b/src/Standards/Squiz/Tests/Operators/IncrementDecrementUsageUnitTest.php @@ -35,6 +35,8 @@ public function getErrorList() 27 => 1, 29 => 1, 31 => 1, + 41 => 1, + 42 => 1, ]; }//end getErrorList() From 3a094b732de00a4d922ec6c71cfef9bcc7a421ed Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:54:36 +0200 Subject: [PATCH 077/733] PHP 8.0 | Squiz/DisallowSizeFunctionsInLoops: allow for nullsafe object operator Includes unit test. --- .../Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php | 4 +++- .../Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php index cb1436631a..a0f1161c6a 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php @@ -95,7 +95,9 @@ public function process(File $phpcsFile, $stackPtr) $functionName = 'object.'.$functionName; } else { // Make sure it isn't a member var. - if ($tokens[($i - 1)]['code'] === T_OBJECT_OPERATOR) { + if ($tokens[($i - 1)]['code'] === T_OBJECT_OPERATOR + || $tokens[($i - 1)]['code'] === T_NULLSAFE_OBJECT_OPERATOR + ) { continue; } diff --git a/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc index 76ec49962c..56802e37aa 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowSizeFunctionsInLoopsUnitTest.inc @@ -55,4 +55,4 @@ do { } while($a->count); for ($i = 0; $i < $a->count; $i++) {} -?> +for ($i = 0; $i < $a?->count; $i++) {} From a222ec65fca98111f0f2cc3943010ca318dc1f51 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:58:58 +0200 Subject: [PATCH 078/733] PHP 8.0 | Squiz/LowercasePHPFunctions: allow for nullsafe object operator Includes unit test. --- src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php | 4 +++- .../Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc | 2 ++ .../Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php b/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php index 486edcc01f..519b7f6ccc 100644 --- a/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php @@ -133,7 +133,9 @@ public function process(File $phpcsFile, $stackPtr) return; } - if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR) { + if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR + || $tokens[$prev]['code'] === T_NULLSAFE_OBJECT_OPERATOR + ) { // Not an inbuilt function. return; } diff --git a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc index 330919d1c0..c67381ad8b 100644 --- a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc @@ -39,3 +39,5 @@ $callToNamespacedFunction = MyNamespace /* phpcs:ignore Standard */ \STR_REPEAT( $callToNamespacedFunction = namespace\STR_REPEAT($a, 2); // Could potentially be false negative. $filePath = new \File($path); + +$count = $object?->Count(); diff --git a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed index eae5b4ade4..40507c0404 100644 --- a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed @@ -39,3 +39,5 @@ $callToNamespacedFunction = MyNamespace /* phpcs:ignore Standard */ \STR_REPEAT( $callToNamespacedFunction = namespace\STR_REPEAT($a, 2); // Could potentially be false negative. $filePath = new \File($path); + +$count = $object?->Count(); From a4f0110268dbd3ed59845fe17caadda03452b468 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:59:25 +0200 Subject: [PATCH 079/733] PHP 8.0 | Squiz/ObjectOperatorSpacing: sniff for nullsafe object operator This adds sniffing for the spacing around nullsafe object operators to the sniff. Includes unit test. --- .../Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php | 1 + .../Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc | 4 ++++ .../Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc.fixed | 4 ++++ .../Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php | 2 ++ 4 files changed, 11 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php index a1e83aadd1..cc5db16446 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php @@ -33,6 +33,7 @@ public function register() return [ T_OBJECT_OPERATOR, T_DOUBLE_COLON, + T_NULLSAFE_OBJECT_OPERATOR, ]; }//end register() diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc index bcea3dc7a8..67f8ee1447 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc @@ -46,3 +46,7 @@ thisObject:: testThis(); // phpcs:set Squiz.WhiteSpace.ObjectOperatorSpacing ignoreNewlines false + +$this?->testThis(); +$this?-> testThis(); +$this ?-> testThis(); diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc.fixed index fdeb6f3140..730b8e4a72 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.inc.fixed @@ -42,3 +42,7 @@ thisObject:: testThis(); // phpcs:set Squiz.WhiteSpace.ObjectOperatorSpacing ignoreNewlines false + +$this?->testThis(); +$this?->testThis(); +$this?->testThis(); diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php index 48208cef32..82a4056f7e 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php @@ -43,6 +43,8 @@ public function getErrorList() 39 => 1, 40 => 2, 42 => 2, + 51 => 1, + 52 => 2, ]; }//end getErrorList() From 49a8d01933a8a029e68f6895eb5347c7f1dbef36 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 02:00:04 +0200 Subject: [PATCH 080/733] PHP 8.0 | Zend/ValidVariableName: allow for nullsafe object operator Includes unit test. --- .../Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php | 4 +++- .../Tests/NamingConventions/ValidVariableNameUnitTest.inc | 4 ++++ .../Tests/NamingConventions/ValidVariableNameUnitTest.php | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php b/src/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php index ea98fd2847..267cd0ad62 100644 --- a/src/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php +++ b/src/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -38,7 +38,9 @@ protected function processVariable(File $phpcsFile, $stackPtr) } $objOperator = $phpcsFile->findNext([T_WHITESPACE], ($stackPtr + 1), null, true); - if ($tokens[$objOperator]['code'] === T_OBJECT_OPERATOR) { + if ($tokens[$objOperator]['code'] === T_OBJECT_OPERATOR + || $tokens[$objOperator]['code'] === T_NULLSAFE_OBJECT_OPERATOR + ) { // Check to see if we are using a variable from an object. $var = $phpcsFile->findNext([T_WHITESPACE], ($objOperator + 1), null, true); if ($tokens[$var]['code'] === T_STRING) { diff --git a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc index 1bf486cab6..8ed8509d2a 100644 --- a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc +++ b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.inc @@ -116,3 +116,7 @@ $anonClass = new class() { $bar_foo = 3; } }; + +echo $obj?->varName; +echo $obj?->var_name; +echo $obj?->varName; diff --git a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php index d22b24fd3f..916b334f1c 100644 --- a/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php +++ b/src/Standards/Zend/Tests/NamingConventions/ValidVariableNameUnitTest.php @@ -53,6 +53,7 @@ public function getErrorList() 99 => 1, 113 => 1, 116 => 1, + 121 => 1, ]; }//end getErrorList() From 4dbd3a9efbfea096636b35e4adde7d74417e5ffc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 2 Sep 2020 01:34:29 +0200 Subject: [PATCH 081/733] Travis: retry composer install on failure The builds are failing a little too often for my taste on the below error. ``` [Composer\Downloader\TransportException] Peer fingerprint did not match ``` I'm prefixing the `composer install` commands now with `travis_retry` in an attempt to get round this problem. Ref: * https://docs.travis-ci.com/user/common-build-problems/#timeouts-installing-dependencies --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 432aa23377..f138ea1906 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,10 +53,10 @@ before_install: # PHPUnit 8.x is not (yet) supported, so prevent issues with Travis images using it. - | if [[ $TRAVIS_PHP_VERSION != "nightly" ]]; then - composer install + travis_retry composer install elif [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then // Allow installing "incompatible" PHPUnit version on PHP 8/nightly. - composer install --ignore-platform-reqs + travis_retry composer install --ignore-platform-reqs fi before_script: From 1c59b291c23c4f43f95356831e1b779aff16e400 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 00:00:51 +0200 Subject: [PATCH 082/733] Tokenizer: fix nullable type tokenization with namespace operator The `namespace` keyword as an operator is perfectly valid to be used as part of a type declaration. This usage was so far not supported in the tokenizer for the determination whether a `?` is a nullable type token or a ternary. Fixed now. --- src/Tokenizers/PHP.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5180a5f0e8..085d74990a 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1149,6 +1149,7 @@ protected function tokenize($string) if ($tokenType === T_STRING || $tokenType === T_ARRAY + || $tokenType === T_NAMESPACE || $tokenType === T_NS_SEPARATOR ) { $lastRelevantNonEmpty = $tokenType; @@ -1382,6 +1383,7 @@ function return types. We want to keep the parenthesis map clean, T_CALLABLE => T_CALLABLE, T_SELF => T_SELF, T_PARENT => T_PARENT, + T_NAMESPACE => T_NAMESPACE, T_NS_SEPARATOR => T_NS_SEPARATOR, ]; From db43214122992786b97d329c6394bd3d860b1ff2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 00:56:56 +0200 Subject: [PATCH 083/733] Tokenizer: fix handling of namespace operator in types for arrow functions The `namespace` keyword as an operator is perfectly valid to be used as part of a type declaration. This usage was so far not supported in the tokenizer for the arrow function backfill. Fixed now. --- src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/BackfillFnTokenTest.inc | 3 +++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 28 ++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 085d74990a..898536d728 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1898,6 +1898,7 @@ protected function processAdditional() T_STRING => T_STRING, T_ARRAY => T_ARRAY, T_COLON => T_COLON, + T_NAMESPACE => T_NAMESPACE, T_NS_SEPARATOR => T_NS_SEPARATOR, T_NULLABLE => T_NULLABLE, T_CALLABLE => T_CALLABLE, diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index 083fe6979c..46c165a2d2 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -63,6 +63,9 @@ $a = fn($x) => yield 'k' => $x; /* testNullableNamespace */ $a = fn(?\DateTime $x) : ?\DateTime => $x; +/* testNamespaceOperatorInTypes */ +$fn = fn(namespace\Foo $a) : ?namespace\Foo => $a; + /* testSelfReturnType */ fn(self $a) : self => $a; diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 55d378333f..9ef8dad442 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -465,6 +465,34 @@ public function testNullableNamespace() }//end testNullableNamespace() + /** + * Test arrow functions that use the namespace operator in the return type. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testNamespaceOperatorInTypes() + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken('/* testNamespaceOperatorInTypes */', T_FN); + $this->backfillHelper($token); + + $this->assertSame($tokens[$token]['scope_opener'], ($token + 16), 'Scope opener is not the arrow token'); + $this->assertSame($tokens[$token]['scope_closer'], ($token + 19), 'Scope closer is not the semicolon token'); + + $opener = $tokens[$token]['scope_opener']; + $this->assertSame($tokens[$opener]['scope_opener'], ($token + 16), 'Opener scope opener is not the arrow token'); + $this->assertSame($tokens[$opener]['scope_closer'], ($token + 19), 'Opener scope closer is not the semicolon token'); + + $closer = $tokens[$token]['scope_closer']; + $this->assertSame($tokens[$closer]['scope_opener'], ($token + 16), 'Closer scope opener is not the arrow token'); + $this->assertSame($tokens[$closer]['scope_closer'], ($token + 19), 'Closer scope closer is not the semicolon token'); + + }//end testNamespaceOperatorInTypes() + + /** * Test arrow functions that use self/parent/callable/array/static return types. * From f2a5abfea738baaeb271a83d3adabd9f09e6d637 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 30 Aug 2020 23:55:08 +0200 Subject: [PATCH 084/733] File::get[Method|Member][Properties|Parameters](): add support for namespace relative type declarations The `namespace` keyword as an operator is perfectly valid to be used as part of a type declaration. This usage was so far not supported in the `File::getMethodParameters()`, `File::getMethodProperties()` and the `File::getMemberProperties()` methods. Fixes now. Including adding a unit test for each. --- src/Files/File.php | 3 +++ tests/Core/File/GetMemberPropertiesTest.inc | 5 +++++ tests/Core/File/GetMemberPropertiesTest.php | 10 +++++++++ tests/Core/File/GetMethodParametersTest.inc | 3 +++ tests/Core/File/GetMethodParametersTest.php | 22 ++++++++++++++++++++ tests/Core/File/GetMethodPropertiesTest.inc | 3 +++ tests/Core/File/GetMethodPropertiesTest.php | 23 +++++++++++++++++++++ 7 files changed, 69 insertions(+) diff --git a/src/Files/File.php b/src/Files/File.php index 9a34bd0f43..1b82ceb1b8 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1441,6 +1441,7 @@ public function getMethodParameters($stackPtr) $typeHintEndToken = $i; } break; + case T_NAMESPACE: case T_NS_SEPARATOR: // Part of a type hint or default value. if ($defaultStart === null) { @@ -1630,6 +1631,7 @@ public function getMethodProperties($stackPtr) T_SELF => T_SELF, T_PARENT => T_PARENT, T_STATIC => T_STATIC, + T_NAMESPACE => T_NAMESPACE, T_NS_SEPARATOR => T_NS_SEPARATOR, ]; @@ -1813,6 +1815,7 @@ public function getMemberProperties($stackPtr) T_CALLABLE => T_CALLABLE, T_SELF => T_SELF, T_PARENT => T_PARENT, + T_NAMESPACE => T_NAMESPACE, T_NS_SEPARATOR => T_NS_SEPARATOR, ]; diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index 8c87c64035..0a511e588d 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -188,3 +188,8 @@ class PHP8Mixed { // Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method. private ?mixed $nullableMixed; } + +class NSOperatorInType { + /* testNamespaceOperatorTypeHint */ + public ?namespace\Name $prop; +} diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index 3e8d164dcd..dfee43e203 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -479,6 +479,16 @@ public function dataGetMemberProperties() 'nullable_type' => true, ], ], + [ + '/* testNamespaceOperatorTypeHint */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => '?namespace\Name', + 'nullable_type' => true, + ], + ], ]; }//end dataGetMemberProperties() diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index e4d5bfca74..4ffd44221f 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -38,3 +38,6 @@ function mixedTypeHint(mixed &...$var1) {} /* testPHP8MixedTypeHintNullable */ // Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method. function mixedTypeHintNullable(?Mixed $var1) {} + +/* testNamespaceOperatorTypeHint */ +function namespaceOperatorTypeHint(?namespace\Name $var1) {} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index f5d43c1a63..92f51959e6 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -318,6 +318,28 @@ public function testPHP8MixedTypeHintNullable() }//end testPHP8MixedTypeHintNullable() + /** + * Verify recognition of type declarations using the namespace operator. + * + * @return void + */ + public function testNamespaceOperatorTypeHint() + { + $expected = []; + $expected[0] = [ + 'name' => '$var1', + 'content' => '?namespace\Name $var1', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?namespace\Name', + 'nullable_type' => true, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testNamespaceOperatorTypeHint() + + /** * Test helper. * diff --git a/tests/Core/File/GetMethodPropertiesTest.inc b/tests/Core/File/GetMethodPropertiesTest.inc index c268ece3a9..82c032efa0 100644 --- a/tests/Core/File/GetMethodPropertiesTest.inc +++ b/tests/Core/File/GetMethodPropertiesTest.inc @@ -80,3 +80,6 @@ function mixedTypeHint() :mixed {} /* testPHP8MixedTypeHintNullable */ // Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method. function mixedTypeHintNullable(): ?mixed {} + +/* testNamespaceOperatorTypeHint */ +function namespaceOperatorTypeHint() : ?namespace\Name {} diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php index 36d57e7301..ee11fc2b9f 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -452,6 +452,29 @@ public function testPHP8MixedTypeHintNullable() }//end testPHP8MixedTypeHintNullable() + /** + * Test a function with return type using the namespace operator. + * + * @return void + */ + public function testNamespaceOperatorTypeHint() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => '?namespace\Name', + 'nullable_return_type' => true, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testNamespaceOperatorTypeHint() + + /** * Test helper. * From 16d2cd1c9ebcf9488ce185558bbd3def31b3f148 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 03:53:46 +0200 Subject: [PATCH 085/733] Tokenizer::recurseScopeMap(): fix scope setting for namespace operators The namespace keyword as a scope opener can never be within another scope, so we can safely ignore it when encountered as it will always be the namespace keyword used as an operator in that case. Includes dedicated unit tests. --- package.xml | 6 ++ src/Tokenizers/Tokenizer.php | 7 ++ .../ScopeSettingWithNamespaceOperatorTest.inc | 19 ++++ .../ScopeSettingWithNamespaceOperatorTest.php | 98 +++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc create mode 100644 tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php diff --git a/package.xml b/package.xml index e88cfd2b11..962d1193d5 100644 --- a/package.xml +++ b/package.xml @@ -114,6 +114,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -1986,6 +1988,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2047,6 +2051,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 24f11d2505..e0bf22fb01 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -1111,6 +1111,13 @@ private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0) continue; } + if ($tokenType === T_NAMESPACE) { + // PHP namespace keywords are special because they can be + // used as blocks but also inline as operators. + // So if we find them nested inside another opener, just skip them. + continue; + } + if ($tokenType === T_FUNCTION && $this->tokens[$stackPtr]['code'] !== T_FUNCTION ) { diff --git a/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc b/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc new file mode 100644 index 0000000000..e2d61bb664 --- /dev/null +++ b/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc @@ -0,0 +1,19 @@ + new namespace\Baz; diff --git a/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php b/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php new file mode 100644 index 0000000000..23cbd9877c --- /dev/null +++ b/tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php @@ -0,0 +1,98 @@ + + * @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 ScopeSettingWithNamespaceOperatorTest extends AbstractMethodUnitTest +{ + + + /** + * Test that the scope opener/closers are set correctly when the namespace keyword is encountered as an operator. + * + * @param string $testMarker The comment which prefaces the target tokens in the test file. + * @param int|string[] $tokenTypes The token type to search for. + * @param int|string[] $open Optional. The token type for the scope opener. + * @param int|string[] $close Optional. The token type for the scope closer. + * + * @dataProvider dataScopeSetting + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap + * + * @return void + */ + public function testScopeSetting($testMarker, $tokenTypes, $open=T_OPEN_CURLY_BRACKET, $close=T_CLOSE_CURLY_BRACKET) + { + $tokens = self::$phpcsFile->getTokens(); + + $target = $this->getTargetToken($testMarker, $tokenTypes); + $opener = $this->getTargetToken($testMarker, $open); + $closer = $this->getTargetToken($testMarker, $close); + + $this->assertArrayHasKey('scope_opener', $tokens[$target], 'Scope opener missing'); + $this->assertArrayHasKey('scope_closer', $tokens[$target], 'Scope closer missing'); + $this->assertSame($opener, $tokens[$target]['scope_opener'], 'Scope opener not same'); + $this->assertSame($closer, $tokens[$target]['scope_closer'], 'Scope closer not same'); + + $this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Scope opener missing for open curly'); + $this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Scope closer missing for open curly'); + $this->assertSame($opener, $tokens[$opener]['scope_opener'], 'Scope opener not same for open curly'); + $this->assertSame($closer, $tokens[$opener]['scope_closer'], 'Scope closer not same for open curly'); + + $this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Scope opener missing for close curly'); + $this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Scope closer missing for close curly'); + $this->assertSame($opener, $tokens[$closer]['scope_opener'], 'Scope opener not same for close curly'); + $this->assertSame($closer, $tokens[$closer]['scope_closer'], 'Scope closer not same for close curly'); + + }//end testScopeSetting() + + + /** + * Data provider. + * + * @see testScopeSetting() + * + * @return array + */ + public function dataScopeSetting() + { + return [ + [ + '/* testClassExtends */', + [T_CLASS], + ], + [ + '/* testClassImplements */', + [T_ANON_CLASS], + ], + [ + '/* testInterfaceExtends */', + [T_INTERFACE], + ], + [ + '/* testFunctionReturnType */', + [T_FUNCTION], + ], + [ + '/* testClosureReturnType */', + [T_CLOSURE], + ], + [ + '/* testArrowFunctionReturnType */', + [T_FN], + [T_FN_ARROW], + [T_SEMICOLON], + ], + ]; + + }//end dataScopeSetting() + + +}//end class From 1371c59bd5da5cfc877ab8478753362edc9bbc9b Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 2 Sep 2020 15:55:26 +1000 Subject: [PATCH 086/733] Added changelog entry for sniff changes --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index 054875f7bc..3c6eb4b784 100644 --- a/package.xml +++ b/package.xml @@ -27,6 +27,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - The PHP 8.0 T_NULLSAFE_OBJECT_OPERATOR token has been made available for older versions + -- Existing sniffs that check for T_OBJECT_OPERATOR have been modified to apply the same rules for the nullsafe object operator -- Thanks to Juliette Reinders Folmer for the patch - Added support for changes to the way PHP 8.0 tokenizes hash comments -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required From 07df22e58706dd1bfd0cc7c556d45be772b2695a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:50:08 +0200 Subject: [PATCH 087/733] PHP 8.0 | Generic/ScopeIndent: allow for nullsafe object operator Includes unit test. --- .../Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php | 3 ++- .../Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc | 9 +++++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed | 9 +++++++++ .../Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc | 9 +++++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed | 9 +++++++++ .../Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 ++++---- 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index f231090827..1537d46c85 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -913,7 +913,8 @@ public function process(File $phpcsFile, $stackPtr) // Don't perform strict checking on chained method calls since they // are often covered by custom rules. if ($checkToken !== null - && $tokens[$checkToken]['code'] === T_OBJECT_OPERATOR + && ($tokens[$checkToken]['code'] === T_OBJECT_OPERATOR + || $tokens[$checkToken]['code'] === T_NULLSAFE_OBJECT_OPERATOR) && $exact === true ) { $exact = false; diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index c81c887aa4..5fc112f521 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1454,6 +1454,15 @@ return [ ]), ]; +echo $string?->append('foo') + ?->outputUsing(); + +// phpcs:set Generic.WhiteSpace.ScopeIndent exact true +echo $string?->append('foo') + ?->outputUsing(); +// phpcs:set Generic.WhiteSpace.ScopeIndent exact false + +/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 7c697357ee..055be55d83 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1454,6 +1454,15 @@ return [ ]), ]; +echo $string?->append('foo') + ?->outputUsing(); + +// phpcs:set Generic.WhiteSpace.ScopeIndent exact true +echo $string?->append('foo') + ?->outputUsing(); +// phpcs:set Generic.WhiteSpace.ScopeIndent exact false + +/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index 275953a883..4bb836ce78 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1454,6 +1454,15 @@ return [ ]), ]; +echo $string?->append('foo') + ?->outputUsing(); + +// phpcs:set Generic.WhiteSpace.ScopeIndent exact true +echo $string?->append('foo') + ?->outputUsing(); +// phpcs:set Generic.WhiteSpace.ScopeIndent exact false + +/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index 5de521a1f2..c923c9a522 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1454,6 +1454,15 @@ return [ ]), ]; +echo $string?->append('foo') + ?->outputUsing(); + +// phpcs:set Generic.WhiteSpace.ScopeIndent exact true +echo $string?->append('foo') + ?->outputUsing(); +// phpcs:set Generic.WhiteSpace.ScopeIndent exact false + +/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index f347678cac..3fd5f9f0f0 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -178,10 +178,10 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1340 => 1, 1342 => 1, 1345 => 1, - 1464 => 1, - 1465 => 1, - 1466 => 1, - 1467 => 1, + 1473 => 1, + 1474 => 1, + 1475 => 1, + 1476 => 1, ]; }//end getErrorList() From 460f212c4539dc93de9980847dfcb0735c9be110 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 1 Aug 2020 01:50:44 +0200 Subject: [PATCH 088/733] PHP 8.0 | PEAR/ObjectOperatorIndent: sniff for nullsafe object operator This adds sniffing for the indentation of nullsafe object operators to the sniff. Includes unit test. --- .../WhiteSpace/ObjectOperatorIndentSniff.php | 18 ++++++++++---- .../ObjectOperatorIndentUnitTest.inc | 24 +++++++++++++++++++ .../ObjectOperatorIndentUnitTest.inc.fixed | 24 +++++++++++++++++++ .../ObjectOperatorIndentUnitTest.php | 4 ++++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/WhiteSpace/ObjectOperatorIndentSniff.php b/src/Standards/PEAR/Sniffs/WhiteSpace/ObjectOperatorIndentSniff.php index 1929b3bbe8..fb1b79a329 100644 --- a/src/Standards/PEAR/Sniffs/WhiteSpace/ObjectOperatorIndentSniff.php +++ b/src/Standards/PEAR/Sniffs/WhiteSpace/ObjectOperatorIndentSniff.php @@ -29,6 +29,16 @@ class ObjectOperatorIndentSniff implements Sniff */ public $multilevel = false; + /** + * Tokens to listen for. + * + * @var array + */ + private $targets = [ + T_OBJECT_OPERATOR, + T_NULLSAFE_OBJECT_OPERATOR, + ]; + /** * Returns an array of tokens this test wants to listen for. @@ -37,7 +47,7 @@ class ObjectOperatorIndentSniff implements Sniff */ public function register() { - return [T_OBJECT_OPERATOR]; + return $this->targets; }//end register() @@ -57,14 +67,14 @@ public function process(File $phpcsFile, $stackPtr) // Make sure this is the first object operator in a chain of them. $start = $phpcsFile->findStartOfStatement($stackPtr); - $prev = $phpcsFile->findPrevious(T_OBJECT_OPERATOR, ($stackPtr - 1), $start); + $prev = $phpcsFile->findPrevious($this->targets, ($stackPtr - 1), $start); if ($prev !== false) { return; } // Make sure this is a chained call. $end = $phpcsFile->findEndOfStatement($stackPtr); - $next = $phpcsFile->findNext(T_OBJECT_OPERATOR, ($stackPtr + 1), $end); + $next = $phpcsFile->findNext($this->targets, ($stackPtr + 1), $end); if ($next === false) { // Not a chained call. return; @@ -179,7 +189,7 @@ public function process(File $phpcsFile, $stackPtr) }//end if $next = $phpcsFile->findNext( - T_OBJECT_OPERATOR, + $this->targets, ($next + 1), null, false, diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc index 70fd3d8ccb..2f3f991b0a 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc +++ b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc @@ -110,3 +110,27 @@ $rootNode ->five(); // phpcs:set PEAR.WhiteSpace.ObjectOperatorIndent multilevel false + +$object + ?->setBar($foo) + ?->setFoo($bar); + +$someObject?->someFunction("some", "parameter") +->someOtherFunc(23, 42)?-> + someOtherFunc2($one, $two) + +->someOtherFunc3(23, 42) + ?->andAThirdFunction(); + +// phpcs:set PEAR.WhiteSpace.ObjectOperatorIndent multilevel true +$object + ?->setBar($foo) + ?->setFoo($bar); + +$someObject?->someFunction("some", "parameter") +->someOtherFunc(23, 42) + ?->someOtherFunc2($one, $two) + +->someOtherFunc3(23, 42) + ?->andAThirdFunction(); +// phpcs:set PEAR.WhiteSpace.ObjectOperatorIndent multilevel false diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed index dfa642f46d..1073c9c01e 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed @@ -110,3 +110,27 @@ $rootNode ->five(); // phpcs:set PEAR.WhiteSpace.ObjectOperatorIndent multilevel false + +$object + ?->setBar($foo) + ?->setFoo($bar); + +$someObject?->someFunction("some", "parameter") + ->someOtherFunc(23, 42) + ?->someOtherFunc2($one, $two) + + ->someOtherFunc3(23, 42) + ?->andAThirdFunction(); + +// phpcs:set PEAR.WhiteSpace.ObjectOperatorIndent multilevel true +$object + ?->setBar($foo) + ?->setFoo($bar); + +$someObject?->someFunction("some", "parameter") + ->someOtherFunc(23, 42) + ?->someOtherFunc2($one, $two) + + ->someOtherFunc3(23, 42) + ?->andAThirdFunction(); +// phpcs:set PEAR.WhiteSpace.ObjectOperatorIndent multilevel false diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php index 0ae7b72ef4..8289b0f564 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php +++ b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php @@ -44,6 +44,10 @@ public function getErrorList() 82 => 1, 95 => 1, 103 => 1, + 119 => 2, + 122 => 1, + 131 => 1, + 134 => 1, ]; }//end getErrorList() From ba6332388cd8ae57103fb9c21295e57b52cea022 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 30 Aug 2020 19:22:21 +0200 Subject: [PATCH 089/733] PHP 8.0: "undo" namespaced names as single token As per the proposal in 3041. This effectively "undoes" the new PHP 8.0 tokenization of identifier names for PHPCS 3.x. Includes extensive unit tests to ensure the correct re-tokenization as well as that the rest of the tokenization is not adversely affected by this change. Includes preventing `function ...` within a group use statement from breaking the retokenization. Includes fixing the nullable tokenization when combined with any of the new PHP 8 identifier name tokens. --- package.xml | 6 + src/Tokenizers/PHP.php | 109 +- src/Util/Tokens.php | 12 + .../UndoNamespacedNameSingleTokenTest.inc | 147 ++ .../UndoNamespacedNameSingleTokenTest.php | 1297 +++++++++++++++++ 5 files changed, 1562 insertions(+), 9 deletions(-) create mode 100644 tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.inc create mode 100644 tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php diff --git a/package.xml b/package.xml index 3c6eb4b784..8d3b63fb50 100644 --- a/package.xml +++ b/package.xml @@ -121,6 +121,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -1997,6 +1999,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2058,6 +2062,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5180a5f0e8..d76e0d60ac 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -815,6 +815,81 @@ protected function tokenize($string) continue; }//end if + /* + As of PHP 8.0 fully qualified, partially qualified and namespace relative + identifier names are tokenized differently. + This "undoes" the new tokenization so the tokenization will be the same in + in PHP 5, 7 and 8. + */ + + if (PHP_VERSION_ID >= 80000 + && $tokenIsArray === true + && ($token[0] === T_NAME_QUALIFIED + || $token[0] === T_NAME_FULLY_QUALIFIED + || $token[0] === T_NAME_RELATIVE) + ) { + $name = $token[1]; + + if ($token[0] === T_NAME_FULLY_QUALIFIED) { + $newToken = []; + $newToken['code'] = T_NS_SEPARATOR; + $newToken['type'] = 'T_NS_SEPARATOR'; + $newToken['content'] = '\\'; + $finalTokens[$newStackPtr] = $newToken; + ++$newStackPtr; + + $name = ltrim($name, '\\'); + } + + if ($token[0] === T_NAME_RELATIVE) { + $newToken = []; + $newToken['code'] = T_NAMESPACE; + $newToken['type'] = 'T_NAMESPACE'; + $newToken['content'] = substr($name, 0, 9); + $finalTokens[$newStackPtr] = $newToken; + ++$newStackPtr; + + $newToken = []; + $newToken['code'] = T_NS_SEPARATOR; + $newToken['type'] = 'T_NS_SEPARATOR'; + $newToken['content'] = '\\'; + $finalTokens[$newStackPtr] = $newToken; + ++$newStackPtr; + + $name = substr($name, 10); + } + + $parts = explode('\\', $name); + $partCount = count($parts); + $lastPart = ($partCount - 1); + + foreach ($parts as $i => $part) { + $newToken = []; + $newToken['code'] = T_STRING; + $newToken['type'] = 'T_STRING'; + $newToken['content'] = $part; + $finalTokens[$newStackPtr] = $newToken; + ++$newStackPtr; + + if ($i !== $lastPart) { + $newToken = []; + $newToken['code'] = T_NS_SEPARATOR; + $newToken['type'] = 'T_NS_SEPARATOR'; + $newToken['content'] = '\\'; + $finalTokens[$newStackPtr] = $newToken; + ++$newStackPtr; + } + } + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = Util\Tokens::tokenName($token[0]); + $content = Util\Common::prepareForOutput($token[1]); + echo "\t\t* token $stackPtr split into individual tokens; was: $type => $content".PHP_EOL; + } + + continue; + }//end if + /* Before PHP 7.0, the "yield from" was tokenized as T_YIELD, T_WHITESPACE and T_STRING. So look for @@ -1131,7 +1206,7 @@ protected function tokenize($string) * Check if the next non-empty token is one of the tokens which can be used * in type declarations. If not, it's definitely a ternary. * At this point, the only token types which need to be taken into consideration - * as potential type declarations are T_STRING, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR. + * as potential type declarations are identifier names, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR. */ $lastRelevantNonEmpty = null; @@ -1148,6 +1223,9 @@ protected function tokenize($string) } if ($tokenType === T_STRING + || $tokenType === T_NAME_FULLY_QUALIFIED + || $tokenType === T_NAME_RELATIVE + || $tokenType === T_NAME_QUALIFIED || $tokenType === T_ARRAY || $tokenType === T_NS_SEPARATOR ) { @@ -1159,7 +1237,10 @@ protected function tokenize($string) && isset($lastRelevantNonEmpty) === false) || ($lastRelevantNonEmpty === T_ARRAY && $tokenType === '(') - || ($lastRelevantNonEmpty === T_STRING + || (($lastRelevantNonEmpty === T_STRING + || $lastRelevantNonEmpty === T_NAME_FULLY_QUALIFIED + || $lastRelevantNonEmpty === T_NAME_RELATIVE + || $lastRelevantNonEmpty === T_NAME_QUALIFIED) && ($tokenType === T_DOUBLE_COLON || $tokenType === '(' || $tokenType === ':')) @@ -1304,6 +1385,10 @@ protected function tokenize($string) 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. */ if ($tokenIsArray === true @@ -1321,7 +1406,10 @@ protected function tokenize($string) } } - if ($x < $numTokens && is_array($tokens[$x]) === true) { + if ($x < $numTokens + && is_array($tokens[$x]) === true + && $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; @@ -1377,12 +1465,15 @@ function return types. We want to keep the parenthesis map clean, && $tokens[$x] === ':' ) { $allowed = [ - T_STRING => T_STRING, - T_ARRAY => T_ARRAY, - T_CALLABLE => T_CALLABLE, - T_SELF => T_SELF, - T_PARENT => T_PARENT, - T_NS_SEPARATOR => T_NS_SEPARATOR, + 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_NS_SEPARATOR => T_NS_SEPARATOR, ]; $allowed += Util\Tokens::$emptyTokens; diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 3994264067..952ccc2c12 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -129,6 +129,18 @@ define('T_NULLSAFE_OBJECT_OPERATOR', 'PHPCS_T_NULLSAFE_OBJECT_OPERATOR'); } +if (defined('T_NAME_QUALIFIED') === false) { + define('T_NAME_QUALIFIED', 'PHPCS_T_NAME_QUALIFIED'); +} + +if (defined('T_NAME_FULLY_QUALIFIED') === false) { + define('T_NAME_FULLY_QUALIFIED', 'PHPCS_T_NAME_FULLY_QUALIFIED'); +} + +if (defined('T_NAME_RELATIVE') === false) { + define('T_NAME_RELATIVE', 'PHPCS_T_NAME_RELATIVE'); +} + // 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'); diff --git a/tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.inc b/tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.inc new file mode 100644 index 0000000000..540f72c9dc --- /dev/null +++ b/tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.inc @@ -0,0 +1,147 @@ + + * @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 UndoNamespacedNameSingleTokenTest extends AbstractMethodUnitTest +{ + + + /** + * Test that identifier names are tokenized the same across PHP versions, based on the PHP 5/7 tokenization. + * + * @param string $testMarker The comment prefacing the test. + * @param array $expectedTokens The tokenization expected. + * + * @dataProvider dataIdentifierTokenization + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testIdentifierTokenization($testMarker, $expectedTokens) + { + $tokens = self::$phpcsFile->getTokens(); + $identifier = $this->getTargetToken($testMarker, constant($expectedTokens[0]['type'])); + + foreach ($expectedTokens as $key => $tokenInfo) { + $this->assertSame(constant($tokenInfo['type']), $tokens[$identifier]['code']); + $this->assertSame($tokenInfo['type'], $tokens[$identifier]['type']); + $this->assertSame($tokenInfo['content'], $tokens[$identifier]['content']); + + ++$identifier; + } + + }//end testIdentifierTokenization() + + + /** + * Data provider. + * + * @see testIdentifierTokenization() + * + * @return array + */ + public function dataIdentifierTokenization() + { + return [ + [ + '/* testNamespaceDeclaration */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Package', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testNamespaceDeclarationWithLevels */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Vendor', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'SubLevel', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Domain', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testUseStatement */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testUseStatementWithLevels */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Vendor', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Domain', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testFunctionUseStatement */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'function', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function_name', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testFunctionUseStatementWithLevels */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'function', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Vendor', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function_in_ns', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testConstantUseStatement */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'const', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'CONSTANT_NAME', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testConstantUseStatementWithLevels */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'const', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Vendor', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'OTHER_CONSTANT', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testMultiUseUnqualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'UnqualifiedClassName', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + ], + ], + [ + '/* testMultiUsePartiallyQualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Sublevel', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'PartiallyClassName', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testGroupUseStatement */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Vendor', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_OPEN_USE_GROUP', + 'content' => '{', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'AnotherDomain', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function_grouped', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'const', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'CONSTANT_GROUPED', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Sub', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'YetAnotherDomain', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'SubLevelA', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function_grouped_too', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'const', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'SubLevelB', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'CONSTANT_GROUPED_TOO', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_CLOSE_USE_GROUP', + 'content' => '}', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testClassName */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'MyClass', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testExtendedFQN */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Vendor', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'FQN', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testImplementsRelative */', + [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + ], + ], + [ + '/* testImplementsFQN */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Fully', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Qualified', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + ], + ], + [ + '/* testImplementsUnqualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Unqualified', + ], + [ + 'type' => 'T_COMMA', + 'content' => ',', + ], + ], + ], + [ + '/* testImplementsPartiallyQualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Sub', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + [ + '/* testFunctionName */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'function_name', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + [ + '/* testTypeDeclarationRelative */', + [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + // TODO: change this to T_TYPE_UNION when #3032 is merged. + [ + 'type' => 'T_BITWISE_OR', + 'content' => '|', + ], + [ + 'type' => 'T_STRING', + 'content' => 'object', + ], + ], + ], + [ + '/* testTypeDeclarationFQN */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Fully', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Qualified', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + ], + ], + [ + '/* testTypeDeclarationUnqualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Unqualified', + ], + // TODO: change this to T_TYPE_UNION when #3032 is merged. + [ + 'type' => 'T_BITWISE_OR', + 'content' => '|', + ], + [ + 'type' => 'T_FALSE', + 'content' => 'false', + ], + ], + ], + [ + '/* testTypeDeclarationPartiallyQualified */', + [ + [ + 'type' => 'T_NULLABLE', + 'content' => '?', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Sublevel', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + ], + ], + [ + '/* testReturnTypeFQN */', + [ + [ + 'type' => 'T_NULLABLE', + 'content' => '?', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + ], + ], + [ + '/* testFunctionCallRelative */', + [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'NameSpace', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function_name', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + [ + '/* testFunctionCallFQN */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Vendor', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Package', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function_name', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + [ + '/* testFunctionCallUnqualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'function_name', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + [ + '/* testFunctionPartiallyQualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function_name', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + [ + '/* testCatchRelative */', + [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'SubLevel', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Exception', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + ], + ], + [ + '/* testCatchFQN */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Exception', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + ], + ], + [ + '/* testCatchUnqualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Exception', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + ], + ], + [ + '/* testCatchPartiallyQualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Exception', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + ], + ], + [ + '/* testNewRelative */', + [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + [ + '/* testNewFQN */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Vendor', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + [ + '/* testNewUnqualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testNewPartiallyQualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testDoubleColonRelative */', + [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_DOUBLE_COLON', + 'content' => '::', + ], + ], + ], + [ + '/* testDoubleColonFQN */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_DOUBLE_COLON', + 'content' => '::', + ], + ], + ], + [ + '/* testDoubleColonUnqualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_DOUBLE_COLON', + 'content' => '::', + ], + ], + ], + [ + '/* testDoubleColonPartiallyQualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Level', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_DOUBLE_COLON', + 'content' => '::', + ], + ], + ], + [ + '/* testInstanceOfRelative */', + [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testInstanceOfFQN */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Full', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_CLOSE_PARENTHESIS', + 'content' => ')', + ], + ], + ], + [ + '/* testInstanceOfUnqualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + ], + ], + [ + '/* testInstanceOfPartiallyQualified */', + [ + [ + 'type' => 'T_STRING', + 'content' => 'Partially', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'ClassName', + ], + [ + 'type' => 'T_SEMICOLON', + 'content' => ';', + ], + ], + ], + [ + '/* testInvalidInPHP8Whitespace */', + [ + [ + 'type' => 'T_NAMESPACE', + 'content' => 'namespace', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Sublevel', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_STRING', + 'content' => 'function_name', + ], + [ + 'type' => 'T_OPEN_PARENTHESIS', + 'content' => '(', + ], + ], + ], + [ + '/* testInvalidInPHP8Comments */', + [ + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Fully', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_PHPCS_IGNORE', + 'content' => '// phpcs:ignore Stnd.Cat.Sniff -- for reasons +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Qualified', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_COMMENT', + 'content' => '/* comment */', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' ', + ], + [ + 'type' => 'T_NS_SEPARATOR', + 'content' => '\\', + ], + [ + 'type' => 'T_STRING', + 'content' => 'Name', + ], + [ + 'type' => 'T_WHITESPACE', + 'content' => ' +', + ], + ], + ], + ]; + + }//end dataIdentifierTokenization() + + +}//end class From 50ddc561c867f698d7f7a8d8f45def67dfdd163b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 2 Sep 2020 14:05:06 +0200 Subject: [PATCH 090/733] Tokenizer/PHP: add missing scope closers Noticed (while working on something else) there were some typical scope closers missing from the array. --- src/Tokenizers/PHP.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5180a5f0e8..9efc77864d 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -283,8 +283,10 @@ class PHP extends Tokenizer T_ENDFOREACH => T_ENDFOREACH, T_ENDWHILE => T_ENDWHILE, T_ENDSWITCH => T_ENDSWITCH, + T_ENDDECLARE => T_ENDDECLARE, T_BREAK => T_BREAK, T_END_HEREDOC => T_END_HEREDOC, + T_END_NOWDOC => T_END_NOWDOC, ]; /** From fc6d400a2fa330e4c103d156fb4cc6080882c515 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Thu, 3 Sep 2020 18:23:30 -0700 Subject: [PATCH 091/733] Add E_EXIT to the list of non-operand tokens of the OperatorSpacing sniff --- src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php | 1 + .../Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc | 2 ++ .../Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php index c69b0b6433..fd4bf44fcc 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -79,6 +79,7 @@ public function register() $this->nonOperandTokens += [ T_RETURN => T_RETURN, T_ECHO => T_ECHO, + T_EXIT => T_EXIT, T_PRINT => T_PRINT, T_YIELD => T_YIELD, ]; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc index 8d0efb3246..dead30680a 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc @@ -463,5 +463,7 @@ $a = [$a, - $b]; $a = $a[- $b]; $a = $a ? - $b : - $b; +exit -1; + /* 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 a03b642003..1179b4e3a2 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed @@ -457,5 +457,7 @@ $a = [$a, - $b]; $a = $a[- $b]; $a = $a ? - $b : - $b; +exit -1; + /* Intentional parse error. This has to be the last test in the file. */ $a = 10 + From 1697fac33c6c049fa6ec778b33f45d1ffee82f72 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Fri, 4 Sep 2020 19:56:57 -0700 Subject: [PATCH 092/733] Ignore the previous element's T_COMMA when looking up the beginning of the previous expression --- .../Generic/Sniffs/Arrays/ArrayIndentSniff.php | 1 + .../Generic/Tests/Arrays/ArrayIndentUnitTest.inc | 11 +++++++++++ .../Tests/Arrays/ArrayIndentUnitTest.inc.fixed | 11 +++++++++++ .../Generic/Tests/Arrays/ArrayIndentUnitTest.php | 3 +++ 4 files changed, 26 insertions(+) diff --git a/src/Standards/Generic/Sniffs/Arrays/ArrayIndentSniff.php b/src/Standards/Generic/Sniffs/Arrays/ArrayIndentSniff.php index 22258ad598..adb74ec8cb 100644 --- a/src/Standards/Generic/Sniffs/Arrays/ArrayIndentSniff.php +++ b/src/Standards/Generic/Sniffs/Arrays/ArrayIndentSniff.php @@ -62,6 +62,7 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array // Determine how far indented the entire array declaration should be. $ignore = Tokens::$emptyTokens; $ignore[] = T_DOUBLE_ARROW; + $ignore[] = T_COMMA; $prev = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); $start = $phpcsFile->findStartOfStatement($prev); $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $start, true); diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc index 77d967745f..44398effb0 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc @@ -77,3 +77,14 @@ $var = [ 2 => 'two', /* three */ 3 => 'three', ]; + +// phpcs:set Generic.Arrays.ArrayIndent indent 4 + +$foo = [ + 'foo' + . 'bar', + [ + 'baz', + 'qux', + ], +]; diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed index ac06a68158..d32d9e3a6d 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed @@ -78,3 +78,14 @@ $var = [ 2 => 'two', /* three */ 3 => 'three', ]; + +// phpcs:set Generic.Arrays.ArrayIndent indent 4 + +$foo = [ + 'foo' + . 'bar', + [ + 'baz', + 'qux', + ], +]; diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php index 42dff594af..a4b89294b4 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php @@ -40,6 +40,9 @@ public function getErrorList() 77 => 1, 78 => 1, 79 => 1, + 87 => 1, + 88 => 1, + 89 => 1, ]; }//end getErrorList() From 76fce40bbc71b1f8c2935c3afc17a888bfa72b2f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 5 Sep 2020 22:54:34 +0200 Subject: [PATCH 093/733] File::isReference(): simplify code The `File::getMethodParameters()` returns a `reference_token` index, so no need to do any token walking. --- src/Files/File.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 9a34bd0f43..b7e35488f0 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1981,19 +1981,7 @@ public function isReference($stackPtr) ) { $params = $this->getMethodParameters($this->tokens[$lastBracket]['parenthesis_owner']); foreach ($params as $param) { - $varToken = $tokenAfter; - if ($param['variable_length'] === true) { - $varToken = $this->findNext( - (Util\Tokens::$emptyTokens + [T_ELLIPSIS]), - ($stackPtr + 1), - null, - true - ); - } - - if ($param['token'] === $varToken - && $param['pass_by_reference'] === true - ) { + if ($param['reference_token'] === $stackPtr) { // Function parameter declared to be passed by reference. return true; } From a9cf57e8e0cf00663d9ea68316caa410c960085a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 5 May 2020 09:17:29 +1000 Subject: [PATCH 094/733] Fixed progress output when the run completes at a wrap boundary (ref #3058) --- src/Runner.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Runner.php b/src/Runner.php index bbed3c9d34..320a8f20a0 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -874,7 +874,10 @@ public function printProgress(File $file, $numFiles, $numProcessed) $percent = round(($numProcessed / $numFiles) * 100); $padding = (strlen($numFiles) - strlen($numProcessed)); - if ($numProcessed === $numFiles && $numFiles > $numPerLine) { + if ($numProcessed === $numFiles + && $numFiles > $numPerLine + && ($numProcessed % $numPerLine) !== 0 + ) { $padding += ($numPerLine - ($numFiles - (floor($numFiles / $numPerLine) * $numPerLine))); } From 674cb96db60f43fd5c2186b2b72fb1bc77052403 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 8 Sep 2020 08:42:16 +1000 Subject: [PATCH 095/733] Changelog for #3058 --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index 3c6eb4b784..0329be5ce6 100644 --- a/package.xml +++ b/package.xml @@ -39,6 +39,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch + - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots From 0d79723446243f12cb575bbfbc680a0d658f3390 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 8 Sep 2020 02:49:05 +0200 Subject: [PATCH 096/733] File::process(): don't apply include/exclude patterns to STDIN Note: using `trim()` to remove potential quotes around `STDIN` which are sometimes passed by IDEs. --- src/Files/File.php | 62 ++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index b7e35488f0..27b10a3b11 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -442,30 +442,11 @@ public function process() continue; } - // If the file path matches one of our ignore patterns, skip it. - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - foreach ($listenerData['ignore'] as $pattern) { - // We assume a / directory separator, as do the exclude rules - // most developers write, so we need a special case for any system - // that is different. - if (DIRECTORY_SEPARATOR === '\\') { - $pattern = str_replace('/', '\\\\', $pattern); - } - - $pattern = '`'.$pattern.'`i'; - if (preg_match($pattern, $this->path) === 1) { - $this->ignoredListeners[$class] = true; - continue(2); - } - } - - // If the file path does not match one of our include patterns, skip it. - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - if (empty($listenerData['include']) === false) { - $included = false; - foreach ($listenerData['include'] as $pattern) { + if (trim($this->path, '\'"') !== 'STDIN') { + // If the file path matches one of our ignore patterns, skip it. + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + foreach ($listenerData['ignore'] as $pattern) { // We assume a / directory separator, as do the exclude rules // most developers write, so we need a special case for any system // that is different. @@ -475,15 +456,36 @@ public function process() $pattern = '`'.$pattern.'`i'; if (preg_match($pattern, $this->path) === 1) { - $included = true; - break; + $this->ignoredListeners[$class] = true; + continue(2); } } - if ($included === false) { - $this->ignoredListeners[$class] = true; - continue; - } + // If the file path does not match one of our include patterns, skip it. + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + if (empty($listenerData['include']) === false) { + $included = false; + foreach ($listenerData['include'] as $pattern) { + // We assume a / directory separator, as do the exclude rules + // most developers write, so we need a special case for any system + // that is different. + if (DIRECTORY_SEPARATOR === '\\') { + $pattern = str_replace('/', '\\\\', $pattern); + } + + $pattern = '`'.$pattern.'`i'; + if (preg_match($pattern, $this->path) === 1) { + $included = true; + break; + } + } + + if ($included === false) { + $this->ignoredListeners[$class] = true; + continue; + } + }//end if }//end if $this->activeListener = $class; From a95569ca6077ecc1529681df3681627e2034a5a4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 8 Sep 2020 02:49:57 +0200 Subject: [PATCH 097/733] File::addMessage(): don't apply include/exclude patterns to STDIN Note: using `trim()` to remove potential quotes around `STDIN` which are sometimes passed by IDEs. --- src/Files/File.php | 94 ++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 27b10a3b11..efb5d22370 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -967,59 +967,63 @@ protected function addMessage($error, $message, $line, $column, $code, $data, $s // Make sure we are not ignoring this file. $included = null; - foreach ($checkCodes as $checkCode) { - $patterns = null; - - if (isset($this->configCache['includePatterns'][$checkCode]) === true) { - $patterns = $this->configCache['includePatterns'][$checkCode]; - $excluding = false; - } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) { - $patterns = $this->configCache['ignorePatterns'][$checkCode]; - $excluding = true; - } - - if ($patterns === null) { - continue; - } - - foreach ($patterns as $pattern => $type) { - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - $replacements = [ - '\\,' => ',', - '*' => '.*', - ]; - - // We assume a / directory separator, as do the exclude rules - // most developers write, so we need a special case for any system - // that is different. - if (DIRECTORY_SEPARATOR === '\\') { - $replacements['/'] = '\\\\'; + if (trim($this->path, '\'"') === 'STDIN') { + $included = true; + } else { + foreach ($checkCodes as $checkCode) { + $patterns = null; + + if (isset($this->configCache['includePatterns'][$checkCode]) === true) { + $patterns = $this->configCache['includePatterns'][$checkCode]; + $excluding = false; + } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) { + $patterns = $this->configCache['ignorePatterns'][$checkCode]; + $excluding = true; } - $pattern = '`'.strtr($pattern, $replacements).'`i'; - $matched = preg_match($pattern, $this->path); + if ($patterns === null) { + continue; + } - if ($matched === 0) { - if ($excluding === false && $included === null) { - // This file path is not being included. - $included = false; + foreach ($patterns as $pattern => $type) { + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + $replacements = [ + '\\,' => ',', + '*' => '.*', + ]; + + // We assume a / directory separator, as do the exclude rules + // most developers write, so we need a special case for any system + // that is different. + if (DIRECTORY_SEPARATOR === '\\') { + $replacements['/'] = '\\\\'; } - continue; - } + $pattern = '`'.strtr($pattern, $replacements).'`i'; + $matched = preg_match($pattern, $this->path); - if ($excluding === true) { - // This file path is being excluded. - $this->ignoredCodes[$checkCode] = true; - return false; - } + if ($matched === 0) { + if ($excluding === false && $included === null) { + // This file path is not being included. + $included = false; + } - // This file path is being included. - $included = true; - break; + continue; + } + + if ($excluding === true) { + // This file path is being excluded. + $this->ignoredCodes[$checkCode] = true; + return false; + } + + // This file path is being included. + $included = true; + break; + }//end foreach }//end foreach - }//end foreach + }//end if if ($included === false) { // There were include rules set, but this file From b86b6a857798d66c2c6470f6ed6ce2272eadcf17 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 10 Sep 2020 08:56:01 +1000 Subject: [PATCH 098/733] Changelog for #2900 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 0329be5ce6..d1b9c0d709 100644 --- a/package.xml +++ b/package.xml @@ -32,6 +32,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Added support for changes to the way PHP 8.0 tokenizes hash comments -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required -- Thanks to Juliette Reinders Folmer for the patch + - Running the unit tests now includes warnings in the found and fixable error code counts + -- Thanks to Juliette Reinders Folmer for the patch - PSR12.Functions.NullableTypeDeclaration now supports the PHP8 static return type -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level From fad249aa099b002dd3908411440cea9200c1cffa Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 14 Sep 2020 08:45:00 +1000 Subject: [PATCH 099/733] Squiz.Formatting.OperatorBracket reporting false positive for exit statements (ref #3099) --- src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php | 1 + .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc | 2 ++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index f19d9a275d..d704f26796 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -107,6 +107,7 @@ public function process(File $phpcsFile, $stackPtr) T_OPEN_CURLY_BRACKET => true, T_OPEN_SHORT_ARRAY => true, T_CASE => true, + T_EXIT => true, ]; if (isset($invalidTokens[$tokens[$previousToken]['code']]) === true) { diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc index d4e1666931..658ea35210 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc @@ -172,3 +172,5 @@ $test = (1 * static::TEST); $test = myfunc(1 * static::TEST); $errorPos = $params[$x]?->getLine() + $commentStart; + +exit -1; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed index 7776cd5f2c..532a618c27 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed @@ -172,3 +172,5 @@ $test = (1 * static::TEST); $test = myfunc(1 * static::TEST); $errorPos = ($params[$x]?->getLine() + $commentStart); + +exit -1; From 9cce6859a595b3fa77fc823d9b0d451951dcf9d2 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 14 Sep 2020 08:46:36 +1000 Subject: [PATCH 100/733] Changelogs for recent false positives when exiting with negative number (ref #3099) --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index d1b9c0d709..d9c856491e 100644 --- a/package.xml +++ b/package.xml @@ -36,12 +36,15 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - PSR12.Functions.NullableTypeDeclaration now supports the PHP8 static return type -- Thanks to Juliette Reinders Folmer for the patch + - Fixed Squiz.Formatting.OperatorBracket false positive when exiting with a negative number - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level - Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots + - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number + -- Thanks to Sergei Morozov for the patch From 4201fd8931e2363afdcec8ef438c4efb04f6a017 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 08:19:31 +1000 Subject: [PATCH 101/733] Fixed Squiz.PHP.DisallowComparisonAssignment false positive for methods called on an object (ref #3089) --- package.xml | 1 + .../Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php | 4 +++- .../Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index d9c856491e..40b971631b 100644 --- a/package.xml +++ b/package.xml @@ -37,6 +37,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - PSR12.Functions.NullableTypeDeclaration now supports the PHP8 static return type -- Thanks to Juliette Reinders Folmer for the patch - Fixed Squiz.Formatting.OperatorBracket false positive when exiting with a negative number + - Fixed Squiz.PHP.DisallowComparisonAssignment false positive for methods called on an object - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level - Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php index aaed48d22a..7ad2b6f6ac 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php @@ -68,9 +68,11 @@ public function process(File $phpcsFile, $stackPtr) // Ignore function calls. $ignore = [ + T_NULLSAFE_OBJECT_OPERATOR, + T_OBJECT_OPERATOR, T_STRING, + T_VARIABLE, T_WHITESPACE, - T_OBJECT_OPERATOR, ]; $next = $phpcsFile->findNext($ignore, ($stackPtr + 1), null, true); diff --git a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc index d4fce10f4d..3a629ff855 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc @@ -62,3 +62,6 @@ $a = [ $a = [ 'a' => ($foo) ? fn() => return 1 : fn() => return 2, ]; + +$var = $foo->something(!$var); +$var = $foo?->something(!$var); From 3e1bbb995f6b7c79d694264b4783833533a85d2e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 08:31:41 +1000 Subject: [PATCH 102/733] Changelog for #3063 (ref #3041) --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index ced5568255..c1e087e943 100644 --- a/package.xml +++ b/package.xml @@ -29,6 +29,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> - The PHP 8.0 T_NULLSAFE_OBJECT_OPERATOR token has been made available for older versions -- Existing sniffs that check for T_OBJECT_OPERATOR have been modified to apply the same rules for the nullsafe object operator -- Thanks to Juliette Reinders Folmer for the patch + - The new method of PHP 8.0 tokenizing for namespaced names has been revert to thr pre 8.0 method + -- This maintains backwards compatible for existing sniffs on PHP 8.0 + -- This change will be removed in PHPCS 4.0 as the PHP 8.0 tokenizing method will be backported for pre 8.0 versions + -- Thanks to Juliette Reinders Folmer for the patch - Added support for changes to the way PHP 8.0 tokenizes hash comments -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required -- Thanks to Juliette Reinders Folmer for the patch From c873d383922f44b2441050e7d43077cf0187b83c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 08:34:21 +1000 Subject: [PATCH 103/733] Changelog for #3066 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index c1e087e943..d4c2aa4b3e 100644 --- a/package.xml +++ b/package.xml @@ -48,6 +48,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots + - Fixed bug #3066 : No support for namespace operator used in type declarations + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number -- Thanks to Sergei Morozov for the patch From a0145ae4bc01730e4f0d75f9b0db89ebcf01580c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Jul 2020 07:30:16 +0200 Subject: [PATCH 104/733] PHP 8.0 | Tokens: add new `T_TYPE_UNION` token ... which will indicate the `|` character in PHP 8.0 union types. --- src/Util/Tokens.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 952ccc2c12..aee035d352 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -75,6 +75,7 @@ define('T_ZSR', 'PHPCS_T_ZSR'); define('T_ZSR_EQUAL', 'PHPCS_T_ZSR_EQUAL'); define('T_FN_ARROW', 'T_FN_ARROW'); +define('T_TYPE_UNION', 'T_TYPE_UNION'); // Some PHP 5.5 tokens, replicated for lower versions. if (defined('T_FINALLY') === false) { From 63c2b22c7a57e3b211cc467a07a00bc9b2934c5d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Jul 2020 10:26:04 +0200 Subject: [PATCH 105/733] PHP 8.0 | Tokenizer/PHP: tokenize the "|" for union types as T_TYPE_UNION This adds a new block of logic to the `PHP::processAdditional()` method which changes the token code and type of `T_BITWISE_OR` `|` tokens in type declarations to `T_TYPE_UNION`. As the `PHP::processAdditional()` method walks backwards through the token stack, the arrow function backfill will not have been done yet, so for those some special conditions have been put in place. I've tried to limit the token walking within the new block as much as possible while still maintaining accuracy. This includes changing all union type operators in a single type declaration in one go, instead of on each individual `T_BITWISE_OR` token, which prevents the same logic having to be executed multiple times for multi-union types like `int|float|null`. Includes dedicated unit tests. Ref: https://wiki.php.net/rfc/union_types_v2 --- package.xml | 6 + src/Tokenizers/PHP.php | 171 +++++++++++++++++++++++++ tests/Core/Tokenizer/BitwiseOrTest.inc | 85 ++++++++++++ tests/Core/Tokenizer/BitwiseOrTest.php | 122 ++++++++++++++++++ 4 files changed, 384 insertions(+) create mode 100644 tests/Core/Tokenizer/BitwiseOrTest.inc create mode 100644 tests/Core/Tokenizer/BitwiseOrTest.php diff --git a/package.xml b/package.xml index d4c2aa4b3e..1dfb29dba5 100644 --- a/package.xml +++ b/package.xml @@ -126,6 +126,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2006,6 +2008,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2071,6 +2075,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 7aa484ef70..1ad85c4156 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2176,6 +2176,177 @@ protected function processAdditional() } } + continue; + } else if ($this->tokens[$i]['code'] === T_BITWISE_OR) { + /* + Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR. + */ + + $allowed = [ + 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_NS_SEPARATOR => T_NS_SEPARATOR, + ]; + + $suspectedType = null; + $typeTokenCount = 0; + + for ($x = ($i + 1); $x < $numTokens; $x++) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) { + continue; + } + + if (isset($allowed[$this->tokens[$x]['code']]) === true) { + ++$typeTokenCount; + continue; + } + + if ($typeTokenCount > 0 + && ($this->tokens[$x]['code'] === T_BITWISE_AND + || $this->tokens[$x]['code'] === T_ELLIPSIS) + ) { + // Skip past reference and variadic indicators for parameter types. + ++$x; + continue; + } + + if ($this->tokens[$x]['code'] === T_VARIABLE) { + // Parameter/Property defaults can not contain variables, so this could be a type. + $suspectedType = 'property or parameter'; + break; + } + + if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) { + // Possible arrow function. + $suspectedType = 'return'; + break; + } + + if ($this->tokens[$x]['code'] === T_SEMICOLON) { + // Possible abstract method or interface method. + $suspectedType = 'return'; + break; + } + + if ($this->tokens[$x]['code'] === T_OPEN_CURLY_BRACKET + && isset($this->tokens[$x]['scope_condition']) === true + && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_FUNCTION + ) { + $suspectedType = 'return'; + } + + break; + }//end for + + if ($typeTokenCount === 0 || isset($suspectedType) === false) { + // Definitely not a union type, move on. + continue; + } + + $typeTokenCount = 0; + $unionOperators = [$i]; + $confirmed = false; + + for ($x = ($i - 1); $x >= 0; $x--) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) { + continue; + } + + if (isset($allowed[$this->tokens[$x]['code']]) === true) { + ++$typeTokenCount; + continue; + } + + // Union 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; + continue; + } + + if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) { + $confirmed = true; + break; + } + + if ($suspectedType === 'property or parameter' + && (isset(Util\Tokens::$scopeModifiers[$this->tokens[$x]['code']]) === true + || $this->tokens[$x]['code'] === T_VAR) + ) { + // This will also confirm constructor property promotion parameters, but that's fine. + $confirmed = true; + } + + break; + }//end for + + if ($confirmed === false + && $suspectedType === 'property or parameter' + && isset($this->tokens[$i]['nested_parenthesis']) === true + ) { + $parens = $this->tokens[$i]['nested_parenthesis']; + $last = end($parens); + + if (isset($this->tokens[$last]['parenthesis_owner']) === true + && $this->tokens[$this->tokens[$last]['parenthesis_owner']]['code'] === T_FUNCTION + ) { + $confirmed = true; + } else { + // No parenthesis owner set, this may be an arrow function which has not yet + // had additional processing done. + if (isset($this->tokens[$last]['parenthesis_opener']) === true) { + for ($x = ($this->tokens[$last]['parenthesis_opener'] - 1); $x >= 0; $x--) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) { + continue; + } + + break; + } + + if ($this->tokens[$x]['code'] === T_FN) { + for (--$x; $x >= 0; $x--) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true + || $this->tokens[$x]['code'] === T_BITWISE_AND + ) { + continue; + } + + break; + } + + if ($this->tokens[$x]['code'] !== T_FUNCTION) { + $confirmed = true; + } + } + }//end if + }//end if + + unset($parens, $last); + }//end if + + if ($confirmed === false) { + // Not a union type after all, move on. + continue; + } + + foreach ($unionOperators as $x) { + $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; + } + } + continue; } else if ($this->tokens[$i]['code'] === T_STATIC) { for ($x = ($i - 1); $x > 0; $x--) { diff --git a/tests/Core/Tokenizer/BitwiseOrTest.inc b/tests/Core/Tokenizer/BitwiseOrTest.inc new file mode 100644 index 0000000000..921ef7f5ae --- /dev/null +++ b/tests/Core/Tokenizer/BitwiseOrTest.inc @@ -0,0 +1,85 @@ + $param | $int; + +/* testTypeUnionArrowReturnType */ +$arrowWithReturnType = fn ($param) : int|null => $param * 10; + +/* testBitwiseOrInArrayKey */ +$array = array( + A | B => /* testBitwiseOrInArrayValue */ B | C +); + +/* testBitwiseOrInShortArrayKey */ +$array = [ + A | B => /* testBitwiseOrInShortArrayValue */ B | C +]; + +/* testBitwiseOrTryCatch */ +try { +} catch ( ExceptionA | ExceptionB $e ) { +} + +/* testBitwiseOrNonArrowFnFunctionCall */ +$obj->fn($something | $else); + +/* testTypeUnionNonArrowFunctionDeclaration */ +function &fn(int|false $something) {} + +/* testLiveCoding */ +// Intentional parse error. This has to be the last test in the file. +return function( type| diff --git a/tests/Core/Tokenizer/BitwiseOrTest.php b/tests/Core/Tokenizer/BitwiseOrTest.php new file mode 100644 index 0000000000..88d8e587a0 --- /dev/null +++ b/tests/Core/Tokenizer/BitwiseOrTest.php @@ -0,0 +1,122 @@ + + * @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 BitwiseOrTest extends AbstractMethodUnitTest +{ + + + /** + * Test that non-union type bitwise or tokens are still tokenized as bitwise or. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataBitwiseOr + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testBitwiseOr($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $opener = $this->getTargetToken($testMarker, [T_BITWISE_OR, T_TYPE_UNION]); + $this->assertSame(T_BITWISE_OR, $tokens[$opener]['code']); + $this->assertSame('T_BITWISE_OR', $tokens[$opener]['type']); + + }//end testBitwiseOr() + + + /** + * Data provider. + * + * @see testBitwiseOr() + * + * @return array + */ + public function dataBitwiseOr() + { + return [ + ['/* testBitwiseOr1 */'], + ['/* testBitwiseOr2 */'], + ['/* testBitwiseOrPropertyDefaultValue */'], + ['/* testBitwiseOrParamDefaultValue */'], + ['/* testBitwiseOr3 */'], + ['/* testBitwiseOrClosureParamDefault */'], + ['/* testBitwiseOrArrowParamDefault */'], + ['/* testBitwiseOrArrowExpression */'], + ['/* testBitwiseOrInArrayKey */'], + ['/* testBitwiseOrInArrayValue */'], + ['/* testBitwiseOrInShortArrayKey */'], + ['/* testBitwiseOrInShortArrayValue */'], + ['/* testBitwiseOrTryCatch */'], + ['/* testBitwiseOrNonArrowFnFunctionCall */'], + ['/* testLiveCoding */'], + ]; + + }//end dataBitwiseOr() + + + /** + * Test that bitwise or tokens when used as part of a union type are tokenized as `T_TYPE_UNION`. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataTypeUnion + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testTypeUnion($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $opener = $this->getTargetToken($testMarker, [T_BITWISE_OR, T_TYPE_UNION]); + $this->assertSame(T_TYPE_UNION, $tokens[$opener]['code']); + $this->assertSame('T_TYPE_UNION', $tokens[$opener]['type']); + + }//end testTypeUnion() + + + /** + * Data provider. + * + * @see testTypeUnion() + * + * @return array + */ + public function dataTypeUnion() + { + return [ + ['/* testTypeUnionPropertySimple */'], + ['/* testTypeUnionPropertyReverseModifierOrder */'], + ['/* testTypeUnionPropertyMulti1 */'], + ['/* testTypeUnionPropertyMulti2 */'], + ['/* testTypeUnionPropertyMulti3 */'], + ['/* testTypeUnionParam1 */'], + ['/* testTypeUnionParam2 */'], + ['/* testTypeUnionParam3 */'], + ['/* testTypeUnionReturnType */'], + ['/* testTypeUnionConstructorPropertyPromotion */'], + ['/* testTypeUnionAbstractMethodReturnType1 */'], + ['/* testTypeUnionAbstractMethodReturnType2 */'], + ['/* testTypeUnionClosureParamIllegalNullable */'], + ['/* testTypeUnionClosureReturn */'], + ['/* testTypeUnionArrowParam */'], + ['/* testTypeUnionArrowReturnType */'], + ['/* testTypeUnionNonArrowFunctionDeclaration */'], + ]; + + }//end dataTypeUnion() + + +}//end class From 8112f56b4ab79e744aa659b3d9de8634e333b099 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 7 Jul 2020 09:09:55 +0200 Subject: [PATCH 106/733] PHP 8.0 | Tokenizer/PHP: array return type keyword to T_STRING vs PHP8 union types `array` keywords used as return types in PHP 8 union types would only be correctly changed to `T_STRING` if they were the first type in the union. Fixed now. Includes adding `T_STATIC` to the array of allowed tokens. While previously it wasn't an issue that the token was not included in the array, it is necessary for the token to be there to support union types. This change will be tested via the union type related tests for the `File::getMethodProperties()` method. --- src/Tokenizers/PHP.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 1ad85c4156..5e6edd2038 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1475,6 +1475,7 @@ function return types. We want to keep the parenthesis map clean, T_SELF => T_SELF, T_PARENT => T_PARENT, T_NAMESPACE => T_NAMESPACE, + T_STATIC => T_STATIC, T_NS_SEPARATOR => T_NS_SEPARATOR, ]; @@ -1509,12 +1510,14 @@ function return types. We want to keep the parenthesis map clean, }//end for // Any T_ARRAY tokens we find between here and the next - // token that can't be part of the return type need to be + // token that can't be part of the return type, need to be // converted to T_STRING tokens. for ($x; $x < $numTokens; $x++) { - if (is_array($tokens[$x]) === false || isset($allowed[$tokens[$x][0]]) === false) { + if ((is_array($tokens[$x]) === false && $tokens[$x] !== '|') + || (is_array($tokens[$x]) === true && isset($allowed[$tokens[$x][0]]) === false) + ) { break; - } else if ($tokens[$x][0] === T_ARRAY) { + } else if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_ARRAY) { $tokens[$x][0] = T_STRING; if (PHP_CODESNIFFER_VERBOSITY > 1) { From dc6fe75c67a206ced2849cb0a5adf7e3d10fff94 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Jul 2020 10:07:13 +0200 Subject: [PATCH 107/733] PHP 8.0 | Tokenizer/PHP: arrow function backfill vs PHP8 union types As the `PHP::processAdditional()` method walks backwards through the file, by the time the `fn` keyword backfill logic is hit, any union type `|` tokens will have already been converted to `T_TYPE_UNION`. So, to make the arrow function backfill compatible with PHP 8 union types, the `T_TYPE_UNION` token needs to be added to the "allowed tokens" (`$ignore`) array. Includes unit tests. --- src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/BackfillFnTokenTest.inc | 9 ++++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 57 ++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5e6edd2038..0d71a46be4 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1999,6 +1999,7 @@ protected function processAdditional() T_PARENT => T_PARENT, T_SELF => T_SELF, T_STATIC => T_STATIC, + T_TYPE_UNION => T_TYPE_UNION, ]; $closer = $this->tokens[$x]['parenthesis_closer']; diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index 46c165a2d2..f470bb0c67 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -81,6 +81,12 @@ fn(array $a) : array => $a; /* testStaticReturnType */ fn(array $a) : static => $a; +/* testUnionParamType */ +$arrowWithUnionParam = fn(int|float $param) : SomeClass => new SomeClass($param); + +/* testUnionReturnType */ +$arrowWithUnionReturn = fn($param) : int|float => $param | 10; + /* testTernary */ $fn = fn($a) => $a ? /* testTernaryThen */ fn() : string => 'a' : /* testTernaryElse */ fn() : string => 'b'; @@ -130,6 +136,9 @@ $a = MyNS\Sub\Fn($param); /* testNonArrowNamespaceOperatorFunctionCall */ $a = namespace\fn($param); +/* testNonArrowFunctionNameWithUnionTypes */ +function fn(int|float $param) : string|null {} + /* testLiveCoding */ // Intentional parse error. This has to be the last test in the file. $fn = fn diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 9ef8dad442..b8ea0170ee 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -531,6 +531,62 @@ public function testKeywordReturnTypes() }//end testKeywordReturnTypes() + /** + * Test arrow function with a union parameter type. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testUnionParamType() + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken('/* testUnionParamType */', T_FN); + $this->backfillHelper($token); + + $this->assertSame($tokens[$token]['scope_opener'], ($token + 13), 'Scope opener is not the arrow token'); + $this->assertSame($tokens[$token]['scope_closer'], ($token + 21), 'Scope closer is not the semicolon token'); + + $opener = $tokens[$token]['scope_opener']; + $this->assertSame($tokens[$opener]['scope_opener'], ($token + 13), 'Opener scope opener is not the arrow token'); + $this->assertSame($tokens[$opener]['scope_closer'], ($token + 21), 'Opener scope closer is not the semicolon token'); + + $closer = $tokens[$token]['scope_closer']; + $this->assertSame($tokens[$closer]['scope_opener'], ($token + 13), 'Closer scope opener is not the arrow token'); + $this->assertSame($tokens[$closer]['scope_closer'], ($token + 21), 'Closer scope closer is not the semicolon token'); + + }//end testUnionParamType() + + + /** + * Test arrow function with a union return type. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testUnionReturnType() + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken('/* testUnionReturnType */', T_FN); + $this->backfillHelper($token); + + $this->assertSame($tokens[$token]['scope_opener'], ($token + 11), 'Scope opener is not the arrow token'); + $this->assertSame($tokens[$token]['scope_closer'], ($token + 18), 'Scope closer is not the semicolon token'); + + $opener = $tokens[$token]['scope_opener']; + $this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), 'Opener scope opener is not the arrow token'); + $this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the semicolon token'); + + $closer = $tokens[$token]['scope_closer']; + $this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), 'Closer scope opener is not the arrow token'); + $this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the semicolon token'); + + }//end testUnionReturnType() + + /** * Test arrow functions used in ternary operators. * @@ -690,6 +746,7 @@ public function dataNotAnArrowFunction() 'Fn', ], ['/* testNonArrowNamespaceOperatorFunctionCall */'], + ['/* testNonArrowFunctionNameWithUnionTypes */'], ['/* testLiveCoding */'], ]; From 564b780d2307f222eb73e3abe2247286f7f1421d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Jul 2020 06:44:32 +0200 Subject: [PATCH 108/733] PHP 8.0 | File::getMethodParameters(): add support for "union" parameter types This adds support for union types to the `File::getMethodParameters()` method. Includes extensive unit tests. --- src/Files/File.php | 3 + tests/Core/File/GetMethodParametersTest.inc | 44 ++++ tests/Core/File/GetMethodParametersTest.php | 278 +++++++++++++++++++- 3 files changed, 324 insertions(+), 1 deletion(-) diff --git a/src/Files/File.php b/src/Files/File.php index d1e9a4da87..3ec37be51d 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1443,6 +1443,9 @@ public function getMethodParameters($stackPtr) break; case T_NAMESPACE: case T_NS_SEPARATOR: + case T_TYPE_UNION: + case T_FALSE: + case T_NULL: // Part of a type hint or default value. if ($defaultStart === null) { if ($typeHintToken === false) { diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index 4ffd44221f..56fa6eae41 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -41,3 +41,47 @@ function mixedTypeHintNullable(?Mixed $var1) {} /* testNamespaceOperatorTypeHint */ function namespaceOperatorTypeHint(?namespace\Name $var1) {} + +/* testPHP8UnionTypesSimple */ +function unionTypeSimple(int|float $number, self|parent &...$obj) {} + +/* testPHP8UnionTypesSimpleWithBitwiseOrInDefault */ +$fn = fn(int|float $var = CONSTANT_A | CONSTANT_B) => $var; + +/* testPHP8UnionTypesTwoClasses */ +function unionTypesTwoClasses(MyClassA|\Package\MyClassB $var) {} + +/* testPHP8UnionTypesAllBaseTypes */ +function unionTypesAllBaseTypes(array|bool|callable|int|float|null|object|string $var) {} + +/* testPHP8UnionTypesAllPseudoTypes */ +// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method. +function unionTypesAllPseudoTypes(false|mixed|self|parent|iterable|Resource $var) {} + +/* testPHP8UnionTypesNullable */ +// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method. +$closure = function (?int|float $number) {}; + +/* testPHP8PseudoTypeNull */ +// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. +function pseudoTypeNull(null $var = null) {} + +/* testPHP8PseudoTypeFalse */ +// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. +function pseudoTypeFalse(false $var = false) {} + +/* testPHP8PseudoTypeFalseAndBool */ +// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method. +function pseudoTypeFalseAndBool(bool|false $var = false) {} + +/* testPHP8ObjectAndClass */ +// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method. +function objectAndClass(object|ClassName $var) {} + +/* testPHP8PseudoTypeIterableAndArray */ +// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method. +function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {} + +/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */ +// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method. +function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index 92f51959e6..ad4cf8b67e 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -340,6 +340,282 @@ public function testNamespaceOperatorTypeHint() }//end testNamespaceOperatorTypeHint() + /** + * Verify recognition of PHP8 union type declaration. + * + * @return void + */ + public function testPHP8UnionTypesSimple() + { + $expected = []; + $expected[0] = [ + 'name' => '$number', + 'content' => 'int|float $number', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'int|float', + 'nullable_type' => false, + ]; + $expected[1] = [ + 'name' => '$obj', + 'content' => 'self|parent &...$obj', + 'pass_by_reference' => true, + 'variable_length' => true, + 'type_hint' => 'self|parent', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesSimple() + + + /** + * Verify recognition of PHP8 union type declaration with a bitwise or in the default value. + * + * @return void + */ + public function testPHP8UnionTypesSimpleWithBitwiseOrInDefault() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'int|float $var = CONSTANT_A | CONSTANT_B', + 'default' => 'CONSTANT_A | CONSTANT_B', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'int|float', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesSimpleWithBitwiseOrInDefault() + + + /** + * Verify recognition of PHP8 union type declaration with two classes. + * + * @return void + */ + public function testPHP8UnionTypesTwoClasses() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'MyClassA|\Package\MyClassB $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'MyClassA|\Package\MyClassB', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesTwoClasses() + + + /** + * Verify recognition of PHP8 union type declaration with all base types. + * + * @return void + */ + public function testPHP8UnionTypesAllBaseTypes() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'array|bool|callable|int|float|null|object|string $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'array|bool|callable|int|float|null|object|string', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesAllBaseTypes() + + + /** + * Verify recognition of PHP8 union type declaration with all pseudo types. + * + * @return void + */ + public function testPHP8UnionTypesAllPseudoTypes() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'false|mixed|self|parent|iterable|Resource $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'false|mixed|self|parent|iterable|Resource', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesAllPseudoTypes() + + + /** + * Verify recognition of PHP8 union type declaration with (illegal) nullability. + * + * @return void + */ + public function testPHP8UnionTypesNullable() + { + $expected = []; + $expected[0] = [ + 'name' => '$number', + 'content' => '?int|float $number', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?int|float', + 'nullable_type' => true, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesNullable() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) single type null. + * + * @return void + */ + public function testPHP8PseudoTypeNull() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'null $var = null', + 'default' => 'null', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'null', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8PseudoTypeNull() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) single type false. + * + * @return void + */ + public function testPHP8PseudoTypeFalse() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'false $var = false', + 'default' => 'false', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'false', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8PseudoTypeFalse() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) type false combined with type bool. + * + * @return void + */ + public function testPHP8PseudoTypeFalseAndBool() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'bool|false $var = false', + 'default' => 'false', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'bool|false', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8PseudoTypeFalseAndBool() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) type object combined with a class name. + * + * @return void + */ + public function testPHP8ObjectAndClass() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'object|ClassName $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'object|ClassName', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8ObjectAndClass() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) type iterable combined with array/Traversable. + * + * @return void + */ + public function testPHP8PseudoTypeIterableAndArray() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'iterable|array|Traversable $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'iterable|array|Traversable', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8PseudoTypeIterableAndArray() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) duplicate types. + * + * @return void + */ + public function testPHP8DuplicateTypeInUnionWhitespaceAndComment() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'int | string /*comment*/ | INT $var', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'int|string|INT', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8DuplicateTypeInUnionWhitespaceAndComment() + + /** * Test helper. * @@ -350,7 +626,7 @@ public function testNamespaceOperatorTypeHint() */ private function getMethodParametersTestHelper($commentString, $expected) { - $function = $this->getTargetToken($commentString, [T_FUNCTION, T_FN]); + $function = $this->getTargetToken($commentString, [T_FUNCTION, T_CLOSURE, T_FN]); $found = self::$phpcsFile->getMethodParameters($function); $this->assertArraySubset($expected, $found, true); From 38c01b340a204b2f6b3f635436f4e1168349512c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Jul 2020 07:32:55 +0200 Subject: [PATCH 109/733] PHP 8.0 | File::getMethodProperties(): add support for "union" return types This adds support for union types to the `File::getMethodProperties()` method. Includes extensive unit tests. --- src/Files/File.php | 3 + tests/Core/File/GetMethodPropertiesTest.inc | 43 ++++ tests/Core/File/GetMethodPropertiesTest.php | 253 ++++++++++++++++++++ 3 files changed, 299 insertions(+) diff --git a/src/Files/File.php b/src/Files/File.php index 3ec37be51d..ec7ba2369b 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1634,8 +1634,11 @@ public function getMethodProperties($stackPtr) 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, ]; for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) { diff --git a/tests/Core/File/GetMethodPropertiesTest.inc b/tests/Core/File/GetMethodPropertiesTest.inc index 82c032efa0..3e9682df6e 100644 --- a/tests/Core/File/GetMethodPropertiesTest.inc +++ b/tests/Core/File/GetMethodPropertiesTest.inc @@ -83,3 +83,46 @@ function mixedTypeHintNullable(): ?mixed {} /* testNamespaceOperatorTypeHint */ function namespaceOperatorTypeHint() : ?namespace\Name {} + +/* testPHP8UnionTypesSimple */ +function unionTypeSimple($number) : int|float {} + +/* testPHP8UnionTypesTwoClasses */ +$fn = fn($var): MyClassA|\Package\MyClassB => $var; + +/* testPHP8UnionTypesAllBaseTypes */ +function unionTypesAllBaseTypes() : array|bool|callable|int|float|null|Object|string {} + +/* testPHP8UnionTypesAllPseudoTypes */ +// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method. +function unionTypesAllPseudoTypes($var) : false|MIXED|self|parent|static|iterable|Resource|void {} + +/* testPHP8UnionTypesNullable */ +// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method. +$closure = function () use($a) :?int|float {}; + +/* testPHP8PseudoTypeNull */ +// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. +function pseudoTypeNull(): null {} + +/* testPHP8PseudoTypeFalse */ +// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. +function pseudoTypeFalse(): false {} + +/* testPHP8PseudoTypeFalseAndBool */ +// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method. +function pseudoTypeFalseAndBool(): bool|false {} + +/* testPHP8ObjectAndClass */ +// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method. +function objectAndClass(): object|ClassName {} + +/* testPHP8PseudoTypeIterableAndArray */ +// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method. +interface FooBar { + public function pseudoTypeIterableAndArray(): iterable|array|Traversable; +} + +/* 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 {} diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php index ee11fc2b9f..d912d6b1ce 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -475,6 +475,259 @@ public function testNamespaceOperatorTypeHint() }//end testNamespaceOperatorTypeHint() + /** + * Verify recognition of PHP8 union type declaration. + * + * @return void + */ + public function testPHP8UnionTypesSimple() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'int|float', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesSimple() + + + /** + * Verify recognition of PHP8 union type declaration with two classes. + * + * @return void + */ + public function testPHP8UnionTypesTwoClasses() + { + $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 testPHP8UnionTypesTwoClasses() + + + /** + * Verify recognition of PHP8 union type declaration with all base types. + * + * @return void + */ + public function testPHP8UnionTypesAllBaseTypes() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'array|bool|callable|int|float|null|Object|string', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesAllBaseTypes() + + + /** + * Verify recognition of PHP8 union type declaration with all pseudo types. + * + * @return void + */ + public function testPHP8UnionTypesAllPseudoTypes() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'false|MIXED|self|parent|static|iterable|Resource|void', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesAllPseudoTypes() + + + /** + * Verify recognition of PHP8 union type declaration with (illegal) nullability. + * + * @return void + */ + public function testPHP8UnionTypesNullable() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => '?int|float', + 'nullable_return_type' => true, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesNullable() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) single type null. + * + * @return void + */ + public function testPHP8PseudoTypeNull() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'null', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8PseudoTypeNull() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) single type false. + * + * @return void + */ + public function testPHP8PseudoTypeFalse() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'false', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8PseudoTypeFalse() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) type false combined with type bool. + * + * @return void + */ + public function testPHP8PseudoTypeFalseAndBool() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'bool|false', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8PseudoTypeFalseAndBool() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) type object combined with a class name. + * + * @return void + */ + public function testPHP8ObjectAndClass() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'object|ClassName', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8ObjectAndClass() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) type iterable combined with array/Traversable. + * + * @return void + */ + public function testPHP8PseudoTypeIterableAndArray() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => true, + 'return_type' => 'iterable|array|Traversable', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => false, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8PseudoTypeIterableAndArray() + + + /** + * Verify recognition of PHP8 type declaration with (illegal) duplicate types. + * + * @return void + */ + public function testPHP8DuplicateTypeInUnionWhitespaceAndComment() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'int|string|INT', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8DuplicateTypeInUnionWhitespaceAndComment() + + /** * Test helper. * From b552768e297b7018f541e2900f194b153367f37c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Jul 2020 09:41:35 +0200 Subject: [PATCH 110/733] PHP 8.0 | File::getMemberProperties(): add support for "union" types This adds support for union types to the `File::getMemberProperties()` method. Includes extensive unit tests. --- src/Files/File.php | 3 + tests/Core/File/GetMemberPropertiesTest.inc | 47 ++++++++ tests/Core/File/GetMemberPropertiesTest.php | 121 ++++++++++++++++++++ 3 files changed, 171 insertions(+) diff --git a/src/Files/File.php b/src/Files/File.php index ec7ba2369b..7050fe16f8 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1821,8 +1821,11 @@ public function getMemberProperties($stackPtr) 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, ]; for ($i; $i < $stackPtr; $i++) { diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index 0a511e588d..9cc56bd53a 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -193,3 +193,50 @@ class NSOperatorInType { /* testNamespaceOperatorTypeHint */ public ?namespace\Name $prop; } + +$anon = class() { + /* testPHP8UnionTypesSimple */ + public int|float $unionTypeSimple; + + /* testPHP8UnionTypesTwoClasses */ + private MyClassA|\Package\MyClassB $unionTypesTwoClasses; + + /* testPHP8UnionTypesAllBaseTypes */ + protected array|bool|int|float|NULL|object|string $unionTypesAllBaseTypes; + + /* testPHP8UnionTypesAllPseudoTypes */ + // Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method. + var false|mixed|self|parent|iterable|Resource $unionTypesAllPseudoTypes; + + /* testPHP8UnionTypesIllegalTypes */ + // Intentional fatal error - types which are not allowed for properties, but that's not the concern of the method. + public callable|static|void $unionTypesIllegalTypes; + + /* testPHP8UnionTypesNullable */ + // Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method. + public ?int|float $unionTypesNullable; + + /* testPHP8PseudoTypeNull */ + // Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. + public null $pseudoTypeNull; + + /* testPHP8PseudoTypeFalse */ + // Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. + public false $pseudoTypeFalse; + + /* testPHP8PseudoTypeFalseAndBool */ + // Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method. + public bool|FALSE $pseudoTypeFalseAndBool; + + /* testPHP8ObjectAndClass */ + // Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method. + public object|ClassName $objectAndClass; + + /* testPHP8PseudoTypeIterableAndArray */ + // Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method. + public iterable|array|Traversable $pseudoTypeIterableAndArray; + + /* 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; +}; diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index dfee43e203..a59effc752 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -489,6 +489,127 @@ public function dataGetMemberProperties() 'nullable_type' => true, ], ], + [ + '/* testPHP8UnionTypesSimple */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'int|float', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8UnionTypesTwoClasses */', + [ + 'scope' => 'private', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'MyClassA|\Package\MyClassB', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8UnionTypesAllBaseTypes */', + [ + 'scope' => 'protected', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'array|bool|int|float|NULL|object|string', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8UnionTypesAllPseudoTypes */', + [ + 'scope' => 'public', + 'scope_specified' => false, + 'is_static' => false, + 'type' => 'false|mixed|self|parent|iterable|Resource', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8UnionTypesIllegalTypes */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + // Missing static, but that's OK as not an allowed syntax. + 'type' => 'callable||void', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8UnionTypesNullable */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => '?int|float', + 'nullable_type' => true, + ], + ], + [ + '/* testPHP8PseudoTypeNull */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'null', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8PseudoTypeFalse */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'false', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8PseudoTypeFalseAndBool */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'bool|FALSE', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8ObjectAndClass */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'object|ClassName', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8PseudoTypeIterableAndArray */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'iterable|array|Traversable', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'int|string|INT', + 'nullable_type' => false, + ], + ], ]; }//end dataGetMemberProperties() From 9a502fdb576b04f7923adbbe0061f2b64648296a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 6 Jul 2020 02:44:46 +0200 Subject: [PATCH 111/733] Docs: update "nullable_type" comments to clarify meaning --- src/Files/File.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 7050fe16f8..eac30ef95c 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1288,7 +1288,8 @@ public function getDeclarationName($stackPtr) * // or FALSE if there is no type hint. * 'type_hint_end_token' => integer, // The stack pointer to the end of the type hint * // or FALSE if there is no type hint. - * 'nullable_type' => boolean, // TRUE if the var type is nullable. + * 'nullable_type' => boolean, // TRUE if the type is preceded by the nullability + * // operator. * 'comma_token' => integer, // The stack pointer to the comma after the param * // or FALSE if this is the last param. * ) @@ -1536,7 +1537,8 @@ public function getMethodParameters($stackPtr) * 'return_type' => '', // The return type of the method. * 'return_type_token' => integer, // The stack pointer to the start of the return type * // or FALSE if there is no return type. - * 'nullable_return_type' => false, // TRUE if the return type is nullable. + * 'nullable_return_type' => false, // TRUE if the return type is preceded by the + * // nullability operator. * 'is_abstract' => false, // TRUE if the abstract keyword was found. * 'is_final' => false, // TRUE if the final keyword was found. * 'is_static' => false, // TRUE if the static keyword was found. @@ -1706,7 +1708,8 @@ public function getMethodProperties($stackPtr) * // or FALSE if there is no type. * 'type_end_token' => integer, // The stack pointer to the end of the type * // or FALSE if there is no type. - * 'nullable_type' => boolean, // TRUE if the type is nullable. + * 'nullable_type' => boolean, // TRUE if the type is preceded by the nullability + * // operator. * ); * * From 53aa40911ff8217008919c5f173d6cd124f318b2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 14:20:04 +0200 Subject: [PATCH 112/733] Tokenizer/PHP: add new token to the $knownLengths property --- src/Tokenizers/PHP.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 0d71a46be4..c0e202a759 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -439,6 +439,7 @@ class PHP extends Tokenizer T_BACKTICK => 1, T_OPEN_SHORT_ARRAY => 1, T_CLOSE_SHORT_ARRAY => 1, + T_TYPE_UNION => 1, ]; /** From 25a2e44822cabae63ee203c337117d51d106e501 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 21 Sep 2020 01:49:46 +0200 Subject: [PATCH 113/733] Tests: update the Tokenizer/UndoNamespacedNameSingleToken test ... to allow for the new `T_TYPE_UNION` token. --- tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php b/tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php index 27cd47783e..24667e486f 100644 --- a/tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php +++ b/tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php @@ -657,9 +657,8 @@ public function dataIdentifierTokenization() 'type' => 'T_STRING', 'content' => 'Name', ], - // TODO: change this to T_TYPE_UNION when #3032 is merged. [ - 'type' => 'T_BITWISE_OR', + 'type' => 'T_TYPE_UNION', 'content' => '|', ], [ @@ -708,9 +707,8 @@ public function dataIdentifierTokenization() 'type' => 'T_STRING', 'content' => 'Unqualified', ], - // TODO: change this to T_TYPE_UNION when #3032 is merged. [ - 'type' => 'T_BITWISE_OR', + 'type' => 'T_TYPE_UNION', 'content' => '|', ], [ From af125252a86c63449716301ff6b0db78e0f56354 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 14:19:43 +1000 Subject: [PATCH 114/733] Added missing unit tests for #3061 --- .../Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php | 5 +++-- .../Tests/Arrays/ArrayDeclarationUnitTest.1.inc | 13 +++++++++++++ .../Arrays/ArrayDeclarationUnitTest.1.inc.fixed | 13 +++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php index 53fddfd0d7..70edbf150a 100644 --- a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -620,10 +620,11 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array $valuePointer = $value['value']; - $ignoreTokens = ([ + $ignoreTokens = [ T_WHITESPACE => T_WHITESPACE, T_COMMA => T_COMMA, - ] + Tokens::$castTokens); + ]; + $ignoreTokens += Tokens::$castTokens; if ($tokens[$valuePointer]['code'] === T_CLOSURE) { $ignoreTokens += [T_STATIC => T_STATIC]; diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc index e6fdbdc1f8..8a4dd89298 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc @@ -447,6 +447,19 @@ $c); array('a' => $a, 'b' => $b, 'c' => $c); +array() + static function() { + return null; + }, + (array) array(), + (bool) array(), + (double) array(), + (int) array(), + (object) array(), + (string) array(), + (unset) array(), +); + // 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 336ab0af9f..02a32424aa 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed @@ -483,6 +483,19 @@ array( 'c' => $c, ); +array() + static function() { + return null; + }, + (array) array(), + (bool) array(), + (double) array(), + (int) array(), + (object) array(), + (string) array(), + (unset) array(), +); + // Intentional syntax error. $a = array( 'a' => From 466297a75f3af93b3bb601df8e295390388272ce Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 14:22:44 +1000 Subject: [PATCH 115/733] Changelogs for fixes resulting from #3061 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index d4c2aa4b3e..e50860dae1 100644 --- a/package.xml +++ b/package.xml @@ -48,6 +48,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots + - Fixed bug #3059 : Squiz.Arrays.ArrayDeclaration false positive when using type casting + -- Thanks to Sergei Morozov for the patch + - Fixed bug #3060 : Squiz.Arrays.ArrayDeclaration false positive for static functions + -- Thanks to Sergei Morozov for the patch - Fixed bug #3066 : No support for namespace operator used in type declarations -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number From 38b487e7b6e2ae0b1b917e708770b838130b07d2 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 14:38:02 +1000 Subject: [PATCH 116/733] Changelog for #3065 (ref #3074) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index e50860dae1..93aae1125f 100644 --- a/package.xml +++ b/package.xml @@ -52,6 +52,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Sergei Morozov for the patch - Fixed bug #3060 : Squiz.Arrays.ArrayDeclaration false positive for static functions -- Thanks to Sergei Morozov for the patch + - Fixed bug #3065 : Should not fix Squiz.Arrays.ArrayDeclaration.SpaceBeforeComma if comment between element and comma + -- Thanks to Sergei Morozov for the patch - Fixed bug #3066 : No support for namespace operator used in type declarations -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number From e94627c06d1f005f92cf2abce28366827c92665e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 14:55:28 +1000 Subject: [PATCH 117/733] Moved tests around to group them (ref #3101) --- .../Tests/Arrays/ArrayIndentUnitTest.inc | 18 ++++++++---------- .../Tests/Arrays/ArrayIndentUnitTest.inc.fixed | 18 ++++++++---------- .../Tests/Arrays/ArrayIndentUnitTest.php | 4 ++-- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc index 44398effb0..da2e7c9c92 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc @@ -70,16 +70,6 @@ $foo = ], ]; -// phpcs:set Generic.Arrays.ArrayIndent indent 2 - -$var = [ -1 => 'one', - 2 => 'two', - /* three */ 3 => 'three', - ]; - -// phpcs:set Generic.Arrays.ArrayIndent indent 4 - $foo = [ 'foo' . 'bar', @@ -88,3 +78,11 @@ $foo = [ 'qux', ], ]; + +// phpcs:set Generic.Arrays.ArrayIndent indent 2 + +$var = [ +1 => 'one', + 2 => 'two', + /* three */ 3 => 'three', + ]; diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed index d32d9e3a6d..503b3c6d15 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed @@ -71,16 +71,6 @@ $foo = ], ]; -// phpcs:set Generic.Arrays.ArrayIndent indent 2 - -$var = [ - 1 => 'one', - 2 => 'two', - /* three */ 3 => 'three', -]; - -// phpcs:set Generic.Arrays.ArrayIndent indent 4 - $foo = [ 'foo' . 'bar', @@ -89,3 +79,11 @@ $foo = [ 'qux', ], ]; + +// phpcs:set Generic.Arrays.ArrayIndent indent 2 + +$var = [ + 1 => 'one', + 2 => 'two', + /* three */ 3 => 'three', +]; diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php index a4b89294b4..77a1014d3e 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php @@ -36,13 +36,13 @@ public function getErrorList() 62 => 1, 63 => 1, 69 => 1, - 76 => 1, 77 => 1, 78 => 1, 79 => 1, + 85 => 1, + 86 => 1, 87 => 1, 88 => 1, - 89 => 1, ]; }//end getErrorList() From 2b845d19431fd0e6624b5e7d73e0c63357493696 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 16:50:19 +1000 Subject: [PATCH 118/733] Changelog for #2988 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 93aae1125f..a7a74c0041 100644 --- a/package.xml +++ b/package.xml @@ -45,6 +45,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level - Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary + - Fixed bug #2988 : Undefined offset in Squiz.Strings.ConcatenationSpacing during live coding + -- Thanks to Thiemo Kreuz for the patch - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots From a0a6cd8632c1ce25befe7d4feab4b64a4e861940 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 21 Sep 2020 17:09:05 +1000 Subject: [PATCH 119/733] Changelog for #2989 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index a7a74c0041..30f8da76b6 100644 --- a/package.xml +++ b/package.xml @@ -47,6 +47,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary - Fixed bug #2988 : Undefined offset in Squiz.Strings.ConcatenationSpacing during live coding -- Thanks to Thiemo Kreuz for the patch + - Fixed bug #2989 : Incorrect auto-fixing in Generic.ControlStructures.InlineControlStructure during live coding + -- Thanks to Thiemo Kreuz for the patch - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots From b50a9a37174f973d4ad877d7e7e0409208300eb3 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 22 Sep 2020 09:23:13 +1000 Subject: [PATCH 120/733] Fixed unintentional syntax error in test file --- src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc | 2 +- .../Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc index e5bd67d6fe..c021b8b305 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc @@ -447,7 +447,7 @@ $c); array('a' => $a, 'b' => $b, 'c' => $c); -array() +array( static function() { return null; }, diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed index 5305947ef4..5a2a4e3717 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed @@ -483,7 +483,7 @@ array( 'c' => $c, ); -array() +array( static function() { return null; }, From edafaae967aee2b536e1a6108a05bac3f85f35f3 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 24 Sep 2020 14:05:18 +1000 Subject: [PATCH 121/733] Fixed bug #3124 : PSR-12 not reporting error for empty lines with only whitespace --- package.xml | 1 + src/Standards/PSR12/ruleset.xml | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/package.xml b/package.xml index 30f8da76b6..2b17bd592f 100644 --- a/package.xml +++ b/package.xml @@ -62,6 +62,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number -- Thanks to Sergei Morozov for the patch + - Fixed bug #3124 : PSR-12 not reporting error for empty lines with only whitespace diff --git a/src/Standards/PSR12/ruleset.xml b/src/Standards/PSR12/ruleset.xml index 312f27fc70..e9cfcc8bf9 100644 --- a/src/Standards/PSR12/ruleset.xml +++ b/src/Standards/PSR12/ruleset.xml @@ -41,11 +41,7 @@ - - - - - + 0 From 954a1c6f37a8a21875cc70c7afe1a1322f86f013 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 24 Sep 2020 14:28:23 +1000 Subject: [PATCH 122/733] Fixed bug #3053 : PSR2 incorrect fix when multiple use statements on same line do not have whitespace between them --- package.xml | 1 + .../PSR2/Sniffs/Namespaces/UseDeclarationSniff.php | 4 ++++ .../PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc | 4 ++++ .../Tests/Namespaces/UseDeclarationUnitTest.2.inc.fixed | 8 ++++++++ .../PSR2/Tests/Namespaces/UseDeclarationUnitTest.php | 5 ++++- 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 2b17bd592f..f95be96af9 100644 --- a/package.xml +++ b/package.xml @@ -51,6 +51,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Thiemo Kreuz for the patch - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch + - Fixed bug #3053 : PSR2 incorrect fix when multiple use statements on same line do not have whitespace between them - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots - Fixed bug #3059 : Squiz.Arrays.ArrayDeclaration false positive when using type casting -- Thanks to Sergei Morozov for the patch diff --git a/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php index ea26657b03..21a9dbe9a3 100644 --- a/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php @@ -77,6 +77,10 @@ public function process(File $phpcsFile, $stackPtr) $baseUse = 'use'; } + if ($tokens[($next + 1)]['code'] !== T_WHITESPACE) { + $baseUse .= ' '; + } + $phpcsFile->fixer->replaceToken($next, ';'.$phpcsFile->eolChar.$baseUse); } } else { diff --git a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc index 068b5233f1..bc0d7a961e 100644 --- a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc +++ b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc @@ -6,6 +6,10 @@ use My\Full\Classname as Another, My\Full\NSname; use function My\Full\functionname as somefunction, My\Full\otherfunction; use const My\Full\constantname as someconstant, My\Full\otherconstant; +use BarClass as Bar,FooClass,BazClass as Baz; +use function My\Full\functionname as somefunction,My\Full\otherfunction; +use const My\Full\constantname as someconstant,My\Full\otherconstant; + namespace AnotherProject; diff --git a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc.fixed b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc.fixed index 21e574ddb2..6579613b12 100644 --- a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc.fixed +++ b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.2.inc.fixed @@ -9,6 +9,14 @@ use function My\Full\otherfunction; use const My\Full\constantname as someconstant; use const My\Full\otherconstant; +use BarClass as Bar; +use FooClass; +use BazClass as Baz; +use function My\Full\functionname as somefunction; +use function My\Full\otherfunction; +use const My\Full\constantname as someconstant; +use const My\Full\otherconstant; + namespace AnotherProject; diff --git a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.php b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.php index b0b47e3530..b424a2c113 100644 --- a/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/Namespaces/UseDeclarationUnitTest.php @@ -34,7 +34,10 @@ public function getErrorList($testFile='') 5 => 1, 6 => 1, 7 => 1, - 12 => 1, + 9 => 1, + 10 => 1, + 11 => 1, + 16 => 1, ]; case 'UseDeclarationUnitTest.3.inc': return [ From 136ba51c4d044afd3fc4ab39be8000f0c9d7ea8c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 25 Sep 2020 09:18:01 +1000 Subject: [PATCH 123/733] Further fix for #3060 (ref #3061, #3112) Sniff wasn't ignore static arrow functions, or fixing the indent correctly when it actually was incorrect --- .../Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php | 10 ++++++---- .../Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc | 8 ++++++++ .../Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed | 8 ++++++++ .../Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc | 8 ++++++++ .../Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed | 8 ++++++++ .../Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php | 4 ++++ 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php index 2c59b9f615..17991bd79e 100644 --- a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -631,7 +631,9 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array ]; $ignoreTokens += Tokens::$castTokens; - if ($tokens[$valuePointer]['code'] === T_CLOSURE) { + if ($tokens[$valuePointer]['code'] === T_CLOSURE + || $tokens[$valuePointer]['code'] === T_FN + ) { $ignoreTokens += [T_STATIC => T_STATIC]; } @@ -667,12 +669,12 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array $found, ]; - $fix = $phpcsFile->addFixableError($error, $valuePointer, 'ValueNotAligned', $data); + $fix = $phpcsFile->addFixableError($error, $first, 'ValueNotAligned', $data); if ($fix === true) { if ($found === 0) { - $phpcsFile->fixer->addContent(($valuePointer - 1), str_repeat(' ', $expected)); + $phpcsFile->fixer->addContent(($first - 1), str_repeat(' ', $expected)); } else { - $phpcsFile->fixer->replaceToken(($valuePointer - 1), str_repeat(' ', $expected)); + $phpcsFile->fixer->replaceToken(($first - 1), str_repeat(' ', $expected)); } } } diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc index c021b8b305..750aaebcc0 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc @@ -467,6 +467,14 @@ array( , ); +yield array( + static fn () : string => '', +); + +yield array( + static fn () : string => '', + ); + // 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 5a2a4e3717..3ecc091da8 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed @@ -503,6 +503,14 @@ array( , ); +yield array( + static fn () : string => '', + ); + +yield array( + static fn () : string => '', + ); + // Intentional syntax error. $a = array( 'a' => diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc index f45fba2edf..621970fa1d 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc @@ -456,6 +456,14 @@ $c]; , ]; +yield [ + static fn () : string => '', +]; + +yield [ + static fn () : string => '', + ]; + // Intentional syntax error. $a = [ 'a' => diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed index 3a2aaf96e1..efe4c450e9 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed @@ -490,6 +490,14 @@ $foo = [ , ]; +yield [ + static fn () : string => '', + ]; + +yield [ + static fn () : string => '', + ]; + // Intentional syntax error. $a = [ 'a' => diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php index e91d86732a..15ce0746ef 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php @@ -122,6 +122,8 @@ public function getErrorList($testFile='') 447 => 2, 448 => 3, 467 => 1, + 471 => 1, + 472 => 1, ]; case 'ArrayDeclarationUnitTest.2.inc': return [ @@ -206,6 +208,8 @@ public function getErrorList($testFile='') 436 => 2, 437 => 3, 456 => 1, + 460 => 1, + 461 => 1, ]; default: return []; From 7c1df7d2cb4dd228c17a53d2edbbbf743b77e1f5 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 25 Sep 2020 13:58:53 +1000 Subject: [PATCH 124/733] Fixed XML formatting --- src/Standards/PSR12/ruleset.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Standards/PSR12/ruleset.xml b/src/Standards/PSR12/ruleset.xml index e9cfcc8bf9..1467004353 100644 --- a/src/Standards/PSR12/ruleset.xml +++ b/src/Standards/PSR12/ruleset.xml @@ -41,7 +41,7 @@ - + 0 From 9d036f84fb206942bbd8d4c1c0718b0e069fb32b Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 28 Sep 2020 08:46:15 +1000 Subject: [PATCH 125/733] Fixed bug #3075 : PSR12.ControlStructures.BooleanOperatorPlacement false positive when operator is the only content on line --- package.xml | 1 + .../BooleanOperatorPlacementSniff.php | 51 +++++++++++++------ .../BooleanOperatorPlacementUnitTest.inc | 10 ++++ ...BooleanOperatorPlacementUnitTest.inc.fixed | 10 ++++ 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/package.xml b/package.xml index f95be96af9..0cd2574fcd 100644 --- a/package.xml +++ b/package.xml @@ -61,6 +61,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Sergei Morozov for the patch - Fixed bug #3066 : No support for namespace operator used in type declarations -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3075 : PSR12.ControlStructures.BooleanOperatorPlacement false positive when operator is the only content on line - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number -- Thanks to Sergei Morozov for the patch - Fixed bug #3124 : PSR-12 not reporting error for empty lines with only whitespace diff --git a/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php b/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php index ad663db986..f101d399c9 100644 --- a/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php +++ b/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php @@ -90,35 +90,56 @@ public function process(File $phpcsFile, $stackPtr) break; } - $operators[] = $operator; - $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($operator - 1), $parenOpener, true); if ($prev === false) { // Parse error. return; } + $next = $phpcsFile->findNext(T_WHITESPACE, ($operator + 1), $parenCloser, true); + if ($next === false) { + // Parse error. + return; + } + + $firstOnLine = false; + $lastOnLine = false; + if ($tokens[$prev]['line'] < $tokens[$operator]['line']) { // The boolean operator is the first content on the line. - if ($position === null) { - $position = 'first'; - } + $firstOnLine = true; + } - if ($position !== 'first') { - $error = true; - } + if ($tokens[$next]['line'] > $tokens[$operator]['line']) { + // The boolean operator is the last content on the line. + $lastOnLine = true; + } + if ($firstOnLine === true && $lastOnLine === true) { + // The operator is the only content on the line. + // Don't record it because we can't determine + // placement information from looking at it. continue; } - $next = $phpcsFile->findNext(T_WHITESPACE, ($operator + 1), $parenCloser, true); - if ($next === false) { - // Parse error. - return; + $operators[] = $operator; + + if ($firstOnLine === false && $lastOnLine === false) { + // It's in the middle of content, so we can't determine + // placement information from looking at it, but we may + // still need to process it. + continue; } - if ($tokens[$next]['line'] > $tokens[$operator]['line']) { - // The boolean operator is the last content on the line. + if ($firstOnLine === true) { + if ($position === null) { + $position = 'first'; + } + + if ($position !== 'first') { + $error = true; + } + } else { if ($position === null) { $position = 'last'; } @@ -126,8 +147,6 @@ public function process(File $phpcsFile, $stackPtr) if ($position !== 'last') { $error = true; } - - continue; } } while ($operator !== false); diff --git a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc index 3289f7eee7..cc2ae92d78 100644 --- a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc +++ b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc @@ -108,3 +108,13 @@ if ( ) { // elseif body } + +if ( + ($value == 1 || + $value == 2) + && + ($value == 3 || + $value == 4) +) { + return 5; +} diff --git a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc.fixed b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc.fixed index 5e8f0c3f9b..19792a7642 100644 --- a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc.fixed @@ -118,3 +118,13 @@ if ( ) { // elseif body } + +if ( + ($value == 1 || + $value == 2) + && + ($value == 3 || + $value == 4) +) { + return 5; +} From caffb06991625b6775a8a46e236b4a93a283f2d3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 5 Sep 2020 06:11:59 +0200 Subject: [PATCH 126/733] File::isReference(): bug fix - arrow function params passed by reference The `&` for parameters passed by reference in an arrow function declaration were incorrectly not recognized as references. This is the root cause of issue 3049, which affected the `PSR12.Operators.OperatorSpacing` and the `Squiz.WhiteSpace.OperatorSpacing` sniffs (and possibly/probably more sniffs). Includes unit test. Fixes 3049 --- src/Files/File.php | 1 + .../PSR12/Tests/Operators/OperatorSpacingUnitTest.inc | 3 +++ .../Tests/Operators/OperatorSpacingUnitTest.inc.fixed | 3 +++ tests/Core/File/IsReferenceTest.inc | 6 ++++++ tests/Core/File/IsReferenceTest.php | 8 ++++++++ 5 files changed, 21 insertions(+) diff --git a/src/Files/File.php b/src/Files/File.php index d1e9a4da87..8133a3527f 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1981,6 +1981,7 @@ public function isReference($stackPtr) $owner = $this->tokens[$this->tokens[$lastBracket]['parenthesis_owner']]; if ($owner['code'] === T_FUNCTION || $owner['code'] === T_CLOSURE + || $owner['code'] === T_FN ) { $params = $this->getMethodParameters($this->tokens[$lastBracket]['parenthesis_owner']); foreach ($params as $param) { diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc index 82c68f2585..48ec186ede 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc @@ -56,3 +56,6 @@ function name($a = -1) {} $a =& $ref; $a = [ 'a' => &$something ]; + +$fn = fn(array &$one) => 1; +$fn = fn(array & $one) => 1; diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed index abab60279d..ebb0e94034 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed @@ -56,3 +56,6 @@ function name($a = -1) {} $a =& $ref; $a = [ 'a' => &$something ]; + +$fn = fn(array &$one) => 1; +$fn = fn(array & $one) => 1; diff --git a/tests/Core/File/IsReferenceTest.inc b/tests/Core/File/IsReferenceTest.inc index c8f5dc2243..cd40ed3ba7 100644 --- a/tests/Core/File/IsReferenceTest.inc +++ b/tests/Core/File/IsReferenceTest.inc @@ -140,5 +140,11 @@ $closure = function() use (&$var){}; /* testArrowFunctionReturnByReference */ fn&($x) => $x; +/* testArrowFunctionPassByReferenceA */ +$fn = fn(array &$one) => 1; + +/* testArrowFunctionPassByReferenceB */ +$fn = fn($param, &...$moreParams) => 1; + /* testClosureReturnByReference */ $closure = function &($param) use ($value) {}; diff --git a/tests/Core/File/IsReferenceTest.php b/tests/Core/File/IsReferenceTest.php index d6c37c6c1f..abf95684b8 100644 --- a/tests/Core/File/IsReferenceTest.php +++ b/tests/Core/File/IsReferenceTest.php @@ -228,6 +228,14 @@ public function dataIsReference() '/* testArrowFunctionReturnByReference */', true, ], + [ + '/* testArrowFunctionPassByReferenceA */', + true, + ], + [ + '/* testArrowFunctionPassByReferenceB */', + true, + ], [ '/* testClosureReturnByReference */', true, From 861acb1e11b20155110d52a0ab2ee69b0eaa5d98 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 29 Sep 2020 17:02:18 +1000 Subject: [PATCH 127/733] Changelog for #3049 (ref #3103) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 0cd2574fcd..bdebe2a941 100644 --- a/package.xml +++ b/package.xml @@ -51,6 +51,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Thiemo Kreuz for the patch - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch + - Fixed bug #3049 : Incorrect error with arrow function and parameter passed as reference + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3053 : PSR2 incorrect fix when multiple use statements on same line do not have whitespace between them - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots - Fixed bug #3059 : Squiz.Arrays.ArrayDeclaration false positive when using type casting From 01370ba8d6ed576829af97aa66b43a4534d75bbf Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 3 Jun 2020 00:06:10 +0200 Subject: [PATCH 128/733] Improve error message when using allowOnly --- .../BooleanOperatorPlacementSniff.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php b/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php index f101d399c9..98f7f84930 100644 --- a/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php +++ b/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php @@ -154,8 +154,18 @@ public function process(File $phpcsFile, $stackPtr) return; } - $error = 'Boolean operators between conditions must be at the beginning or end of the line, but not both'; - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'FoundMixed'); + switch ($this->allowOnly) { + case 'first': + $error = 'Boolean operators between conditions must be at the beginning of the line'; + break; + case 'last': + $error = 'Boolean operators between conditions must be at the end of the line'; + break; + default: + $error = 'Boolean operators between conditions must be at the beginning or end of the line, but not both'; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'FoundMixed'); if ($fix === false) { return; } From 4897e7447f9540a65fab4b1428174556e793dc90 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 29 Sep 2020 17:04:32 +1000 Subject: [PATCH 129/733] Changelog for #2981 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index bdebe2a941..4a900dc719 100644 --- a/package.xml +++ b/package.xml @@ -38,6 +38,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Running the unit tests now includes warnings in the found and fixable error code counts -- Thanks to Juliette Reinders Folmer for the patch + - PSR12.ControlStructures.BooleanOperatorPlacement.FoundMixed error message is now more accurate when using the allowOnly setting + -- Thanks to Vincent Langlet for the patch - PSR12.Functions.NullableTypeDeclaration now supports the PHP8 static return type -- Thanks to Juliette Reinders Folmer for the patch - Fixed Squiz.Formatting.OperatorBracket false positive when exiting with a negative number From a20ff6d6cace6358f1bd9cb4c3974726173d6e8f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 5 Sep 2020 06:02:15 +0200 Subject: [PATCH 130/733] PSR12/Squiz/OperatorSpacing: bug fix with arrow functions The plus/minus in a default value of a function parameter declaration is ignored for normal functions and closures, but wasn't ignored yet for arrow functions. Fixed now. Includes unit test + test for the same for closures, which so far wasn't tested yet. This also, by extension, fixes the same issue in the `PSR12.Operators.OperatorSpacing` sniff. --- .../Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php | 1 + .../Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc | 5 +++++ .../Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php index fd4bf44fcc..b33880554c 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -345,6 +345,7 @@ protected function isOperator(File $phpcsFile, $stackPtr) $function = $tokens[$bracket]['parenthesis_owner']; if ($tokens[$function]['code'] === T_FUNCTION || $tokens[$function]['code'] === T_CLOSURE + || $tokens[$function]['code'] === T_FN || $tokens[$function]['code'] === T_DECLARE ) { return false; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc index dead30680a..e204d0ee2a 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc @@ -465,5 +465,10 @@ $a = $a ? - $b : - $b; exit -1; +$cl = function ($boo =-1) {}; +$cl = function ($boo =+1) {}; +$fn = fn ($boo =-1) => $boo; +$fn = fn ($boo =+1) => $boo; + /* 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 1179b4e3a2..b1978f0bbe 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed @@ -459,5 +459,10 @@ $a = $a ? - $b : - $b; exit -1; +$cl = function ($boo =-1) {}; +$cl = function ($boo =+1) {}; +$fn = fn ($boo =-1) => $boo; +$fn = fn ($boo =+1) => $boo; + /* Intentional parse error. This has to be the last test in the file. */ $a = 10 + From afca5acf1fa35f69e8ac6118a6be56aec757534f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 29 Sep 2020 17:29:53 +1000 Subject: [PATCH 131/733] Changelog for #3102 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 4a900dc719..070550e823 100644 --- a/package.xml +++ b/package.xml @@ -68,6 +68,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3075 : PSR12.ControlStructures.BooleanOperatorPlacement false positive when operator is the only content on line - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number -- Thanks to Sergei Morozov for the patch + - Fixed bug #3102 : PSR12.Squiz.OperatorSpacing false positive for default values of arrow functions + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3124 : PSR-12 not reporting error for empty lines with only whitespace From 1b768c550918b37063f583cac99cafe02d806455 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 8 Sep 2020 03:29:21 +0200 Subject: [PATCH 132/733] Squiz/PSR12/OperatorSpacing: bug fix - unary plus/minus in arrow function return A plus/minus directly after the arrow of an arrow function, will always be a unary plus/minus and should therefore be ignored by these sniffs. Includes unit tests. Includes two additional simplifications: * `T_DOUBLE_ARROW` is already part of the `Tokens::$assignmentTokens` array, so doesn't need to be added separately. * The cast tokens array was incomplete (missing `T_BINARY_CAST`) and can be replaced by the more complete `Tokens::$castTokens` array anyhow. Fixes 3043 --- .../Operators/OperatorSpacingUnitTest.inc | 2 ++ .../OperatorSpacingUnitTest.inc.fixed | 2 ++ .../WhiteSpace/OperatorSpacingSniff.php | 22 ++++++------------- .../WhiteSpace/OperatorSpacingUnitTest.inc | 2 ++ .../OperatorSpacingUnitTest.inc.fixed | 2 ++ 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc index 48ec186ede..6cc482af60 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc @@ -59,3 +59,5 @@ $a = [ 'a' => &$something ]; $fn = fn(array &$one) => 1; $fn = fn(array & $one) => 1; + +$fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed index ebb0e94034..c90fb9a753 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed @@ -59,3 +59,5 @@ $a = [ 'a' => &$something ]; $fn = fn(array &$one) => 1; $fn = fn(array & $one) => 1; + +$fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php index b33880554c..ee44545aea 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -77,11 +77,12 @@ 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_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, ]; // Trying to use a negative value; eg. myFunction($var, -2). @@ -90,7 +91,6 @@ public function register() T_OPEN_PARENTHESIS => T_OPEN_PARENTHESIS, T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET, T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY, - T_DOUBLE_ARROW => T_DOUBLE_ARROW, T_COLON => T_COLON, T_INLINE_THEN => T_INLINE_THEN, T_INLINE_ELSE => T_INLINE_ELSE, @@ -99,15 +99,7 @@ public function register() ]; // Casting a negative value; eg. (array) -$a. - $this->nonOperandTokens += [ - T_ARRAY_CAST => T_ARRAY_CAST, - T_BOOL_CAST => T_BOOL_CAST, - T_DOUBLE_CAST => T_DOUBLE_CAST, - T_INT_CAST => T_INT_CAST, - T_OBJECT_CAST => T_OBJECT_CAST, - T_STRING_CAST => T_STRING_CAST, - T_UNSET_CAST => T_UNSET_CAST, - ]; + $this->nonOperandTokens += Tokens::$castTokens; /* These are the tokens the sniff is looking for. diff --git a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc index e204d0ee2a..82d4af15be 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc @@ -470,5 +470,7 @@ $cl = function ($boo =+1) {}; $fn = fn ($boo =-1) => $boo; $fn = fn ($boo =+1) => $boo; +$fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); + /* 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 b1978f0bbe..ee4060bc11 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed @@ -464,5 +464,7 @@ $cl = function ($boo =+1) {}; $fn = fn ($boo =-1) => $boo; $fn = fn ($boo =+1) => $boo; +$fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); + /* Intentional parse error. This has to be the last test in the file. */ $a = 10 + From 1de501bcc043ed71e6bda995f739a976910d57ed Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Tue, 29 Sep 2020 12:34:47 +0200 Subject: [PATCH 133/733] Fix PHP 7.4 regression, changed behavior of get_declared_classes() Since PHP 7.4 get_declared_classes() does not guarantee any order. That implies that parent classes aren't the first any more, rendering the array_reverse() technique futile for the loop & break code that follows. So, additionally, let's try to reduce the list of candidates by removing all the classes known to be "parents". That way, at the end, only the "main" class just included with remain. Source: https://raw.githubusercontent.com/php/php-src/PHP-7.4/UPGRADING Text: Previously get_declared_classes() always returned parent classes before child classes. This is no longer the case. No particular order is guaranteed for the get_declared_classes() return value. --- autoload.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/autoload.php b/autoload.php index 165ff1a6a0..0690b75cd0 100644 --- a/autoload.php +++ b/autoload.php @@ -168,6 +168,19 @@ public static function loadFile($path) $className = null; $newClasses = array_reverse(array_diff(get_declared_classes(), $classes)); + // Since PHP 7.4 get_declared_classes() does not guarantee any order. That + // implies that parent classes aren't the first any more, rendering the + // array_reverse() technique futile for the loop & break code that follows. + // So, additionally, let's try to reduce the list of candidates by removing all + // the classes known to be "parents". That way, at the end, only the "main" + // class just included with remain. + $newClasses = array_reduce( + $newClasses, + function ($remaining, $current) { + return array_diff($remaining, class_parents($current)); + }, + $newClasses + ); foreach ($newClasses as $name) { if (isset(self::$loadedFiles[$name]) === false) { $className = $name; From 1428f2f531f4900ea113930ce26c27423a258537 Mon Sep 17 00:00:00 2001 From: Michael Moll Date: Thu, 1 Oct 2020 01:34:43 +0200 Subject: [PATCH 134/733] Fix docblocks for File::findPrevious and File::findNext --- src/Files/File.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 8133a3527f..41c80bb327 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2095,12 +2095,12 @@ public function getTokensAsString($start, $length, $origContent=false) * @param int|string|array $types The type(s) of tokens to search for. * @param int $start The position to start searching from in the * token stack. - * @param int $end The end position to fail if no token is found. + * @param int|null $end The end position to fail if no token is found. * if not specified or null, end will default to * the start of the token stack. * @param bool $exclude If true, find the previous token that is NOT of * the types specified in $types. - * @param string $value The value that the token(s) must be equal to. + * @param string|null $value The value that the token(s) must be equal to. * If value is omitted, tokens with any value will * be returned. * @param bool $local If true, tokens outside the current statement @@ -2176,12 +2176,12 @@ public function findPrevious( * @param int|string|array $types The type(s) of tokens to search for. * @param int $start The position to start searching from in the * token stack. - * @param int $end The end position to fail if no token is found. + * @param int|null $end The end position to fail if no token is found. * if not specified or null, end will default to * the end of the token stack. * @param bool $exclude If true, find the next token that is NOT of * a type specified in $types. - * @param string $value The value that the token(s) must be equal to. + * @param string|null $value The value that the token(s) must be equal to. * If value is omitted, tokens with any value will * be returned. * @param bool $local If true, tokens outside the current statement From 95e69ac528c1fd3294a922c60fe6b889d5cdfad1 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 1 Oct 2020 13:05:51 +1000 Subject: [PATCH 135/733] Broke out the class name detection to make it easier to test --- autoload.php | 60 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/autoload.php b/autoload.php index 0690b75cd0..1691d7e8de 100644 --- a/autoload.php +++ b/autoload.php @@ -160,20 +160,47 @@ public static function loadFile($path) return self::$loadedClasses[$path]; } - $classes = get_declared_classes(); - $interfaces = get_declared_interfaces(); - $traits = get_declared_traits(); + $classesBeforeLoad = [ + 'classes' => get_declared_classes(), + 'interfaces' => get_declared_interfaces(), + 'traits' => get_declared_traits(), + ]; include $path; - $className = null; - $newClasses = array_reverse(array_diff(get_declared_classes(), $classes)); - // Since PHP 7.4 get_declared_classes() does not guarantee any order. That - // implies that parent classes aren't the first any more, rendering the - // array_reverse() technique futile for the loop & break code that follows. - // So, additionally, let's try to reduce the list of candidates by removing all - // the classes known to be "parents". That way, at the end, only the "main" - // class just included with remain. + $classesAfterLoad = [ + 'classes' => get_declared_classes(), + 'interfaces' => get_declared_interfaces(), + 'traits' => get_declared_traits(), + ]; + + $className = self::determineLoadedClass($classesBeforeLoad, $classesAfterLoad); + + self::$loadedClasses[$path] = $className; + self::$loadedFiles[$className] = $path; + return self::$loadedClasses[$path]; + + }//end loadFile() + + + /** + * Determine which class was loaded based on the before and after lists of loaded classes. + * + * @param array $classesBeforeLoad The classes/interfaces/traits before the file was included. + * @param array $classesAfterLoad The classes/interfaces/traits after the file was included. + * + * @return string The fully qualified name of the class in the loaded file. + */ + public static function determineLoadedClass($classesBeforeLoad, $classesAfterLoad) + { + $className = null; + + $newClasses = array_diff($classesAfterLoad['classes'], $classesBeforeLoad['classes']); + + // Since PHP 7.4 get_declared_classes() does not guarantee any order, making + // it impossible to use order to determine which is the parent an which is the child. + // Let's reduce the list of candidates by removing all the classes known to be "parents". + // That way, at the end, only the "main" class just included will remain. $newClasses = array_reduce( $newClasses, function ($remaining, $current) { @@ -181,6 +208,7 @@ function ($remaining, $current) { }, $newClasses ); + foreach ($newClasses as $name) { if (isset(self::$loadedFiles[$name]) === false) { $className = $name; @@ -189,7 +217,7 @@ function ($remaining, $current) { } if ($className === null) { - $newClasses = array_reverse(array_diff(get_declared_interfaces(), $interfaces)); + $newClasses = array_reverse(array_diff($classesAfterLoad['interfaces'], $classesBeforeLoad['interfaces'])); foreach ($newClasses as $name) { if (isset(self::$loadedFiles[$name]) === false) { $className = $name; @@ -199,7 +227,7 @@ function ($remaining, $current) { } if ($className === null) { - $newClasses = array_reverse(array_diff(get_declared_traits(), $traits)); + $newClasses = array_reverse(array_diff($classesAfterLoad['traits'], $classesBeforeLoad['traits'])); foreach ($newClasses as $name) { if (isset(self::$loadedFiles[$name]) === false) { $className = $name; @@ -208,11 +236,9 @@ function ($remaining, $current) { } } - self::$loadedClasses[$path] = $className; - self::$loadedFiles[$className] = $path; - return self::$loadedClasses[$path]; + return $className; - }//end loadFile() + }//end determineLoadedClass() /** From ed83c67a1dc21096a8b31c5426a541eb2b42f176 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 1 Oct 2020 13:50:49 +1000 Subject: [PATCH 136/733] Tests for autoloader class name detection --- package.xml | 21 ++++ phpcs.xml.dist | 2 +- .../Autoloader/DetermineLoadedClassTest.php | 118 ++++++++++++++++++ tests/Core/Autoloader/TestFiles/A.inc | 3 + tests/Core/Autoloader/TestFiles/B.inc | 4 + tests/Core/Autoloader/TestFiles/C.inc | 4 + tests/Core/Autoloader/TestFiles/Sub/C.inc | 5 + 7 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 tests/Core/Autoloader/DetermineLoadedClassTest.php create mode 100644 tests/Core/Autoloader/TestFiles/A.inc create mode 100644 tests/Core/Autoloader/TestFiles/B.inc create mode 100644 tests/Core/Autoloader/TestFiles/C.inc create mode 100644 tests/Core/Autoloader/TestFiles/Sub/C.inc diff --git a/package.xml b/package.xml index 070550e823..52240fafb2 100644 --- a/package.xml +++ b/package.xml @@ -102,6 +102,17 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + + + + @@ -1993,6 +2004,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + @@ -2058,6 +2074,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + diff --git a/phpcs.xml.dist b/phpcs.xml.dist index f7f32e6be4..c9dfc72ee8 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -9,7 +9,7 @@ tests */src/Standards/*/Tests/*\.(inc|css|js)$ - */tests/Core/*/*Test\.(inc|css|js)$ + */tests/Core/*/*\.(inc|css|js)$ diff --git a/tests/Core/Autoloader/DetermineLoadedClassTest.php b/tests/Core/Autoloader/DetermineLoadedClassTest.php new file mode 100644 index 0000000000..65542b670b --- /dev/null +++ b/tests/Core/Autoloader/DetermineLoadedClassTest.php @@ -0,0 +1,118 @@ + + * @copyright 2006-2015 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\Autoloader; + +use PHPUnit\Framework\TestCase; + +class DetermineLoadedClassTest extends TestCase +{ + + + /** + * Load the test files. + * + * @return void + */ + public static function setUpBeforeClass() + { + include __DIR__.'/TestFiles/Sub/C.inc'; + + }//end setUpBeforeClass() + + + /** + * Test for when class list is ordered. + * + * @return void + */ + public function testOrdered() + { + $classesBeforeLoad = [ + 'classes' => [], + 'interfaces' => [], + 'traits' => [], + ]; + + $classesAfterLoad = [ + 'classes' => [ + 'PHP_CodeSniffer\Tests\Core\Autoloader\A', + 'PHP_CodeSniffer\Tests\Core\Autoloader\B', + 'PHP_CodeSniffer\Tests\Core\Autoloader\C', + 'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', + ], + 'interfaces' => [], + 'traits' => [], + ]; + + $className = \PHP_CodeSniffer\Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad); + $this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className); + + }//end testOrdered() + + + /** + * Test for when class list is out of order. + * + * @return void + */ + public function testUnordered() + { + $classesBeforeLoad = [ + 'classes' => [], + 'interfaces' => [], + 'traits' => [], + ]; + + $classesAfterLoad = [ + 'classes' => [ + 'PHP_CodeSniffer\Tests\Core\Autoloader\A', + 'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', + 'PHP_CodeSniffer\Tests\Core\Autoloader\C', + 'PHP_CodeSniffer\Tests\Core\Autoloader\B', + ], + 'interfaces' => [], + 'traits' => [], + ]; + + $className = \PHP_CodeSniffer\Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad); + $this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className); + + $classesAfterLoad = [ + 'classes' => [ + 'PHP_CodeSniffer\Tests\Core\Autoloader\A', + 'PHP_CodeSniffer\Tests\Core\Autoloader\C', + 'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', + 'PHP_CodeSniffer\Tests\Core\Autoloader\B', + ], + 'interfaces' => [], + 'traits' => [], + ]; + + $className = \PHP_CodeSniffer\Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad); + $this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className); + + $classesAfterLoad = [ + 'classes' => [ + 'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', + 'PHP_CodeSniffer\Tests\Core\Autoloader\A', + 'PHP_CodeSniffer\Tests\Core\Autoloader\C', + 'PHP_CodeSniffer\Tests\Core\Autoloader\B', + ], + 'interfaces' => [], + 'traits' => [], + ]; + + $className = \PHP_CodeSniffer\Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad); + $this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className); + + }//end testUnordered() + + +}//end class diff --git a/tests/Core/Autoloader/TestFiles/A.inc b/tests/Core/Autoloader/TestFiles/A.inc new file mode 100644 index 0000000000..c1433718bb --- /dev/null +++ b/tests/Core/Autoloader/TestFiles/A.inc @@ -0,0 +1,3 @@ + Date: Thu, 1 Oct 2020 13:57:33 +1000 Subject: [PATCH 137/733] Changelog for #3130 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 52240fafb2..3b3e258327 100644 --- a/package.xml +++ b/package.xml @@ -36,6 +36,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Added support for changes to the way PHP 8.0 tokenizes hash comments -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required -- Thanks to Juliette Reinders Folmer for the patch + - The autoloader has been changed to fix sniff class name detection issues that may occur when running on PHP 7.4+ + -- Thanks to Eloy Lafuente for the patch - Running the unit tests now includes warnings in the found and fixable error code counts -- Thanks to Juliette Reinders Folmer for the patch - PSR12.ControlStructures.BooleanOperatorPlacement.FoundMixed error message is now more accurate when using the allowOnly setting From 4877700b24f03ef79b8792cc5d37b2f29b53677d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 2 Oct 2020 06:17:10 +0200 Subject: [PATCH 138/733] PHP 8.0 compatibility: bug fix - ignore annotations are broken A very recent change in PHP 8.0 changes the possible return values of the `substr()` function from: > Pre-PHP 8: > Returns the extracted part of string; or FALSE on failure, or an empty string. > PHP 8-RC1: > Returns the extracted part of string; or an empty string. This is an insidious change as basically all code (strict) checking the return value of `substr()` against `false` will now be broken. Checking the return value with `empty()` will fix this in a cross-version compatible manner as it allows for both `false` as well as an empty string being returned. This change broke the ignore annotations as implemented in PHPCS. The existing unit tests for the ignore annotations cover this change. Includes removing some unnecessary, duplicate function calls to `substr()`. Ref: https://github.com/php/php-src/pull/6182 --- src/Tokenizers/Tokenizer.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index e0bf22fb01..ac9aa20254 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -424,10 +424,10 @@ private function createPositionMap() $disabledSniffs = []; $additionalText = substr($commentText, 14); - if ($additionalText === false) { + if (empty($additionalText) === true) { $ignoring = ['.all' => true]; } else { - $parts = explode(',', substr($commentText, 13)); + $parts = explode(',', $additionalText); foreach ($parts as $sniffCode) { $sniffCode = trim($sniffCode); $disabledSniffs[$sniffCode] = true; @@ -459,10 +459,10 @@ private function createPositionMap() $enabledSniffs = []; $additionalText = substr($commentText, 13); - if ($additionalText === false) { + if (empty($additionalText) === true) { $ignoring = null; } else { - $parts = explode(',', substr($commentText, 13)); + $parts = explode(',', $additionalText); foreach ($parts as $sniffCode) { $sniffCode = trim($sniffCode); $enabledSniffs[$sniffCode] = true; @@ -520,10 +520,10 @@ private function createPositionMap() $ignoreRules = []; $additionalText = substr($commentText, 13); - if ($additionalText === false) { + if (empty($additionalText) === true) { $ignoreRules = ['.all' => true]; } else { - $parts = explode(',', substr($commentText, 13)); + $parts = explode(',', $additionalText); foreach ($parts as $sniffCode) { $ignoreRules[trim($sniffCode)] = true; } From 2ecd990f9f19075632773b8c75cbec013eef74bf Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 14 Oct 2020 08:35:14 +1100 Subject: [PATCH 139/733] Changelog for #3135 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 3b3e258327..d497086d17 100644 --- a/package.xml +++ b/package.xml @@ -73,6 +73,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3102 : PSR12.Squiz.OperatorSpacing false positive for default values of arrow functions -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3124 : PSR-12 not reporting error for empty lines with only whitespace + - Fixed bug #3135 : Ignore annotations are broken on PHP 8.0 + -- Thanks to Juliette Reinders Folmer for the patch From d4ba5fc8183e6fc0c5e2b891bc1d8f9f0d34512d Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 14 Oct 2020 09:05:34 +1100 Subject: [PATCH 140/733] Changelog for #3043 (ref #3129) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index d497086d17..51769f8e62 100644 --- a/package.xml +++ b/package.xml @@ -55,6 +55,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Thiemo Kreuz for the patch - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same -- Thanks to Steve Talbot for the patch + - Fixed bug #3043 : Squiz.WhiteSpace.OperatorSpacing false positive for negation in arrow function + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3049 : Incorrect error with arrow function and parameter passed as reference -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3053 : PSR2 incorrect fix when multiple use statements on same line do not have whitespace between them From 4dbf1d59cd3f155c6f906f018a8574bc53b5be30 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 23 Oct 2020 08:09:11 +1100 Subject: [PATCH 141/733] Prepare for 3.5.7 release --- package.xml | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 51769f8e62..b27ca10716 100644 --- a/package.xml +++ b/package.xml @@ -14,8 +14,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> gsherwood@squiz.net yes - 2020-08-10 - + 2020-10-23 + 3.5.7 3.5.7 @@ -2136,6 +2136,71 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 3.5.7 + 3.5.7 + + + stable + stable + + 2020-10-23 + BSD License + + - The PHP 8.0 T_NULLSAFE_OBJECT_OPERATOR token has been made available for older versions + -- Existing sniffs that check for T_OBJECT_OPERATOR have been modified to apply the same rules for the nullsafe object operator + -- Thanks to Juliette Reinders Folmer for the patch + - The new method of PHP 8.0 tokenizing for namespaced names has been revert to thr pre 8.0 method + -- This maintains backwards compatible for existing sniffs on PHP 8.0 + -- This change will be removed in PHPCS 4.0 as the PHP 8.0 tokenizing method will be backported for pre 8.0 versions + -- Thanks to Juliette Reinders Folmer for the patch + - Added support for changes to the way PHP 8.0 tokenizes hash comments + -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required + -- Thanks to Juliette Reinders Folmer for the patch + - The autoloader has been changed to fix sniff class name detection issues that may occur when running on PHP 7.4+ + -- Thanks to Eloy Lafuente for the patch + - Running the unit tests now includes warnings in the found and fixable error code counts + -- Thanks to Juliette Reinders Folmer for the patch + - PSR12.ControlStructures.BooleanOperatorPlacement.FoundMixed error message is now more accurate when using the allowOnly setting + -- Thanks to Vincent Langlet for the patch + - PSR12.Functions.NullableTypeDeclaration now supports the PHP8 static return type + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed Squiz.Formatting.OperatorBracket false positive when exiting with a negative number + - Fixed Squiz.PHP.DisallowComparisonAssignment false positive for methods called on an object + - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level + - Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC + - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary + - Fixed bug #2988 : Undefined offset in Squiz.Strings.ConcatenationSpacing during live coding + -- Thanks to Thiemo Kreuz for the patch + - Fixed bug #2989 : Incorrect auto-fixing in Generic.ControlStructures.InlineControlStructure during live coding + -- Thanks to Thiemo Kreuz for the patch + - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same + -- Thanks to Steve Talbot for the patch + - Fixed bug #3043 : Squiz.WhiteSpace.OperatorSpacing false positive for negation in arrow function + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3049 : Incorrect error with arrow function and parameter passed as reference + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3053 : PSR2 incorrect fix when multiple use statements on same line do not have whitespace between them + - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots + - Fixed bug #3059 : Squiz.Arrays.ArrayDeclaration false positive when using type casting + -- Thanks to Sergei Morozov for the patch + - Fixed bug #3060 : Squiz.Arrays.ArrayDeclaration false positive for static functions + -- Thanks to Sergei Morozov for the patch + - Fixed bug #3065 : Should not fix Squiz.Arrays.ArrayDeclaration.SpaceBeforeComma if comment between element and comma + -- Thanks to Sergei Morozov for the patch + - Fixed bug #3066 : No support for namespace operator used in type declarations + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3075 : PSR12.ControlStructures.BooleanOperatorPlacement false positive when operator is the only content on line + - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number + -- Thanks to Sergei Morozov for the patch + - Fixed bug #3102 : PSR12.Squiz.OperatorSpacing false positive for default values of arrow functions + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3124 : PSR-12 not reporting error for empty lines with only whitespace + - Fixed bug #3135 : Ignore annotations are broken on PHP 8.0 + -- Thanks to Juliette Reinders Folmer for the patch + + 3.5.6 From a97824680104c8134fe657ca595f67b9a02ee3c9 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 23 Oct 2020 10:23:47 +1100 Subject: [PATCH 142/733] Revert "File::process(): don't apply include/exclude patterns to STDIN" This reverts commit 0d79723446243f12cb575bbfbc680a0d658f3390. --- src/Files/File.php | 62 ++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 6204ab9753..0d5d0296f9 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -442,11 +442,30 @@ public function process() continue; } - if (trim($this->path, '\'"') !== 'STDIN') { - // If the file path matches one of our ignore patterns, skip it. - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - foreach ($listenerData['ignore'] as $pattern) { + // If the file path matches one of our ignore patterns, skip it. + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + foreach ($listenerData['ignore'] as $pattern) { + // We assume a / directory separator, as do the exclude rules + // most developers write, so we need a special case for any system + // that is different. + if (DIRECTORY_SEPARATOR === '\\') { + $pattern = str_replace('/', '\\\\', $pattern); + } + + $pattern = '`'.$pattern.'`i'; + if (preg_match($pattern, $this->path) === 1) { + $this->ignoredListeners[$class] = true; + continue(2); + } + } + + // If the file path does not match one of our include patterns, skip it. + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + if (empty($listenerData['include']) === false) { + $included = false; + foreach ($listenerData['include'] as $pattern) { // We assume a / directory separator, as do the exclude rules // most developers write, so we need a special case for any system // that is different. @@ -456,36 +475,15 @@ public function process() $pattern = '`'.$pattern.'`i'; if (preg_match($pattern, $this->path) === 1) { - $this->ignoredListeners[$class] = true; - continue(2); + $included = true; + break; } } - // If the file path does not match one of our include patterns, skip it. - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - if (empty($listenerData['include']) === false) { - $included = false; - foreach ($listenerData['include'] as $pattern) { - // We assume a / directory separator, as do the exclude rules - // most developers write, so we need a special case for any system - // that is different. - if (DIRECTORY_SEPARATOR === '\\') { - $pattern = str_replace('/', '\\\\', $pattern); - } - - $pattern = '`'.$pattern.'`i'; - if (preg_match($pattern, $this->path) === 1) { - $included = true; - break; - } - } - - if ($included === false) { - $this->ignoredListeners[$class] = true; - continue; - } - }//end if + if ($included === false) { + $this->ignoredListeners[$class] = true; + continue; + } }//end if $this->activeListener = $class; From 9dd6d649caa9e3293b366278afc547797ad5fa4f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 23 Oct 2020 10:23:54 +1100 Subject: [PATCH 143/733] Revert "File::addMessage(): don't apply include/exclude patterns to STDIN" This reverts commit a95569ca6077ecc1529681df3681627e2034a5a4. --- src/Files/File.php | 92 ++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 0d5d0296f9..8f50d6d332 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -965,63 +965,59 @@ protected function addMessage($error, $message, $line, $column, $code, $data, $s // Make sure we are not ignoring this file. $included = null; - if (trim($this->path, '\'"') === 'STDIN') { - $included = true; - } else { - foreach ($checkCodes as $checkCode) { - $patterns = null; - - if (isset($this->configCache['includePatterns'][$checkCode]) === true) { - $patterns = $this->configCache['includePatterns'][$checkCode]; - $excluding = false; - } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) { - $patterns = $this->configCache['ignorePatterns'][$checkCode]; - $excluding = true; - } + foreach ($checkCodes as $checkCode) { + $patterns = null; + + if (isset($this->configCache['includePatterns'][$checkCode]) === true) { + $patterns = $this->configCache['includePatterns'][$checkCode]; + $excluding = false; + } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) { + $patterns = $this->configCache['ignorePatterns'][$checkCode]; + $excluding = true; + } - if ($patterns === null) { - continue; + if ($patterns === null) { + continue; + } + + foreach ($patterns as $pattern => $type) { + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + $replacements = [ + '\\,' => ',', + '*' => '.*', + ]; + + // We assume a / directory separator, as do the exclude rules + // most developers write, so we need a special case for any system + // that is different. + if (DIRECTORY_SEPARATOR === '\\') { + $replacements['/'] = '\\\\'; } - foreach ($patterns as $pattern => $type) { - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - $replacements = [ - '\\,' => ',', - '*' => '.*', - ]; + $pattern = '`'.strtr($pattern, $replacements).'`i'; + $matched = preg_match($pattern, $this->path); - // We assume a / directory separator, as do the exclude rules - // most developers write, so we need a special case for any system - // that is different. - if (DIRECTORY_SEPARATOR === '\\') { - $replacements['/'] = '\\\\'; + if ($matched === 0) { + if ($excluding === false && $included === null) { + // This file path is not being included. + $included = false; } - $pattern = '`'.strtr($pattern, $replacements).'`i'; - $matched = preg_match($pattern, $this->path); - - if ($matched === 0) { - if ($excluding === false && $included === null) { - // This file path is not being included. - $included = false; - } - - continue; - } + continue; + } - if ($excluding === true) { - // This file path is being excluded. - $this->ignoredCodes[$checkCode] = true; - return false; - } + if ($excluding === true) { + // This file path is being excluded. + $this->ignoredCodes[$checkCode] = true; + return false; + } - // This file path is being included. - $included = true; - break; - }//end foreach + // This file path is being included. + $included = true; + break; }//end foreach - }//end if + }//end foreach if ($included === false) { // There were include rules set, but this file From 9d583721a7157ee997f235f327de038e7ea6dac4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 23 Oct 2020 13:01:07 +1100 Subject: [PATCH 144/733] Prepare for 3.5.8 release --- package.xml | 75 ++++++++++++++------------------------------------ src/Config.php | 2 +- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/package.xml b/package.xml index b27ca10716..3249ae8a57 100644 --- a/package.xml +++ b/package.xml @@ -15,10 +15,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> yes 2020-10-23 - + - 3.5.7 - 3.5.7 + 3.5.8 + 3.5.8 stable @@ -26,57 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - - The PHP 8.0 T_NULLSAFE_OBJECT_OPERATOR token has been made available for older versions - -- Existing sniffs that check for T_OBJECT_OPERATOR have been modified to apply the same rules for the nullsafe object operator - -- Thanks to Juliette Reinders Folmer for the patch - - The new method of PHP 8.0 tokenizing for namespaced names has been revert to thr pre 8.0 method - -- This maintains backwards compatible for existing sniffs on PHP 8.0 - -- This change will be removed in PHPCS 4.0 as the PHP 8.0 tokenizing method will be backported for pre 8.0 versions - -- Thanks to Juliette Reinders Folmer for the patch - - Added support for changes to the way PHP 8.0 tokenizes hash comments - -- The existing PHP 5-7 behaviour has been replicated for version 8, so no sniff changes are required - -- Thanks to Juliette Reinders Folmer for the patch - - The autoloader has been changed to fix sniff class name detection issues that may occur when running on PHP 7.4+ - -- Thanks to Eloy Lafuente for the patch - - Running the unit tests now includes warnings in the found and fixable error code counts - -- Thanks to Juliette Reinders Folmer for the patch - - PSR12.ControlStructures.BooleanOperatorPlacement.FoundMixed error message is now more accurate when using the allowOnly setting - -- Thanks to Vincent Langlet for the patch - - PSR12.Functions.NullableTypeDeclaration now supports the PHP8 static return type - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed Squiz.Formatting.OperatorBracket false positive when exiting with a negative number - - Fixed Squiz.PHP.DisallowComparisonAssignment false positive for methods called on an object - - Fixed bug #2882 : Generic.Arrays.ArrayIndent can request close brace indent to be less than the statement indent level - - Fixed bug #2883 : Generic.WhiteSpace.ScopeIndent.Incorrect issue after NOWDOC - - Fixed bug #2975 : Undefined offset in PSR12.Functions.ReturnTypeDeclaration when checking function return type inside ternary - - Fixed bug #2988 : Undefined offset in Squiz.Strings.ConcatenationSpacing during live coding - -- Thanks to Thiemo Kreuz for the patch - - Fixed bug #2989 : Incorrect auto-fixing in Generic.ControlStructures.InlineControlStructure during live coding - -- Thanks to Thiemo Kreuz for the patch - - Fixed bug #3007 : Directory exclude pattern improperly excludes directories with names that start the same - -- Thanks to Steve Talbot for the patch - - Fixed bug #3043 : Squiz.WhiteSpace.OperatorSpacing false positive for negation in arrow function - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3049 : Incorrect error with arrow function and parameter passed as reference - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3053 : PSR2 incorrect fix when multiple use statements on same line do not have whitespace between them - - Fixed bug #3058 : Progress gets unaligned when 100% happens at the end of the available dots - - Fixed bug #3059 : Squiz.Arrays.ArrayDeclaration false positive when using type casting - -- Thanks to Sergei Morozov for the patch - - Fixed bug #3060 : Squiz.Arrays.ArrayDeclaration false positive for static functions - -- Thanks to Sergei Morozov for the patch - - Fixed bug #3065 : Should not fix Squiz.Arrays.ArrayDeclaration.SpaceBeforeComma if comment between element and comma - -- Thanks to Sergei Morozov for the patch - - Fixed bug #3066 : No support for namespace operator used in type declarations - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3075 : PSR12.ControlStructures.BooleanOperatorPlacement false positive when operator is the only content on line - - Fixed bug #3099 : Squiz.WhiteSpace.OperatorSpacing false positive when exiting with negative number - -- Thanks to Sergei Morozov for the patch - - Fixed bug #3102 : PSR12.Squiz.OperatorSpacing false positive for default values of arrow functions - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3124 : PSR-12 not reporting error for empty lines with only whitespace - - Fixed bug #3135 : Ignore annotations are broken on PHP 8.0 - -- Thanks to Juliette Reinders Folmer for the patch + - Reverted a change to the way include/exclude patterns are processed for STDIN content + -- This change is not backwards compatible and will be re-introduced in version 3.6.0 @@ -2136,6 +2087,22 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 3.5.8 + 3.5.8 + + + stable + stable + + 2020-10-23 + BSD License + + - Reverted a change to the way include/exclude patterns are processed for STDIN content + -- This change is not backwards compatible and will be re-introduced in version 3.6.0 + + 3.5.7 diff --git a/src/Config.php b/src/Config.php index f77eaef473..be451ff531 100644 --- a/src/Config.php +++ b/src/Config.php @@ -79,7 +79,7 @@ class Config * * @var string */ - const VERSION = '3.5.7'; + const VERSION = '3.5.8'; /** * Package stability; either stable, beta or alpha. From b78c46af7e20bbdc7e5eed086afa8d909fc25952 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Mon, 26 Oct 2020 00:39:53 +0100 Subject: [PATCH 145/733] PSR-2 is officially deprecated, use PSR-12 PSR-2 is deprecated, they advice to use PSR-12 from now onwards. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7452f7f101..33985901ed 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,9 @@ Or if you wish to check an entire directory you can specify the directory locati $ phpcs /path/to/code-directory -If you wish to check your code against the PSR-2 coding standard, use the `--standard` command line argument: +If you wish to check your code against the PSR-12 coding standard, use the `--standard` command line argument: - $ phpcs --standard=PSR2 /path/to/code-directory + $ phpcs --standard=PSR12 /path/to/code-directory If PHP_CodeSniffer finds any coding standard errors, a report will be shown after running the command. From 7171973f14bfc430bf89c6e238bca7612fe0d88d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 8 Sep 2020 02:49:05 +0200 Subject: [PATCH 146/733] File::process(): don't apply include/exclude patterns to STDIN Note: using `trim()` to remove potential quotes around `STDIN` which are sometimes passed by IDEs. --- src/Files/File.php | 62 ++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 8f50d6d332..14943f9082 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -442,30 +442,11 @@ public function process() continue; } - // If the file path matches one of our ignore patterns, skip it. - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - foreach ($listenerData['ignore'] as $pattern) { - // We assume a / directory separator, as do the exclude rules - // most developers write, so we need a special case for any system - // that is different. - if (DIRECTORY_SEPARATOR === '\\') { - $pattern = str_replace('/', '\\\\', $pattern); - } - - $pattern = '`'.$pattern.'`i'; - if (preg_match($pattern, $this->path) === 1) { - $this->ignoredListeners[$class] = true; - continue(2); - } - } - - // If the file path does not match one of our include patterns, skip it. - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - if (empty($listenerData['include']) === false) { - $included = false; - foreach ($listenerData['include'] as $pattern) { + if (trim($this->path, '\'"') !== 'STDIN') { + // If the file path matches one of our ignore patterns, skip it. + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + foreach ($listenerData['ignore'] as $pattern) { // We assume a / directory separator, as do the exclude rules // most developers write, so we need a special case for any system // that is different. @@ -475,15 +456,36 @@ public function process() $pattern = '`'.$pattern.'`i'; if (preg_match($pattern, $this->path) === 1) { - $included = true; - break; + $this->ignoredListeners[$class] = true; + continue(2); } } - if ($included === false) { - $this->ignoredListeners[$class] = true; - continue; - } + // If the file path does not match one of our include patterns, skip it. + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + if (empty($listenerData['include']) === false) { + $included = false; + foreach ($listenerData['include'] as $pattern) { + // We assume a / directory separator, as do the exclude rules + // most developers write, so we need a special case for any system + // that is different. + if (DIRECTORY_SEPARATOR === '\\') { + $pattern = str_replace('/', '\\\\', $pattern); + } + + $pattern = '`'.$pattern.'`i'; + if (preg_match($pattern, $this->path) === 1) { + $included = true; + break; + } + } + + if ($included === false) { + $this->ignoredListeners[$class] = true; + continue; + } + }//end if }//end if $this->activeListener = $class; From 8dcebc693b8ea1d538a90cd9196f9c33a98a446b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 8 Sep 2020 02:49:57 +0200 Subject: [PATCH 147/733] File::addMessage(): don't apply include/exclude patterns to STDIN Note: using `trim()` to remove potential quotes around `STDIN` which are sometimes passed by IDEs. --- src/Files/File.php | 94 ++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 14943f9082..6204ab9753 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -967,59 +967,63 @@ protected function addMessage($error, $message, $line, $column, $code, $data, $s // Make sure we are not ignoring this file. $included = null; - foreach ($checkCodes as $checkCode) { - $patterns = null; - - if (isset($this->configCache['includePatterns'][$checkCode]) === true) { - $patterns = $this->configCache['includePatterns'][$checkCode]; - $excluding = false; - } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) { - $patterns = $this->configCache['ignorePatterns'][$checkCode]; - $excluding = true; - } - - if ($patterns === null) { - continue; - } - - foreach ($patterns as $pattern => $type) { - // While there is support for a type of each pattern - // (absolute or relative) we don't actually support it here. - $replacements = [ - '\\,' => ',', - '*' => '.*', - ]; - - // We assume a / directory separator, as do the exclude rules - // most developers write, so we need a special case for any system - // that is different. - if (DIRECTORY_SEPARATOR === '\\') { - $replacements['/'] = '\\\\'; + if (trim($this->path, '\'"') === 'STDIN') { + $included = true; + } else { + foreach ($checkCodes as $checkCode) { + $patterns = null; + + if (isset($this->configCache['includePatterns'][$checkCode]) === true) { + $patterns = $this->configCache['includePatterns'][$checkCode]; + $excluding = false; + } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) { + $patterns = $this->configCache['ignorePatterns'][$checkCode]; + $excluding = true; } - $pattern = '`'.strtr($pattern, $replacements).'`i'; - $matched = preg_match($pattern, $this->path); + if ($patterns === null) { + continue; + } - if ($matched === 0) { - if ($excluding === false && $included === null) { - // This file path is not being included. - $included = false; + foreach ($patterns as $pattern => $type) { + // While there is support for a type of each pattern + // (absolute or relative) we don't actually support it here. + $replacements = [ + '\\,' => ',', + '*' => '.*', + ]; + + // We assume a / directory separator, as do the exclude rules + // most developers write, so we need a special case for any system + // that is different. + if (DIRECTORY_SEPARATOR === '\\') { + $replacements['/'] = '\\\\'; } - continue; - } + $pattern = '`'.strtr($pattern, $replacements).'`i'; + $matched = preg_match($pattern, $this->path); - if ($excluding === true) { - // This file path is being excluded. - $this->ignoredCodes[$checkCode] = true; - return false; - } + if ($matched === 0) { + if ($excluding === false && $included === null) { + // This file path is not being included. + $included = false; + } - // This file path is being included. - $included = true; - break; + continue; + } + + if ($excluding === true) { + // This file path is being excluded. + $this->ignoredCodes[$checkCode] = true; + return false; + } + + // This file path is being included. + $included = true; + break; + }//end foreach }//end foreach - }//end foreach + }//end if if ($included === false) { // There were include rules set, but this file From 7c01187e14b8e56f2bc358915a466c35b8f84eb8 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 27 Oct 2020 09:22:45 +1100 Subject: [PATCH 148/733] Changelog + version bump for #3107 (ref #3108) --- package.xml | 11 +++++++---- src/Config.php | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.xml b/package.xml index 3249ae8a57..3be5501734 100644 --- a/package.xml +++ b/package.xml @@ -17,8 +17,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2020-10-23 - 3.5.8 - 3.5.8 + 3.6.0 + 3.6.0 stable @@ -26,8 +26,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - - Reverted a change to the way include/exclude patterns are processed for STDIN content - -- This change is not backwards compatible and will be re-introduced in version 3.6.0 + - Include patterns are now ignored when processing STDIN + -- Previously, checks using include patterns were excluded when processing STDIN when no file path was provided via --stdin-path + -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run + -- If you want include and exclude rules enforced when checking STDIN, use --stdin-path to set the file path + -- Thanks to Juliette Reinders Folmer for the patch diff --git a/src/Config.php b/src/Config.php index be451ff531..22b2049210 100644 --- a/src/Config.php +++ b/src/Config.php @@ -79,7 +79,7 @@ class Config * * @var string */ - const VERSION = '3.5.8'; + const VERSION = '3.6.0'; /** * Package stability; either stable, beta or alpha. From a2ec4deb0536eec6d1bbc3cbe18834083c665ece Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 30 Oct 2020 11:20:16 +1100 Subject: [PATCH 149/733] Removed unused variable (ref #2815) --- .../PEAR/Sniffs/Commenting/FunctionCommentSniff.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php index a9a9d7b562..dd08e84b3d 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php @@ -18,7 +18,8 @@ class FunctionCommentSniff implements Sniff /** * Disable the check for functions with a lower visibility than the value given. - * Allowed values are public, protected and private. + * + * Allowed values are public, protected, and private. * * @var string */ @@ -48,11 +49,6 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - - $ignore = Tokens::$emptyTokens; - $ignore[] = T_STATIC; - $scopeModifier = $phpcsFile->getMethodProperties($stackPtr)['scope']; if ($scopeModifier === 'protected' && $this->minimumVisibility === 'public' @@ -62,6 +58,7 @@ public function process(File $phpcsFile, $stackPtr) return; } + $tokens = $phpcsFile->getTokens(); $ignore = Tokens::$methodPrefixes; $ignore[] = T_WHITESPACE; From 71de9e1817a67ec9f1c83ab413aa44ee3fc4eb0c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 30 Oct 2020 11:29:20 +1100 Subject: [PATCH 150/733] Changelog for #2077 (ref #2815) --- package.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.xml b/package.xml index 3be5501734..4fd5e773ee 100644 --- a/package.xml +++ b/package.xml @@ -31,6 +31,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run -- If you want include and exclude rules enforced when checking STDIN, use --stdin-path to set the file path -- Thanks to Juliette Reinders Folmer for the patch + - PEAR.Commenting.FunctionComment and Squiz.Commenting.FunctionComment sniffs can now ignore private and protected methods + -- Set the "minimumVisibility" sniff property to "protected" to ignore private methods + -- Set the "minimumVisibility" sniff property to "public" to ignore both private and protected methods + -- The default remains at "private", so all methods are checked + -- Thanks to Vincent Langlet for the patch From 9ff2b302b521eb636dc2bed6beeafca87326c753 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 30 Oct 2020 12:09:32 +1100 Subject: [PATCH 151/733] The error message is now reported on the correct token (ref #3028) --- package.xml | 2 ++ .../Functions/FunctionCallSignatureSniff.php | 2 +- .../Methods/FunctionCallSignatureUnitTest.php | 25 +++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/package.xml b/package.xml index 4fd5e773ee..264d827351 100644 --- a/package.xml +++ b/package.xml @@ -36,6 +36,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Set the "minimumVisibility" sniff property to "public" to ignore both private and protected methods -- The default remains at "private", so all methods are checked -- Thanks to Vincent Langlet for the patch + - The PSR2.Methods.FunctionCallSignature.SpaceBeforeCloseBracket error message is now reported on the closing parenthesis token + -- Previously, the error was being reported on the function keyword, leading to confusing line numbers in the error report diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php index a4131fe65c..db86e6d301 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php @@ -272,7 +272,7 @@ public function processSingleLineCall(File $phpcsFile, $stackPtr, $openBracket, $requiredSpacesBeforeClose, $spaceBeforeClose, ]; - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceBeforeCloseBracket', $data); + $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceBeforeCloseBracket', $data); if ($fix === true) { $padding = str_repeat(' ', $requiredSpacesBeforeClose); diff --git a/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.php b/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.php index c6862ca17f..fb5532219d 100644 --- a/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.php +++ b/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.php @@ -35,20 +35,19 @@ public function getErrorList() 103 => 1, 111 => 1, 117 => 4, - 121 => 1, - 125 => 1, - 129 => 1, - 133 => 1, - 138 => 1, - 146 => 1, - 150 => 1, - 154 => 1, - 158 => 1, - 162 => 1, - 167 => 1, - 172 => 1, + 123 => 1, + 127 => 1, + 131 => 1, + 136 => 1, + 143 => 1, + 148 => 1, + 152 => 1, + 156 => 1, + 160 => 1, + 165 => 1, + 170 => 1, 175 => 1, - 178 => 1, + 178 => 2, 186 => 1, 187 => 1, 194 => 3, From a3e045313cb7651b587acca17e190cbfd0c81fae Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 30 Oct 2020 13:10:22 +1100 Subject: [PATCH 152/733] Fixed an issue that could occurr when checking files on network drives (ref #2965) --- package.xml | 3 +++ src/Files/LocalFile.php | 3 ++- src/Util/Common.php | 28 ++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 264d827351..c61da0dac4 100644 --- a/package.xml +++ b/package.xml @@ -38,6 +38,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Vincent Langlet for the patch - The PSR2.Methods.FunctionCallSignature.SpaceBeforeCloseBracket error message is now reported on the closing parenthesis token -- Previously, the error was being reported on the function keyword, leading to confusing line numbers in the error report + - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 + -- This works around a long-standing PHP bug with is_readable() + -- Thanks to Michael S for the patch diff --git a/src/Files/LocalFile.php b/src/Files/LocalFile.php index 7c2c671d39..ca2e74ad39 100644 --- a/src/Files/LocalFile.php +++ b/src/Files/LocalFile.php @@ -12,6 +12,7 @@ use PHP_CodeSniffer\Ruleset; use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Util\Cache; +use PHP_CodeSniffer\Util\Common; class LocalFile extends File { @@ -29,7 +30,7 @@ class LocalFile extends File public function __construct($path, Ruleset $ruleset, Config $config) { $this->path = trim($path); - if (is_readable($this->path) === false) { + if (Common::isReadable($this->path) === false) { parent::__construct($this->path, $ruleset, $config); $error = 'Error opening file; file no longer exists or you do not have access to read the file'; $this->addMessage(true, $error, 1, 1, 'Internal.LocalFile', [], 5, false); diff --git a/src/Util/Common.php b/src/Util/Common.php index f939fc76de..49fbe23d00 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -48,6 +48,34 @@ public static function isPharFile($path) }//end isPharFile() + /** + * Checks if a file is readable. + * + * Addresses PHP bug related to reading files from network drives on Windows. + * e.g. when using WSL2. + * + * @param string $path The path to the file. + * + * @return boolean + */ + public static function isReadable($path) + { + if (is_readable($path) === true) { + return true; + } + + if (file_exists($path) === true && is_file($path) === true) { + $f = @fopen($path, 'rb'); + if (fclose($f) === true) { + return true; + } + } + + return false; + + }//end isReadable() + + /** * CodeSniffer alternative for realpath. * From 457afdfb1bff2d4fb79149aca8f8ba1551e56887 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 2 Nov 2020 16:21:01 +1100 Subject: [PATCH 153/733] Changelog for #3032 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index 2b68a1ed4c..7f633f17b2 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - Added support for PHP 8.0 union types + -- A new T_TYPE_UNION token is available to represent the pipe character + -- File::getMethodParameters(), getMethodProperties(), and getMemberProperties() will now return union types + -- Thanks to Juliette Reinders Folmer for the patch - Include patterns are now ignored when processing STDIN -- Previously, checks using include patterns were excluded when processing STDIN when no file path was provided via --stdin-path -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run From a16e88ff217eb517393e46667949902bc24f8a55 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 2 Nov 2020 16:33:25 +1100 Subject: [PATCH 154/733] Renamed new sniff property --- .../MultipleStatementAlignmentSniff.php | 16 +++------------- .../MultipleStatementAlignmentUnitTest.inc | 4 ++-- .../MultipleStatementAlignmentUnitTest.inc.fixed | 4 ++-- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php index ddf6f3771e..f7f3f0656e 100644 --- a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php +++ b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php @@ -48,21 +48,11 @@ class MultipleStatementAlignmentSniff implements Sniff public $maxPadding = 1000; /** - * Controls which side of the assignment token is used for alignment - * - * The default is to use the end of the assignemnt token: - * - * $test = 'Hello'; - * $test .= ' World'; - * - * Setting to false reverses the alignment: - * - * $test = 'Hello'; - * $test .= 'World'; + * Controls which side of the assignment token is used for alignment. * * @var boolean */ - public $alignAtEndOfAssignToken = true; + public $alignAtEnd = true; /** @@ -270,7 +260,7 @@ public function checkAlignment($phpcsFile, $stackPtr, $end=null) // padding length if they aligned with us. $varEnd = $tokens[($var + 1)]['column']; $assignLen = $tokens[$assign]['length']; - if ($this->alignAtEndOfAssignToken !== true) { + if ($this->alignAtEnd !== true) { $assignLen = 1; } diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc index f1d3c5ee0f..44d0cef27a 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc @@ -421,7 +421,7 @@ $foo .= ' World'; $test = 1; $test <<= 6; -// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEndOfAssignToken false +// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEnd false // Valid $foo = 'Hello'; @@ -454,7 +454,7 @@ $varonetwothreefour = 'four'; $one <<= 8; $onetwothree = 3; -// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEndOfAssignToken true +// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEnd true $one <<= 8; $onetwothree = 3; diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed index cb7e8f6f02..d2159e560f 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed @@ -421,7 +421,7 @@ $foo .= ' World'; $test = 1; $test <<= 6; -// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEndOfAssignToken false +// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEnd false // Valid $foo = 'Hello'; @@ -454,7 +454,7 @@ $varonetwothreefour = 'four'; $one <<= 8; $onetwothree = 3; -// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEndOfAssignToken true +// phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEnd true $one <<= 8; $onetwothree = 3; From 38c8ae6b788cd9f94c809210f3dd7af16c73bb56 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 2 Nov 2020 16:37:53 +1100 Subject: [PATCH 155/733] Changelog for #2896 (ref #1941) --- package.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.xml b/package.xml index 7f633f17b2..af5d2e3897 100644 --- a/package.xml +++ b/package.xml @@ -35,6 +35,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run -- If you want include and exclude rules enforced when checking STDIN, use --stdin-path to set the file path -- Thanks to Juliette Reinders Folmer for the patch + - Generic.Formatting.MultipleStatementAlignment can now align statements at the start of the assignment token + -- Previously, the sniff enforced that the values were aligned, even if this meant the assignment tokens were not + -- Now, the sniff can enforce that the assignment tokens are aligned, even if this means the values are not + -- Set the "alignAtEnd" sniff property to "false" to align the assignment tokens + -- The default remains at "true", so the assigned values are aligned + -- Thanks to John P. Bloch for the patch - PEAR.Commenting.FunctionComment and Squiz.Commenting.FunctionComment sniffs can now ignore private and protected methods -- Set the "minimumVisibility" sniff property to "protected" to ignore private methods -- Set the "minimumVisibility" sniff property to "public" to ignore both private and protected methods From 3eea1b013811f70f36fb2aaa2c93f5ac19de2522 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 14 Jul 2020 12:49:52 +0200 Subject: [PATCH 156/733] PHP 8.0 | File::getMethodParameters(): add support for PHP 8 constructor property promotion PHP 8 introduces constructor property promotion. This commit adds support for constructor property promotion to the `File::getMethodParameters()` method. This change introduces two new keys - `property_visibility` and `visibility_token` - to the return array which will only be included if constructor property promotion is detected. The method does not check whether property promotion is _valid_ in the function/method in which it is used. That is not the concern of this method. Includes unit tests. Ref: https://wiki.php.net/rfc/constructor_promotion --- src/Files/File.php | 20 ++- tests/Core/File/GetMethodParametersTest.inc | 32 ++++ tests/Core/File/GetMethodParametersTest.php | 172 ++++++++++++++++++++ 3 files changed, 223 insertions(+), 1 deletion(-) diff --git a/src/Files/File.php b/src/Files/File.php index 8d1f1a8416..c5f85c143b 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1301,11 +1301,15 @@ public function getDeclarationName($stackPtr) * ) * * - * Parameters with default values have an additional array indexes of: + * Parameters with default values have additional array indexes of: * 'default' => string, // The full content of the default value. * 'default_token' => integer, // The stack pointer to the start of the default value. * 'default_equal_token' => integer, // The stack pointer to the equals sign. * + * 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. + * * @param int $stackPtr The position in the stack of the function token * to acquire the parameters for. * @@ -1359,6 +1363,7 @@ public function getMethodParameters($stackPtr) $typeHintToken = false; $typeHintEndToken = false; $nullableType = false; + $visibilityToken = null; for ($i = $paramStart; $i <= $closer; $i++) { // Check to see if this token has a parenthesis or bracket opener. If it does @@ -1470,6 +1475,13 @@ public function getMethodParameters($stackPtr) $typeHintEndToken = $i; } break; + case T_PUBLIC: + case T_PROTECTED: + case T_PRIVATE: + if ($defaultStart === null) { + $visibilityToken = $i; + } + break; case T_CLOSE_PARENTHESIS: case T_COMMA: // If it's null, then there must be no parameters for this @@ -1498,6 +1510,11 @@ public function getMethodParameters($stackPtr) $vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken; $vars[$paramCount]['nullable_type'] = $nullableType; + if ($visibilityToken !== null) { + $vars[$paramCount]['property_visibility'] = $this->tokens[$visibilityToken]['content']; + $vars[$paramCount]['visibility_token'] = $visibilityToken; + } + if ($this->tokens[$i]['code'] === T_COMMA) { $vars[$paramCount]['comma_token'] = $i; } else { @@ -1517,6 +1534,7 @@ public function getMethodParameters($stackPtr) $typeHintToken = false; $typeHintEndToken = false; $nullableType = false; + $visibilityToken = null; $paramCount++; break; diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index 56fa6eae41..0b7a71fc6d 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -85,3 +85,35 @@ function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {} /* testPHP8DuplicateTypeInUnionWhitespaceAndComment */ // Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method. function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {} + +class ConstructorPropertyPromotionNoTypes { + /* testPHP8ConstructorPropertyPromotionNoTypes */ + public function __construct( + public $x = 0.0, + protected $y = '', + private $z = null, + ) {} +} + +class ConstructorPropertyPromotionWithTypes { + /* testPHP8ConstructorPropertyPromotionWithTypes */ + public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {} +} + +class ConstructorPropertyPromotionAndNormalParams { + /* testPHP8ConstructorPropertyPromotionAndNormalParam */ + public function __construct(public int $promotedProp, ?int $normalArg) {} +} + +/* testPHP8ConstructorPropertyPromotionGlobalFunction */ +// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method. +function globalFunction(private $x) {} + +abstract class ConstructorPropertyPromotionAbstractMethod { + /* testPHP8ConstructorPropertyPromotionAbstractMethod */ + // Intentional fatal error. + // 1. Property promotion not allowed in abstract method, but that's not the concern of this method. + // 2. Variadic arguments not allowed in property promotion, but that's not the concern of this method. + // 3. The callable type is not supported for properties, but that's not the concern of this method. + abstract public function __construct(public callable $y, private ...$x); +} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index ad4cf8b67e..9c88fcb684 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -616,6 +616,178 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment() }//end testPHP8DuplicateTypeInUnionWhitespaceAndComment() + /** + * Verify recognition of PHP8 constructor property promotion without type declaration, with defaults. + * + * @return void + */ + public function testPHP8ConstructorPropertyPromotionNoTypes() + { + $expected = []; + $expected[0] = [ + 'name' => '$x', + 'content' => 'public $x = 0.0', + 'default' => '0.0', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + 'property_visibility' => 'public', + ]; + $expected[1] = [ + 'name' => '$y', + 'content' => 'protected $y = \'\'', + 'default' => "''", + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + 'property_visibility' => 'protected', + ]; + $expected[2] = [ + 'name' => '$z', + 'content' => 'private $z = null', + 'default' => 'null', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + 'property_visibility' => 'private', + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8ConstructorPropertyPromotionNoTypes() + + + /** + * Verify recognition of PHP8 constructor property promotion with type declarations. + * + * @return void + */ + public function testPHP8ConstructorPropertyPromotionWithTypes() + { + $expected = []; + $expected[0] = [ + 'name' => '$x', + 'content' => 'protected float|int $x', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'float|int', + 'nullable_type' => false, + 'property_visibility' => 'protected', + ]; + $expected[1] = [ + 'name' => '$y', + 'content' => 'public ?string &$y = \'test\'', + 'default' => "'test'", + 'pass_by_reference' => true, + 'variable_length' => false, + 'type_hint' => '?string', + 'nullable_type' => true, + 'property_visibility' => 'public', + ]; + $expected[2] = [ + 'name' => '$z', + 'content' => 'private mixed $z', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'mixed', + 'nullable_type' => false, + 'property_visibility' => 'private', + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8ConstructorPropertyPromotionWithTypes() + + + /** + * Verify recognition of PHP8 constructor with both property promotion as well as normal parameters. + * + * @return void + */ + public function testPHP8ConstructorPropertyPromotionAndNormalParam() + { + $expected = []; + $expected[0] = [ + 'name' => '$promotedProp', + 'content' => 'public int $promotedProp', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'int', + 'nullable_type' => false, + 'property_visibility' => 'public', + ]; + $expected[1] = [ + 'name' => '$normalArg', + 'content' => '?int $normalArg', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?int', + 'nullable_type' => true, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8ConstructorPropertyPromotionAndNormalParam() + + + /** + * Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax. + * + * @return void + */ + public function testPHP8ConstructorPropertyPromotionGlobalFunction() + { + $expected = []; + $expected[0] = [ + 'name' => '$x', + 'content' => 'private $x', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + 'property_visibility' => 'private', + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8ConstructorPropertyPromotionGlobalFunction() + + + /** + * Verify behaviour when an abstract constructor uses PHP 8 property promotion syntax. + * + * @return void + */ + public function testPHP8ConstructorPropertyPromotionAbstractMethod() + { + $expected = []; + $expected[0] = [ + 'name' => '$y', + 'content' => 'public callable $y', + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'callable', + 'nullable_type' => false, + 'property_visibility' => 'public', + ]; + $expected[1] = [ + 'name' => '$x', + 'content' => 'private ...$x', + 'pass_by_reference' => false, + 'variable_length' => true, + 'type_hint' => '', + 'nullable_type' => false, + 'property_visibility' => 'private', + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8ConstructorPropertyPromotionAbstractMethod() + + /** * Test helper. * From 9662f431bd95ab253f75d68ea8265981d41edcc6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 8 Jul 2020 16:06:28 +0200 Subject: [PATCH 157/733] File::getMethodProperties(): add new `return_type_end_token` index to return value With union types, even non-class return types may consist of multiple tokens, so having access to the stackPtr for the exact end of the return type becomes relevant for sniffs which include fixers. This commit adds a `return_type_end_token` index key to the array return value of the `File::getMethodProperties()` method. --- src/Files/File.php | 49 +++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 8d1f1a8416..d3c799d2ca 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1538,17 +1538,19 @@ public function getMethodParameters($stackPtr) * The format of the return value is: * * array( - * 'scope' => 'public', // Public, private, or protected - * 'scope_specified' => true, // TRUE if the scope keyword was found. - * 'return_type' => '', // The return type of the method. - * 'return_type_token' => integer, // The stack pointer to the start of the return type - * // or FALSE if there is no return type. - * 'nullable_return_type' => false, // TRUE if the return type is preceded by the - * // nullability operator. - * 'is_abstract' => false, // TRUE if the abstract keyword was found. - * 'is_final' => false, // TRUE if the final keyword was found. - * 'is_static' => false, // TRUE if the static keyword was found. - * 'has_body' => false, // TRUE if the method has a body + * 'scope' => 'public', // Public, private, or protected + * 'scope_specified' => true, // TRUE if the scope keyword was found. + * 'return_type' => '', // The return type of the method. + * 'return_type_token' => integer, // The stack pointer to the start of the return type + * // or FALSE if there is no return type. + * 'return_type_end_token' => integer, // The stack pointer to the end of the return type + * // or FALSE if there is no return type. + * 'nullable_return_type' => false, // TRUE if the return type is preceded by the + * // nullability operator. + * 'is_abstract' => false, // TRUE if the abstract keyword was found. + * 'is_final' => false, // TRUE if the final keyword was found. + * 'is_static' => false, // TRUE if the static keyword was found. + * 'has_body' => false, // TRUE if the method has a body * ); * * @@ -1627,6 +1629,7 @@ public function getMethodProperties($stackPtr) $returnType = ''; $returnTypeToken = false; + $returnTypeEndToken = false; $nullableReturnType = false; $hasBody = true; @@ -1666,9 +1669,10 @@ public function getMethodProperties($stackPtr) $returnTypeToken = $i; } - $returnType .= $this->tokens[$i]['content']; + $returnType .= $this->tokens[$i]['content']; + $returnTypeEndToken = $i; } - } + }//end for if ($this->tokens[$stackPtr]['code'] === T_FN) { $bodyToken = T_FN_ARROW; @@ -1685,15 +1689,16 @@ public function getMethodProperties($stackPtr) } return [ - 'scope' => $scope, - 'scope_specified' => $scopeSpecified, - 'return_type' => $returnType, - 'return_type_token' => $returnTypeToken, - 'nullable_return_type' => $nullableReturnType, - 'is_abstract' => $isAbstract, - 'is_final' => $isFinal, - 'is_static' => $isStatic, - 'has_body' => $hasBody, + 'scope' => $scope, + 'scope_specified' => $scopeSpecified, + 'return_type' => $returnType, + 'return_type_token' => $returnTypeToken, + 'return_type_end_token' => $returnTypeEndToken, + 'nullable_return_type' => $nullableReturnType, + 'is_abstract' => $isAbstract, + 'is_final' => $isFinal, + 'is_static' => $isStatic, + 'has_body' => $hasBody, ]; }//end getMethodProperties() From 808494801d54932f88ec1cb053677e51aeb8a4b6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 9 Jul 2020 01:32:09 +0200 Subject: [PATCH 158/733] Generic/LowerCaseType: minor tweak to a unit test ... to verify and safeguard that spacing within a type cast is not touched by the sniff. --- src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc | 2 +- src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index f4e0dd4628..aae4c720a8 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -15,7 +15,7 @@ $foo = (Int) $bar; $foo = (INTEGER) $bar; $foo = (BOOL) $bar; $foo = (String) $bar; -$foo = (Array) $bar; +$foo = ( Array ) $bar; function foo(int $a, string $b, bool $c, array $d, Foo\Bar $e) : int {} function foo(Int $a, String $b, BOOL $c, Array $d, Foo\Bar $e) : Foo\Bar {} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed index a64a4400d7..42704d8cd5 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -15,7 +15,7 @@ $foo = (int) $bar; $foo = (integer) $bar; $foo = (bool) $bar; $foo = (string) $bar; -$foo = (array) $bar; +$foo = ( array ) $bar; function foo(int $a, string $b, bool $c, array $d, Foo\Bar $e) : int {} function foo(int $a, string $b, bool $c, array $d, Foo\Bar $e) : Foo\Bar {} From 7e0f82241bfbbf4f1aad5550141fb1e294628119 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 9 Jul 2020 00:57:07 +0200 Subject: [PATCH 159/733] Generic/LowerCaseType: refactor [1] - move array to property Move the `$phpTypes` array to a property and add the new types introduced in PHP 8.0 to it. --- .../Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index 27ef5ecac9..7aaae2e010 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -16,6 +16,29 @@ class LowerCaseTypeSniff implements Sniff { + /** + * Native types supported by PHP. + * + * @var array + */ + private $phpTypes = [ + 'self' => true, + 'parent' => true, + 'array' => true, + 'callable' => true, + 'bool' => true, + 'float' => true, + 'int' => true, + 'string' => true, + 'iterable' => true, + 'void' => true, + 'object' => true, + 'mixed' => true, + 'static' => true, + 'false' => true, + 'null' => true, + ]; + /** * Returns an array of tokens this test wants to listen for. @@ -71,20 +94,6 @@ public function process(File $phpcsFile, $stackPtr) return; }//end if - $phpTypes = [ - 'self' => true, - 'parent' => true, - 'array' => true, - 'callable' => true, - 'bool' => true, - 'float' => true, - 'int' => true, - 'string' => true, - 'iterable' => true, - 'void' => true, - 'object' => true, - ]; - $props = $phpcsFile->getMethodProperties($stackPtr); // Strip off potential nullable indication. @@ -92,7 +101,7 @@ public function process(File $phpcsFile, $stackPtr) $returnTypeLower = strtolower($returnType); if ($returnType !== '' - && isset($phpTypes[$returnTypeLower]) === true + && isset($this->phpTypes[$returnTypeLower]) === true ) { // A function return type. if ($returnTypeLower !== $returnType) { @@ -129,7 +138,7 @@ public function process(File $phpcsFile, $stackPtr) $typeHintLower = strtolower($typeHint); if ($typeHint !== '' - && isset($phpTypes[$typeHintLower]) === true + && isset($this->phpTypes[$typeHintLower]) === true ) { // A function return type. if ($typeHintLower !== $typeHint) { From 4c92103a14d6803bf7be71aeca6c424c4406d679 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 9 Jul 2020 01:30:58 +0200 Subject: [PATCH 160/733] Generic/LowerCaseType: refactor [2] - remove duplicate code Extract duplicate code out to a separate method and call the method instead. --- .../Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 134 +++++++++--------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index 7aaae2e010..e9f04539a3 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -70,29 +70,20 @@ public function process(File $phpcsFile, $stackPtr) if (isset(Tokens::$castTokens[$tokens[$stackPtr]['code']]) === true) { // A cast token. - if (strtolower($tokens[$stackPtr]['content']) !== $tokens[$stackPtr]['content']) { - if ($tokens[$stackPtr]['content'] === strtoupper($tokens[$stackPtr]['content'])) { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'upper'); - } else { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'mixed'); - } - - $error = 'PHP type casts must be lowercase; expected "%s" but found "%s"'; - $data = [ - strtolower($tokens[$stackPtr]['content']), - $tokens[$stackPtr]['content'], - ]; - - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'TypeCastFound', $data); - if ($fix === true) { - $phpcsFile->fixer->replaceToken($stackPtr, strtolower($tokens[$stackPtr]['content'])); - } - } else { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'lower'); - }//end if + $this->processType( + $phpcsFile, + $stackPtr, + $tokens[$stackPtr]['content'], + 'PHP type casts must be lowercase; expected "%s" but found "%s"', + 'TypeCastFound' + ); return; - }//end if + } + + /* + * Check function return type. + */ $props = $phpcsFile->getMethodProperties($stackPtr); @@ -103,29 +94,15 @@ public function process(File $phpcsFile, $stackPtr) if ($returnType !== '' && isset($this->phpTypes[$returnTypeLower]) === true ) { - // A function return type. - if ($returnTypeLower !== $returnType) { - if ($returnType === strtoupper($returnType)) { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'upper'); - } else { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'mixed'); - } - - $error = 'PHP return type declarations must be lowercase; expected "%s" but found "%s"'; - $token = $props['return_type_token']; - $data = [ - $returnTypeLower, - $returnType, - ]; - - $fix = $phpcsFile->addFixableError($error, $token, 'ReturnTypeFound', $data); - if ($fix === true) { - $phpcsFile->fixer->replaceToken($token, $returnTypeLower); - } - } else { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'lower'); - }//end if - }//end if + $error = 'PHP return type declarations must be lowercase; expected "%s" but found "%s"'; + $errorCode = 'ReturnTypeFound'; + + $this->processType($phpcsFile, $props['return_type_token'], $returnType, $error, $errorCode); + } + + /* + * Check function parameter types. + */ $params = $phpcsFile->getMethodParameters($stackPtr); if (empty($params) === true) { @@ -140,32 +117,53 @@ public function process(File $phpcsFile, $stackPtr) if ($typeHint !== '' && isset($this->phpTypes[$typeHintLower]) === true ) { - // A function return type. - if ($typeHintLower !== $typeHint) { - if ($typeHint === strtoupper($typeHint)) { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'upper'); - } else { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'mixed'); - } - - $error = 'PHP parameter type declarations must be lowercase; expected "%s" but found "%s"'; - $token = $param['type_hint_token']; - $data = [ - $typeHintLower, - $typeHint, - ]; - - $fix = $phpcsFile->addFixableError($error, $token, 'ParamTypeFound', $data); - if ($fix === true) { - $phpcsFile->fixer->replaceToken($token, $typeHintLower); - } - } else { - $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'lower'); - }//end if - }//end if - }//end foreach + $error = 'PHP parameter type declarations must be lowercase; expected "%s" but found "%s"'; + $errorCode = 'ParamTypeFound'; + + $this->processType($phpcsFile, $param['type_hint_token'], $typeHint, $error, $errorCode); + } + } }//end process() + /** + * Processes a type cast or a singular type declaration. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the type token. + * @param string $type The type found. + * @param string $error Error message template. + * @param string $errorCode The error code. + * + * @return void + */ + protected function processType(File $phpcsFile, $stackPtr, $type, $error, $errorCode) + { + $typeLower = strtolower($type); + + if ($typeLower === $type) { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'lower'); + return; + } + + if ($type === strtoupper($type)) { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'upper'); + } else { + $phpcsFile->recordMetric($stackPtr, 'PHP type case', 'mixed'); + } + + $data = [ + $typeLower, + $type, + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $errorCode, $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken($stackPtr, $typeLower); + } + + }//end processType() + + }//end class From b4313a785215b17af46394d82644e2fd772d501b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 9 Jul 2020 02:21:14 +0200 Subject: [PATCH 161/733] PHP 8.0 | Generic/LowerCaseType: refactor [3] - allow for union types Allow for union types, as introduced in PHP 8.0, in return and parameter type declarations. Includes unit tests. --- .../Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 88 ++++++++++++++++--- .../Tests/PHP/LowerCaseTypeUnitTest.inc | 8 ++ .../Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 8 ++ .../Tests/PHP/LowerCaseTypeUnitTest.php | 4 + 4 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index e9f04539a3..55debf4934 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -88,16 +88,23 @@ public function process(File $phpcsFile, $stackPtr) $props = $phpcsFile->getMethodProperties($stackPtr); // Strip off potential nullable indication. - $returnType = ltrim($props['return_type'], '?'); - $returnTypeLower = strtolower($returnType); + $returnType = ltrim($props['return_type'], '?'); - if ($returnType !== '' - && isset($this->phpTypes[$returnTypeLower]) === true - ) { + if ($returnType !== '') { $error = 'PHP return type declarations must be lowercase; expected "%s" but found "%s"'; $errorCode = 'ReturnTypeFound'; - $this->processType($phpcsFile, $props['return_type_token'], $returnType, $error, $errorCode); + if (strpos($returnType, '|') !== false) { + $this->processUnionType( + $phpcsFile, + $props['return_type_token'], + $props['return_type_end_token'], + $error, + $errorCode + ); + } else if (isset($this->phpTypes[strtolower($returnType)]) === true) { + $this->processType($phpcsFile, $props['return_type_token'], $returnType, $error, $errorCode); + } } /* @@ -111,22 +118,77 @@ public function process(File $phpcsFile, $stackPtr) foreach ($params as $param) { // Strip off potential nullable indication. - $typeHint = ltrim($param['type_hint'], '?'); - $typeHintLower = strtolower($typeHint); + $typeHint = ltrim($param['type_hint'], '?'); - if ($typeHint !== '' - && isset($this->phpTypes[$typeHintLower]) === true - ) { + if ($typeHint !== '') { $error = 'PHP parameter type declarations must be lowercase; expected "%s" but found "%s"'; $errorCode = 'ParamTypeFound'; - $this->processType($phpcsFile, $param['type_hint_token'], $typeHint, $error, $errorCode); + if (strpos($typeHint, '|') !== false) { + $this->processUnionType( + $phpcsFile, + $param['type_hint_token'], + $param['type_hint_end_token'], + $error, + $errorCode + ); + } else if (isset($this->phpTypes[strtolower($typeHint)]) === true) { + $this->processType($phpcsFile, $param['type_hint_token'], $typeHint, $error, $errorCode); + } } - } + }//end foreach }//end process() + /** + * Processes a union type declaration. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $typeDeclStart The position of the start of the type token. + * @param int $typeDeclEnd The position of the end of the type token. + * @param string $error Error message template. + * @param string $errorCode The error code. + * + * @return void + */ + protected function processUnionType(File $phpcsFile, $typeDeclStart, $typeDeclEnd, $error, $errorCode) + { + $tokens = $phpcsFile->getTokens(); + $current = $typeDeclStart; + + do { + $endOfType = $phpcsFile->findNext(T_TYPE_UNION, $current, $typeDeclEnd); + if ($endOfType === false) { + // This must be the last type in the union. + $endOfType = ($typeDeclEnd + 1); + } + + $hasNsSep = $phpcsFile->findNext(T_NS_SEPARATOR, $current, $endOfType); + if ($hasNsSep !== false) { + // Multi-token class based type. Ignore. + $current = ($endOfType + 1); + continue; + } + + // Type consisting of a single token. + $startOfType = $phpcsFile->findNext(Tokens::$emptyTokens, $current, $endOfType, true); + if ($startOfType === false) { + // Parse error. + return; + } + + $type = $tokens[$startOfType]['content']; + if (isset($this->phpTypes[strtolower($type)]) === true) { + $this->processType($phpcsFile, $startOfType, $type, $error, $errorCode); + } + + $current = ($endOfType + 1); + } while ($current <= $typeDeclEnd); + + }//end processUnionType() + + /** * Processes a type cast or a singular type declaration. * diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index aae4c720a8..5c3d51c08c 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -45,3 +45,11 @@ $foo = function (?Int $a, ? Callable $b) $var = (BInARY) $string; $var = (binary)$string; + +function unionParamTypesA (bool|array| /* nullability operator not allowed in union */ NULL $var) {} + +function unionParamTypesB (\Package\ClassName | Int | \Package\Other_Class | FALSE $var) {} + +function unionReturnTypesA ($var): bool|array| /* nullability operator not allowed in union */ NULL {} + +function unionReturnTypesB ($var): \Package\ClassName | Int | \Package\Other_Class | FALSE {} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed index 42704d8cd5..d9e6489ee7 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -45,3 +45,11 @@ $foo = function (?int $a, ? callable $b) $var = (binary) $string; $var = (binary)$string; + +function unionParamTypesA (bool|array| /* nullability operator not allowed in union */ null $var) {} + +function unionParamTypesB (\Package\ClassName | int | \Package\Other_Class | false $var) {} + +function unionReturnTypesA ($var): bool|array| /* nullability operator not allowed in union */ null {} + +function unionReturnTypesB ($var): \Package\ClassName | int | \Package\Other_Class | false {} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php index 0a5f5e03ea..ea7946d224 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php @@ -45,6 +45,10 @@ public function getErrorList() 43 => 2, 44 => 1, 46 => 1, + 49 => 1, + 51 => 2, + 53 => 1, + 55 => 2, ]; }//end getErrorList() From d1fdf3200bd8ac8ec7d0ee9317730edfb29fbc61 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 9 Jul 2020 02:34:49 +0200 Subject: [PATCH 162/733] PHP 7.4 | Generic/LowerCaseType: add support for typed properties PHP 7.4 introduced typed properties, but this sniff did not examine those yet. Includes unit tests. --- .../Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 37 +++++++++++++++++++ .../Tests/PHP/LowerCaseTypeUnitTest.inc | 20 ++++++++++ .../Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 20 ++++++++++ .../Tests/PHP/LowerCaseTypeUnitTest.php | 14 +++++++ 4 files changed, 91 insertions(+) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index 55debf4934..a738c30d66 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -9,6 +9,7 @@ namespace PHP_CodeSniffer\Standards\Generic\Sniffs\PHP; +use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; @@ -50,6 +51,7 @@ public function register() $tokens = Tokens::$castTokens; $tokens[] = T_FUNCTION; $tokens[] = T_CLOSURE; + $tokens[] = T_VARIABLE; return $tokens; }//end register() @@ -81,6 +83,41 @@ public function process(File $phpcsFile, $stackPtr) return; } + /* + * Check property types. + */ + + if ($tokens[$stackPtr]['code'] === T_VARIABLE) { + try { + $props = $phpcsFile->getMemberProperties($stackPtr); + } catch (RuntimeException $e) { + // Not an OO property. + return; + } + + // Strip off potential nullable indication. + $type = ltrim($props['type'], '?'); + + if ($type !== '') { + $error = 'PHP property type declarations must be lowercase; expected "%s" but found "%s"'; + $errorCode = 'PropertyTypeFound'; + + if (strpos($type, '|') !== false) { + $this->processUnionType( + $phpcsFile, + $props['type_token'], + $props['type_end_token'], + $error, + $errorCode + ); + } else if (isset($this->phpTypes[strtolower($type)]) === true) { + $this->processType($phpcsFile, $props['type_token'], $type, $error, $errorCode); + } + } + + return; + }//end if + /* * Check function return type. */ diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index 5c3d51c08c..28e696ccc9 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -53,3 +53,23 @@ function unionParamTypesB (\Package\ClassName | Int | \Package\Other_Class | FAL function unionReturnTypesA ($var): bool|array| /* nullability operator not allowed in union */ NULL {} function unionReturnTypesB ($var): \Package\ClassName | Int | \Package\Other_Class | FALSE {} + +class TypedProperties +{ + protected ClassName $class; + public Int $int; + private ?BOOL $bool; + public Self $self; + protected PaRenT $parent; + private ARRAY $array; + public Float $float; + protected ?STRING $string; + private IterablE $iterable; + public Object $object; + protected Mixed $mixed; + + public Iterable|FALSE|NULL $unionTypeA; + protected SELF|Parent /* comment */ |\Fully\Qualified\ClassName|UnQualifiedClass $unionTypeB; + private ClassName|/*comment*/Float|STRING|False $unionTypeC; + public sTRing | aRRaY | FaLSe $unionTypeD; +} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed index d9e6489ee7..302c0faf0a 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -53,3 +53,23 @@ function unionParamTypesB (\Package\ClassName | int | \Package\Other_Class | fal function unionReturnTypesA ($var): bool|array| /* nullability operator not allowed in union */ null {} function unionReturnTypesB ($var): \Package\ClassName | int | \Package\Other_Class | false {} + +class TypedProperties +{ + protected ClassName $class; + public int $int; + private ?bool $bool; + public self $self; + protected parent $parent; + private array $array; + public float $float; + protected ?string $string; + private iterable $iterable; + public object $object; + protected mixed $mixed; + + public iterable|false|null $unionTypeA; + protected self|parent /* comment */ |\Fully\Qualified\ClassName|UnQualifiedClass $unionTypeB; + private ClassName|/*comment*/float|string|false $unionTypeC; + public string | array | false $unionTypeD; +} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php index ea7946d224..005c8280d9 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php @@ -49,6 +49,20 @@ public function getErrorList() 51 => 2, 53 => 1, 55 => 2, + 60 => 1, + 61 => 1, + 62 => 1, + 63 => 1, + 64 => 1, + 65 => 1, + 66 => 1, + 67 => 1, + 68 => 1, + 69 => 1, + 71 => 3, + 72 => 2, + 73 => 3, + 74 => 3, ]; }//end getErrorList() From 0277ae10d4ec13c35e763e7c77c85fa151eecfd4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 3 Nov 2020 08:23:50 +1100 Subject: [PATCH 163/733] Changelog for #3152 --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index af5d2e3897..c41a9d3cf5 100644 --- a/package.xml +++ b/package.xml @@ -30,6 +30,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- A new T_TYPE_UNION token is available to represent the pipe character -- File::getMethodParameters(), getMethodProperties(), and getMemberProperties() will now return union types -- Thanks to Juliette Reinders Folmer for the patch + - File::getMethodParameters() now supports PHP 8.0 constructor property promotion + -- Returned method params now include a `property_visibility` and `visibility_token` index if property promotion is detected + -- Thanks to Juliette Reinders Folmer for the patch - Include patterns are now ignored when processing STDIN -- Previously, checks using include patterns were excluded when processing STDIN when no file path was provided via --stdin-path -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run From dbfd9e6ae07c0ee3a65e5adc3a1dbf9faddcfed0 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 3 Nov 2020 08:27:24 +1100 Subject: [PATCH 164/733] Changelog for #3153 --- package.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index c41a9d3cf5..0019f315d7 100644 --- a/package.xml +++ b/package.xml @@ -31,7 +31,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- File::getMethodParameters(), getMethodProperties(), and getMemberProperties() will now return union types -- Thanks to Juliette Reinders Folmer for the patch - File::getMethodParameters() now supports PHP 8.0 constructor property promotion - -- Returned method params now include a `property_visibility` and `visibility_token` index if property promotion is detected + -- Returned method params now include a "property_visibility" and "visibility_token" index if property promotion is detected + -- Thanks to Juliette Reinders Folmer for the patch + - File::getMethodProperties() now includes a "return_type_end_token" index in the return value + -- This indicates the last token in the return type, which is helpful when checking union types -- Thanks to Juliette Reinders Folmer for the patch - Include patterns are now ignored when processing STDIN -- Previously, checks using include patterns were excluded when processing STDIN when no file path was provided via --stdin-path @@ -44,6 +47,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Set the "alignAtEnd" sniff property to "false" to align the assignment tokens -- The default remains at "true", so the assigned values are aligned -- Thanks to John P. Bloch for the patch + - Generic.PHP.LowerCaseType now supports checking of typed properties + -- Thanks to Juliette Reinders Folmer for the patch + - Generic.PHP.LowerCaseType now supports checking of union types + -- Thanks to Juliette Reinders Folmer for the patch - PEAR.Commenting.FunctionComment and Squiz.Commenting.FunctionComment sniffs can now ignore private and protected methods -- Set the "minimumVisibility" sniff property to "protected" to ignore private methods -- Set the "minimumVisibility" sniff property to "public" to ignore both private and protected methods From 75a0512294978845cde961119c2ca96a9fdf20bc Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 4 Nov 2020 08:54:07 +1100 Subject: [PATCH 165/733] Added missing autofix test file --- package.xml | 1 + .../SwitchDeclarationUnitTest.inc.fixed | 342 ++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 src/Standards/Squiz/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed diff --git a/package.xml b/package.xml index 0019f315d7..7d0cf64e29 100644 --- a/package.xml +++ b/package.xml @@ -1622,6 +1622,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/Squiz/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/Squiz/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed new file mode 100644 index 0000000000..fd8ad1e683 --- /dev/null +++ b/src/Standards/Squiz/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed @@ -0,0 +1,342 @@ + Date: Wed, 4 Nov 2020 09:22:47 +1100 Subject: [PATCH 166/733] Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented --- package.xml | 1 + .../ControlStructures/SwitchDeclarationSniff.php | 2 +- .../ControlStructures/SwitchDeclarationUnitTest.inc | 10 ++++++++++ .../SwitchDeclarationUnitTest.inc.fixed | 10 ++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 7d0cf64e29..8efb23c5b6 100644 --- a/package.xml +++ b/package.xml @@ -61,6 +61,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 -- This works around a long-standing PHP bug with is_readable() -- Thanks to Michael S for the patch + - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php index c38a10d2d3..0db0cd98b8 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php @@ -152,7 +152,7 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->fixer->replaceToken($nextCloser, trim($tokens[$nextCloser]['content'])); } } else { - $diff = ($caseAlignment + $this->indent - $tokens[$nextCloser]['column']); + $diff = ($tokens[$nextCase]['column'] + $this->indent - $tokens[$nextCloser]['column']); if ($diff !== 0) { $error = 'Terminating statement must be indented to the same level as the CASE body'; $fix = $phpcsFile->addFixableError($error, $nextCloser, 'BreakIndent'); diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc index af605cd71d..572e6ada05 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc @@ -271,3 +271,13 @@ $foo = $foo ? } } : null; + +switch ($foo) { +case Foo::INTERFACE: + echo '1'; + return self::INTERFACE; +case Foo::TRAIT: +case Foo::ARRAY: + echo '1'; + return self::VALUE; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed index d5671feea3..e454cd3318 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed @@ -274,3 +274,13 @@ $foo = $foo ? } } : null; + +switch ($foo) { +case Foo::INTERFACE: + echo '1'; + return self::INTERFACE; +case Foo::TRAIT: +case Foo::ARRAY: + echo '1'; + return self::VALUE; +} From 8421eeb737248e000a94b124f7cc2ef61e7fb3f9 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 17 Nov 2020 11:09:27 +1100 Subject: [PATCH 167/733] Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional --- package.xml | 1 + .../Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php | 9 ++++++++- .../Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc | 6 ++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed | 6 ++++++ .../Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc | 6 ++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed | 6 ++++++ .../Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 ++++---- 7 files changed, 37 insertions(+), 5 deletions(-) diff --git a/package.xml b/package.xml index 8efb23c5b6..4336e79855 100644 --- a/package.xml +++ b/package.xml @@ -61,6 +61,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 -- This works around a long-standing PHP bug with is_readable() -- Thanks to Michael S for the patch + - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index 1537d46c85..0ad9c59b48 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -337,7 +337,14 @@ public function process(File $phpcsFile, $stackPtr) echo "\t* open tag is inside condition; using open tag *".PHP_EOL; } - $checkIndent = ($tokens[$lastOpenTag]['column'] - 1); + $first = $phpcsFile->findFirstOnLine([T_WHITESPACE, T_INLINE_HTML], $lastOpenTag, true); + if ($this->debug === true) { + $line = $tokens[$first]['line']; + $type = $tokens[$first]['type']; + echo "\t* first token on line $line is $first ($type) *".PHP_EOL; + } + + $checkIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$condition]) === true) { $checkIndent += $adjustments[$condition]; } diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index 5fc112f521..d0bf0873c3 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1462,6 +1462,12 @@ echo $string?->append('foo') ?->outputUsing(); // phpcs:set Generic.WhiteSpace.ScopeIndent exact false +if (true) { + ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 055be55d83..a5c18bdb62 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1462,6 +1462,12 @@ echo $string?->append('foo') ?->outputUsing(); // phpcs:set Generic.WhiteSpace.ScopeIndent exact false +if (true) { + ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index 4bb836ce78..1c7ff38a2d 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1462,6 +1462,12 @@ echo $string?->append('foo') ?->outputUsing(); // phpcs:set Generic.WhiteSpace.ScopeIndent exact false +if (true) { + ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index c923c9a522..eb159fb7af 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1462,6 +1462,12 @@ echo $string?->append('foo') ?->outputUsing(); // phpcs:set Generic.WhiteSpace.ScopeIndent exact false +if (true) { + ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index 3fd5f9f0f0..aeb41b73c5 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -178,10 +178,10 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1340 => 1, 1342 => 1, 1345 => 1, - 1473 => 1, - 1474 => 1, - 1475 => 1, - 1476 => 1, + 1479 => 1, + 1480 => 1, + 1481 => 1, + 1482 => 1, ]; }//end getErrorList() From 6f2a22d75eed4b5c1ada6926bee05e8d198df172 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 18 Nov 2020 08:10:09 +1100 Subject: [PATCH 168/733] Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure --- package.xml | 1 + .../Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php | 5 +++-- .../Tests/PHP/DisallowComparisonAssignmentUnitTest.inc | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 4336e79855..4d1053fe40 100644 --- a/package.xml +++ b/package.xml @@ -63,6 +63,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Michael S for the patch - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented + - Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php index 7ad2b6f6ac..7c7e2246f5 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php @@ -76,8 +76,9 @@ public function process(File $phpcsFile, $stackPtr) ]; $next = $phpcsFile->findNext($ignore, ($stackPtr + 1), null, true); - if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS - && $tokens[($next - 1)]['code'] === T_STRING + if ($tokens[$next]['code'] === T_CLOSURE + || ($tokens[$next]['code'] === T_OPEN_PARENTHESIS + && $tokens[($next - 1)]['code'] === T_STRING) ) { // Code will look like: $var = myFunction( // and will be ignored. diff --git a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc index 3a629ff855..022aca739e 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowComparisonAssignmentUnitTest.inc @@ -65,3 +65,9 @@ $a = [ $var = $foo->something(!$var); $var = $foo?->something(!$var); + +$callback = function ($value) { + if ($value > 10) { + return false; + } +}; From c5d48fccddbbe0632466f9a024d62ae034ca35e4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 19 Nov 2020 10:16:03 +1100 Subject: [PATCH 169/733] Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion --- package.xml | 1 + .../Sniffs/WhiteSpace/ScopeIndentSniff.php | 28 ++++++++++++++----- .../WhiteSpace/ScopeIndentUnitTest.1.inc | 8 ++++++ .../ScopeIndentUnitTest.1.inc.fixed | 8 ++++++ .../WhiteSpace/ScopeIndentUnitTest.2.inc | 8 ++++++ .../ScopeIndentUnitTest.2.inc.fixed | 8 ++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 +++--- 7 files changed, 58 insertions(+), 11 deletions(-) diff --git a/package.xml b/package.xml index 4d1053fe40..d98e3ab320 100644 --- a/package.xml +++ b/package.xml @@ -64,6 +64,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented - Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure + - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index 0ad9c59b48..7cb7b39e85 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -856,15 +856,29 @@ public function process(File $phpcsFile, $stackPtr) && $tokens[$next]['code'] !== T_VARIABLE && $tokens[$next]['code'] !== T_FN) ) { - if ($this->debug === true) { - $line = $tokens[$checkToken]['line']; - $type = $tokens[$checkToken]['type']; - echo "\t* method prefix ($type) found on line $line; indent set to exact *".PHP_EOL; + $isMethodPrefix = true; + if (isset($tokens[$checkToken]['nested_parenthesis']) === true) { + $parenthesis = array_keys($tokens[$checkToken]['nested_parenthesis']); + $deepestOpen = array_pop($parenthesis); + if (isset($tokens[$deepestOpen]['parenthesis_owner']) === true + && $tokens[$tokens[$deepestOpen]['parenthesis_owner']]['code'] === T_FUNCTION + ) { + // This is constructor property promotion and not a method prefix. + $isMethodPrefix = false; + } } - $exact = true; - } - } + if ($isMethodPrefix === true) { + if ($this->debug === true) { + $line = $tokens[$checkToken]['line']; + $type = $tokens[$checkToken]['type']; + echo "\t* method prefix ($type) found on line $line; indent set to exact *".PHP_EOL; + } + + $exact = true; + } + }//end if + }//end if // JS property indentation has to be exact or else if will break // things like function and object indentation. diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index d0bf0873c3..291d00f683 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1468,6 +1468,14 @@ if (true) { )?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index a5c18bdb62..36dcadb790 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1468,6 +1468,14 @@ if (true) { )?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index 1c7ff38a2d..acffeb1e3a 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1468,6 +1468,14 @@ if (true) { )?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index eb159fb7af..390b2aa7e6 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1468,6 +1468,14 @@ if (true) { )?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index aeb41b73c5..66e82c91e3 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -178,10 +178,10 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1340 => 1, 1342 => 1, 1345 => 1, - 1479 => 1, - 1480 => 1, - 1481 => 1, - 1482 => 1, + 1487 => 1, + 1488 => 1, + 1489 => 1, + 1490 => 1, ]; }//end getErrorList() From bb906ff8c4a1218abb030eab19e8b24fe9cd6c66 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 20 Nov 2020 16:25:26 +1100 Subject: [PATCH 170/733] Fixed bug #2992 : Enabling caching using a ruleset produces invalid cache files when using --sniffs and --exclude CLI args --- package.xml | 1 + src/Ruleset.php | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package.xml b/package.xml index d98e3ab320..772eb40e9f 100644 --- a/package.xml +++ b/package.xml @@ -62,6 +62,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- This works around a long-standing PHP bug with is_readable() -- Thanks to Michael S for the patch - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional + - Fixed bug #2992 : Enabling caching using a ruleset produces invalid cache files when using --sniffs and --exclude CLI args - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented - Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion diff --git a/src/Ruleset.php b/src/Ruleset.php index 3220a18f22..085d18b4de 100644 --- a/src/Ruleset.php +++ b/src/Ruleset.php @@ -126,15 +126,9 @@ class Ruleset */ public function __construct(Config $config) { - // Ignore sniff restrictions if caching is on. - $restrictions = []; - $exclusions = []; - if ($config->cache === false) { - $restrictions = $config->sniffs; - $exclusions = $config->exclude; - } - $this->config = $config; + $restrictions = $config->sniffs; + $exclusions = $config->exclude; $sniffs = []; $standardPaths = []; @@ -200,6 +194,12 @@ public function __construct(Config $config) $sniffs = array_merge($sniffs, $this->processRuleset($standard)); }//end foreach + // Ignore sniff restrictions if caching is on. + if ($config->cache === true) { + $restrictions = []; + $exclusions = []; + } + $sniffRestrictions = []; foreach ($restrictions as $sniffCode) { $parts = explode('.', strtolower($sniffCode)); From 3cd1f674bbaa2767083396a23730a8de5ea3dbac Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 23 Nov 2020 05:55:32 +0100 Subject: [PATCH 171/733] PHP 8.0 | Tokenizer/PHP: support PHP8 dereferencing of text strings with interpolated variables As of PHP 8, interpolated text strings can be dereferenced, however, the square brackets are incorrectly tokenized as _short array_ brackets instead of as "normal" square brackets. Fixed by adding the `T_DOUBLE_QUOTED_STRING` token to the allowed tokens for leaving the brackets alone in the PHP Tokenizer class. Includes unit test. Ref: https://wiki.php.net/rfc/variable_syntax_tweaks#interpolated_and_non-interpolated_strings --- src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/ShortArrayTest.inc | 3 +++ tests/Core/Tokenizer/ShortArrayTest.php | 1 + 3 files changed, 5 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index da30b44b9f..8459289bae 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2147,6 +2147,7 @@ protected function processAdditional() T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR, T_STRING => T_STRING, T_CONSTANT_ENCAPSED_STRING => T_CONSTANT_ENCAPSED_STRING, + T_DOUBLE_QUOTED_STRING => T_DOUBLE_QUOTED_STRING, ]; $allowed += Util\Tokens::$magicConstants; diff --git a/tests/Core/Tokenizer/ShortArrayTest.inc b/tests/Core/Tokenizer/ShortArrayTest.inc index b052ee5681..54065f213d 100644 --- a/tests/Core/Tokenizer/ShortArrayTest.inc +++ b/tests/Core/Tokenizer/ShortArrayTest.inc @@ -65,6 +65,9 @@ echo (clone $iterable)[20]; /* testNullsafeMethodCallDereferencing */ $var = $obj?->function_call()[$x]; +/* testInterpolatedStringDereferencing */ +$var = "PHP{$rocks}"[1]; + /* * Short array brackets. */ diff --git a/tests/Core/Tokenizer/ShortArrayTest.php b/tests/Core/Tokenizer/ShortArrayTest.php index 45f6c4cf88..0484d4fcb0 100644 --- a/tests/Core/Tokenizer/ShortArrayTest.php +++ b/tests/Core/Tokenizer/ShortArrayTest.php @@ -73,6 +73,7 @@ public function dataSquareBrackets() ['/* testClassMemberDereferencingOnInstantiation2 */'], ['/* testClassMemberDereferencingOnClone */'], ['/* testNullsafeMethodCallDereferencing */'], + ['/* testInterpolatedStringDereferencing */'], ['/* testLiveCoding */'], ]; From bbada4bc3b29b6dbf65e9b325a73d45dfdc2725a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 24 Nov 2020 09:38:47 +1100 Subject: [PATCH 172/733] Changelog for #3172 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 772eb40e9f..8504e4697f 100644 --- a/package.xml +++ b/package.xml @@ -30,6 +30,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- A new T_TYPE_UNION token is available to represent the pipe character -- File::getMethodParameters(), getMethodProperties(), and getMemberProperties() will now return union types -- Thanks to Juliette Reinders Folmer for the patch + - Added support for PHP 8.0 dereferencing of text strings with interpolated variables + -- Thanks to Juliette Reinders Folmer for the patch - File::getMethodParameters() now supports PHP 8.0 constructor property promotion -- Returned method params now include a "property_visibility" and "visibility_token" index if property promotion is detected -- Thanks to Juliette Reinders Folmer for the patch From cda358face567f4cd8356c8d5411e38f30e2d3e9 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 24 Nov 2020 10:43:09 +1100 Subject: [PATCH 173/733] Fixed bug #3170 : Squiz.WhiteSpace.OperatorSpacing false positive when using negation with string concat --- package.xml | 2 ++ .../Sniffs/WhiteSpace/OperatorSpacingSniff.php | 13 +++++++------ .../Tests/WhiteSpace/OperatorSpacingUnitTest.inc | 5 +++++ .../WhiteSpace/OperatorSpacingUnitTest.inc.fixed | 5 +++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/package.xml b/package.xml index 8504e4697f..ec5302a08a 100644 --- a/package.xml +++ b/package.xml @@ -68,6 +68,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented - Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion + - Fixed bug #3170 : Squiz.WhiteSpace.OperatorSpacing false positive when using negation with string concat + -- This also fixes the same issue in the PSR12.Operators.OperatorSpacing sniff diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php index ee44545aea..2627d10d98 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php @@ -87,15 +87,16 @@ public function register() // Trying to use a negative value; eg. myFunction($var, -2). $this->nonOperandTokens += [ - T_COMMA => T_COMMA, - T_OPEN_PARENTHESIS => T_OPEN_PARENTHESIS, - T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET, - T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY, + T_CASE => T_CASE, T_COLON => T_COLON, - T_INLINE_THEN => T_INLINE_THEN, + T_COMMA => T_COMMA, T_INLINE_ELSE => T_INLINE_ELSE, - T_CASE => T_CASE, + T_INLINE_THEN => T_INLINE_THEN, T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_OPEN_PARENTHESIS => T_OPEN_PARENTHESIS, + T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY, + T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET, + T_STRING_CONCAT => T_STRING_CONCAT, ]; // Casting a negative value; eg. (array) -$a. diff --git a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc index 82d4af15be..f89cf08d5c 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc @@ -472,5 +472,10 @@ $fn = fn ($boo =+1) => $boo; $fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); +$a = 'a '.-MY_CONSTANT; +$a = 'a '.-$b; +$a = 'a '.- MY_CONSTANT; +$a = 'a '.- $b; + /* 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 ee4060bc11..138616e752 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.inc.fixed @@ -466,5 +466,10 @@ $fn = fn ($boo =+1) => $boo; $fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); +$a = 'a '.-MY_CONSTANT; +$a = 'a '.-$b; +$a = 'a '.- MY_CONSTANT; +$a = 'a '.- $b; + /* Intentional parse error. This has to be the last test in the file. */ $a = 10 + From 2351cc90e43531ead6f40fcabdc51c435401d2f5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 28 Nov 2020 00:39:46 +0100 Subject: [PATCH 174/733] Tokenizer/PHP: bugfix goto tokenization logic The logic could get confused when `goto` would be used in a mixed PHP/HTML file. Includes adding a limited set of unit tests for the `T_GOTO_LABEL` tokenization. --- package.xml | 6 + src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/GotoLabelTest.inc | 53 ++++++++ tests/Core/Tokenizer/GotoLabelTest.php | 171 +++++++++++++++++++++++++ 4 files changed, 231 insertions(+) create mode 100644 tests/Core/Tokenizer/GotoLabelTest.inc create mode 100644 tests/Core/Tokenizer/GotoLabelTest.php diff --git a/package.xml b/package.xml index ec5302a08a..71115c0306 100644 --- a/package.xml +++ b/package.xml @@ -157,6 +157,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2045,6 +2047,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2117,6 +2121,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 8459289bae..81db29a59e 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1572,6 +1572,7 @@ function return types. We want to keep the parenthesis map clean, $stopTokens = [ T_CASE => true, T_SEMICOLON => true, + T_OPEN_TAG => true, T_OPEN_CURLY_BRACKET => true, T_INLINE_THEN => true, ]; diff --git a/tests/Core/Tokenizer/GotoLabelTest.inc b/tests/Core/Tokenizer/GotoLabelTest.inc new file mode 100644 index 0000000000..f6920f54fa --- /dev/null +++ b/tests/Core/Tokenizer/GotoLabelTest.inc @@ -0,0 +1,53 @@ + +
+ +property: + // Do something. + break; +} + +switch (true) { + /* testNotGotoDeclarationGlobalConstantInTernary */ + case $x === ($cond) ? CONST_A : CONST_B: + // Do something. + break; +} diff --git a/tests/Core/Tokenizer/GotoLabelTest.php b/tests/Core/Tokenizer/GotoLabelTest.php new file mode 100644 index 0000000000..fa6f8cfb47 --- /dev/null +++ b/tests/Core/Tokenizer/GotoLabelTest.php @@ -0,0 +1,171 @@ + + * @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 GotoLabelTest extends AbstractMethodUnitTest +{ + + + /** + * Verify that the label in a goto statement is tokenized as T_STRING. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to expect. + * + * @dataProvider dataGotoStatement + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testGotoStatement($testMarker, $testContent) + { + $tokens = self::$phpcsFile->getTokens(); + + $label = $this->getTargetToken($testMarker, T_STRING); + + $this->assertInternalType('int', $label); + $this->assertSame($testContent, $tokens[$label]['content']); + + }//end testGotoStatement() + + + /** + * Data provider. + * + * @see testGotoStatement() + * + * @return array + */ + public function dataGotoStatement() + { + return [ + [ + '/* testGotoStatement */', + 'marker', + ], + [ + '/* testGotoStatementInLoop */', + 'end', + ], + ]; + + }//end dataGotoStatement() + + + /** + * Verify that the label in a goto declaration is tokenized as T_GOTO_LABEL. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to expect. + * + * @dataProvider dataGotoDeclaration + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testGotoDeclaration($testMarker, $testContent) + { + $tokens = self::$phpcsFile->getTokens(); + + $label = $this->getTargetToken($testMarker, T_GOTO_LABEL); + + $this->assertInternalType('int', $label); + $this->assertSame($testContent, $tokens[$label]['content']); + + }//end testGotoDeclaration() + + + /** + * Data provider. + * + * @see testGotoDeclaration() + * + * @return array + */ + public function dataGotoDeclaration() + { + return [ + [ + '/* testGotoDeclaration */', + 'marker:', + ], + [ + '/* testGotoDeclarationOutsideLoop */', + 'end:', + ], + ]; + + }//end dataGotoDeclaration() + + + /** + * Verify that the constant used in a switch - case statement is not confused with a goto label. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to expect. + * + * @dataProvider dataNotAGotoDeclaration + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testNotAGotoDeclaration($testMarker, $testContent) + { + $tokens = self::$phpcsFile->getTokens(); + $target = $this->getTargetToken($testMarker, [T_GOTO_LABEL, T_STRING], $testContent); + + $this->assertSame(T_STRING, $tokens[$target]['code']); + $this->assertSame('T_STRING', $tokens[$target]['type']); + + }//end testNotAGotoDeclaration() + + + /** + * Data provider. + * + * @see testNotAGotoDeclaration() + * + * @return array + */ + public function dataNotAGotoDeclaration() + { + return [ + [ + '/* testNotGotoDeclarationGlobalConstant */', + 'CONSTANT', + ], + [ + '/* testNotGotoDeclarationNamespacedConstant */', + 'CONSTANT', + ], + [ + '/* testNotGotoDeclarationClassConstant */', + 'CONSTANT', + ], + [ + '/* testNotGotoDeclarationClassProperty */', + 'property', + ], + [ + '/* testNotGotoDeclarationGlobalConstantInTernary */', + 'CONST_A', + ], + [ + '/* testNotGotoDeclarationGlobalConstantInTernary */', + 'CONST_B', + ], + ]; + + }//end dataNotAGotoDeclaration() + + +}//end class From 845335af03e4bb38256eeafb92ad7140e8117376 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 28 Nov 2020 17:04:08 +0100 Subject: [PATCH 175/733] PHP 8.0 | Add support for named function call arguments PHP 8.0 introduces named function call parameters: ```php array_fill(start_index: 0, num: 100, value: 50); // Using reserved keywords as names is allowed. array_foobar(array: $array, switch: $switch, class: $class); ``` Ref: https://wiki.php.net/rfc/named_params This PR adds support to PHPCS for named function call arguments by adding a special custom token `T_PARAM_NAME` and tokenizing the _labels_ in function calls using named arguments to that new token, as per the proposal in 3159. I also ensured that the colon _after_ a parameter label is always tokenized as `T_COLON`. Includes some minor efficiency fixes to the code which deals with the colon vs inline else determination as there is no need to run the "is this a return type" or the "is this a `case` statement" checks if it has already been established that the colon is a colon and not an inline else. Includes a ridiculous amount of unit tests to safeguard the correct tokenization of both the parameter label as well as the colon after it (and potential inline else colons in the same statement). Please also see my comment about this here: https://github.com/squizlabs/PHP_CodeSniffer/issues/3159#issuecomment-735247066 **Note**: The only code samples I could come up with which would result in "incorrect" tokenization to `T_PARAM_NAME` are all either parse errors or compile errors. I've elected to let those tokenize as `T_PARAM_NAME` anyway as: 1. When there is a parse error/compile error, there will be more tokenizer issues anyway, so working around those cases seems redundant. 2. The code will at least tokenize consistently (the same) across PHP versions. (which wasn't the case for parse errors/compile errors with numeric literals or arrow functions, which is why they needed additional safeguards previously). Fixes 3159 --- package.xml | 6 + src/Tokenizers/PHP.php | 168 +++- src/Util/Tokens.php | 1 + .../NamedFunctionCallArgumentsTest.inc | 398 ++++++++ .../NamedFunctionCallArgumentsTest.php | 882 ++++++++++++++++++ 5 files changed, 1410 insertions(+), 45 deletions(-) create mode 100644 tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc create mode 100644 tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php diff --git a/package.xml b/package.xml index ec5302a08a..90f97f4232 100644 --- a/package.xml +++ b/package.xml @@ -157,6 +157,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2045,6 +2047,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2117,6 +2121,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 8459289bae..b15a5e7224 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -893,6 +893,62 @@ protected function tokenize($string) continue; }//end if + /* + 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. + */ + + if ($tokenIsArray === true + && preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1 + ) { + // 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]) === false + && $tokens[$i] === ':' + ) { + // Get the previous non-empty token. + for ($j = ($stackPtr - 1); $j > 0; $j--) { + if (is_array($tokens[$j]) === false + || isset(Util\Tokens::$emptyTokens[$tokens[$j][0]]) === false + ) { + break; + } + } + + if (is_array($tokens[$j]) === false + && ($tokens[$j] === '(' + || $tokens[$j] === ',') + ) { + $newToken = []; + $newToken['code'] = T_PARAM_NAME; + $newToken['type'] = 'T_PARAM_NAME'; + $newToken['content'] = $token[1]; + $finalTokens[$newStackPtr] = $newToken; + + $newStackPtr++; + + // Modify the original token stack so that future checks, like + // determining T_COLON vs T_INLINE_ELSE can handle this correctly. + $tokens[$stackPtr][0] = T_PARAM_NAME; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $type = Util\Tokens::tokenName($token[0]); + echo "\t\t* token $stackPtr changed from $type to T_PARAM_NAME".PHP_EOL; + } + + continue; + } + }//end if + }//end if + /* Before PHP 7.0, the "yield from" was tokenized as T_YIELD, T_WHITESPACE and T_STRING. So look for @@ -1700,76 +1756,98 @@ function return types. We want to keep the parenthesis map clean, // Convert colons that are actually the ELSE component of an // inline IF statement. if (empty($insideInlineIf) === false && $newToken['code'] === T_COLON) { - // Make sure this isn't a return type separator. $isInlineIf = true; + + // Make sure this isn't a named parameter label. + // Get the previous non-empty token. for ($i = ($stackPtr - 1); $i > 0; $i--) { if (is_array($tokens[$i]) === false - || ($tokens[$i][0] !== T_DOC_COMMENT - && $tokens[$i][0] !== T_COMMENT - && $tokens[$i][0] !== T_WHITESPACE) + || isset(Util\Tokens::$emptyTokens[$tokens[$i][0]]) === false ) { break; } } - if ($tokens[$i] === ')') { - $parenCount = 1; - for ($i--; $i > 0; $i--) { - if ($tokens[$i] === '(') { - $parenCount--; - if ($parenCount === 0) { - break; - } - } else if ($tokens[$i] === ')') { - $parenCount++; - } + if ($tokens[$i][0] === T_PARAM_NAME) { + $isInlineIf = false; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token is parameter label, not T_INLINE_ELSE".PHP_EOL; } + } - // We've found the open parenthesis, so if the previous - // non-empty token is FUNCTION or USE, this is a return type. - // Note that we need to skip T_STRING tokens here as these - // can be function names. - for ($i--; $i > 0; $i--) { + if ($isInlineIf === true) { + // Make sure this isn't a return type separator. + for ($i = ($stackPtr - 1); $i > 0; $i--) { if (is_array($tokens[$i]) === false || ($tokens[$i][0] !== T_DOC_COMMENT && $tokens[$i][0] !== T_COMMENT - && $tokens[$i][0] !== T_WHITESPACE - && $tokens[$i][0] !== T_STRING) + && $tokens[$i][0] !== T_WHITESPACE) ) { break; } } - if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) { - $isInlineIf = false; - if (PHP_CODESNIFFER_VERBOSITY > 1) { - echo "\t\t* token is return type, not T_INLINE_ELSE".PHP_EOL; + if ($tokens[$i] === ')') { + $parenCount = 1; + for ($i--; $i > 0; $i--) { + if ($tokens[$i] === '(') { + $parenCount--; + if ($parenCount === 0) { + break; + } + } else if ($tokens[$i] === ')') { + $parenCount++; + } } - } + + // We've found the open parenthesis, so if the previous + // non-empty token is FUNCTION or USE, this is a return type. + // Note that we need to skip T_STRING tokens here as these + // can be function names. + for ($i--; $i > 0; $i--) { + if (is_array($tokens[$i]) === false + || ($tokens[$i][0] !== T_DOC_COMMENT + && $tokens[$i][0] !== T_COMMENT + && $tokens[$i][0] !== T_WHITESPACE + && $tokens[$i][0] !== T_STRING) + ) { + break; + } + } + + if ($tokens[$i][0] === T_FUNCTION || $tokens[$i][0] === T_FN || $tokens[$i][0] === T_USE) { + $isInlineIf = false; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token is return type, not T_INLINE_ELSE".PHP_EOL; + } + } + }//end if }//end if // Check to see if this is a CASE or DEFAULT opener. - $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)]; - for ($i = $stackPtr; $i > $inlineIfToken; $i--) { - if (is_array($tokens[$i]) === true - && ($tokens[$i][0] === T_CASE - || $tokens[$i][0] === T_DEFAULT) - ) { - $isInlineIf = false; - if (PHP_CODESNIFFER_VERBOSITY > 1) { - echo "\t\t* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE".PHP_EOL; - } + if ($isInlineIf === true) { + $inlineIfToken = $insideInlineIf[(count($insideInlineIf) - 1)]; + for ($i = $stackPtr; $i > $inlineIfToken; $i--) { + if (is_array($tokens[$i]) === true + && ($tokens[$i][0] === T_CASE + || $tokens[$i][0] === T_DEFAULT) + ) { + $isInlineIf = false; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token is T_CASE or T_DEFAULT opener, not T_INLINE_ELSE".PHP_EOL; + } - break; - } + break; + } - if (is_array($tokens[$i]) === false - && ($tokens[$i] === ';' - || $tokens[$i] === '{') - ) { - break; + if (is_array($tokens[$i]) === false + && ($tokens[$i] === ';' + || $tokens[$i] === '{') + ) { + break; + } } - } + }//end if if ($isInlineIf === true) { array_pop($insideInlineIf); diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index aee035d352..ac05362676 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -76,6 +76,7 @@ define('T_ZSR_EQUAL', 'PHPCS_T_ZSR_EQUAL'); define('T_FN_ARROW', 'T_FN_ARROW'); define('T_TYPE_UNION', 'T_TYPE_UNION'); +define('T_PARAM_NAME', 'T_PARAM_NAME'); // Some PHP 5.5 tokens, replicated for lower versions. if (defined('T_FINALLY') === false) { diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc new file mode 100644 index 0000000000..d05d27d951 --- /dev/null +++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc @@ -0,0 +1,398 @@ +getPos(skip: false), + count: count(array_or_countable: $array), + value: 50 +); + +array_fill( + start_index: /* testNestedFunctionCallInner1 */ $obj->getPos(skip: false), + count: /* testNestedFunctionCallInner2 */ count(array_or_countable: $array), + value: 50 +); + +/* testNamespaceOperatorFunction */ +namespace\function_name(label:$string, more: false); + +/* testNamespaceRelativeFunction */ +Partially\Qualified\function_name(label:$string, more: false); + +/* testNamespacedFQNFunction */ +\Fully\Qualified\function_name(label: $string, more:false); + +/* testVariableFunction */ +$fn(label: $string, more:false); + +/* testVariableVariableFunction */ +${$fn}(label: $string, more:false); + +/* testMethodCall */ +$obj->methodName(label: $foo, more: $bar); + +/* testVariableMethodCall */ +$obj->{$var}(label: $foo, more: $bar); + +/* testClassInstantiation */ +$obj = new MyClass(label: $string, more:false); + +/* testClassInstantiationSelf */ +$obj = new self(label: $string, more:true); + +/* testClassInstantiationStatic */ +$obj = new static(label: $string, more:false); + +/* testAnonClass */ +$anon = new class(label: $string, more: false) { + public function __construct($label, $more) {} +}; + +function myfoo( $💩💩💩, $Пасха, $_valid) {} +/* testNonAsciiNames */ +foo(💩💩💩: [], Пасха: 'text', _valid: 123); + +/* testMixedPositionalAndNamedArgsWithTernary */ +foo( $cond ? true : false, name: $value2 ); + +/* testNamedArgWithTernary */ +foo( label: $cond ? true : false, more: $cond ? CONSTANT_A : CONSTANT_B ); + +/* testTernaryWithFunctionCallsInThenElse */ +echo $cond ? foo( label: $something ) : foo( more: $something_else ); + +/* testTernaryWithConstantsInThenElse */ +echo $cond ? CONSTANT_NAME : OTHER_CONSTANT; + +switch ($s) { + /* testSwitchCaseWithConstant */ + case MY_CONSTANT: + // Do something. + break; + + /* testSwitchCaseWithClassProperty */ + case $obj->property: + // Do something. + break; + + /* testSwitchDefault */ + default: + // Do something. + break; +} + +/* testTernaryWithClosuresAndReturnTypes */ +$closure = $cond ? function() : bool {return true;} : function() : int {return 123;}; + +/* testTernaryWithArrowFunctionsAndReturnTypes */ +$fn = $cond ? fn() : bool => true : fn() : int => 123; + + +/* testCompileErrorNamedBeforePositional */ +// Not the concern of PHPCS. Should still be handled. +test(param: $bar, $foo); + +/* testDuplicateName1 */ +// Error Exception, but not the concern of PHPCS. Should still be handled. +test(param: 1, /* testDuplicateName2 */ param: 2); + +/* testIncorrectOrderWithVariadic */ +// Error Exception, but not the concern of PHPCS. Should still be handled. +array_fill(start_index: 0, ...[100, 50]); + +/* testCompileErrorIncorrectOrderWithVariadic */ +// Not the concern of PHPCS. Should still be handled. +test(...$values, param: $value); // Compile-time error + +/* testParseErrorNoValue */ +// Not the concern of PHPCS. Should still be handled. +test(param1:, param2:); + +/* testParseErrorDynamicName */ +// Parse error. Ignore. +function_name($variableStoringParamName: $value); + +/* testParseErrorExit */ +// Exit is a language construct, not a function. Named params not supported, handle it anyway. +exit(status: $value); + +/* testParseErrorEmpty */ +// Empty is a language construct, not a function. Named params not supported, handle it anyway. +empty(variable: $value); + +/* testParseErrorEval */ +// Eval is a language construct, not a function. Named params not supported, handle it anyway. +eval(code: $value); + +/* testParseErrorArbitraryParentheses */ +// Parse error. Not named param, handle it anyway. +$calc = (something: $value / $other); + + +/* testReservedKeywordAbstract1 */ +foobar(abstract: $value, /* testReservedKeywordAbstract2 */ abstract: $value); + +/* testReservedKeywordAnd1 */ +foobar(and: $value, /* testReservedKeywordAnd2 */ and: $value); + +/* testReservedKeywordArray1 */ +foobar(array: $value, /* testReservedKeywordArray2 */ array: $value); + +/* testReservedKeywordAs1 */ +foobar(as: $value, /* testReservedKeywordAs2 */ as: $value); + +/* testReservedKeywordBreak1 */ +foobar(break: $value, /* testReservedKeywordBreak2 */ break: $value); + +/* testReservedKeywordCallable1 */ +foobar(callable: $value, /* testReservedKeywordCallable2 */ callable: $value); + +/* testReservedKeywordCase1 */ +foobar(case: $value, /* testReservedKeywordCase2 */ case: $value); + +/* testReservedKeywordCatch1 */ +foobar(catch: $value, /* testReservedKeywordCatch2 */ catch: $value); + +/* testReservedKeywordClass1 */ +foobar(class: $value, /* testReservedKeywordClass2 */ class: $value); + +/* testReservedKeywordClone1 */ +foobar(clone: $value, /* testReservedKeywordClone2 */ clone: $value); + +/* testReservedKeywordConst1 */ +foobar(const: $value, /* testReservedKeywordConst2 */ const: $value); + +/* testReservedKeywordContinue1 */ +foobar(continue: $value, /* testReservedKeywordContinue2 */ continue: $value); + +/* testReservedKeywordDeclare1 */ +foobar(declare: $value, /* testReservedKeywordDeclare2 */ declare: $value); + +/* testReservedKeywordDefault1 */ +foobar(default: $value, /* testReservedKeywordDefault2 */ default: $value); + +/* testReservedKeywordDie1 */ +foobar(die: $value, /* testReservedKeywordDie2 */ die: $value); + +/* testReservedKeywordDo1 */ +foobar(do: $value, /* testReservedKeywordDo2 */ do: $value); + +/* testReservedKeywordEcho1 */ +foobar(echo: $value, /* testReservedKeywordEcho2 */ echo: $value); + +/* testReservedKeywordElse1 */ +foobar(else: $value, /* testReservedKeywordElse2 */ else: $value); + +/* testReservedKeywordElseif1 */ +foobar(elseif: $value, /* testReservedKeywordElseif2 */ elseif: $value); + +/* testReservedKeywordEmpty1 */ +foobar(empty: $value, /* testReservedKeywordEmpty2 */ empty: $value); + +/* testReservedKeywordEnddeclare1 */ +foobar(enddeclare: $value, /* testReservedKeywordEnddeclare2 */ enddeclare: $value); + +/* testReservedKeywordEndfor1 */ +foobar(endfor: $value, /* testReservedKeywordEndfor2 */ endfor: $value); + +/* testReservedKeywordEndforeach1 */ +foobar(endforeach: $value, /* testReservedKeywordEndforeach2 */ endforeach: $value); + +/* testReservedKeywordEndif1 */ +foobar(endif: $value, /* testReservedKeywordEndif2 */ endif: $value); + +/* testReservedKeywordEndswitch1 */ +foobar(endswitch: $value, /* testReservedKeywordEndswitch2 */ endswitch: $value); + +/* testReservedKeywordEndwhile1 */ +foobar(endwhile: $value, /* testReservedKeywordEndwhile2 */ endwhile: $value); + +/* testReservedKeywordEval1 */ +foobar(eval: $value, /* testReservedKeywordEval2 */ eval: $value); + +/* testReservedKeywordExit1 */ +foobar(exit: $value, /* testReservedKeywordExit2 */ exit: $value); + +/* testReservedKeywordExtends1 */ +foobar(extends: $value, /* testReservedKeywordExtends2 */ extends: $value); + +/* testReservedKeywordFinal1 */ +foobar(final: $value, /* testReservedKeywordFinal2 */ final: $value); + +/* testReservedKeywordFinally1 */ +foobar(finally: $value, /* testReservedKeywordFinally2 */ finally: $value); + +/* testReservedKeywordFn1 */ +foobar(fn: $value, /* testReservedKeywordFn2 */ fn: $value); + +/* testReservedKeywordFor1 */ +foobar(for: $value, /* testReservedKeywordFor2 */ for: $value); + +/* testReservedKeywordForeach1 */ +foobar(foreach: $value, /* testReservedKeywordForeach2 */ foreach: $value); + +/* testReservedKeywordFunction1 */ +foobar(function: $value, /* testReservedKeywordFunction2 */ function: $value); + +/* testReservedKeywordGlobal1 */ +foobar(global: $value, /* testReservedKeywordGlobal2 */ global: $value); + +/* testReservedKeywordGoto1 */ +foobar(goto: $value, /* testReservedKeywordGoto2 */ goto: $value); + +/* testReservedKeywordIf1 */ +foobar(if: $value, /* testReservedKeywordIf2 */ if: $value); + +/* testReservedKeywordImplements1 */ +foobar(implements: $value, /* testReservedKeywordImplements2 */ implements: $value); + +/* testReservedKeywordInclude1 */ +foobar(include: $value, /* testReservedKeywordInclude2 */ include: $value); + +/* testReservedKeywordInclude_once1 */ +foobar(include_once: $value, /* testReservedKeywordInclude_once2 */ include_once: $value); + +/* testReservedKeywordInstanceof1 */ +foobar(instanceof: $value, /* testReservedKeywordInstanceof2 */ instanceof: $value); + +/* testReservedKeywordInsteadof1 */ +foobar(insteadof: $value, /* testReservedKeywordInsteadof2 */ insteadof: $value); + +/* testReservedKeywordInterface1 */ +foobar(interface: $value, /* testReservedKeywordInterface2 */ interface: $value); + +/* testReservedKeywordIsset1 */ +foobar(isset: $value, /* testReservedKeywordIsset2 */ isset: $value); + +/* testReservedKeywordList1 */ +foobar(list: $value, /* testReservedKeywordList2 */ list: $value); + +/* testReservedKeywordMatch1 */ +foobar(match: $value, /* testReservedKeywordMatch2 */ match: $value); + +/* testReservedKeywordNamespace1 */ +foobar(namespace: $value, /* testReservedKeywordNamespace2 */ namespace: $value); + +/* testReservedKeywordNew1 */ +foobar(new: $value, /* testReservedKeywordNew2 */ new: $value); + +/* testReservedKeywordOr1 */ +foobar(or: $value, /* testReservedKeywordOr2 */ or: $value); + +/* testReservedKeywordPrint1 */ +foobar(print: $value, /* testReservedKeywordPrint2 */ print: $value); + +/* testReservedKeywordPrivate1 */ +foobar(private: $value, /* testReservedKeywordPrivate2 */ private: $value); + +/* testReservedKeywordProtected1 */ +foobar(protected: $value, /* testReservedKeywordProtected2 */ protected: $value); + +/* testReservedKeywordPublic1 */ +foobar(public: $value, /* testReservedKeywordPublic2 */ public: $value); + +/* testReservedKeywordRequire1 */ +foobar(require: $value, /* testReservedKeywordRequire2 */ require: $value); + +/* testReservedKeywordRequire_once1 */ +foobar(require_once: $value, /* testReservedKeywordRequire_once2 */ require_once: $value); + +/* testReservedKeywordReturn1 */ +foobar(return: $value, /* testReservedKeywordReturn2 */ return: $value); + +/* testReservedKeywordStatic1 */ +foobar(static: $value, /* testReservedKeywordStatic2 */ static: $value); + +/* testReservedKeywordSwitch1 */ +foobar(switch: $value, /* testReservedKeywordSwitch2 */ switch: $value); + +/* testReservedKeywordThrow1 */ +foobar(throw: $value, /* testReservedKeywordThrow2 */ throw: $value); + +/* testReservedKeywordTrait1 */ +foobar(trait: $value, /* testReservedKeywordTrait2 */ trait: $value); + +/* testReservedKeywordTry1 */ +foobar(try: $value, /* testReservedKeywordTry2 */ try: $value); + +/* testReservedKeywordUnset1 */ +foobar(unset: $value, /* testReservedKeywordUnset2 */ unset: $value); + +/* testReservedKeywordUse1 */ +foobar(use: $value, /* testReservedKeywordUse2 */ use: $value); + +/* testReservedKeywordVar1 */ +foobar(var: $value, /* testReservedKeywordVar2 */ var: $value); + +/* testReservedKeywordWhile1 */ +foobar(while: $value, /* testReservedKeywordWhile2 */ while: $value); + +/* testReservedKeywordXor1 */ +foobar(xor: $value, /* testReservedKeywordXor2 */ xor: $value); + +/* testReservedKeywordYield1 */ +foobar(yield: $value, /* testReservedKeywordYield2 */ yield: $value); + +/* testReservedKeywordInt1 */ +foobar(int: $value, /* testReservedKeywordInt2 */ int: $value); + +/* testReservedKeywordFloat1 */ +foobar(float: $value, /* testReservedKeywordFloat2 */ float: $value); + +/* testReservedKeywordBool1 */ +foobar(bool: $value, /* testReservedKeywordBool2 */ bool: $value); + +/* testReservedKeywordString1 */ +foobar(string: $value, /* testReservedKeywordString2 */ string: $value); + +/* testReservedKeywordTrue1 */ +foobar(true: $value, /* testReservedKeywordTrue2 */ true: $value); + +/* testReservedKeywordFalse1 */ +foobar(false: $value, /* testReservedKeywordFalse2 */ false: $value); + +/* testReservedKeywordNull1 */ +foobar(null: $value, /* testReservedKeywordNull2 */ null: $value); + +/* testReservedKeywordVoid1 */ +foobar(void: $value, /* testReservedKeywordVoid2 */ void: $value); + +/* testReservedKeywordIterable1 */ +foobar(iterable: $value, /* testReservedKeywordIterable2 */ iterable: $value); + +/* testReservedKeywordObject1 */ +foobar(object: $value, /* testReservedKeywordObject2 */ object: $value); + +/* testReservedKeywordResource1 */ +foobar(resource: $value, /* testReservedKeywordResource2 */ resource: $value); + +/* testReservedKeywordMixed1 */ +foobar(mixed: $value, /* testReservedKeywordMixed2 */ mixed: $value); + +/* testReservedKeywordNumeric1 */ +foobar(numeric: $value, /* testReservedKeywordNumeric2 */ numeric: $value); + +/* testReservedKeywordParent1 */ +foobar(parent: $value, /* testReservedKeywordParent2 */ parent: $value); + +/* testReservedKeywordSelf1 */ +foobar(self: $value, /* testReservedKeywordSelf2 */ self: $value); diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php new file mode 100644 index 0000000000..13be10e5f6 --- /dev/null +++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php @@ -0,0 +1,882 @@ + + * @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 NamedFunctionCallArgumentsTest extends AbstractMethodUnitTest +{ + + + /** + * Verify that parameter labels are tokenized as T_PARAM_NAME and that + * the colon after it is tokenized as a T_COLON. + * + * @param string $testMarker The comment prefacing the target token. + * @param array $parameters The token content for each parameter label to look for. + * + * @dataProvider dataNamedFunctionCallArguments + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testNamedFunctionCallArguments($testMarker, $parameters) + { + $tokens = self::$phpcsFile->getTokens(); + + foreach ($parameters as $content) { + $label = $this->getTargetToken($testMarker, [T_STRING, T_PARAM_NAME], $content); + + $this->assertSame( + T_PARAM_NAME, + $tokens[$label]['code'], + 'Token tokenized as '.$tokens[$label]['code'].', not T_PARAM_NAME (code)' + ); + $this->assertSame( + 'T_PARAM_NAME', + $tokens[$label]['type'], + 'Token tokenized as '.$tokens[$label]['type'].', not T_PARAM_NAME (type)' + ); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + ':', + $tokens[$colon]['content'], + 'Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content'] + ); + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + }//end foreach + + }//end testNamedFunctionCallArguments() + + + /** + * Data provider. + * + * @see testNamedFunctionCallArguments() + * + * @return array + */ + public function dataNamedFunctionCallArguments() + { + return [ + [ + '/* testNamedArgs */', + [ + 'start_index', + 'count', + 'value', + ], + ], + [ + '/* testNamedArgsMultiline */', + [ + 'start_index', + 'count', + 'value', + ], + ], + [ + '/* testNamedArgsWithWhitespaceAndComments */', + [ + 'start_index', + 'count', + 'value', + ], + ], + [ + '/* testMixedPositionalAndNamedArgs */', + ['double_encode'], + ], + [ + '/* testNestedFunctionCallOuter */', + [ + 'start_index', + 'count', + 'value', + ], + ], + [ + '/* testNestedFunctionCallInner1 */', + ['skip'], + ], + [ + '/* testNestedFunctionCallInner2 */', + ['array_or_countable'], + ], + [ + '/* testNamespaceOperatorFunction */', + [ + 'label', + 'more', + ], + ], + [ + '/* testNamespaceRelativeFunction */', + [ + 'label', + 'more', + ], + ], + [ + '/* testNamespacedFQNFunction */', + [ + 'label', + 'more', + ], + ], + [ + '/* testVariableFunction */', + [ + 'label', + 'more', + ], + ], + [ + '/* testVariableVariableFunction */', + [ + 'label', + 'more', + ], + ], + [ + '/* testMethodCall */', + [ + 'label', + 'more', + ], + ], + [ + '/* testVariableMethodCall */', + [ + 'label', + 'more', + ], + ], + [ + '/* testClassInstantiation */', + [ + 'label', + 'more', + ], + ], + [ + '/* testClassInstantiationSelf */', + [ + 'label', + 'more', + ], + ], + [ + '/* testClassInstantiationStatic */', + [ + 'label', + 'more', + ], + ], + [ + '/* testAnonClass */', + [ + 'label', + 'more', + ], + ], + [ + '/* testNonAsciiNames */', + [ + '💩💩💩', + 'Пасха', + '_valid', + ], + ], + + // Coding errors which should still be handled. + [ + '/* testCompileErrorNamedBeforePositional */', + ['param'], + ], + [ + '/* testDuplicateName1 */', + ['param'], + ], + [ + '/* testDuplicateName2 */', + ['param'], + ], + [ + '/* testIncorrectOrderWithVariadic */', + ['start_index'], + ], + [ + '/* testCompileErrorIncorrectOrderWithVariadic */', + ['param'], + ], + [ + '/* testParseErrorNoValue */', + [ + 'param1', + 'param2', + ], + ], + [ + '/* testParseErrorExit */', + ['status'], + ], + [ + '/* testParseErrorEmpty */', + ['variable'], + ], + [ + '/* testParseErrorEval */', + ['code'], + ], + [ + '/* testParseErrorArbitraryParentheses */', + ['something'], + ], + ]; + + }//end dataNamedFunctionCallArguments() + + + /** + * Verify that other T_STRING tokens within a function call are still tokenized as T_STRING. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $content The token content to look for. + * + * @dataProvider dataOtherTstringInFunctionCall + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testOtherTstringInFunctionCall($testMarker, $content) + { + $tokens = self::$phpcsFile->getTokens(); + + $label = $this->getTargetToken($testMarker, [T_STRING, T_PARAM_NAME], $content); + + $this->assertSame( + T_STRING, + $tokens[$label]['code'], + 'Token tokenized as '.$tokens[$label]['code'].', not T_STRING (code)' + ); + $this->assertSame( + 'T_STRING', + $tokens[$label]['type'], + 'Token tokenized as '.$tokens[$label]['type'].', not T_STRING (type)' + ); + + }//end testOtherTstringInFunctionCall() + + + /** + * Data provider. + * + * @see testOtherTstringInFunctionCall() + * + * @return array + */ + public function dataOtherTstringInFunctionCall() + { + return [ + [ + '/* testPositionalArgs */', + 'START_INDEX', + ], + [ + '/* testPositionalArgs */', + 'COUNT', + ], + [ + '/* testPositionalArgs */', + 'VALUE', + ], + [ + '/* testNestedFunctionCallInner2 */', + 'count', + ], + ]; + + }//end dataOtherTstringInFunctionCall() + + + /** + * Verify whether the colons are tokenized correctly when a ternary is used in a mixed + * positional and named arguments function call. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testMixedPositionalAndNamedArgsWithTernary() + { + $tokens = self::$phpcsFile->getTokens(); + + $true = $this->getTargetToken('/* testMixedPositionalAndNamedArgsWithTernary */', T_TRUE); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($true + 1), null, true); + + $this->assertSame( + T_INLINE_ELSE, + $tokens[$colon]['code'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)' + ); + $this->assertSame( + 'T_INLINE_ELSE', + $tokens[$colon]['type'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)' + ); + + $label = $this->getTargetToken('/* testMixedPositionalAndNamedArgsWithTernary */', T_PARAM_NAME, 'name'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + ':', + $tokens[$colon]['content'], + 'Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content'] + ); + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + }//end testMixedPositionalAndNamedArgsWithTernary() + + + /** + * Verify whether the colons are tokenized correctly when a ternary is used + * in a named arguments function call. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testNamedArgWithTernary() + { + $tokens = self::$phpcsFile->getTokens(); + + /* + * First argument. + */ + + $label = $this->getTargetToken('/* testNamedArgWithTernary */', T_PARAM_NAME, 'label'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + ':', + $tokens[$colon]['content'], + 'First arg: Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content'] + ); + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'First arg: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'First arg: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + $true = $this->getTargetToken('/* testNamedArgWithTernary */', T_TRUE); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($true + 1), null, true); + + $this->assertSame( + T_INLINE_ELSE, + $tokens[$colon]['code'], + 'First arg ternary: Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)' + ); + $this->assertSame( + 'T_INLINE_ELSE', + $tokens[$colon]['type'], + 'First arg ternary: Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)' + ); + + /* + * Second argument. + */ + + $label = $this->getTargetToken('/* testNamedArgWithTernary */', T_PARAM_NAME, 'more'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + ':', + $tokens[$colon]['content'], + 'Second arg: Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content'] + ); + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Second arg: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Second arg: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + $true = $this->getTargetToken('/* testNamedArgWithTernary */', T_STRING, 'CONSTANT_A'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($true + 1), null, true); + + $this->assertSame( + T_INLINE_ELSE, + $tokens[$colon]['code'], + 'Second arg ternary: Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)' + ); + $this->assertSame( + 'T_INLINE_ELSE', + $tokens[$colon]['type'], + 'Second arg ternary: Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)' + ); + + }//end testNamedArgWithTernary() + + + /** + * Verify whether the colons are tokenized correctly when named arguments + * function calls are used in a ternary. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testTernaryWithFunctionCallsInThenElse() + { + $tokens = self::$phpcsFile->getTokens(); + + /* + * Then. + */ + + $label = $this->getTargetToken('/* testTernaryWithFunctionCallsInThenElse */', T_PARAM_NAME, 'label'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + ':', + $tokens[$colon]['content'], + 'Function in then: Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content'] + ); + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Function in then: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Function in then: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + $closeParens = $this->getTargetToken('/* testTernaryWithFunctionCallsInThenElse */', T_CLOSE_PARENTHESIS); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($closeParens + 1), null, true); + + $this->assertSame( + T_INLINE_ELSE, + $tokens[$colon]['code'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)' + ); + $this->assertSame( + 'T_INLINE_ELSE', + $tokens[$colon]['type'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)' + ); + + /* + * Else. + */ + + $label = $this->getTargetToken('/* testTernaryWithFunctionCallsInThenElse */', T_PARAM_NAME, 'more'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + ':', + $tokens[$colon]['content'], + 'Function in else: Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content'] + ); + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Function in else: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Function in else: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + }//end testTernaryWithFunctionCallsInThenElse() + + + /** + * Verify whether the colons are tokenized correctly when constants are used in a ternary. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testTernaryWithConstantsInThenElse() + { + $tokens = self::$phpcsFile->getTokens(); + + $constant = $this->getTargetToken('/* testTernaryWithConstantsInThenElse */', T_STRING, 'CONSTANT_NAME'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($constant + 1), null, true); + + $this->assertSame( + T_INLINE_ELSE, + $tokens[$colon]['code'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)' + ); + $this->assertSame( + 'T_INLINE_ELSE', + $tokens[$colon]['type'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)' + ); + + }//end testTernaryWithConstantsInThenElse() + + + /** + * Verify whether the colons are tokenized correctly in a switch statement. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testSwitchStatement() + { + $tokens = self::$phpcsFile->getTokens(); + + $label = $this->getTargetToken('/* testSwitchCaseWithConstant */', T_STRING, 'MY_CONSTANT'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'First case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'First case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + $label = $this->getTargetToken('/* testSwitchCaseWithClassProperty */', T_STRING, 'property'); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Second case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Second case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + $default = $this->getTargetToken('/* testSwitchDefault */', T_DEFAULT); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($default + 1), null, true); + + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Default case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Default case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + }//end testSwitchStatement() + + + /** + * Verify that a variable parameter label (parse error) is still tokenized as T_VARIABLE. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testParseErrorVariableLabel() + { + $tokens = self::$phpcsFile->getTokens(); + + $label = $this->getTargetToken('/* testParseErrorDynamicName */', [T_VARIABLE, T_PARAM_NAME], '$variableStoringParamName'); + + $this->assertSame( + T_VARIABLE, + $tokens[$label]['code'], + 'Token tokenized as '.$tokens[$label]['type'].', not T_VARIABLE (code)' + ); + $this->assertSame( + 'T_VARIABLE', + $tokens[$label]['type'], + 'Token tokenized as '.$tokens[$label]['type'].', not T_VARIABLE (type)' + ); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + ':', + $tokens[$colon]['content'], + 'Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content'] + ); + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + }//end testParseErrorVariableLabel() + + + /** + * Verify that reserved keywords used as a parameter label are tokenized as T_PARAM_NAME + * and that the colon after it is tokenized as a T_COLON. + * + * @param string $testMarker The comment prefacing the target token. + * @param array $tokenTypes The token codes to look for. + * @param string $tokenContent The token content to look for. + * + * @dataProvider dataReservedKeywordsAsName + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testReservedKeywordsAsName($testMarker, $tokenTypes, $tokenContent) + { + $tokens = self::$phpcsFile->getTokens(); + $label = $this->getTargetToken($testMarker, $tokenTypes, $tokenContent); + + $this->assertSame( + T_PARAM_NAME, + $tokens[$label]['code'], + 'Token tokenized as '.$tokens[$label]['code'].', not T_PARAM_NAME (code)' + ); + $this->assertSame( + 'T_PARAM_NAME', + $tokens[$label]['type'], + 'Token tokenized as '.$tokens[$label]['type'].', not T_PARAM_NAME (type)' + ); + + // Get the next non-empty token. + $colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true); + + $this->assertSame( + ':', + $tokens[$colon]['content'], + 'Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content'] + ); + $this->assertSame( + T_COLON, + $tokens[$colon]['code'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)' + ); + $this->assertSame( + 'T_COLON', + $tokens[$colon]['type'], + 'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)' + ); + + }//end testReservedKeywordsAsName() + + + /** + * Data provider. + * + * @see testReservedKeywordsAsName() + * + * @return array + */ + public function dataReservedKeywordsAsName() + { + $reservedKeywords = [ + // '__halt_compiler', NOT TESTABLE + 'abstract', + 'and', + 'array', + 'as', + 'break', + 'callable', + 'case', + 'catch', + 'class', + 'clone', + 'const', + 'continue', + 'declare', + 'default', + 'die', + 'do', + 'echo', + 'else', + 'elseif', + 'empty', + 'enddeclare', + 'endfor', + 'endforeach', + 'endif', + 'endswitch', + 'endwhile', + 'eval', + 'exit', + 'extends', + 'final', + 'finally', + 'fn', + 'for', + 'foreach', + 'function', + 'global', + 'goto', + 'if', + 'implements', + 'include', + 'include_once', + 'instanceof', + 'insteadof', + 'interface', + 'isset', + 'list', + 'match', + 'namespace', + 'new', + 'or', + 'print', + 'private', + 'protected', + 'public', + 'require', + 'require_once', + 'return', + 'static', + 'switch', + 'throw', + 'trait', + 'try', + 'unset', + 'use', + 'var', + 'while', + 'xor', + 'yield', + 'int', + 'float', + 'bool', + 'string', + 'true', + 'false', + 'null', + 'void', + 'iterable', + 'object', + 'resource', + 'mixed', + 'numeric', + + // Not reserved keyword, but do have their own token in PHPCS. + 'parent', + 'self', + ]; + + $data = []; + + foreach ($reservedKeywords as $keyword) { + $tokensTypes = [ + T_PARAM_NAME, + T_STRING, + T_GOTO_LABEL, + ]; + $tokenName = 'T_'.strtoupper($keyword); + + if ($keyword === 'and') { + $tokensTypes[] = T_LOGICAL_AND; + } else if ($keyword === 'die') { + $tokensTypes[] = T_EXIT; + } else if ($keyword === 'or') { + $tokensTypes[] = T_LOGICAL_OR; + } else if ($keyword === 'xor') { + $tokensTypes[] = T_LOGICAL_XOR; + } else if ($keyword === '__halt_compiler') { + $tokensTypes[] = T_HALT_COMPILER; + } else if (defined($tokenName) === true) { + $tokensTypes[] = constant($tokenName); + } + + $data[$keyword.'FirstParam'] = [ + '/* testReservedKeyword'.ucfirst($keyword).'1 */', + $tokensTypes, + $keyword, + ]; + + $data[$keyword.'SecondParam'] = [ + '/* testReservedKeyword'.ucfirst($keyword).'2 */', + $tokensTypes, + $keyword, + ]; + }//end foreach + + return $data; + + }//end dataReservedKeywordsAsName() + + +}//end class From 72eaae338e280f004faa9b60ad4734004aaa2df9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 4 Dec 2020 05:38:17 +0100 Subject: [PATCH 176/733] Travis: add build against PHP 8.0 PHP 8.0 has been branched off two months ago, so `nightly` is now PHP 8.1 and in the mean time PHP 8.0 was released last week. As of today, there is a PHP 8.0 image available on Travis. This PR adds a new build against PHP 8.0 to the matrix and, as PHP 8.0 has been released, that build is not allowed to fail. --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index c6a6773091..f930372de1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,8 @@ jobs: - php: 7.2 - php: 7.3 - php: 7.4 - # Nightly is PHP 8.0 since Feb 2019. + - php: 8.0 + # Nightly is PHP 8.1 since Oct 2020. - php: nightly addons: apt: @@ -97,13 +98,12 @@ jobs: before_install: # Speed up build time by disabling Xdebug when its not needed. - phpenv config-rm xdebug.ini || echo 'No xdebug config.' - # PHPUnit 8.x is not (yet) supported, so prevent issues with Travis images using it. - | - if [[ $TRAVIS_PHP_VERSION != "nightly" ]]; then - travis_retry composer install - elif [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then + if [[ $TRAVIS_PHP_VERSION == "nightly" || $TRAVIS_PHP_VERSION == "8.0" ]]; then // Allow installing "incompatible" PHPUnit version on PHP 8/nightly. travis_retry composer install --ignore-platform-reqs + else + travis_retry composer install fi script: From deb125abf531d5eb67054923c014976bd8315ea9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 9 Dec 2020 08:18:55 +0100 Subject: [PATCH 177/733] PSR2/NamespaceDeclaration: false positive on namespace operator When the `namespace` keyword was used as an operator, the sniff would incorrectly throw an error too. Fixed now. Includes unit test. --- .../PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php | 7 +++++++ .../PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc | 4 ++++ .../Namespaces/NamespaceDeclarationUnitTest.inc.fixed | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php index 9607dccee2..bf37af09bb 100644 --- a/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Tokens; class NamespaceDeclarationSniff implements Sniff { @@ -41,6 +42,12 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($tokens[$nextNonEmpty]['code'] === T_NS_SEPARATOR) { + // Namespace keyword as operator. Not a declaration. + return; + } + $end = $phpcsFile->findEndOfStatement($stackPtr); for ($i = ($end + 1); $i < ($phpcsFile->numTokens - 1); $i++) { if ($tokens[$i]['line'] === $tokens[$end]['line']) { diff --git a/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc index 2500b6448e..703393396f 100644 --- a/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc @@ -20,3 +20,7 @@ namespace Vendor\ Package; namespace Vendor\Package; + +$call = namespace\function_name(); +echo namespace\CONSTANT_NAME; +// Something which is not a blank line. diff --git a/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc.fixed index a98985d117..0b39bfa086 100644 --- a/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/Namespaces/NamespaceDeclarationUnitTest.inc.fixed @@ -22,3 +22,7 @@ namespace Vendor\ Package; namespace Vendor\Package; + +$call = namespace\function_name(); +echo namespace\CONSTANT_NAME; +// Something which is not a blank line. From 9bcb29cce831a8f9b2c8aee0bba1a57445f9dbf1 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 15 Dec 2020 13:38:00 +1100 Subject: [PATCH 178/733] Changelog for #3184 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index ec5302a08a..31440e0959 100644 --- a/package.xml +++ b/package.xml @@ -70,6 +70,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion - Fixed bug #3170 : Squiz.WhiteSpace.OperatorSpacing false positive when using negation with string concat -- This also fixes the same issue in the PSR12.Operators.OperatorSpacing sniff + - Fixed bug #3184 : PSR2.Namespace.NamespaceDeclaration false positive on namespace operator + -- Thanks to Juliette Reinders Folmer for the patch From e1300d30ede78449c4f973de9627ed695ba6ee01 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 15 Dec 2020 20:54:38 +0100 Subject: [PATCH 179/733] Handle nested switch --- .../SwitchDeclarationSniff.php | 39 +++++++++++++++++-- .../SwitchDeclarationUnitTest.inc | 37 ++++++++++++++++++ .../SwitchDeclarationUnitTest.inc.fixed | 37 ++++++++++++++++++ .../SwitchDeclarationUnitTest.php | 2 + 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php index 0db0cd98b8..6b05227c15 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php @@ -251,8 +251,8 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) $lastToken = $phpcsFile->findPrevious(T_WHITESPACE, ($end - 1), $stackPtr, true); if ($lastToken !== false) { if ($tokens[$lastToken]['code'] === T_CLOSE_CURLY_BRACKET) { - // We found a closing curly bracket and want to check if its - // block belongs to an IF, ELSEIF or ELSE clause. If yes, we + // We found a closing curly bracket and want to check if its block + // belongs to a SWITCH or an IF, ELSEIF or ELSE clause. If yes, we // continue searching for a terminating statement within that // block. Note that we have to make sure that every block of // the entire if/else statement has a terminating statement. @@ -267,7 +267,7 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) return false; } - // IF and ELSEIF clauses possess a condition we have to account for. + // SWITCH, IF and ELSEIF clauses possess a condition we have to account for. if ($tokens[$prevToken]['code'] === T_CLOSE_PARENTHESIS) { $prevToken = $tokens[$prevToken]['parenthesis_owner']; } @@ -294,6 +294,39 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) if ($tokens[$prevToken]['code'] === T_ELSE) { $hasElseBlock = true; } + } else if ($tokens[$prevToken]['code'] === T_SWITCH) { + $hasDefaultBlock = false; + $endOfSwitch = $tokens[$prevToken]['scope_closer']; + $nextCase = $prevToken; + + // We look for a terminating statement within every blocks. + while (($nextCase = $this->findNextCase($phpcsFile, ($nextCase + 1), $endOfSwitch)) !== false) { + if ($tokens[$nextCase]['code'] === T_DEFAULT) { + $hasDefaultBlock = true; + } + + $opener = $tokens[$nextCase]['scope_opener']; + + $nextCode = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), $endOfSwitch, true); + if ($tokens[$nextCode]['code'] === T_CASE || $tokens[$nextCode]['code'] === T_DEFAULT) { + // This case statement has no content. We skip it. + continue; + } + + $nextCode = $this->findNextCase($phpcsFile, ($opener + 1), $endOfSwitch); + if ($nextCode === false) { + $nextCode = $endOfSwitch; + } + + $hasTerminator = $this->findNestedTerminator($phpcsFile, ($opener + 1), $nextCode); + if ($hasTerminator === false) { + return false; + } + }//end while + + // If we have not encountered a DEFAULT block by now, we cannot + // be sure that the whole statement terminates in every case. + return $hasDefaultBlock; } else { return false; }//end if diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc index 572e6ada05..3dc1064fc3 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc @@ -281,3 +281,40 @@ case Foo::ARRAY: echo '1'; return self::VALUE; } + +// OK: Every clause terminates +switch ($foo) { + case 1: + switch ($bar) { + case 1: + return; + default: + return; + } + case 2: + return 2; +} + +// KO: Not every clause terminates +switch ($foo) { + case 1: + switch ($bar) { + case 1: + return; + } + case 2: + return 2; +} + +// KO: Not every clause terminates +switch ($foo) { + case 1: + switch ($bar) { + case 1: + return; + default: + $a = 1; + } + case 2: + return 2; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed index e454cd3318..1b38419016 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed @@ -284,3 +284,40 @@ case Foo::ARRAY: echo '1'; return self::VALUE; } + +// OK: Every clause terminates +switch ($foo) { + case 1: + switch ($bar) { + case 1: + return; + default: + return; + } + case 2: + return 2; +} + +// KO: Not every clause terminates +switch ($foo) { + case 1: + switch ($bar) { + case 1: + return; + } + case 2: + return 2; +} + +// KO: Not every clause terminates +switch ($foo) { + case 1: + switch ($bar) { + case 1: + return; + default: + $a = 1; + } + case 2: + return 2; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php index 9c1735563f..97a68704f5 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php @@ -47,6 +47,8 @@ public function getErrorList() 224 => 1, 236 => 1, 260 => 1, + 300 => 1, + 311 => 1, ]; }//end getErrorList() From 63fd430159957a83e5c0e2153d5773000e97bd55 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 19 Dec 2020 17:00:48 +0100 Subject: [PATCH 180/733] CI: switch to GitHub Actions - step 1: phpstan This commit: * Adds a GH Actions workflow for the PHPStan check which was previously run on Travis. * Removes that part of the `.travis.yml` configuration. Notes: Builds will run on all pushes and on pull requests, with the exception of those just modifying files which are irrelevant to this workflow. --- .github/workflows/phpstan.yml | 38 +++++++++++++++++++++++++++++++++++ .travis.yml | 16 --------------- 2 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/phpstan.yml diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000000..293fdb30bb --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,38 @@ +name: PHPStan + +on: + # Run on all pushes and on all pull requests. + # Prevent the build from running when there are only irrelevant changes. + push: + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + +jobs: + phpstan: + name: "PHP: 7.4 | PHPStan" + + runs-on: "ubuntu-latest" + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + + - name: 'Composer: require PHPStan' + run: composer require --no-update --dev phpstan/phpstan + + # Install dependencies and handle caching in one go. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies + uses: "ramsey/composer-install@v1" + + - name: Run PHPStan + run: vendor/bin/phpstan analyse --configuration=phpstan.neon diff --git a/.travis.yml b/.travis.yml index f930372de1..9b54cd1934 100644 --- a/.travis.yml +++ b/.travis.yml @@ -76,23 +76,7 @@ jobs: - diff -B ./src/Standards/Squiz/ruleset.xml <(xmllint --format "./src/Standards/Squiz/ruleset.xml") - diff -B ./src/Standards/Zend/ruleset.xml <(xmllint --format "./src/Standards/Zend/ruleset.xml") - # Build running just and only PHPStan. - - php: 7.4 - name: "PHP: 7.4 | PHPStan" - env: PHPSTAN=1 - addons: - apt: - packages: - - libonig-dev - before_install: - - phpenv config-rm xdebug.ini || echo 'No xdebug config.' - script: - - composer require --dev phpstan/phpstan - - php vendor/bin/phpstan analyse --configuration=phpstan.neon - allow_failures: - - php: 7.4 - env: PHPSTAN=1 - php: nightly before_install: From 7eda8140c9bbd0eb17440dedb525d78373f611b0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 19 Dec 2020 17:16:59 +0100 Subject: [PATCH 181/733] CI: switch to GitHub Actions - step 2: XML and PEAR validation This commit: * Adds a GH Actions workflow for the build which validated the XML file against schema and checked their code style consistency, as well as validate the PEAR package file, as was previously run on Travis. * Removes that part of the `.travis.yml` configuration. Notes: Builds will run on all pushes and on pull requests, with the exception of those just modifying files which are irrelevant to this workflow. --- .github/workflows/validate.yml | 73 ++++++++++++++++++++++++++++++++++ .travis.yml | 30 -------------- 2 files changed, 73 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/validate.yml diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000000..d55a548ef4 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,73 @@ +name: Validate + +on: + # Run on all pushes and on all pull requests. + # Prevent the build from running when there are only irrelevant changes. + push: + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + +jobs: + checkxml: + name: Check XML files + runs-on: ubuntu-latest + + env: + XMLLINT_INDENT: ' ' + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install xmllint + run: 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 + + # Show XML violations inline in the file diff. + # @link https://github.com/marketplace/actions/xmllint-problem-matcher + - uses: korelstar/xmllint-problem-matcher@v1 + + # Validate the XML ruleset files. + # @link http://xmlsoft.org/xmllint.html + - name: Validate rulesets against schema + run: xmllint --noout --schema phpcs.xsd ./src/Standards/*/ruleset.xml + + # Validate the XSD file. + # @link http://xmlsoft.org/xmllint.html + - name: Validate XSD against schema + run: xmllint --noout --schema ./XMLSchema.xsd ./phpcs.xsd + + # Check the code-style consistency of the XML files. + - name: Check XML code style + run: | + diff -B ./phpcs.xml.dist <(xmllint --format "./phpcs.xml.dist") + diff -B ./src/Standards/Generic/ruleset.xml <(xmllint --format "./src/Standards/Generic/ruleset.xml") + diff -B ./src/Standards/MySource/ruleset.xml <(xmllint --format "./src/Standards/MySource/ruleset.xml") + diff -B ./src/Standards/PEAR/ruleset.xml <(xmllint --format "./src/Standards/PEAR/ruleset.xml") + diff -B ./src/Standards/PSR1/ruleset.xml <(xmllint --format "./src/Standards/PSR1/ruleset.xml") + diff -B ./src/Standards/PSR2/ruleset.xml <(xmllint --format "./src/Standards/PSR2/ruleset.xml") + diff -B ./src/Standards/PSR12/ruleset.xml <(xmllint --format "./src/Standards/PSR12/ruleset.xml") + diff -B ./src/Standards/Squiz/ruleset.xml <(xmllint --format "./src/Standards/Squiz/ruleset.xml") + diff -B ./src/Standards/Zend/ruleset.xml <(xmllint --format "./src/Standards/Zend/ruleset.xml") + + pear: + name: "PHP: 7.4 | PEAR package validation" + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + + - name: Validate the PEAR package file contents + run: php scripts/validate-pear-package.php diff --git a/.travis.yml b/.travis.yml index 9b54cd1934..e2c49aec65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,36 +46,6 @@ jobs: - php bin/phpcs --config-set php_path php - vendor/bin/phpunit tests/AllTests.php - # Build running just the PEAR package file and XML file validation and code style check. - - php: 7.4 - name: "PHP: 7.4 | Pear + XML validate" - addons: - apt: - packages: - - libxml2-utils - before_install: - - export XMLLINT_INDENT=" " - - phpenv config-rm xdebug.ini || echo 'No xdebug config.' - install: - - curl -O https://www.w3.org/2012/04/XMLSchema.xsd - script: - # Validate the Pear Package file contents. - - php scripts/validate-pear-package.php - # Validate the xml ruleset files. - # @link http://xmlsoft.org/xmllint.html - - xmllint --noout --schema phpcs.xsd ./src/Standards/*/ruleset.xml - - xmllint --noout --schema ./XMLSchema.xsd ./phpcs.xsd - # Check the code-style consistency of the xml files. - - diff -B ./phpcs.xml.dist <(xmllint --format "./phpcs.xml.dist") - - diff -B ./src/Standards/Generic/ruleset.xml <(xmllint --format "./src/Standards/Generic/ruleset.xml") - - diff -B ./src/Standards/MySource/ruleset.xml <(xmllint --format "./src/Standards/MySource/ruleset.xml") - - diff -B ./src/Standards/PEAR/ruleset.xml <(xmllint --format "./src/Standards/PEAR/ruleset.xml") - - diff -B ./src/Standards/PSR1/ruleset.xml <(xmllint --format "./src/Standards/PSR1/ruleset.xml") - - diff -B ./src/Standards/PSR2/ruleset.xml <(xmllint --format "./src/Standards/PSR2/ruleset.xml") - - diff -B ./src/Standards/PSR12/ruleset.xml <(xmllint --format "./src/Standards/PSR12/ruleset.xml") - - diff -B ./src/Standards/Squiz/ruleset.xml <(xmllint --format "./src/Standards/Squiz/ruleset.xml") - - diff -B ./src/Standards/Zend/ruleset.xml <(xmllint --format "./src/Standards/Zend/ruleset.xml") - allow_failures: - php: nightly From 079244147199825ba2d06e8310e310d728550850 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 19 Dec 2020 17:49:07 +0100 Subject: [PATCH 182/733] CI: switch to GitHub Actions - step 3: move the PEAR package validation The PEAR native package validation script used to be run on every PHP build, but the results should be no different on any particular PHP version. To that end, move the running of the script from the "test" cycles, to the one-time only run PEAR specific job. --- .github/workflows/validate.yml | 3 +++ .travis.yml | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index d55a548ef4..3eb24fb7c8 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -71,3 +71,6 @@ jobs: - name: Validate the PEAR package file contents run: php scripts/validate-pear-package.php + + - name: Validate the PEAR package + run: pear package-validate package.xml diff --git a/.travis.yml b/.travis.yml index e2c49aec65..d8e8be292d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,7 +64,6 @@ script: - php bin/phpcs --config-set php_path php - vendor/bin/phpunit tests/AllTests.php - php bin/phpcs --no-cache --parallel=1 - - pear package-validate package.xml - composer validate --no-check-all --strict - php scripts/build-phar.php - php phpcs.phar From 2b20b5690ae2f40f9e75f3577393ceb5ef7d417c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 19 Dec 2020 19:30:39 +0100 Subject: [PATCH 183/733] CI: switch to GitHub Actions - step 4: tests This commit: * Adds a GH Actions workflow for the CI checks which were previously run on Travis as the default test script. Includes only running the base tests for the builds using custom ini setting. * Removes the, now redundant, `.travis.yml` configuration. * Remove the custom PHP ini configurations. Setting these is now handled in the script itself. * Updates the `.gitattributes` file. * Updates the "Build Status" badge in the Readme to use the results from the GH `Test` Actions runs. --- .gitattributes | 3 -- .github/workflows/test.yml | 105 +++++++++++++++++++++++++++++++++++++ .travis.yml | 69 ------------------------ README.md | 5 +- php5-testingConfig.ini | 12 ----- php7-testingConfig.ini | 8 --- 6 files changed, 109 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml delete mode 100644 php5-testingConfig.ini delete mode 100644 php7-testingConfig.ini diff --git a/.gitattributes b/.gitattributes index be47b07508..eeeb67169a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ -.travis.yml export-ignore .cspell.json export-ignore .gitattributes export-ignore .gitignore export-ignore @@ -6,8 +5,6 @@ phpcs.xml.dist export-ignore phpstan.neon export-ignore package.xml export-ignore phpunit.xml.dist export-ignore -php5-testingConfig.ini export-ignore -php7-testingConfig.ini export-ignore scripts/ export-ignore # Declare files that should always have CRLF line endings on checkout. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..2f1b4a74de --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,105 @@ +name: Test + +on: + # Run on all pushes and on all pull requests. + # Prevent the build from running when there are only irrelevant changes. + push: + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + # Keys: + # - custom_ini: Whether to run with specific custom ini settings to hit very specific + # code conditions. + # - experimental: Whether the build is "allowed to fail". + matrix: + php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] + custom_ini: [false] + experimental: [false] + + include: + # Builds running the basic tests with different PHP ini settings. + - php: '5.5' + custom_ini: true + experimental: false + - php: '7.0' + custom_ini: true + experimental: false + + # Nightly. + - php: '8.1' + custom_ini: false + experimental: true + + name: "PHP: ${{ matrix.php }} ${{ matrix.custom_ini && ' with custom ini settings' || '' }}" + + continue-on-error: ${{ matrix.experimental }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup ini config + id: set_ini + run: | + # On stable PHPCS versions, allow for PHP deprecation notices. + # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. + # Also set the "short_open_tag" ini to make sure specific conditions are tested. + if [[ ${{ matrix.custom_ini }} == true && "${{ matrix.php }}" == '5.5' ]]; then + echo '::set-output name=PHP_INI::phar.readonly=Off, date.timezone=Australia/Sydney, short_open_tag=On, asp_tags=On' + elif [[ ${{ matrix.custom_ini }} == true && "${{ matrix.php }}" == '7.0' ]]; then + echo '::set-output name=PHP_INI::phar.readonly=Off, date.timezone=Australia/Sydney, short_open_tag=On' + else + echo '::set-output name=PHP_INI::phar.readonly=Off' + fi + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + ini-values: ${{ steps.set_ini.outputs.PHP_INI }} + coverage: none + + # Install dependencies and handle caching in one go. + # @link https://github.com/marketplace/actions/install-composer-dependencies + - name: Install Composer dependencies - normal + if: ${{ matrix.php < 8.0 }} + uses: "ramsey/composer-install@v1" + + # 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" + with: + composer-options: --ignore-platform-reqs + + # Note: The code style check is run multiple times against every PHP version + # as it also acts as an integration test. + - name: 'PHPCS: set the path to PHP' + run: php bin/phpcs --config-set php_path php + + - name: 'PHPUnit: run the tests' + run: vendor/bin/phpunit tests/AllTests.php + + - name: 'PHPCS: check code style without cache, no parallel' + if: ${{ matrix.custom_ini == false }} + run: php bin/phpcs --no-cache --parallel=1 + + - name: 'Composer: validate config' + 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: 'PHPCS: check code style using the Phar file' + if: ${{ matrix.custom_ini == false }} + run: php phpcs.phar diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d8e8be292d..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,69 +0,0 @@ -os: linux -dist: xenial -language: php - -jobs: - fast_finish: true - include: - ############################################# - # Builds using the default test script. - ############################################# - - php: 5.4 - dist: trusty - - php: 5.5 - dist: trusty - - php: 5.6 - - php: 7.0 - - php: 7.1 - - php: 7.2 - - php: 7.3 - - php: 7.4 - - php: 8.0 - # Nightly is PHP 8.1 since Oct 2020. - - php: nightly - addons: - apt: - packages: - - libonig-dev - - ############################################# - # Builds which don't use the default test script. - ############################################# - # Builds running the basic tests with different PHP ini settings. - - php: 5.5 - name: "PHP: 5.5 | Unit tests with custom PHP ini" - dist: trusty - before_script: - - phpenv config-add php5-testingConfig.ini - script: - - php bin/phpcs --config-set php_path php - - vendor/bin/phpunit tests/AllTests.php - - php: 7.0 - name: "PHP: 7.4 | Unit tests with custom PHP ini" - before_script: - - phpenv config-add php7-testingConfig.ini - script: - - php bin/phpcs --config-set php_path php - - vendor/bin/phpunit tests/AllTests.php - - allow_failures: - - php: nightly - -before_install: - # Speed up build time by disabling Xdebug when its not needed. - - phpenv config-rm xdebug.ini || echo 'No xdebug config.' - - | - if [[ $TRAVIS_PHP_VERSION == "nightly" || $TRAVIS_PHP_VERSION == "8.0" ]]; then - // Allow installing "incompatible" PHPUnit version on PHP 8/nightly. - travis_retry composer install --ignore-platform-reqs - else - travis_retry composer install - fi - -script: - - php bin/phpcs --config-set php_path php - - vendor/bin/phpunit tests/AllTests.php - - php bin/phpcs --no-cache --parallel=1 - - composer validate --no-check-all --strict - - php scripts/build-phar.php - - php phpcs.phar diff --git a/README.md b/README.md index 33985901ed..c0ea8e391a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ PHP_CodeSniffer is a set of two PHP scripts; the main `phpcs` script that tokenizes PHP, JavaScript and CSS files to detect violations of a defined coding standard, and a second `phpcbf` script to automatically correct coding standard violations. PHP_CodeSniffer is an essential development tool that ensures your code remains clean and consistent. -[![Build Status](https://travis-ci.org/squizlabs/PHP_CodeSniffer.svg?branch=phpcs-fixer)](https://travis-ci.org/squizlabs/PHP_CodeSniffer) [![Code consistency](http://squizlabs.github.io/PHP_CodeSniffer/analysis/squizlabs/PHP_CodeSniffer/grade.svg)](http://squizlabs.github.io/PHP_CodeSniffer/analysis/squizlabs/PHP_CodeSniffer) [![Join the chat at https://gitter.im/squizlabs/PHP_CodeSniffer](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/squizlabs/PHP_CodeSniffer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build Status](https://github.com/squizlabs/PHP_CodeSniffer/workflows/Validate/badge.svg?branch=master)](https://github.com/squizlabs/PHP_CodeSniffer/actions) +[![Build Status](https://github.com/squizlabs/PHP_CodeSniffer/workflows/Test/badge.svg?branch=master)](https://github.com/squizlabs/PHP_CodeSniffer/actions) +[![Code consistency](http://squizlabs.github.io/PHP_CodeSniffer/analysis/squizlabs/PHP_CodeSniffer/grade.svg)](http://squizlabs.github.io/PHP_CodeSniffer/analysis/squizlabs/PHP_CodeSniffer) +[![Join the chat at https://gitter.im/squizlabs/PHP_CodeSniffer](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/squizlabs/PHP_CodeSniffer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Requirements diff --git a/php5-testingConfig.ini b/php5-testingConfig.ini deleted file mode 100644 index 4f0aa048a3..0000000000 --- a/php5-testingConfig.ini +++ /dev/null @@ -1,12 +0,0 @@ -; Defines the default timezone used by the date functions -; http://php.net/date.timezone -date.timezone = "Australia/Sydney" - -; This directive determines whether or not PHP will recognize code between -; tags as PHP source which should be processed as such. -; http://php.net/short-open-tag -short_open_tag = On - -; Allow ASP-style <% %> tags. -; http://php.net/asp-tags -asp_tags = On diff --git a/php7-testingConfig.ini b/php7-testingConfig.ini deleted file mode 100644 index ac008640d3..0000000000 --- a/php7-testingConfig.ini +++ /dev/null @@ -1,8 +0,0 @@ -; Defines the default timezone used by the date functions -; http://php.net/date.timezone -date.timezone = "Australia/Sydney" - -; This directive determines whether or not PHP will recognize code between -; tags as PHP source which should be processed as such. -; http://php.net/short-open-tag -short_open_tag = On From cf69afb6379445d94e68352eadcc95b1c4954bc6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 24 Dec 2020 19:39:58 +0100 Subject: [PATCH 184/733] PHP 8.0 | Squiz/ScopeKeywordSpacing: fix false positive on static as return type This adds some additional safeguards to the sniff to prevent it from triggering when `static` is used in a return type declaration, as allowed since PHP 8.0. Includes unit tests. Fixes 3188 --- .../WhiteSpace/ScopeKeywordSpacingSniff.php | 34 +++++++++++++++---- .../ScopeKeywordSpacingUnitTest.inc | 16 ++++++++- .../ScopeKeywordSpacingUnitTest.inc.fixed | 14 ++++++++ .../ScopeKeywordSpacingUnitTest.php | 1 + 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php index e0fab85a8d..de92697db0 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php @@ -51,13 +51,33 @@ public function process(File $phpcsFile, $stackPtr) $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); - if ($tokens[$stackPtr]['code'] === T_STATIC - && (($nextToken === false || $tokens[$nextToken]['code'] === T_DOUBLE_COLON) - || $tokens[$prevToken]['code'] === T_NEW) - ) { - // Late static binding, e.g., static:: OR new static() usage or live coding. - return; - } + if ($tokens[$stackPtr]['code'] === T_STATIC) { + if (($nextToken === false || $tokens[$nextToken]['code'] === T_DOUBLE_COLON) + || $tokens[$prevToken]['code'] === T_NEW + ) { + // Late static binding, e.g., static:: OR new static() usage or live coding. + return; + } + + if ($prevToken !== false + && $tokens[$prevToken]['code'] === T_TYPE_UNION + ) { + // Not a scope keyword, but a union return type. + return; + } + + if ($prevToken !== false + && $tokens[$prevToken]['code'] === T_COLON + ) { + $prevPrevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevToken - 1), null, true); + if ($prevPrevToken !== false + && $tokens[$prevPrevToken]['code'] === T_CLOSE_PARENTHESIS + ) { + // Not a scope keyword, but a return type. + return; + } + } + }//end if if ($tokens[$prevToken]['code'] === T_AS) { // Trait visibility change, e.g., "use HelloWorld { sayHello as private; }". diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc index 791cc83f93..50a7a5cca8 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc @@ -25,7 +25,7 @@ class MyClass public static$var = null; - public + public static $var = null; } @@ -82,3 +82,17 @@ class MyOtherClass $varS, $varT } + +// Issue #3188 - static as return type. +public static function fCreate($attributes = []): static +{ + return static::factory()->create($attributes); +} + +// Also account for static used within union types. +public function fCreate($attributes = []): object|static +{ +} + +// Ensure that static as a scope keyword when preceeded by a colon which is not for a type dclaration is still handled. +$callback = $cond ? get_fn_name() : static function ($a) { return $a * 10; }; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed index a4b792b3c5..5a29fb7c4d 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed @@ -77,3 +77,17 @@ class MyOtherClass $varS, $varT } + +// Issue #3188 - static as return type. +public static function fCreate($attributes = []): static +{ + return static::factory()->create($attributes); +} + +// Also account for static used within union types. +public function fCreate($attributes = []): object|static +{ +} + +// Ensure that static as a scope keyword when preceeded by a colon which is not for a type dclaration is still handled. +$callback = $cond ? get_fn_name() : static function ($a) { return $a * 10; }; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php index c24dcc49d7..d94e53fbcb 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php @@ -38,6 +38,7 @@ public function getErrorList() 64 => 1, 67 => 1, 71 => 1, + 98 => 1, ]; }//end getErrorList() From 18a0e54735bb9b3850fec266e5f4c50dacf618ea Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 3 Jan 2021 02:56:46 +0100 Subject: [PATCH 185/733] Fix findStartOfStatement inside of switch/case --- src/Files/File.php | 5 +++++ .../SwitchDeclarationUnitTest.inc | 17 +++++++++++++++-- .../SwitchDeclarationUnitTest.inc.fixed | 17 +++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index c5d581f28d..a14bd0bdd8 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2313,6 +2313,11 @@ public function findStartOfStatement($start, $ignore=null) && $this->tokens[$i]['code'] !== T_CLOSE_PARENTHESIS && $this->tokens[$i]['code'] !== T_END_NOWDOC && $this->tokens[$i]['code'] !== T_END_HEREDOC + && $this->tokens[$i]['code'] !== T_BREAK + && $this->tokens[$i]['code'] !== T_RETURN + && $this->tokens[$i]['code'] !== T_CONTINUE + && $this->tokens[$i]['code'] !== T_THROW + && $this->tokens[$i]['code'] !== T_EXIT ) { // Found the end of the previous scope block. return $lastNotEmpty; diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc index 3dc1064fc3..8050b636f6 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc @@ -287,9 +287,9 @@ switch ($foo) { case 1: switch ($bar) { case 1: - return; + return 1; default: - return; + return 3; } case 2: return 2; @@ -318,3 +318,16 @@ switch ($foo) { case 2: return 2; } + +// OK: Every clause terminates +switch ($foo) { + case 1: + switch ($bar) { + case 1: + return 1; + default: + throw new \Exception(); + } + case 2: + return 2; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed index 1b38419016..bebc66619f 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed @@ -290,9 +290,9 @@ switch ($foo) { case 1: switch ($bar) { case 1: - return; + return 1; default: - return; + return 3; } case 2: return 2; @@ -321,3 +321,16 @@ switch ($foo) { case 2: return 2; } + +// OK: Every clause terminates +switch ($foo) { + case 1: + switch ($bar) { + case 1: + return 1; + default: + throw new \Exception(); + } + case 2: + return 2; +} From 46dc37a862c2cd54fd8bc56fd29782c815da8d6e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 3 Jan 2021 03:06:54 +0100 Subject: [PATCH 186/733] Fix tests --- .../Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed | 2 +- .../Squiz/Tests/Commenting/FileCommentUnitTest.1.js.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed index bae8e7f810..5cc4764205 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 2020 Squiz Pty Ltd (ABN 77 084 670 600) +* @copyright 2021 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 82d63d9036..b2b071f485 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 2020 Squiz Pty Ltd (ABN 77 084 670 600) +* @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600) * @license http://www.php.net/license/3_0.txt * @summary An unknown summary tag * From d6c224d4f1a14f8c640376fa71a2f153900886cc Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 13 Jan 2021 13:55:42 +1100 Subject: [PATCH 187/733] Updated year in test files --- .../Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed | 2 +- .../Squiz/Tests/Commenting/FileCommentUnitTest.1.js.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed index bae8e7f810..5cc4764205 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 2020 Squiz Pty Ltd (ABN 77 084 670 600) +* @copyright 2021 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 82d63d9036..b2b071f485 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 2020 Squiz Pty Ltd (ABN 77 084 670 600) +* @copyright 2021 Squiz Pty Ltd (ABN 77 084 670 600) * @license http://www.php.net/license/3_0.txt * @summary An unknown summary tag * From b0786e1c632b4e3b804fe07d791cc7392acc7085 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 13 Jan 2021 13:55:55 +1100 Subject: [PATCH 188/733] Fixed bug #3145 : Autoloading of sniff fails when multiple classes declared in same file --- autoload.php | 3 +++ package.xml | 1 + 2 files changed, 4 insertions(+) diff --git a/autoload.php b/autoload.php index 1691d7e8de..0dcf1b4c81 100644 --- a/autoload.php +++ b/autoload.php @@ -196,6 +196,9 @@ public static function determineLoadedClass($classesBeforeLoad, $classesAfterLoa $className = null; $newClasses = array_diff($classesAfterLoad['classes'], $classesBeforeLoad['classes']); + if (PHP_VERSION_ID < 70400) { + $newClasses = array_reverse($newClasses); + } // Since PHP 7.4 get_declared_classes() does not guarantee any order, making // it impossible to use order to determine which is the parent an which is the child. diff --git a/package.xml b/package.xml index 31440e0959..acf1bf214e 100644 --- a/package.xml +++ b/package.xml @@ -65,6 +65,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Michael S for the patch - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional - Fixed bug #2992 : Enabling caching using a ruleset produces invalid cache files when using --sniffs and --exclude CLI args + - Fixed bug #3145 : Autoloading of sniff fails when multiple classes declared in same file - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented - Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion From e5e129e16c824b7202f83067f1da01232d19522f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 13 Jan 2021 14:14:18 +1100 Subject: [PATCH 189/733] Changelog for #3177 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index c3f0ebba62..451130d418 100644 --- a/package.xml +++ b/package.xml @@ -71,6 +71,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion - Fixed bug #3170 : Squiz.WhiteSpace.OperatorSpacing false positive when using negation with string concat -- This also fixes the same issue in the PSR12.Operators.OperatorSpacing sniff + - Fixed bug #3177 : Incorrect tokenization of GOTO statements in mixed PHP/HTML files + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3184 : PSR2.Namespace.NamespaceDeclaration false positive on namespace operator -- Thanks to Juliette Reinders Folmer for the patch From 44a09f060a021277622bfe82427eca9784146c18 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 13 Jan 2021 16:49:56 +1100 Subject: [PATCH 190/733] Renamed sniffs and tweaked error messages (ref #3055) --- package.xml | 24 +++++++++---------- ...ml => AbstractClassNamePrefixStandard.xml} | 2 +- ...rd.xml => InterfaceNameSuffixStandard.xml} | 4 ++-- ...andard.xml => TraitNameSuffixStandard.xml} | 4 ++-- ...f.php => AbstractClassNamePrefixSniff.php} | 4 ++-- ...Sniff.php => InterfaceNameSuffixSniff.php} | 4 ++-- ...raitSniff.php => TraitNameSuffixSniff.php} | 4 ++-- ...nc => AbstractClassNamePrefixUnitTest.inc} | 0 ...hp => AbstractClassNamePrefixUnitTest.php} | 4 ++-- ...st.inc => InterfaceNameSuffixUnitTest.inc} | 0 ...st.php => InterfaceNameSuffixUnitTest.php} | 4 ++-- ...itTest.inc => TraitNameSuffixUnitTest.inc} | 0 ...itTest.php => TraitNameSuffixUnitTest.php} | 4 ++-- 13 files changed, 29 insertions(+), 29 deletions(-) rename src/Standards/Generic/Docs/NamingConventions/{AbstractPrefixRequiredForAbstractClassStandard.xml => AbstractClassNamePrefixStandard.xml} (83%) rename src/Standards/Generic/Docs/NamingConventions/{InterfaceSuffixRequiredForInterfaceStandard.xml => InterfaceNameSuffixStandard.xml} (73%) rename src/Standards/Generic/Docs/NamingConventions/{TraitSuffixRequiredForTraitStandard.xml => TraitNameSuffixStandard.xml} (75%) rename src/Standards/Generic/Sniffs/NamingConventions/{AbstractPrefixRequiredForAbstractClassSniff.php => AbstractClassNamePrefixSniff.php} (87%) rename src/Standards/Generic/Sniffs/NamingConventions/{InterfaceSuffixRequiredForInterfaceSniff.php => InterfaceNameSuffixSniff.php} (85%) rename src/Standards/Generic/Sniffs/NamingConventions/{TraitSuffixRequiredForTraitSniff.php => TraitNameSuffixSniff.php} (86%) rename src/Standards/Generic/Tests/NamingConventions/{AbstractPrefixRequiredForAbstractClassUnitTest.inc => AbstractClassNamePrefixUnitTest.inc} (100%) rename src/Standards/Generic/Tests/NamingConventions/{AbstractPrefixRequiredForAbstractClassUnitTest.php => AbstractClassNamePrefixUnitTest.php} (87%) rename src/Standards/Generic/Tests/NamingConventions/{InterfaceSuffixRequiredForInterfaceUnitTest.inc => InterfaceNameSuffixUnitTest.inc} (100%) rename src/Standards/Generic/Tests/NamingConventions/{InterfaceSuffixRequiredForInterfaceUnitTest.php => InterfaceNameSuffixUnitTest.php} (87%) rename src/Standards/Generic/Tests/NamingConventions/{TraitSuffixRequiredForTraitUnitTest.inc => TraitNameSuffixUnitTest.inc} (100%) rename src/Standards/Generic/Tests/NamingConventions/{TraitSuffixRequiredForTraitUnitTest.php => TraitNameSuffixUnitTest.php} (88%) diff --git a/package.xml b/package.xml index f8da7ac8fd..68b7734cf8 100644 --- a/package.xml +++ b/package.xml @@ -320,11 +320,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + - - + + @@ -428,11 +428,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + - - + + @@ -676,16 +676,16 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - + + - - - - + + + + diff --git a/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml b/src/Standards/Generic/Docs/NamingConventions/AbstractClassNamePrefixStandard.xml similarity index 83% rename from src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml rename to src/Standards/Generic/Docs/NamingConventions/AbstractClassNamePrefixStandard.xml index 4f84fa4465..c30d26e9d5 100644 --- a/src/Standards/Generic/Docs/NamingConventions/AbstractPrefixRequiredForAbstractClassStandard.xml +++ b/src/Standards/Generic/Docs/NamingConventions/AbstractClassNamePrefixStandard.xml @@ -1,7 +1,7 @@ diff --git a/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml b/src/Standards/Generic/Docs/NamingConventions/InterfaceNameSuffixStandard.xml similarity index 73% rename from src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml rename to src/Standards/Generic/Docs/NamingConventions/InterfaceNameSuffixStandard.xml index d4f38447e5..0aa0c76e4d 100644 --- a/src/Standards/Generic/Docs/NamingConventions/InterfaceSuffixRequiredForInterfaceStandard.xml +++ b/src/Standards/Generic/Docs/NamingConventions/InterfaceNameSuffixStandard.xml @@ -1,7 +1,7 @@ - + diff --git a/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml b/src/Standards/Generic/Docs/NamingConventions/TraitNameSuffixStandard.xml similarity index 75% rename from src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml rename to src/Standards/Generic/Docs/NamingConventions/TraitNameSuffixStandard.xml index 62c9b26d46..711867e451 100644 --- a/src/Standards/Generic/Docs/NamingConventions/TraitSuffixRequiredForTraitStandard.xml +++ b/src/Standards/Generic/Docs/NamingConventions/TraitNameSuffixStandard.xml @@ -1,7 +1,7 @@ - + diff --git a/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/AbstractClassNamePrefixSniff.php similarity index 87% rename from src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php rename to src/Standards/Generic/Sniffs/NamingConventions/AbstractClassNamePrefixSniff.php index 7892e111fc..3e3af830d3 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/AbstractPrefixRequiredForAbstractClassSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/AbstractClassNamePrefixSniff.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -class AbstractPrefixRequiredForAbstractClassSniff implements Sniff +class AbstractClassNamePrefixSniff implements Sniff { @@ -51,7 +51,7 @@ public function process(File $phpcsFile, $stackPtr) $prefix = substr($className, 0, 8); if (strtolower($prefix) !== 'abstract') { - $phpcsFile->addError('Abstract classes MUST be prefixed by Abstract: e.g. AbstractBar. Found: %s', $stackPtr, 'Missing', [$className]); + $phpcsFile->addError('Abstract class names must be prefixed with "Abstract"; found "%s"', $stackPtr, 'Missing', [$className]); } }//end process() diff --git a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceNameSuffixSniff.php similarity index 85% rename from src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php rename to src/Standards/Generic/Sniffs/NamingConventions/InterfaceNameSuffixSniff.php index 0f388a178b..c5dc34d489 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/InterfaceSuffixRequiredForInterfaceSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/InterfaceNameSuffixSniff.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -class InterfaceSuffixRequiredForInterfaceSniff implements Sniff +class InterfaceNameSuffixSniff implements Sniff { @@ -45,7 +45,7 @@ public function process(File $phpcsFile, $stackPtr) $suffix = substr($interfaceName, -9); if (strtolower($suffix) !== 'interface') { - $phpcsFile->addError('Interfaces MUST be suffixed by Interface: e.g. BarInterface. Found: %s', $stackPtr, 'Missing', [$interfaceName]); + $phpcsFile->addError('Interface names must be suffixed with "Interface"; found "%s"', $stackPtr, 'Missing', [$interfaceName]); } }//end process() diff --git a/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/TraitNameSuffixSniff.php similarity index 86% rename from src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php rename to src/Standards/Generic/Sniffs/NamingConventions/TraitNameSuffixSniff.php index d886669cb1..4e3b211dff 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/TraitSuffixRequiredForTraitSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/TraitNameSuffixSniff.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -class TraitSuffixRequiredForTraitSniff implements Sniff +class TraitNameSuffixSniff implements Sniff { @@ -45,7 +45,7 @@ public function process(File $phpcsFile, $stackPtr) $suffix = substr($traitName, -5); if (strtolower($suffix) !== 'trait') { - $phpcsFile->addError('Traits MUST be suffixed by Trait: e.g. BarTrait. Found: %s', $stackPtr, 'Missing', [$traitName]); + $phpcsFile->addError('Trait names must be suffixed with "Trait"; found "%s"', $stackPtr, 'Missing', [$traitName]); } }//end process() diff --git a/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/AbstractClassNamePrefixUnitTest.inc similarity index 100% rename from src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.inc rename to src/Standards/Generic/Tests/NamingConventions/AbstractClassNamePrefixUnitTest.inc diff --git a/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/AbstractClassNamePrefixUnitTest.php similarity index 87% rename from src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.php rename to src/Standards/Generic/Tests/NamingConventions/AbstractClassNamePrefixUnitTest.php index 94aa0dc296..7be4e8f887 100644 --- a/src/Standards/Generic/Tests/NamingConventions/AbstractPrefixRequiredForAbstractClassUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/AbstractClassNamePrefixUnitTest.php @@ -1,6 +1,6 @@ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence @@ -10,7 +10,7 @@ use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; -class AbstractPrefixRequiredForAbstractClassUnitTest extends AbstractSniffUnitTest +class AbstractClassNamePrefixUnitTest extends AbstractSniffUnitTest { diff --git a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.inc similarity index 100% rename from src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.inc rename to src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.inc diff --git a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.php similarity index 87% rename from src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.php rename to src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.php index ccbf634893..b4d9696f91 100644 --- a/src/Standards/Generic/Tests/NamingConventions/InterfaceSuffixRequiredForInterfaceUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.php @@ -1,6 +1,6 @@ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence @@ -10,7 +10,7 @@ use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; -class InterfaceSuffixRequiredForInterfaceUnitTest extends AbstractSniffUnitTest +class InterfaceSuffixNameUnitTest extends AbstractSniffUnitTest { diff --git a/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/TraitNameSuffixUnitTest.inc similarity index 100% rename from src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.inc rename to src/Standards/Generic/Tests/NamingConventions/TraitNameSuffixUnitTest.inc diff --git a/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/TraitNameSuffixUnitTest.php similarity index 88% rename from src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.php rename to src/Standards/Generic/Tests/NamingConventions/TraitNameSuffixUnitTest.php index 07f1b4c843..742fdd1ab4 100644 --- a/src/Standards/Generic/Tests/NamingConventions/TraitSuffixRequiredForTraitUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/TraitNameSuffixUnitTest.php @@ -1,6 +1,6 @@ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence @@ -10,7 +10,7 @@ use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; -class TraitSuffixRequiredForTraitUnitTest extends AbstractSniffUnitTest +class TraitNameSuffixUnitTest extends AbstractSniffUnitTest { From 4af4d8462b66f33b24d7a9c4a362184e3ee0aa3a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 13 Jan 2021 16:52:33 +1100 Subject: [PATCH 191/733] Changelog for #3055 --- package.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.xml b/package.xml index 68b7734cf8..b081eb36a7 100644 --- a/package.xml +++ b/package.xml @@ -43,6 +43,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run -- If you want include and exclude rules enforced when checking STDIN, use --stdin-path to set the file path -- Thanks to Juliette Reinders Folmer for the patch + - Added Generic.NamingConventions.AbstractClassNamePrefix to enforce that class names are prefixed with "Abstract" + -- Thanks to Anna Borzenko for the contribution + - Added Generic.NamingConventions.InterfaceNameSuffix to enforce that interface names are suffixed with "Interface" + -- Thanks to Anna Borzenko for the contribution + - Added Generic.NamingConventions.TraitNameSuffix to enforce that trait names are suffixed with "Trait" + -- Thanks to Anna Borzenko for the contribution - Generic.Formatting.MultipleStatementAlignment can now align statements at the start of the assignment token -- Previously, the sniff enforced that the values were aligned, even if this meant the assignment tokens were not -- Now, the sniff can enforce that the assignment tokens are aligned, even if this means the values are not From 072042a1305d3801ecbf3bb113a86da37f299b2a Mon Sep 17 00:00:00 2001 From: Ismo Vuorinen Date: Wed, 11 Nov 2020 12:48:21 +0200 Subject: [PATCH 192/733] Files/FileList::current() - Check files array key For some unknown reason, doing git precommit hook with husky and PHP 7.4, FileList method `current` tries to access undefined array index and throws RuntimeException. After adding the `isset($this->files[$path]) === false` check, linting works as expected. Remove redundant check --- src/Files/FileList.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Files/FileList.php b/src/Files/FileList.php index 877b1c003e..e889fc3d7e 100644 --- a/src/Files/FileList.php +++ b/src/Files/FileList.php @@ -184,7 +184,7 @@ public function rewind() public function current() { $path = key($this->files); - if ($this->files[$path] === null) { + if (isset($this->files[$path]) === false) { $this->files[$path] = new LocalFile($path, $this->ruleset, $this->config); } From 7f5df0848df16d55f21412e6c268c30f395867f4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 14 Jan 2021 08:47:18 +1100 Subject: [PATCH 193/733] Changelog for #3163 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index b081eb36a7..e6eca0b0e3 100644 --- a/package.xml +++ b/package.xml @@ -73,6 +73,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #2992 : Enabling caching using a ruleset produces invalid cache files when using --sniffs and --exclude CLI args - Fixed bug #3145 : Autoloading of sniff fails when multiple classes declared in same file - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented + - Fixed bug #3163 : Undefined index error with pre-commit hook using husky on PHP 7.4 + -- Thanks to Ismo Vuorinen for the patch - Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion - Fixed bug #3170 : Squiz.WhiteSpace.OperatorSpacing false positive when using negation with string concat From 019f21a31f89bbf0e7c76623963557ea35a40134 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 14 Jan 2021 10:43:51 +1100 Subject: [PATCH 194/733] Don't need to run the regex if the token is a string (ref #3178) --- src/Tokenizers/PHP.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index f1c363c002..88f8126bc4 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -899,7 +899,8 @@ protected function tokenize($string) */ if ($tokenIsArray === true - && preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1 + && ($token[0] === T_STRING + || preg_match('`^[a-zA-Z_\x80-\xff]`', $token[1]) === 1) ) { // Get the next non-empty token. for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { From f6a0cffc2320862a3bb1614b36b9f7b63839536f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 14 Jan 2021 11:16:56 +1100 Subject: [PATCH 195/733] Changelog for #3178 --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index 5fb7008143..482db9fb67 100644 --- a/package.xml +++ b/package.xml @@ -30,6 +30,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- A new T_TYPE_UNION token is available to represent the pipe character -- File::getMethodParameters(), getMethodProperties(), and getMemberProperties() will now return union types -- Thanks to Juliette Reinders Folmer for the patch + - Added support for PHP 8.0 named function call arguments + -- A new T_PARAM_NAME token is available to represent the label with the name of the function argument in it + -- Thanks to Juliette Reinders Folmer for the patch - Added support for PHP 8.0 dereferencing of text strings with interpolated variables -- Thanks to Juliette Reinders Folmer for the patch - File::getMethodParameters() now supports PHP 8.0 constructor property promotion From 1a2dcf78ab4fb862cfa3272333e362fda4f7d984 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 14 Jan 2021 15:23:03 +1100 Subject: [PATCH 196/733] Remove use of deprecated autoload feature --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index bfd91301f5..f0ab0473ed 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,7 +2,7 @@ parameters: level: 0 paths: - src - autoload_files: + bootstrapFiles: - tests/bootstrap.php ignoreErrors: - From 59d4c8d3990274f341f1d8cfc691c5d22751c69a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 14 Jan 2021 15:41:46 +1100 Subject: [PATCH 197/733] Changelog for #3188 (ref #3189) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 482db9fb67..5795c3fe53 100644 --- a/package.xml +++ b/package.xml @@ -86,6 +86,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3184 : PSR2.Namespace.NamespaceDeclaration false positive on namespace operator -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3188 : Squiz.WhiteSpace.ScopeKeywordSpacing false positive for static return type + -- Thanks to Juliette Reinders Folmer for the patch From 0aeb095248f307cc7bac7b0268ae4d5d3440f300 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 15 Jan 2021 10:47:51 +1100 Subject: [PATCH 198/733] Fixed bug #3197 : Squiz.NamingConventions.ValidVariableName does not use correct error code for all member vars The sniff now uses the MemberNotCamelCaps error code when member vars are being used, not just when they are defined. --- package.xml | 1 + .../ValidVariableNameSniff.php | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/package.xml b/package.xml index 5795c3fe53..301076f71a 100644 --- a/package.xml +++ b/package.xml @@ -88,6 +88,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3188 : Squiz.WhiteSpace.ScopeKeywordSpacing false positive for static return type -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3197 : Squiz.NamingConventions.ValidVariableName does not use correct error code for all member vars diff --git a/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php b/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php index 5f17a08b53..5c3a74930e 100644 --- a/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php +++ b/src/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php @@ -57,28 +57,38 @@ protected function processVariable(File $phpcsFile, $stackPtr) } if (Common::isCamelCaps($objVarName, false, true, false) === false) { - $error = 'Variable "%s" is not in valid camel caps format'; + $error = 'Member variable "%s" is not in valid camel caps format'; $data = [$originalVarName]; - $phpcsFile->addError($error, $var, 'NotCamelCaps', $data); + $phpcsFile->addError($error, $var, 'MemberNotCamelCaps', $data); } }//end if }//end if }//end if + $objOperator = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + if ($tokens[$objOperator]['code'] === T_DOUBLE_COLON) { + // The variable lives within a class, and is referenced like + // this: MyClass::$_variable, so we don't know its scope. + $objVarName = $varName; + if (substr($objVarName, 0, 1) === '_') { + $objVarName = substr($objVarName, 1); + } + + if (Common::isCamelCaps($objVarName, false, true, false) === false) { + $error = 'Member variable "%s" is not in valid camel caps format'; + $data = [$tokens[$stackPtr]['content']]; + $phpcsFile->addError($error, $stackPtr, 'MemberNotCamelCaps', $data); + } + + return; + } + // There is no way for us to know if the var is public or private, // so we have to ignore a leading underscore if there is one and just // check the main part of the variable name. $originalVarName = $varName; if (substr($varName, 0, 1) === '_') { - $objOperator = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); - if ($tokens[$objOperator]['code'] === T_DOUBLE_COLON) { - // The variable lives within a class, and is referenced like - // this: MyClass::$_variable, so we don't know its scope. - $inClass = true; - } else { - $inClass = $phpcsFile->hasCondition($stackPtr, Tokens::$ooScopeTokens); - } - + $inClass = $phpcsFile->hasCondition($stackPtr, Tokens::$ooScopeTokens); if ($inClass === true) { $varName = substr($varName, 1); } From 7c5e889ec5198ea36592953217b893eb11658fb5 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 18 Jan 2021 14:24:41 +1100 Subject: [PATCH 199/733] Changelog for #3017 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index 301076f71a..8b8b8620fe 100644 --- a/package.xml +++ b/package.xml @@ -52,6 +52,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Anna Borzenko for the contribution - Added Generic.NamingConventions.TraitNameSuffix to enforce that trait names are suffixed with "Trait" -- Thanks to Anna Borzenko for the contribution + - Generic.CodeAnalysis.UnusedFunctionParameter can now be configured to ignore variable usage for specific type hints + -- This allows you to suppress warnings for some variables that are not required, but leave warnings for others + -- Set the ignoreTypeHints array property to a list of type hints to ignore + -- Thanks to Petr Bugyík for the patch - Generic.Formatting.MultipleStatementAlignment can now align statements at the start of the assignment token -- Previously, the sniff enforced that the values were aligned, even if this meant the assignment tokens were not -- Now, the sniff can enforce that the assignment tokens are aligned, even if this means the values are not From 9010cf40257b733179af4663f843d7a2135b0133 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 18 Jan 2021 16:34:11 +1100 Subject: [PATCH 200/733] Fixed bug #3003 : Squiz.Formatting.OperatorBracket autofix incorrect when assignment used with null coalescing operator --- package.xml | 1 + .../Squiz/Sniffs/Formatting/OperatorBracketSniff.php | 4 ++++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc | 2 ++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed | 2 ++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.php | 1 + 5 files changed, 10 insertions(+) diff --git a/package.xml b/package.xml index 8b8b8620fe..7e8de888eb 100644 --- a/package.xml +++ b/package.xml @@ -78,6 +78,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Michael S for the patch - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional - Fixed bug #2992 : Enabling caching using a ruleset produces invalid cache files when using --sniffs and --exclude CLI args + - Fixed bug #3003 : Squiz.Formatting.OperatorBracket autofix incorrect when assignment used with null coalescing operator - Fixed bug #3145 : Autoloading of sniff fails when multiple classes declared in same file - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented - Fixed bug #3163 : Undefined index error with pre-commit hook using husky on PHP 7.4 diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index d704f26796..2c650983ff 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -330,6 +330,10 @@ public function addMissingBracketsError($phpcsFile, $stackPtr) $before = $phpcsFile->findNext(Tokens::$emptyTokens, ($before + 1), null, true); + // A few extra tokens are allowed to be on the right side of the expression. + $allowed[T_EQUAL] = true; + $allowed[T_NEW] = true; + // Find the last token in the expression. for ($after = ($stackPtr + 1); $after < $phpcsFile->numTokens; $after++) { // Special case for plus operators because we can't tell if they are used diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc index 658ea35210..6021889cec 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc @@ -173,4 +173,6 @@ $test = myfunc(1 * static::TEST); $errorPos = $params[$x]?->getLine() + $commentStart; +$foo = $this->gmail ?? $this->gmail = new Google_Service_Gmail($this->google); + exit -1; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed index 532a618c27..1215605e61 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed @@ -173,4 +173,6 @@ $test = myfunc(1 * static::TEST); $errorPos = ($params[$x]?->getLine() + $commentStart); +$foo = ($this->gmail ?? $this->gmail = new Google_Service_Gmail($this->google)); + exit -1; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php index 925eca14c2..1621cc9ea8 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php @@ -67,6 +67,7 @@ public function getErrorList($testFile='OperatorBracketUnitTest.inc') 165 => 2, 169 => 1, 174 => 1, + 176 => 1, ]; break; case 'OperatorBracketUnitTest.js': From b1c2254f40e4dab485da4a0a464357d5184534be Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 19 Jan 2021 11:28:10 +1100 Subject: [PATCH 201/733] Unit tests for #3051 (ref #2770) --- .../Commenting/FunctionCommentUnitTest.inc | 12 ++ .../FunctionCommentUnitTest.inc.fixed | 12 ++ .../Commenting/FunctionCommentUnitTest.php | 176 +++++++++--------- 3 files changed, 113 insertions(+), 87 deletions(-) diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc index 1a7020003f..2f79a6218d 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc @@ -1000,3 +1000,15 @@ public function foo(object $a, ?object $b) {} * @return void */ function foo($foo) {} + +/** + * {@inheritDoc} + */ +public function foo($a, $b) {} + +// phpcs:set Squiz.Commenting.FunctionComment skipIfInheritdoc true + +/** + * {@inheritDoc} + */ +public function foo($a, $b) {} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 4d91ecdcab..be5811281b 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -1000,3 +1000,15 @@ public function foo(object $a, ?object $b) {} * @return void */ function foo($foo) {} + +/** + * {@inheritDoc} + */ +public function foo($a, $b) {} + +// phpcs:set Squiz.Commenting.FunctionComment skipIfInheritdoc true + +/** + * {@inheritDoc} + */ +public function foo($a, $b) {} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php index e8e02f8496..69628243c1 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php @@ -26,93 +26,95 @@ class FunctionCommentUnitTest extends AbstractSniffUnitTest public function getErrorList() { $errors = [ - 5 => 1, - 10 => 3, - 12 => 2, - 13 => 2, - 14 => 1, - 15 => 1, - 28 => 1, - 43 => 1, - 76 => 1, - 87 => 1, - 103 => 1, - 109 => 1, - 112 => 1, - 122 => 1, - 123 => 3, - 124 => 2, - 125 => 1, - 126 => 1, - 137 => 4, - 138 => 4, - 139 => 4, - 143 => 2, - 152 => 1, - 155 => 2, - 159 => 1, - 166 => 1, - 173 => 1, - 183 => 1, - 190 => 2, - 193 => 2, - 196 => 1, - 199 => 2, - 210 => 1, - 211 => 1, - 222 => 1, - 223 => 1, - 224 => 1, - 225 => 1, - 226 => 1, - 227 => 1, - 230 => 2, - 232 => 2, - 246 => 1, - 248 => 4, - 261 => 1, - 263 => 1, - 276 => 1, - 277 => 1, - 278 => 1, - 279 => 1, - 280 => 1, - 281 => 1, - 284 => 1, - 286 => 7, - 294 => 1, - 302 => 1, - 312 => 1, - 358 => 1, - 359 => 2, - 372 => 1, - 373 => 1, - 387 => 1, - 407 => 1, - 441 => 1, - 500 => 1, - 526 => 1, - 548 => 1, - 641 => 1, - 669 => 1, - 688 => 1, - 744 => 1, - 748 => 1, - 767 => 1, - 789 => 1, - 792 => 1, - 794 => 1, - 797 => 1, - 801 => 1, - 828 => 1, - 840 => 1, - 852 => 1, - 864 => 1, - 886 => 1, - 888 => 1, - 890 => 1, - 978 => 1, - 997 => 1, + 5 => 1, + 10 => 3, + 12 => 2, + 13 => 2, + 14 => 1, + 15 => 1, + 28 => 1, + 43 => 1, + 76 => 1, + 87 => 1, + 103 => 1, + 109 => 1, + 112 => 1, + 122 => 1, + 123 => 3, + 124 => 2, + 125 => 1, + 126 => 1, + 137 => 4, + 138 => 4, + 139 => 4, + 143 => 2, + 152 => 1, + 155 => 2, + 159 => 1, + 166 => 1, + 173 => 1, + 183 => 1, + 190 => 2, + 193 => 2, + 196 => 1, + 199 => 2, + 210 => 1, + 211 => 1, + 222 => 1, + 223 => 1, + 224 => 1, + 225 => 1, + 226 => 1, + 227 => 1, + 230 => 2, + 232 => 2, + 246 => 1, + 248 => 4, + 261 => 1, + 263 => 1, + 276 => 1, + 277 => 1, + 278 => 1, + 279 => 1, + 280 => 1, + 281 => 1, + 284 => 1, + 286 => 7, + 294 => 1, + 302 => 1, + 312 => 1, + 358 => 1, + 359 => 2, + 372 => 1, + 373 => 1, + 387 => 1, + 407 => 1, + 441 => 1, + 500 => 1, + 526 => 1, + 548 => 1, + 641 => 1, + 669 => 1, + 688 => 1, + 744 => 1, + 748 => 1, + 767 => 1, + 789 => 1, + 792 => 1, + 794 => 1, + 797 => 1, + 801 => 1, + 828 => 1, + 840 => 1, + 852 => 1, + 864 => 1, + 886 => 1, + 888 => 1, + 890 => 1, + 978 => 1, + 997 => 1, + 1004 => 2, + 1006 => 1, ]; // Scalar type hints only work from PHP 7 onwards. From e5f3e8b68eba7c5688264e651b93eeaaddee44fb Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 19 Jan 2021 11:33:35 +1100 Subject: [PATCH 202/733] Changelog for #3051 (ref #2770) --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index 7e8de888eb..0748b15791 100644 --- a/package.xml +++ b/package.xml @@ -73,6 +73,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Vincent Langlet for the patch - The PSR2.Methods.FunctionCallSignature.SpaceBeforeCloseBracket error message is now reported on the closing parenthesis token -- Previously, the error was being reported on the function keyword, leading to confusing line numbers in the error report + - Squiz.Commenting.FunctionComment is now able to ignore function comments that are only inheritdoc statements + -- Set the skipIfInheritdoc sniff property to "true" to skip checking function comments if the content is only {@inhertidoc} + -- The default remains at "false", so these comments will continue to report errors + -- Thanks to Jess Myrbo for the patch - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 -- This works around a long-standing PHP bug with is_readable() -- Thanks to Michael S for the patch From e8501357c2563cf2ae578579ffeda8c64d89cf20 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 19 Jan 2021 13:19:34 +1100 Subject: [PATCH 203/733] Unit test for #2990 --- src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc index 163c312569..ed7f01151c 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc @@ -296,3 +296,6 @@ class TestAlternativeControlStructures { $var_after_class_in_global_space = 1; do_something_else(); + +// Intentional syntax error. +return array_map( From 285de0d0e77f76363ee2fda1b6ba1335f5015530 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 19 Jan 2021 13:21:11 +1100 Subject: [PATCH 204/733] Changelog for #2990 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 0748b15791..4a28925f2b 100644 --- a/package.xml +++ b/package.xml @@ -77,6 +77,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Set the skipIfInheritdoc sniff property to "true" to skip checking function comments if the content is only {@inhertidoc} -- The default remains at "false", so these comments will continue to report errors -- Thanks to Jess Myrbo for the patch + - Squiz.PHP.NonExecutableCode now has improved handling of syntax errors + -- Thanks to Thiemo Kreuz for the patch - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 -- This works around a long-standing PHP bug with is_readable() -- Thanks to Michael S for the patch From e73f14b2659035d670bdada90f72e15f35e91985 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sun, 31 Jan 2021 12:07:22 +1100 Subject: [PATCH 205/733] Export ignore .github dir (ref #3204) --- .gitattributes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index eeeb67169a..df3ee2b7f9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,10 @@ .cspell.json export-ignore .gitattributes export-ignore +.github/ export-ignore .gitignore export-ignore +package.xml export-ignore phpcs.xml.dist export-ignore phpstan.neon export-ignore -package.xml export-ignore phpunit.xml.dist export-ignore scripts/ export-ignore From c2a33b812fbaa4526fc7ae85eec58bab586cc54c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 1 Feb 2021 13:53:01 +1100 Subject: [PATCH 206/733] Minor refactoring and doc correction --- .../SwitchDeclarationSniff.php | 187 +++++++++--------- 1 file changed, 98 insertions(+), 89 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php index 6b05227c15..faac9dcbbe 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php @@ -229,118 +229,127 @@ private function findNextCase($phpcsFile, $stackPtr, $end) /** - * Returns true if a nested terminating statement is found. + * Returns the position of the nested terminating statement. + * + * Returns false if no terminating statement was found. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position to start looking at. * @param int $end The position to stop looking at. * - * @return bool + * @return int|false */ private function findNestedTerminator($phpcsFile, $stackPtr, $end) { - $tokens = $phpcsFile->getTokens(); - $terminators = [ - T_RETURN, - T_BREAK, - T_CONTINUE, - T_THROW, - T_EXIT, - ]; + $tokens = $phpcsFile->getTokens(); $lastToken = $phpcsFile->findPrevious(T_WHITESPACE, ($end - 1), $stackPtr, true); - if ($lastToken !== false) { - if ($tokens[$lastToken]['code'] === T_CLOSE_CURLY_BRACKET) { - // We found a closing curly bracket and want to check if its block - // belongs to a SWITCH or an IF, ELSEIF or ELSE clause. If yes, we - // continue searching for a terminating statement within that - // block. Note that we have to make sure that every block of - // the entire if/else statement has a terminating statement. - $currentCloser = $lastToken; - $hasElseBlock = false; - do { - $scopeOpener = $tokens[$currentCloser]['scope_opener']; - $scopeCloser = $tokens[$currentCloser]['scope_closer']; - - $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($scopeOpener - 1), $stackPtr, true); - if ($prevToken === false) { + if ($lastToken === false) { + return false; + } + + if ($tokens[$lastToken]['code'] === T_CLOSE_CURLY_BRACKET) { + // We found a closing curly bracket and want to check if its block + // belongs to a SWITCH, IF, ELSEIF or ELSE clause. If yes, we + // continue searching for a terminating statement within that + // block. Note that we have to make sure that every block of + // the entire if/else/switch statement has a terminating statement. + $currentCloser = $lastToken; + $hasElseBlock = false; + do { + $scopeOpener = $tokens[$currentCloser]['scope_opener']; + $scopeCloser = $tokens[$currentCloser]['scope_closer']; + + $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($scopeOpener - 1), $stackPtr, true); + if ($prevToken === false) { + return false; + } + + // SWITCH, IF and ELSEIF clauses possess a condition we have to account for. + if ($tokens[$prevToken]['code'] === T_CLOSE_PARENTHESIS) { + $prevToken = $tokens[$prevToken]['parenthesis_owner']; + } + + if ($tokens[$prevToken]['code'] === T_IF) { + // If we have not encountered an ELSE clause by now, we cannot + // be sure that the whole statement terminates in every case. + if ($hasElseBlock === false) { return false; } - // SWITCH, IF and ELSEIF clauses possess a condition we have to account for. - if ($tokens[$prevToken]['code'] === T_CLOSE_PARENTHESIS) { - $prevToken = $tokens[$prevToken]['parenthesis_owner']; + return $this->findNestedTerminator($phpcsFile, ($scopeOpener + 1), $scopeCloser); + } else if ($tokens[$prevToken]['code'] === T_ELSEIF + || $tokens[$prevToken]['code'] === T_ELSE + ) { + // If we find a terminating statement within this block, + // we continue with the previous ELSEIF or IF clause. + $hasTerminator = $this->findNestedTerminator($phpcsFile, ($scopeOpener + 1), $scopeCloser); + if ($hasTerminator === false) { + return false; } - if ($tokens[$prevToken]['code'] === T_IF) { - // If we have not encountered an ELSE clause by now, we cannot - // be sure that the whole statement terminates in every case. - if ($hasElseBlock === false) { - return false; + $currentCloser = $phpcsFile->findPrevious(T_WHITESPACE, ($prevToken - 1), $stackPtr, true); + if ($tokens[$prevToken]['code'] === T_ELSE) { + $hasElseBlock = true; + } + } else if ($tokens[$prevToken]['code'] === T_SWITCH) { + $hasDefaultBlock = false; + $endOfSwitch = $tokens[$prevToken]['scope_closer']; + $nextCase = $prevToken; + + // We look for a terminating statement within every blocks. + while (($nextCase = $this->findNextCase($phpcsFile, ($nextCase + 1), $endOfSwitch)) !== false) { + if ($tokens[$nextCase]['code'] === T_DEFAULT) { + $hasDefaultBlock = true; } - return $this->findNestedTerminator($phpcsFile, ($scopeOpener + 1), $scopeCloser); - } else if ($tokens[$prevToken]['code'] === T_ELSEIF - || $tokens[$prevToken]['code'] === T_ELSE - ) { - // If we find a terminating statement within this block, - // we continue with the previous ELSEIF or IF clause. - $hasTerminator = $this->findNestedTerminator($phpcsFile, ($scopeOpener + 1), $scopeCloser); - if ($hasTerminator === false) { - return false; - } + $opener = $tokens[$nextCase]['scope_opener']; - $currentCloser = $phpcsFile->findPrevious(T_WHITESPACE, ($prevToken - 1), $stackPtr, true); - if ($tokens[$prevToken]['code'] === T_ELSE) { - $hasElseBlock = true; + $nextCode = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), $endOfSwitch, true); + if ($tokens[$nextCode]['code'] === T_CASE || $tokens[$nextCode]['code'] === T_DEFAULT) { + // This case statement has no content, so skip it. + continue; } - } else if ($tokens[$prevToken]['code'] === T_SWITCH) { - $hasDefaultBlock = false; - $endOfSwitch = $tokens[$prevToken]['scope_closer']; - $nextCase = $prevToken; - - // We look for a terminating statement within every blocks. - while (($nextCase = $this->findNextCase($phpcsFile, ($nextCase + 1), $endOfSwitch)) !== false) { - if ($tokens[$nextCase]['code'] === T_DEFAULT) { - $hasDefaultBlock = true; - } - $opener = $tokens[$nextCase]['scope_opener']; - - $nextCode = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), $endOfSwitch, true); - if ($tokens[$nextCode]['code'] === T_CASE || $tokens[$nextCode]['code'] === T_DEFAULT) { - // This case statement has no content. We skip it. - continue; - } - - $nextCode = $this->findNextCase($phpcsFile, ($opener + 1), $endOfSwitch); - if ($nextCode === false) { - $nextCode = $endOfSwitch; - } + $endOfCase = $this->findNextCase($phpcsFile, ($opener + 1), $endOfSwitch); + if ($endOfCase === false) { + $endOfCase = $endOfSwitch; + } - $hasTerminator = $this->findNestedTerminator($phpcsFile, ($opener + 1), $nextCode); - if ($hasTerminator === false) { - return false; - } - }//end while + $hasTerminator = $this->findNestedTerminator($phpcsFile, ($opener + 1), $endOfCase); + if ($hasTerminator === false) { + return false; + } + }//end while - // If we have not encountered a DEFAULT block by now, we cannot - // be sure that the whole statement terminates in every case. - return $hasDefaultBlock; - } else { + // If we have not encountered a DEFAULT block by now, we cannot + // be sure that the whole statement terminates in every case. + if ($hasDefaultBlock === false) { return false; - }//end if - } while ($currentCloser !== false && $tokens[$currentCloser]['code'] === T_CLOSE_CURLY_BRACKET); - - return true; - } else if ($tokens[$lastToken]['code'] === T_SEMICOLON) { - // We found the last statement of the CASE. Now we want to - // check whether it is a terminating one. - $terminator = $phpcsFile->findStartOfStatement(($lastToken - 1)); - if (in_array($tokens[$terminator]['code'], $terminators, true) === true) { - return $terminator; - } - }//end if + } + + return $hasTerminator; + } else { + return false; + }//end if + } while ($currentCloser !== false && $tokens[$currentCloser]['code'] === T_CLOSE_CURLY_BRACKET); + + return true; + } else if ($tokens[$lastToken]['code'] === T_SEMICOLON) { + // We found the last statement of the CASE. Now we want to + // check whether it is a terminating one. + $terminators = [ + T_RETURN, + T_BREAK, + T_CONTINUE, + T_THROW, + T_EXIT, + ]; + + $terminator = $phpcsFile->findStartOfStatement(($lastToken - 1)); + if (in_array($tokens[$terminator]['code'], $terminators, true) === true) { + return $terminator; + } }//end if return false; From 7e1cbca00c4d8a0531da750808754b40177b2874 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 1 Feb 2021 13:58:15 +1100 Subject: [PATCH 207/733] Changelog for #3186 (ref #2800, #3192) --- package.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.xml b/package.xml index 4a28925f2b..2a1c246a9f 100644 --- a/package.xml +++ b/package.xml @@ -71,6 +71,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Set the "minimumVisibility" sniff property to "public" to ignore both private and protected methods -- The default remains at "private", so all methods are checked -- Thanks to Vincent Langlet for the patch + - PSR2.ControlStructures.SwitchDeclaration now supports nested switch statements where every branch terminates + -- Previously, if a CASE only contained a SWITCH and no direct terminating statement, a fall-through error was displayed + -- Now, the error is surpressed if every branch of the SWITCH has a terminating statement + -- Thanks to Vincent Langlet for the patch - The PSR2.Methods.FunctionCallSignature.SpaceBeforeCloseBracket error message is now reported on the closing parenthesis token -- Previously, the error was being reported on the function keyword, leading to confusing line numbers in the error report - Squiz.Commenting.FunctionComment is now able to ignore function comments that are only inheritdoc statements @@ -99,6 +103,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3188 : Squiz.WhiteSpace.ScopeKeywordSpacing false positive for static return type -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3192 : findStartOfStatement doesn't work correctly inside switch + -- Thanks to Vincent Langlet for the patch - Fixed bug #3197 : Squiz.NamingConventions.ValidVariableName does not use correct error code for all member vars From 500d1dbafc6628e1ec119fbf653c83716951c4a5 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 1 Feb 2021 16:49:59 +1100 Subject: [PATCH 208/733] Remove the PHPUnit config file during testing Running PHPUnit 7 under PHP 8.1 causes an error when reading config files due to the assignment of by reference. By removing the config file and specifying the settings on the command line, this bit of code is skipped. The additional config options in the config file have also been removed as they didn't seem to be necessary, but this needs to be validated in the pipeline first. --- .github/workflows/test.yml | 18 ++++++++++++------ phpunit.xml.dist | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2f1b4a74de..20b694f35f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: # 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 }} + if: ${{ matrix.custom_ini == false }} uses: "ramsey/composer-install@v1" with: composer-options: --ignore-platform-reqs @@ -85,21 +85,27 @@ jobs: - name: 'PHPCS: set the path to PHP' run: php bin/phpcs --config-set php_path php + # We need to remove the config file so that PHPUnit doesn't try to read it. + # This causes an error on PHP 8.1+ with PHPunit 7, but it's not needed here anyway as the + # phpunit command (below) specifies all required settings. + - name: 'PHPUnit: remove config file' + run: rm phpunit.xml.dist + - name: 'PHPUnit: run the tests' - run: vendor/bin/phpunit tests/AllTests.php + run: vendor/bin/phpunit tests/AllTests.php --bootstrap=tests/bootstrap.php - name: 'PHPCS: check code style without cache, no parallel' - if: ${{ matrix.custom_ini == false }} + if: ${{ matrix.custom_ini == false }} run: php bin/phpcs --no-cache --parallel=1 - name: 'Composer: validate config' - if: ${{ matrix.custom_ini == false }} + if: ${{ matrix.custom_ini == false }} run: composer validate --no-check-all --strict - name: Build the phar - if: ${{ matrix.custom_ini == false }} + if: ${{ matrix.custom_ini == false }} run: php scripts/build-phar.php - name: 'PHPCS: check code style using the Phar file' - if: ${{ matrix.custom_ini == false }} + if: ${{ matrix.custom_ini == false }} run: php phpcs.phar diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ff288b9151..0705658a30 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ - + tests/AllTests.php From 9f6385eb32a5f4625e01c54d06a1f9d66201eec2 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 1 Feb 2021 16:56:29 +1100 Subject: [PATCH 209/733] No need to report useless tests --- .github/workflows/test.yml | 2 +- phpunit.xml.dist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 20b694f35f..1bcbf20230 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: run: rm phpunit.xml.dist - name: 'PHPUnit: run the tests' - run: vendor/bin/phpunit tests/AllTests.php --bootstrap=tests/bootstrap.php + run: vendor/bin/phpunit tests/AllTests.php --bootstrap=tests/bootstrap.php --dont-report-useless-tests - name: 'PHPCS: check code style without cache, no parallel' if: ${{ matrix.custom_ini == false }} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0705658a30..34b4afcded 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ - + tests/AllTests.php From 2da6904087d28af217b430a2cb4d8fcca4b9f5be Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 1 Feb 2021 17:06:08 +1100 Subject: [PATCH 210/733] Useless tests were not reported for before PHPUnit 6, so it doesn't understand the CLI argument --- .github/workflows/test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1bcbf20230..bac9f7ee07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -91,7 +91,14 @@ jobs: - name: 'PHPUnit: remove config file' run: rm phpunit.xml.dist + # Useless tests were not reported for before PHPUnit 6, so it doesn't + # understand the CLI argument. - name: 'PHPUnit: run the tests' + if: ${{ matrix.php < 7.0 }} + run: vendor/bin/phpunit tests/AllTests.php --bootstrap=tests/bootstrap.php + + - name: 'PHPUnit: run the tests, dont report useless' + if: ${{ matrix.php >= 7.0 }} run: vendor/bin/phpunit tests/AllTests.php --bootstrap=tests/bootstrap.php --dont-report-useless-tests - name: 'PHPCS: check code style without cache, no parallel' From e9a62e5d40379d625c95a71e917e384645aa6daa Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 13 Feb 2021 09:02:11 +0100 Subject: [PATCH 211/733] JSLint: bug fix The `$name` passed to `Config::getExecutablePath()` appears to be incorrect which would result in a duplicate `jslint jslint` command. --- src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php b/src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php index c50e764e48..420613fc8c 100644 --- a/src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php +++ b/src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php @@ -48,7 +48,7 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $rhinoPath = Config::getExecutablePath('jslint'); + $rhinoPath = Config::getExecutablePath('rhino'); $jslintPath = Config::getExecutablePath('jslint'); if ($rhinoPath === null || $jslintPath === null) { return; From a0c3943d3a7442641498452a013546abab0e2db7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 13 Feb 2021 09:01:40 +0100 Subject: [PATCH 212/733] Windows: fix escaping of external commands The `escapeshellcmd()` function apparently does not escape spaces within a path on Windows which can result in broken functionality. While the sniffs and report affected by this are apparently not used that much based on the lack of bug reports, fixing it still seemed like the _right thing to do_. Noticed while running the unit tests on a fresh install on Windows 10. At some point over the past years, Node has apparently changed their default install directory on Windows and the order in which they register their paths to the Windows system `PATH`. This means that `where csslint` may result in a `$cmd` path like `C:\Program Files\nodejs\csslint.cmd`, which would be escaped to `C:^\Program Files^\nodejs^\csslint.cmd` on Windows, which in turn results in a `'C:\Program' is not recognized as an internal or external command, operable program or batch file.` error. I could have changed the install path for NVM on my machine, but that would just have hidden the underlying issue. It does appear to be a known issue with the function based on the last two comments in this upstream bug report: https://bugs.php.net/bug.php?id=43261, however as that issue is closed, I don't expect this to be fixed in PHP itself, though it might be worth it to open a new issue upstream about it (as those two comments were left on a closed issue years after the close). Fixed now by checking an escaped path for unescaped spaces when on Windows and if necessary, escaping them. The escaping is done in such a way that, even if PHP itself would start escaping these spaces, the `Common::escapeshellcmd()` function will still handle this correctly. --- src/Reports/Notifysend.php | 3 ++- .../Generic/Sniffs/Debug/CSSLintSniff.php | 3 ++- .../Sniffs/Debug/ClosureLinterSniff.php | 3 ++- .../Generic/Sniffs/Debug/ESLintSniff.php | 3 ++- .../Generic/Sniffs/Debug/JSHintSniff.php | 5 +++-- .../Generic/Sniffs/PHP/SyntaxSniff.php | 3 ++- .../Squiz/Sniffs/Debug/JSLintSniff.php | 5 +++-- .../Sniffs/Debug/JavaScriptLintSniff.php | 3 ++- .../Zend/Sniffs/Debug/CodeAnalyzerSniff.php | 3 ++- src/Util/Common.php | 22 +++++++++++++++++++ 10 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/Reports/Notifysend.php b/src/Reports/Notifysend.php index 5ffc5baf5d..0841662234 100644 --- a/src/Reports/Notifysend.php +++ b/src/Reports/Notifysend.php @@ -18,6 +18,7 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Common; class Notifysend implements Report { @@ -58,7 +59,7 @@ public function __construct() { $path = Config::getExecutablePath('notifysend'); if ($path !== null) { - $this->path = escapeshellcmd($path); + $this->path = Common::escapeshellcmd($path); } $timeout = Config::getConfigData('notifysend_timeout'); diff --git a/src/Standards/Generic/Sniffs/Debug/CSSLintSniff.php b/src/Standards/Generic/Sniffs/Debug/CSSLintSniff.php index d299dc845c..81284787a2 100644 --- a/src/Standards/Generic/Sniffs/Debug/CSSLintSniff.php +++ b/src/Standards/Generic/Sniffs/Debug/CSSLintSniff.php @@ -12,6 +12,7 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Common; class CSSLintSniff implements Sniff { @@ -54,7 +55,7 @@ public function process(File $phpcsFile, $stackPtr) $fileName = $phpcsFile->getFilename(); - $cmd = escapeshellcmd($csslintPath).' '.escapeshellarg($fileName).' 2>&1'; + $cmd = Common::escapeshellcmd($csslintPath).' '.escapeshellarg($fileName).' 2>&1'; exec($cmd, $output, $retval); if (is_array($output) === false) { diff --git a/src/Standards/Generic/Sniffs/Debug/ClosureLinterSniff.php b/src/Standards/Generic/Sniffs/Debug/ClosureLinterSniff.php index f6b6b729bc..19204718b0 100644 --- a/src/Standards/Generic/Sniffs/Debug/ClosureLinterSniff.php +++ b/src/Standards/Generic/Sniffs/Debug/ClosureLinterSniff.php @@ -12,6 +12,7 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Common; class ClosureLinterSniff implements Sniff { @@ -71,7 +72,7 @@ public function process(File $phpcsFile, $stackPtr) $fileName = $phpcsFile->getFilename(); - $lintPath = escapeshellcmd($lintPath); + $lintPath = Common::escapeshellcmd($lintPath); $cmd = $lintPath.' --nosummary --notime --unix_mode '.escapeshellarg($fileName); exec($cmd, $output, $retval); diff --git a/src/Standards/Generic/Sniffs/Debug/ESLintSniff.php b/src/Standards/Generic/Sniffs/Debug/ESLintSniff.php index 081c3c1ebf..d11d347050 100644 --- a/src/Standards/Generic/Sniffs/Debug/ESLintSniff.php +++ b/src/Standards/Generic/Sniffs/Debug/ESLintSniff.php @@ -12,6 +12,7 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Common; class ESLintSniff implements Sniff { @@ -76,7 +77,7 @@ public function process(File $phpcsFile, $stackPtr) $eslintOptions[] = '--config '.escapeshellarg($configFile); } - $cmd = escapeshellcmd(escapeshellarg($eslintPath).' '.implode(' ', $eslintOptions).' '.escapeshellarg($filename)); + $cmd = Common::escapeshellcmd(escapeshellarg($eslintPath).' '.implode(' ', $eslintOptions).' '.escapeshellarg($filename)); // Execute! exec($cmd, $stdout, $code); diff --git a/src/Standards/Generic/Sniffs/Debug/JSHintSniff.php b/src/Standards/Generic/Sniffs/Debug/JSHintSniff.php index 8e1f18bd0d..06de35940a 100644 --- a/src/Standards/Generic/Sniffs/Debug/JSHintSniff.php +++ b/src/Standards/Generic/Sniffs/Debug/JSHintSniff.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Common; class JSHintSniff implements Sniff { @@ -56,10 +57,10 @@ public function process(File $phpcsFile, $stackPtr) } $fileName = $phpcsFile->getFilename(); - $jshintPath = escapeshellcmd($jshintPath); + $jshintPath = Common::escapeshellcmd($jshintPath); if ($rhinoPath !== null) { - $rhinoPath = escapeshellcmd($rhinoPath); + $rhinoPath = Common::escapeshellcmd($rhinoPath); $cmd = "$rhinoPath \"$jshintPath\" ".escapeshellarg($fileName); exec($cmd, $output, $retval); diff --git a/src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php b/src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php index 9ef8c2ea66..1519aa1376 100644 --- a/src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Common; class SyntaxSniff implements Sniff { @@ -53,7 +54,7 @@ public function process(File $phpcsFile, $stackPtr) } $fileName = escapeshellarg($phpcsFile->getFilename()); - $cmd = escapeshellcmd($this->phpPath)." -l -d display_errors=1 -d error_prepend_string='' $fileName 2>&1"; + $cmd = Common::escapeshellcmd($this->phpPath)." -l -d display_errors=1 -d error_prepend_string='' $fileName 2>&1"; $output = shell_exec($cmd); $matches = []; if (preg_match('/^.*error:(.*) in .* on line ([0-9]+)/m', trim($output), $matches) === 1) { diff --git a/src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php b/src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php index c50e764e48..e4b49b520a 100644 --- a/src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php +++ b/src/Standards/Squiz/Sniffs/Debug/JSLintSniff.php @@ -12,6 +12,7 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Common; class JSLintSniff implements Sniff { @@ -56,8 +57,8 @@ public function process(File $phpcsFile, $stackPtr) $fileName = $phpcsFile->getFilename(); - $rhinoPath = escapeshellcmd($rhinoPath); - $jslintPath = escapeshellcmd($jslintPath); + $rhinoPath = Common::escapeshellcmd($rhinoPath); + $jslintPath = Common::escapeshellcmd($jslintPath); $cmd = "$rhinoPath \"$jslintPath\" ".escapeshellarg($fileName); exec($cmd, $output, $retval); diff --git a/src/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php b/src/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php index 0c0f35566e..30dca6722d 100644 --- a/src/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php +++ b/src/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Common; class JavaScriptLintSniff implements Sniff { @@ -56,7 +57,7 @@ public function process(File $phpcsFile, $stackPtr) $fileName = $phpcsFile->getFilename(); - $cmd = '"'.escapeshellcmd($jslPath).'" -nologo -nofilelisting -nocontext -nosummary -output-format __LINE__:__ERROR__ -process '.escapeshellarg($fileName); + $cmd = '"'.Common::escapeshellcmd($jslPath).'" -nologo -nofilelisting -nocontext -nosummary -output-format __LINE__:__ERROR__ -process '.escapeshellarg($fileName); $msg = exec($cmd, $output, $retval); // Variable $exitCode is the last line of $output if no error occurs, on diff --git a/src/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php b/src/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php index d18c947294..5df4b0fc2c 100644 --- a/src/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php +++ b/src/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php @@ -14,6 +14,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Util\Common; class CodeAnalyzerSniff implements Sniff { @@ -53,7 +54,7 @@ public function process(File $phpcsFile, $stackPtr) // In the command, 2>&1 is important because the code analyzer sends its // findings to stderr. $output normally contains only stdout, so using 2>&1 // will pipe even stderr to stdout. - $cmd = escapeshellcmd($analyzerPath).' '.escapeshellarg($fileName).' 2>&1'; + $cmd = Common::escapeshellcmd($analyzerPath).' '.escapeshellarg($fileName).' 2>&1'; // There is the possibility to pass "--ide" as an option to the analyzer. // This would result in an output format which would be easier to parse. diff --git a/src/Util/Common.php b/src/Util/Common.php index 49fbe23d00..e60eec1df2 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -239,6 +239,28 @@ public static function isStdinATTY() }//end isStdinATTY() + /** + * Escape a path to a system command. + * + * @param string $cmd The path to the system command. + * + * @return string + */ + public static function escapeshellcmd($cmd) + { + $cmd = escapeshellcmd($cmd); + + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + // Spaces are not escaped by escapeshellcmd on Windows, but need to be + // for the command to be able to execute. + $cmd = preg_replace('`(? Date: Sat, 13 Feb 2021 11:44:37 +0100 Subject: [PATCH 213/733] Arrow function tests: add verification of the arrow retokenization --- tests/Core/Tokenizer/BackfillFnTokenTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index b8ea0170ee..edcb235dea 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -783,6 +783,8 @@ private function backfillHelper($token, $skipScopeCloserCheck=false) $this->assertTrue(array_key_exists('scope_opener', $tokens[$opener]), 'Opener scope opener is not set'); $this->assertTrue(array_key_exists('scope_closer', $tokens[$opener]), 'Opener scope closer is not set'); $this->assertSame($tokens[$opener]['scope_condition'], $token, 'Opener scope condition is not the T_FN token'); + $this->assertSame(T_FN_ARROW, $tokens[$opener]['code'], 'Arrow scope opener not tokenized as T_FN_ARROW (code)'); + $this->assertSame('T_FN_ARROW', $tokens[$opener]['type'], 'Arrow scope opener not tokenized as T_FN_ARROW (type)'); $closer = $tokens[$token]['scope_closer']; $this->assertTrue(array_key_exists('scope_condition', $tokens[$closer]), 'Closer scope condition is not set'); From f3bf5f519d09b79441ad199ca18e704b1662d61d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 13 Feb 2021 12:17:55 +0100 Subject: [PATCH 214/733] Arrow function tests: remove lots of code duplication * Simplify the arrow function testing by using a helper function to check the token positions. * Fix incorrect parameter order of the `$expected` vs `$result` parameters in `assertSame()`. While when the test passes, this makes no difference, it does make a difference in the error output when a test fails. --- tests/Core/Tokenizer/BackfillFnTokenTest.php | 272 ++++--------------- 1 file changed, 52 insertions(+), 220 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index edcb235dea..e6c7caf4a3 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -29,17 +29,7 @@ public function testSimple() foreach (['/* testStandard */', '/* testMixedCase */'] as $comment) { $token = $this->getTargetToken($comment, T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 5, 12); } }//end testSimple() @@ -58,17 +48,7 @@ public function testWhitespace() $token = $this->getTargetToken('/* testWhitespace */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 6), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 13), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 6), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 13), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 6), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 13), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 6, 13); }//end testWhitespace() @@ -86,17 +66,7 @@ public function testComment() $token = $this->getTargetToken('/* testComment */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 15), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 15), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 15), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 8, 15); }//end testComment() @@ -114,17 +84,7 @@ public function testHeredoc() $token = $this->getTargetToken('/* testHeredoc */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 4), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 9), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 4), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 4), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 4, 9); }//end testHeredoc() @@ -142,17 +102,7 @@ public function testNestedOuter() $token = $this->getTargetToken('/* testNestedOuter */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 25), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 25), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 25), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 5, 25); }//end testNestedOuter() @@ -198,17 +148,7 @@ public function testFunctionCall() $token = $this->getTargetToken('/* testFunctionCall */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 17), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 17), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 17), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 5, 17); }//end testFunctionCall() @@ -226,17 +166,7 @@ public function testChainedFunctionCall() $token = $this->getTargetToken('/* testChainedFunctionCall */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer is not the bracket token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer is not the bracket token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer is not the bracket token'); + $this->scopePositionTestHelper($token, 5, 12, 'bracket'); }//end testChainedFunctionCall() @@ -254,17 +184,7 @@ public function testFunctionArgument() $token = $this->getTargetToken('/* testFunctionArgument */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 15), 'Scope closer is not the comma token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 15), 'Opener scope closer is not the comma token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 15), 'Closer scope closer is not the comma token'); + $this->scopePositionTestHelper($token, 8, 15, 'comma'); }//end testFunctionArgument() @@ -282,17 +202,7 @@ public function testClosure() $token = $this->getTargetToken('/* testClosure */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 60), 'Scope closer is not the comma token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 60), 'Opener scope closer is not the comma token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 60), 'Closer scope closer is not the comma token'); + $this->scopePositionTestHelper($token, 5, 60, 'comma'); }//end testClosure() @@ -310,17 +220,7 @@ public function testReturnType() $token = $this->getTargetToken('/* testReturnType */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 11), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 18), 'Scope closer is not the comma token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the comma token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the comma token'); + $this->scopePositionTestHelper($token, 11, 18, 'comma'); }//end testReturnType() @@ -338,17 +238,7 @@ public function testReference() $token = $this->getTargetToken('/* testReference */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 6), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 9), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 6), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 6), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 6, 9); }//end testReference() @@ -366,17 +256,7 @@ public function testGrouped() $token = $this->getTargetToken('/* testGrouped */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 8), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 8), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 8), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 5, 8); }//end testGrouped() @@ -394,17 +274,7 @@ public function testArrayValue() $token = $this->getTargetToken('/* testArrayValue */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 4), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 9), 'Scope closer is not the comma token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 4), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the comma token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 4), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the comma token'); + $this->scopePositionTestHelper($token, 4, 9, 'comma'); }//end testArrayValue() @@ -422,17 +292,7 @@ public function testYield() $token = $this->getTargetToken('/* testYield */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 14), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 14), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 14), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 5, 14); }//end testYield() @@ -450,17 +310,7 @@ public function testNullableNamespace() $token = $this->getTargetToken('/* testNullableNamespace */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 15), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 18), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 15), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 15), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 15, 18); }//end testNullableNamespace() @@ -478,17 +328,7 @@ public function testNamespaceOperatorInTypes() $token = $this->getTargetToken('/* testNamespaceOperatorInTypes */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 16), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 19), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 16), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 19), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 16), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 19), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 16, 19); }//end testNamespaceOperatorInTypes() @@ -544,17 +384,7 @@ public function testUnionParamType() $token = $this->getTargetToken('/* testUnionParamType */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 13), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 21), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 13), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 21), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 13), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 21), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 13, 21); }//end testUnionParamType() @@ -572,17 +402,7 @@ public function testUnionReturnType() $token = $this->getTargetToken('/* testUnionReturnType */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 11), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 18), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 11, 18); }//end testUnionReturnType() @@ -600,17 +420,7 @@ public function testTernary() $token = $this->getTargetToken('/* testTernary */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 40), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 40), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 40), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 5, 40); $token = $this->getTargetToken('/* testTernaryThen */', T_FN); $this->backfillHelper($token); @@ -656,17 +466,7 @@ public function testNestedInMethod() $token = $this->getTargetToken('/* testNestedInMethod */', T_FN); $this->backfillHelper($token); - - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 17), 'Scope closer is not the semicolon token'); - - $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 17), 'Opener scope closer is not the semicolon token'); - - $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 17), 'Closer scope closer is not the semicolon token'); + $this->scopePositionTestHelper($token, 5, 17); }//end testNestedInMethod() @@ -805,4 +605,36 @@ private function backfillHelper($token, $skipScopeCloserCheck=false) }//end backfillHelper() + /** + * Helper function to check that the scope opener/closer positions are correctly set for T_FN tokens. + * + * @param int $token The T_FN token to check. + * @param int $openerOffset The expected offset of the scope opener in relation to + * the fn keyword. + * @param int $closerOffset The expected offset of the scope closer in relation to + * the fn keyword. + * @param string $expectedCloserType Optional. The type of token expected for the scope closer. + * + * @return void + */ + private function scopePositionTestHelper($token, $openerOffset, $closerOffset, $expectedCloserType='semicolon') + { + $tokens = self::$phpcsFile->getTokens(); + $expectedScopeOpener = ($token + $openerOffset); + $expectedScopeCloser = ($token + $closerOffset); + + $this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer is not the '.$expectedCloserType.' token'); + + $opener = $tokens[$token]['scope_opener']; + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer is not the '.$expectedCloserType.' token'); + + $closer = $tokens[$token]['scope_closer']; + $this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], 'Closer scope opener is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer is not the '.$expectedCloserType.' token'); + + }//end scopePositionTestHelper() + + }//end class From 88ac139766a17bc2457869a66c59275746a92e45 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 13 Feb 2021 12:21:03 +0100 Subject: [PATCH 215/733] Arrow function tests: some simplifications in the rest of the tests This addresses the same issues as in the previous commit for those functions which need custom "error" messages or have a shared scope closer, resulting in the `opener` for the `scope_closer` pointing to another construct * Remove duplicate calculations of the scope opener/closer positions. * Fix incorrect parameter order of the `$expected` vs `$result` parameters in `assertSame()`. While when the test passes, this makes no difference, it does make a difference in the error output when a test fails. --- tests/Core/Tokenizer/BackfillFnTokenTest.php | 60 ++++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index e6c7caf4a3..f460234ecf 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -121,16 +121,19 @@ public function testNestedInner() $token = $this->getTargetToken('/* testNestedInner */', T_FN); $this->backfillHelper($token, true); - $this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 16), 'Scope closer is not the semicolon token'); + $expectedScopeOpener = ($token + 5); + $expectedScopeCloser = ($token + 16); + + $this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer is not the semicolon token'); $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 16), 'Opener scope closer is not the semicolon token'); + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer is not the semicolon token'); $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token - 4), 'Closer scope opener is not the arrow token of the "outer" arrow function (shared scope closer)'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 16), 'Closer scope closer is not the semicolon token'); + $this->assertSame(($token - 4), $tokens[$closer]['scope_opener'], 'Closer scope opener is not the arrow token of the "outer" arrow function (shared scope closer)'); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer is not the semicolon token'); }//end testNestedInner() @@ -356,16 +359,19 @@ public function testKeywordReturnTypes() $token = $this->getTargetToken('/* test'.$marker.'ReturnType */', T_FN); $this->backfillHelper($token); - $this->assertSame($tokens[$token]['scope_opener'], ($token + 11), "Scope opener is not the arrow token (for $marker)"); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 14), "Scope closer is not the semicolon token(for $marker)"); + $expectedScopeOpener = ($token + 11); + $expectedScopeCloser = ($token + 14); + + $this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], "Scope opener is not the arrow token (for $marker)"); + $this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], "Scope closer is not the semicolon token(for $marker)"); $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), "Opener scope opener is not the arrow token(for $marker)"); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 14), "Opener scope closer is not the semicolon token(for $marker)"); + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], "Opener scope opener is not the arrow token(for $marker)"); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], "Opener scope closer is not the semicolon token(for $marker)"); $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), "Closer scope opener is not the arrow token(for $marker)"); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 14), "Closer scope closer is not the semicolon token(for $marker)"); + $this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], "Closer scope opener is not the arrow token(for $marker)"); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], "Closer scope closer is not the semicolon token(for $marker)"); } }//end testKeywordReturnTypes() @@ -425,30 +431,36 @@ public function testTernary() $token = $this->getTargetToken('/* testTernaryThen */', T_FN); $this->backfillHelper($token); - $this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener for THEN is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer for THEN is not the semicolon token'); + $expectedScopeOpener = ($token + 8); + $expectedScopeCloser = ($token + 12); + + $this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener for THEN is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer for THEN is not the semicolon token'); $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener for THEN is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer for THEN is not the semicolon token'); + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener for THEN is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer for THEN is not the semicolon token'); $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener for THEN is not the arrow token'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer for THEN is not the semicolon token'); + $this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], 'Closer scope opener for THEN is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer for THEN is not the semicolon token'); $token = $this->getTargetToken('/* testTernaryElse */', T_FN); $this->backfillHelper($token, true); - $this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener for ELSE is not the arrow token'); - $this->assertSame($tokens[$token]['scope_closer'], ($token + 11), 'Scope closer for ELSE is not the semicolon token'); + $expectedScopeOpener = ($token + 8); + $expectedScopeCloser = ($token + 11); + + $this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener for ELSE is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer for ELSE is not the semicolon token'); $opener = $tokens[$token]['scope_opener']; - $this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener for ELSE is not the arrow token'); - $this->assertSame($tokens[$opener]['scope_closer'], ($token + 11), 'Opener scope closer for ELSE is not the semicolon token'); + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener for ELSE is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer for ELSE is not the semicolon token'); $closer = $tokens[$token]['scope_closer']; - $this->assertSame($tokens[$closer]['scope_opener'], ($token - 24), 'Closer scope opener for ELSE is not the arrow token of the "outer" arrow function (shared scope closer)'); - $this->assertSame($tokens[$closer]['scope_closer'], ($token + 11), 'Closer scope closer for ELSE is not the semicolon token'); + $this->assertSame(($token - 24), $tokens[$closer]['scope_opener'], 'Closer scope opener for ELSE is not the arrow token of the "outer" arrow function (shared scope closer)'); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer for ELSE is not the semicolon token'); }//end testTernary() From 0ddfeaea7ae47ba0f694ce0910b8b95a520e12a4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 13 Feb 2021 12:21:37 +0100 Subject: [PATCH 216/733] Arrow function tests: minor documentation fix --- tests/Core/Tokenizer/BackfillFnTokenTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index f460234ecf..b3fb50878d 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -568,12 +568,12 @@ public function dataNotAnArrowFunction() /** * Helper function to check that all token keys are correctly set for T_FN tokens. * - * @param string $token The T_FN token to check. - * @param bool $skipScopeCloserCheck Whether to skip the scope closer check. - * This should be set to "true" when testing nested arrow functions, - * where the "inner" arrow function shares a scope closer with the - * "outer" arrow function, as the 'scope_condition' for the scope closer - * of the "inner" arrow function will point to the "outer" arrow function. + * @param int $token The T_FN token to check. + * @param bool $skipScopeCloserCheck Whether to skip the scope closer check. + * This should be set to "true" when testing nested arrow functions, + * where the "inner" arrow function shares a scope closer with the + * "outer" arrow function, as the 'scope_condition' for the scope closer + * of the "inner" arrow function will point to the "outer" arrow function. * * @return void */ From d70bb9d71bc386d25c8da49e295d7a3e12b3b370 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 28 Nov 2020 18:36:31 +0100 Subject: [PATCH 217/733] Generic/FunctionCallArgumentSpacing: add test with named function call args This verifies that named function call arguments do not affect the functioning of the sniff as-is. --- .../Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc | 4 ++++ .../Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed | 4 ++++ .../Tests/Functions/FunctionCallArgumentSpacingUnitTest.php | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc index 2747553cdb..c46517df04 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc @@ -149,3 +149,7 @@ $foobar = functionCallAnonClassParam( }, $args=array(), ); + +$result = myFunction(param1: $arg1, param2: $arg2); +$result = myFunction(param1: $arg1 , param2:$arg2); +$result = myFunction(param1: $arg1, param2:$arg2, param3: $arg3,param4:$arg4, param5:$arg5); diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed index 0ca70ca05f..1b51933eb9 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed @@ -149,3 +149,7 @@ $foobar = functionCallAnonClassParam( }, $args=array(), ); + +$result = myFunction(param1: $arg1, param2: $arg2); +$result = myFunction(param1: $arg1, param2:$arg2); +$result = myFunction(param1: $arg1, param2:$arg2, param3: $arg3, param4:$arg4, param5:$arg5); diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php index cb1c2e0867..9f83fc9b77 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php @@ -52,6 +52,8 @@ public function getErrorList() 132 => 2, 133 => 2, 134 => 1, + 154 => 2, + 155 => 1, ]; }//end getErrorList() From d570825efda3fc27e7207573a1469549e274dfc1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 13 Feb 2021 18:52:51 +0100 Subject: [PATCH 218/733] Tokenizer/PHP: minor tweak Prevents some unnecessary token changes of the type `token 4 changed from T_STRING to T_STRING`. --- src/Tokenizers/PHP.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 88f8126bc4..7a62245929 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1469,6 +1469,7 @@ protected function tokenize($string) if ($x < $numTokens && is_array($tokens[$x]) === true + && $tokens[$x][0] !== T_STRING && $tokens[$x][0] !== T_NAME_QUALIFIED ) { if (PHP_CODESNIFFER_VERBOSITY > 1) { From 10559a38856ee2b2504f42570cb7de47a72e193d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 13 Feb 2021 18:58:20 +0100 Subject: [PATCH 219/733] Tokens: fix up some PHPCS native token values The PHPCS native token types are normally prefixed with `PHPCS_`. This wasn't the case for the most recent three additions, `T_FN_ARROW` as introduced in PHPCS 3.5.3, `T_TYPE_UNION` and `T_PARAM_NAME` as will be introduced in PHPCS 3.6.0 (not yet released). While the change to the value for `T_FN_ARROW` could be considered a breaking change, it is exceedingly rare for a sniff to use the _value_ of a token constant, so IMO opinion, this is a safe change to make. As for the other two tokens, as they have not been in a release yet, they can be safely updated no matter what. --- src/Util/Tokens.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index ac05362676..1920e0987f 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -74,9 +74,9 @@ define('T_CLOSE_USE_GROUP', 'PHPCS_T_CLOSE_USE_GROUP'); define('T_ZSR', 'PHPCS_T_ZSR'); define('T_ZSR_EQUAL', 'PHPCS_T_ZSR_EQUAL'); -define('T_FN_ARROW', 'T_FN_ARROW'); -define('T_TYPE_UNION', 'T_TYPE_UNION'); -define('T_PARAM_NAME', 'T_PARAM_NAME'); +define('T_FN_ARROW', 'PHPCS_T_FN_ARROW'); +define('T_TYPE_UNION', 'PHPCS_T_TYPE_UNION'); +define('T_PARAM_NAME', 'PHPCS_T_PARAM_NAME'); // Some PHP 5.5 tokens, replicated for lower versions. if (defined('T_FINALLY') === false) { From ea8158c054a82e30514172574d0b21a46b634d39 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 15 Feb 2021 08:44:14 +1100 Subject: [PATCH 220/733] Changelog for #3214 and #3218 --- package.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.xml b/package.xml index 2a1c246a9f..d7366dcc41 100644 --- a/package.xml +++ b/package.xml @@ -35,6 +35,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Added support for PHP 8.0 dereferencing of text strings with interpolated variables -- Thanks to Juliette Reinders Folmer for the patch + - The value of the T_FN_ARROW token has changed from "T_FN_ARROW" to "PHPCS_T_FN_ARROW" to avoid package conflicts + -- This will have no impact on custom sniffs unless they are specifically looking at the value of the T_FN_ARROW constant + -- If sniffs are just using constant to find arrow functions, they will continue to work without modification + -- Thanks to Juliette Reinders Folmer for the patch - File::getMethodParameters() now supports PHP 8.0 constructor property promotion -- Returned method params now include a "property_visibility" and "visibility_token" index if property promotion is detected -- Thanks to Juliette Reinders Folmer for the patch @@ -46,6 +50,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run -- If you want include and exclude rules enforced when checking STDIN, use --stdin-path to set the file path -- Thanks to Juliette Reinders Folmer for the patch + - Spaces are now correctly escaped in the paths to external on Windows + -- Thanks to Juliette Reinders Folmer for the patch - Added Generic.NamingConventions.AbstractClassNamePrefix to enforce that class names are prefixed with "Abstract" -- Thanks to Anna Borzenko for the contribution - Added Generic.NamingConventions.InterfaceNameSuffix to enforce that interface names are suffixed with "Interface" From 2ba5393126dc291307518650ee13b3cce56bfd1e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 15 Feb 2021 09:40:25 +1100 Subject: [PATCH 221/733] Fixed bug #3219 : Generic.Formatting.MultipleStatementAlignment false positive for empty anonymous classes and closures --- package.xml | 1 + .../MultipleStatementAlignmentSniff.php | 20 ++++++++++++++++--- .../MultipleStatementAlignmentUnitTest.inc | 12 +++++++++-- ...ltipleStatementAlignmentUnitTest.inc.fixed | 12 +++++++++-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/package.xml b/package.xml index d7366dcc41..431592b90b 100644 --- a/package.xml +++ b/package.xml @@ -112,6 +112,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3192 : findStartOfStatement doesn't work correctly inside switch -- Thanks to Vincent Langlet for the patch - Fixed bug #3197 : Squiz.NamingConventions.ValidVariableName does not use correct error code for all member vars + - Fixed bug #3219 : Generic.Formatting.MultipleStatementAlignment false positive for empty anonymous classes and closures diff --git a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php index f7f3f0656e..b1b3cc64e3 100644 --- a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php +++ b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php @@ -147,11 +147,25 @@ public function checkAlignment($phpcsFile, $stackPtr, $end=null) break; } - if (isset($scopes[$tokens[$assign]['code']]) === true - && isset($tokens[$assign]['scope_opener']) === true + if (isset($tokens[$assign]['scope_opener']) === true && $tokens[$assign]['level'] === $tokens[$stackPtr]['level'] ) { - break; + if (isset($scopes[$tokens[$assign]['code']]) === true) { + // This type of scope indicates that the assignment block is over. + break; + } + + // Skip over the scope block because it is seen as part of the assignment block, + // but also process any assignment blocks that are inside as well. + $nextAssign = $phpcsFile->findNext($find, ($assign + 1), ($tokens[$assign]['scope_closer'] - 1)); + if ($nextAssign !== false) { + $assign = $this->checkAlignment($phpcsFile, $nextAssign); + } else { + $assign = $tokens[$assign]['scope_closer']; + } + + $lastCode = $assign; + continue; } if ($assign === $arrayEnd) { diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc index 44d0cef27a..286fdf1e75 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc @@ -454,9 +454,17 @@ $varonetwothreefour = 'four'; $one <<= 8; $onetwothree = 3; +// phpcs:set Generic.Formatting.MultipleStatementAlignment maxPadding 1000 + +$a = 123; +$model = new class() { + // empty +}; +$resource = new class() { + // empty +}; + // phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEnd true $one <<= 8; $onetwothree = 3; - -// phpcs:set Generic.Formatting.MultipleStatementAlignment maxPadding 1000 diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed index d2159e560f..f08312ddf9 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed @@ -454,9 +454,17 @@ $varonetwothreefour = 'four'; $one <<= 8; $onetwothree = 3; +// phpcs:set Generic.Formatting.MultipleStatementAlignment maxPadding 1000 + +$a = 123; +$model = new class() { + // empty +}; +$resource = new class() { + // empty +}; + // phpcs:set Generic.Formatting.MultipleStatementAlignment alignAtEnd true $one <<= 8; $onetwothree = 3; - -// phpcs:set Generic.Formatting.MultipleStatementAlignment maxPadding 1000 From f5645cdaed95bddd7dd72fd27ae9d0e4a229ab86 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 20 Feb 2021 11:05:07 +0100 Subject: [PATCH 222/733] Arrow function tests: remove some stray function calls Follow-up on #3215 which made these redundant, but didn't remove them. --- tests/Core/Tokenizer/BackfillFnTokenTest.php | 38 -------------------- 1 file changed, 38 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index b3fb50878d..c2096352f6 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -24,8 +24,6 @@ class BackfillFnTokenTest extends AbstractMethodUnitTest */ public function testSimple() { - $tokens = self::$phpcsFile->getTokens(); - foreach (['/* testStandard */', '/* testMixedCase */'] as $comment) { $token = $this->getTargetToken($comment, T_FN); $this->backfillHelper($token); @@ -44,8 +42,6 @@ public function testSimple() */ public function testWhitespace() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testWhitespace */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 6, 13); @@ -62,8 +58,6 @@ public function testWhitespace() */ public function testComment() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testComment */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 8, 15); @@ -80,8 +74,6 @@ public function testComment() */ public function testHeredoc() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testHeredoc */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 4, 9); @@ -98,8 +90,6 @@ public function testHeredoc() */ public function testNestedOuter() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testNestedOuter */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 5, 25); @@ -147,8 +137,6 @@ public function testNestedInner() */ public function testFunctionCall() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testFunctionCall */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 5, 17); @@ -165,8 +153,6 @@ public function testFunctionCall() */ public function testChainedFunctionCall() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testChainedFunctionCall */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 5, 12, 'bracket'); @@ -183,8 +169,6 @@ public function testChainedFunctionCall() */ public function testFunctionArgument() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testFunctionArgument */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 8, 15, 'comma'); @@ -201,8 +185,6 @@ public function testFunctionArgument() */ public function testClosure() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testClosure */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 5, 60, 'comma'); @@ -219,8 +201,6 @@ public function testClosure() */ public function testReturnType() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testReturnType */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 11, 18, 'comma'); @@ -237,8 +217,6 @@ public function testReturnType() */ public function testReference() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testReference */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 6, 9); @@ -255,8 +233,6 @@ public function testReference() */ public function testGrouped() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testGrouped */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 5, 8); @@ -273,8 +249,6 @@ public function testGrouped() */ public function testArrayValue() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testArrayValue */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 4, 9, 'comma'); @@ -291,8 +265,6 @@ public function testArrayValue() */ public function testYield() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testYield */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 5, 14); @@ -309,8 +281,6 @@ public function testYield() */ public function testNullableNamespace() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testNullableNamespace */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 15, 18); @@ -327,8 +297,6 @@ public function testNullableNamespace() */ public function testNamespaceOperatorInTypes() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testNamespaceOperatorInTypes */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 16, 19); @@ -386,8 +354,6 @@ public function testKeywordReturnTypes() */ public function testUnionParamType() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testUnionParamType */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 13, 21); @@ -404,8 +370,6 @@ public function testUnionParamType() */ public function testUnionReturnType() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testUnionReturnType */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 11, 18); @@ -474,8 +438,6 @@ public function testTernary() */ public function testNestedInMethod() { - $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken('/* testNestedInMethod */', T_FN); $this->backfillHelper($token); $this->scopePositionTestHelper($token, 5, 17); From b48d20277e0e9339466baf8e6a96a7e341d38dfa Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 03:07:52 +0200 Subject: [PATCH 223/733] PHP 8.0 | Tokens: replicate the `T_MATCH` token --- src/Util/Tokens.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 1920e0987f..04b07c13c5 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -143,6 +143,10 @@ define('T_NAME_RELATIVE', 'PHPCS_T_NAME_RELATIVE'); } +if (defined('T_MATCH') === false) { + define('T_MATCH', 'PHPCS_T_MATCH'); +} + // 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'); @@ -189,6 +193,7 @@ final class Tokens T_CATCH => 50, T_FINALLY => 50, T_SWITCH => 50, + T_MATCH => 50, T_SELF => 25, T_PARENT => 25, From 966ae7c1c6aa746ff22a20cbf50be75c8c68a435 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 03:10:42 +0200 Subject: [PATCH 224/733] PHP 8.0 | Tokens: add T_MATCH to $parenthesisOpeners and $scopeOpener --- src/Util/Tokens.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 04b07c13c5..d2b81ee895 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -386,6 +386,7 @@ final class Tokens T_ELSEIF => T_ELSEIF, T_CATCH => T_CATCH, T_DECLARE => T_DECLARE, + T_MATCH => T_MATCH, ]; /** @@ -418,6 +419,7 @@ final class Tokens T_PROPERTY => T_PROPERTY, T_OBJECT => T_OBJECT, T_USE => T_USE, + T_MATCH => T_MATCH, ]; /** From 08a946f008e0d3349f137f1719d6f4105d098315 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 21 Feb 2021 17:35:15 +0100 Subject: [PATCH 225/733] PHP 8.0 | Tokenizer/PHP: backfill the `T_MATCH` tokenization PHP 8.0 introduces a new type of control structure: match expressions. > A match expression is similar to a `switch` control structure but with safer semantics and the ability to return values. Ref: https://wiki.php.net/rfc/match_expression_v2 This commit adds initial support for match expressions to PHPCS. * In PHP < 8: Retokenizes `T_STRING` tokens containing the `match` keyword to `T_MATCH` when they are in actual fact match expressions. * In PHP 8: Retokenizes `T_MATCH` tokens to `T_STRING` when the `match` keyword is used outside the context of a match expression, like in a method declaration or call. * Ensures that the `match` keyword for match expressions will be recognized as a scope owner and that the appropriate `scope_*` array indexes are set for the curly braces belonging to the match expression, as well as that the `match` condition is added to the tokens within the match expression. Note: in contrast to `switch` control structures, "cases" in a match expression will not be recognized as scopes and no `scope_owner`, `scope_opener` or `scope_closer` array indexes will be set for the individual cases in a match expression. This also applies to the `default` case when used in a match expression. Includes extensive unit tests. Note: at this point, not all unit tests will pass, this will be fixed in follow-on commits. --- package.xml | 6 + src/Tokenizers/PHP.php | 119 ++++ .../Core/Tokenizer/BackfillMatchTokenTest.inc | 307 +++++++++++ .../Core/Tokenizer/BackfillMatchTokenTest.php | 517 ++++++++++++++++++ 4 files changed, 949 insertions(+) create mode 100644 tests/Core/Tokenizer/BackfillMatchTokenTest.inc create mode 100644 tests/Core/Tokenizer/BackfillMatchTokenTest.php diff --git a/package.xml b/package.xml index 431592b90b..efb13e6d48 100644 --- a/package.xml +++ b/package.xml @@ -196,6 +196,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2100,6 +2102,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2176,6 +2180,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 7a62245929..92e7d94ab1 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -251,6 +251,13 @@ class PHP extends Tokenizer T_SWITCH => T_SWITCH, ], ], + T_MATCH => [ + 'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET], + 'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET], + 'strict' => true, + 'shared' => false, + 'with' => [], + ], T_START_HEREDOC => [ 'start' => [T_START_HEREDOC => T_START_HEREDOC], 'end' => [T_END_HEREDOC => T_END_HEREDOC], @@ -365,6 +372,7 @@ class PHP extends Tokenizer T_LOGICAL_AND => 3, T_LOGICAL_OR => 2, T_LOGICAL_XOR => 3, + T_MATCH => 5, T_METHOD_C => 10, T_MINUS_EQUAL => 2, T_POW_EQUAL => 3, @@ -1254,6 +1262,87 @@ protected function tokenize($string) continue; }//end if + /* + Backfill the T_MATCH token for PHP versions < 8.0 and + do initial correction for non-match expression T_MATCH tokens + to T_STRING for PHP >= 8.0. + A final check for non-match expression T_MATCH tokens is done + in PHP::processAdditional(). + */ + + if ($tokenIsArray === true + && (($token[0] === T_STRING + && strtolower($token[1]) === 'match') + || $token[0] === T_MATCH) + ) { + $isMatch = false; + for ($x = ($stackPtr + 1); $x < $numTokens; $x++) { + if (isset($tokens[$x][0], Util\Tokens::$emptyTokens[$tokens[$x][0]]) === true) { + continue; + } + + if ($tokens[$x] !== '(') { + // This is not a match expression. + break; + } + + // Next was an open parenthesis, now check what is before the match keyword. + for ($y = ($stackPtr - 1); $y >= 0; $y--) { + if (isset(Util\Tokens::$emptyTokens[$tokens[$y][0]]) === true) { + continue; + } + + if (is_array($tokens[$y]) === true + && ($tokens[$y][0] === T_PAAMAYIM_NEKUDOTAYIM + || $tokens[$y][0] === T_OBJECT_OPERATOR + || $tokens[$y][0] === T_NS_SEPARATOR + || $tokens[$y][0] === T_NEW + || $tokens[$y][0] === T_FUNCTION + || $tokens[$y][0] === T_CLASS + || $tokens[$y][0] === T_INTERFACE + || $tokens[$y][0] === T_TRAIT + || $tokens[$y][0] === T_NAMESPACE + || $tokens[$y][0] === T_CONST) + ) { + // This is not a match expression. + break 2; + } + + $isMatch = true; + break 2; + }//end for + }//end for + + if ($isMatch === true && $token[0] === T_STRING) { + $newToken = []; + $newToken['code'] = T_MATCH; + $newToken['type'] = 'T_MATCH'; + $newToken['content'] = $token[1]; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token $stackPtr changed from T_STRING to T_MATCH".PHP_EOL; + } + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + continue; + } else if ($isMatch === false && $token[0] === T_MATCH) { + // PHP 8.0, match keyword, but not a match expression. + $newToken = []; + $newToken['code'] = T_STRING; + $newToken['type'] = 'T_STRING'; + $newToken['content'] = $token[1]; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token $stackPtr changed from T_MATCH to T_STRING".PHP_EOL; + } + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + continue; + }//end if + }//end if + /* Convert ? to T_NULLABLE OR T_INLINE_THEN */ @@ -2265,6 +2354,36 @@ protected function processAdditional() } } + continue; + } else if ($this->tokens[$i]['code'] === T_MATCH) { + if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) { + // Not a match expression after all. + $this->tokens[$i]['code'] = T_STRING; + $this->tokens[$i]['type'] = 'T_STRING'; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token $i changed from T_MATCH to T_STRING".PHP_EOL; + } + + if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) { + $opener = $this->tokens[$i]['parenthesis_opener']; + $closer = $this->tokens[$i]['parenthesis_closer']; + unset( + $this->tokens[$opener]['parenthesis_owner'], + $this->tokens[$closer]['parenthesis_owner'] + ); + unset( + $this->tokens[$i]['parenthesis_opener'], + $this->tokens[$i]['parenthesis_closer'], + $this->tokens[$i]['parenthesis_owner'] + ); + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* cleaned parenthesis of token $i *".PHP_EOL; + } + } + }//end if + continue; } else if ($this->tokens[$i]['code'] === T_BITWISE_OR) { /* diff --git a/tests/Core/Tokenizer/BackfillMatchTokenTest.inc b/tests/Core/Tokenizer/BackfillMatchTokenTest.inc new file mode 100644 index 0000000000..3edc9cd5b6 --- /dev/null +++ b/tests/Core/Tokenizer/BackfillMatchTokenTest.inc @@ -0,0 +1,307 @@ + 'Zero', + 1 => 'One', + 2 => 'Two', + }; +} + +function matchNoTrailingComma($bool) { + /* testMatchNoTrailingComma */ + echo match ($bool) { + true => "true\n", + false => "false\n" + }; +} + +function matchWithDefault($i) { + /* testMatchWithDefault */ + return match ($i) { + 1 => 1, + 2 => 2, + default => 'default', + }; +} + +function matchExpressionInCondition($i) { + /* testMatchExpressionInCondition */ + return match (true) { + $i >= 50 => '50+', + $i >= 40 => '40-50', + $i >= 30 => '30-40', + $i >= 20 => '20-30', + $i >= 10 => '10-20', + default => '0-10', + }; +} + +function matchMultiCase($day) { + /* testMatchMultiCase */ + return match ($day) { + 1, 7 => false, + 2, 3, 4, 5, 6 => true, + }; +} + +function matchMultiCaseTrailingCommaInCase($bool) { + /* testMatchMultiCaseTrailingCommaInCase */ + echo match ($bool) { + false, + 0, + => "false\n", + true, + 1, + => "true\n", + default, + => "not bool\n", + }; +} + +assert((function () { + /* testMatchInClosureNotLowercase */ + Match ('foo') { + 'foo', 'bar' => false, + 'baz' => 'a', + default => 'b', + }; +})()); + +function matchInArrowFunction($x) { + /* testMatchInArrowFunction */ + $fn = fn($x) => match(true) { + 1, 2, 3, 4, 5 => 'foo', + default => 'bar', + }; +} + +function arrowFunctionInMatchNoTrailingComma($x) { + /* testArrowFunctionInMatchNoTrailingComma */ + return match ($x) { + 1 => fn($y) => callMe($y), + default => fn($y) => callThem($y) + }; +} + +/* testMatchInFunctionCallParamNotLowercase */ +var_dump(MATCH ( 'foo' ) { + 'foo' => dump_and_return('foo'), + 'bar' => dump_and_return('bar'), +}); + +/* testMatchInMethodCallParam */ +Test::usesValue(match(true) { true => $i }); + +/* testMatchDiscardResult */ +match (1) { + 1 => print "Executed\n", +}; + +/* testMatchWithDuplicateConditionsWithComments */ +echo match /*comment*/ ( $value /*comment*/ ) { + // Comment. + 2, 1 => '2, 1', + 1 => 1, + 3 => 3, + 4 => 4, + 5 => 5, +}; + +/* testNestedMatchOuter */ +$x = match ($y) { + /* testNestedMatchInner */ + default => match ($z) { 1 => 1 }, +}; + +/* testMatchInTernaryCondition */ +$x = match ($test) { 1 => 'a', 2 => 'b' } ? + /* testMatchInTernaryThen */ match ($test) { 1 => 'a', 2 => 'b' } : + /* testMatchInTernaryElse */ match ($notTest) { 3 => 'a', 4 => 'b' }; + +/* testMatchInArrayValue */ +$array = array( + match ($test) { 1 => 'a', 2 => 'b' }, +); + +/* testMatchInArrayKey */ +$array = [ + match ($test) { 1 => 'a', 2 => 'b' } => 'dynamic keys, woho!', +]; + +/* testMatchreturningArray */ +$matcher = match ($x) { + 0 => array( 0 => 1, 'a' => 2, 'b' => 3 ), + 1 => [1, 2, 3], + 2 => array( 1, [1, 2, 3], 2, 3), + 3 => [ 0 => 1, 'a' => array(1, 2, 3), 'b' => 2, 3], +}; + +/* testSwitchContainingMatch */ +switch ($something) { + /* testMatchWithDefaultNestedInSwitchCase1 */ + case 'foo': + $var = [1, 2, 3]; + $var = match ($i) { + 1 => 1, + default => 'default', + }; + continue 2; + + /* testMatchWithDefaultNestedInSwitchCase2 */ + case 'bar' ; + $i = callMe($a, $b); + return match ($i) { + 1 => 1, + default => 'default', + }; + + /* testMatchWithDefaultNestedInSwitchDefault */ + default: + echo 'something', match ($i) { + 1 => 1, + default => 'default', + }; + break; +} + +/* testMatchContainingSwitch */ +$x = match ($y) { + 5, 8 => function($z) { + /* testSwitchNestedInMatch1 */ + switch($z) { + case 'a': + $var = [1, 2, 3]; + return 'a'; + /* testSwitchDefaultNestedInMatchCase */ + default: + $i = callMe($a, $b); + return 'default1'; + } + }, + default => function($z) { + /* testSwitchNestedInMatch2 */ + switch($z) { + case 'a'; + $i = callMe($a, $b); + return 'b'; + /* testSwitchDefaultNestedInMatchDefault */ + default; + $var = [1, 2, 3]; + return 'default2'; + } + } +}; + +/* testMatchNoCases */ +// Intentional fatal error. +$x = match (true) {}; + +/* testMatchMultiDefault */ +// Intentional fatal error. +echo match (1) { + default => 'foo', + 1 => 'bar', + 2 => 'baz', + default => 'qux', +}; + +/* testNoMatchStaticMethodCall */ +$a = Foo::match($param); + +/* testNoMatchClassConstantAccess */ +$a = MyClass::MATCH; + +/* testNoMatchClassConstantArrayAccessMixedCase */ +$a = MyClass::Match[$a]; + +/* testNoMatchMethodCall */ +$a = $obj->match($param); + +/* testNoMatchMethodCallUpper */ +$a = $obj->MATCH()->chain($param); + +/* testNoMatchPropertyAccess */ +$a = $obj->match; + +/* testNoMatchNamespacedFunctionCall */ +// Intentional fatal error. +$a = MyNS\Sub\match($param); + +/* testNoMatchNamespaceOperatorFunctionCall */ +// Intentional fatal error. +$a = namespace\match($param); + +interface MatchInterface { + /* testNoMatchInterfaceMethodDeclaration */ + public static function match($param); +} + +class MatchClass { + /* testNoMatchClassConstantDeclarationLower */ + const match = 'a'; + + /* testNoMatchClassMethodDeclaration */ + public static function match($param) { + /* testNoMatchPropertyAssignment */ + $this->match = 'a'; + } +} + +/* testNoMatchClassInstantiation */ +$obj = new Match(); + +$anon = new class() { + /* testNoMatchAnonClassMethodDeclaration */ + protected function maTCH($param) { + } +}; + +/* testNoMatchClassDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +class Match {} + +/* testNoMatchInterfaceDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +interface Match {} + +/* testNoMatchTraitDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +trait Match {} + +/* testNoMatchConstantDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +const MATCH = '1'; + +/* testNoMatchFunctionDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +function match() {} + +/* testNoMatchNamespaceDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +namespace Match {} + +function brokenMatchNoCurlies($x) { + /* testNoMatchMissingCurlies */ + // Intentional fatal error. New control structure is not supported without curly braces. + return match ($x) + 0 => 'Zero', + 1 => 'One', + 2 => 'Two', + ; +} + +function brokenMatchAlternativeSyntax($x) { + /* testNoMatchAlternativeSyntax */ + // Intentional fatal error. Alternative syntax is not supported. + return match ($x) : + 0 => 'Zero', + 1 => 'One', + 2 => 'Two', + endmatch; +} + +/* testLiveCoding */ +// Intentional parse error. This has to be the last test in the file. +echo match diff --git a/tests/Core/Tokenizer/BackfillMatchTokenTest.php b/tests/Core/Tokenizer/BackfillMatchTokenTest.php new file mode 100644 index 0000000000..c9f7fad89d --- /dev/null +++ b/tests/Core/Tokenizer/BackfillMatchTokenTest.php @@ -0,0 +1,517 @@ + + * @copyright 2020-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; +use PHP_CodeSniffer\Util\Tokens; + +class BackfillMatchTokenTest extends AbstractMethodUnitTest +{ + + + /** + * Test tokenization of match expressions. + * + * @param string $testMarker The comment prefacing the target token. + * @param int $openerOffset The expected offset of the scope opener in relation to the testMarker. + * @param int $closerOffset The expected offset of the scope closer in relation to the testMarker. + * @param string $testContent The token content to look for. + * + * @dataProvider dataMatchExpression + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testMatchExpression($testMarker, $openerOffset, $closerOffset, $testContent='match') + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_STRING, T_MATCH], $testContent); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_MATCH, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH (code)'); + $this->assertSame('T_MATCH', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH (type)'); + + $this->scopeTestHelper($token, $openerOffset, $closerOffset); + $this->parenthesisTestHelper($token); + + }//end testMatchExpression() + + + /** + * Data provider. + * + * @see testMatchExpression() + * + * @return array + */ + public function dataMatchExpression() + { + return [ + 'simple_match' => [ + '/* testMatchSimple */', + 6, + 33, + ], + 'no_trailing_comma' => [ + '/* testMatchNoTrailingComma */', + 6, + 24, + ], + 'with_default_case' => [ + '/* testMatchWithDefault */', + 6, + 33, + ], + 'expression_in_condition' => [ + '/* testMatchExpressionInCondition */', + 6, + 77, + ], + 'multicase' => [ + '/* testMatchMultiCase */', + 6, + 40, + ], + 'multicase_trailing_comma_in_case' => [ + '/* testMatchMultiCaseTrailingCommaInCase */', + 6, + 47, + ], + 'in_closure_not_lowercase' => [ + '/* testMatchInClosureNotLowercase */', + 6, + 36, + 'Match', + ], + 'in_arrow_function' => [ + '/* testMatchInArrowFunction */', + 5, + 36, + ], + 'arrow_function_in_match_no_trailing_comma' => [ + '/* testArrowFunctionInMatchNoTrailingComma */', + 6, + 44, + ], + 'in_function_call_param_not_lowercase' => [ + '/* testMatchInFunctionCallParamNotLowercase */', + 8, + 32, + 'MATCH', + ], + 'in_method_call_param' => [ + '/* testMatchInMethodCallParam */', + 5, + 13, + ], + 'discard_result' => [ + '/* testMatchDiscardResult */', + 6, + 18, + ], + 'duplicate_conditions_and_comments' => [ + '/* testMatchWithDuplicateConditionsWithComments */', + 12, + 59, + ], + 'nested_match_outer' => [ + '/* testNestedMatchOuter */', + 6, + 33, + ], + 'nested_match_inner' => [ + '/* testNestedMatchInner */', + 6, + 14, + ], + 'ternary_condition' => [ + '/* testMatchInTernaryCondition */', + 6, + 21, + ], + 'ternary_then' => [ + '/* testMatchInTernaryThen */', + 6, + 21, + ], + 'ternary_else' => [ + '/* testMatchInTernaryElse */', + 6, + 21, + ], + 'array_value' => [ + '/* testMatchInArrayValue */', + 6, + 21, + ], + 'array_key' => [ + '/* testMatchInArrayKey */', + 6, + 21, + ], + 'returning_array' => [ + '/* testMatchreturningArray */', + 6, + 125, + ], + 'nested_in_switch_case_1' => [ + '/* testMatchWithDefaultNestedInSwitchCase1 */', + 6, + 25, + ], + 'nested_in_switch_case_2' => [ + '/* testMatchWithDefaultNestedInSwitchCase2 */', + 6, + 25, + ], + 'nested_in_switch_default' => [ + '/* testMatchWithDefaultNestedInSwitchDefault */', + 6, + 25, + ], + 'match_with_nested_switch' => [ + '/* testMatchContainingSwitch */', + 6, + 180, + ], + 'no_cases' => [ + '/* testMatchNoCases */', + 6, + 7, + ], + 'multi_default' => [ + '/* testMatchMultiDefault */', + 6, + 40, + ], + ]; + + }//end dataMatchExpression() + + + /** + * Verify that "match" keywords which are not match control structures get tokenized as T_STRING + * and don't have the extra token array indexes. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. + * + * @dataProvider dataNotAMatchStructure + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testNotAMatchStructure($testMarker, $testContent='match') + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_STRING, T_MATCH], $testContent); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)'); + $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)'); + + $this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set'); + $this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set'); + $this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set'); + $this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set'); + $this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set'); + $this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set'); + + $next = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($token + 1), null, true); + if ($next !== false && $tokens[$next]['code'] === T_OPEN_PARENTHESIS) { + $this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set for opener after'); + } + + }//end testNotAMatchStructure() + + + /** + * Data provider. + * + * @see testNotAMatchStructure() + * + * @return array + */ + public function dataNotAMatchStructure() + { + return [ + 'static_method_call' => ['/* testNoMatchStaticMethodCall */'], + 'class_constant_access' => [ + '/* testNoMatchClassConstantAccess */', + 'MATCH', + ], + 'class_constant_array_access' => [ + '/* testNoMatchClassConstantArrayAccessMixedCase */', + 'Match', + ], + 'method_call' => ['/* testNoMatchMethodCall */'], + 'method_call_uppercase' => [ + '/* testNoMatchMethodCallUpper */', + 'MATCH', + ], + 'property_access' => ['/* testNoMatchPropertyAccess */'], + 'namespaced_function_call' => ['/* testNoMatchNamespacedFunctionCall */'], + 'namespace_operator_function_call' => ['/* testNoMatchNamespaceOperatorFunctionCall */'], + 'interface_method_declaration' => ['/* testNoMatchInterfaceMethodDeclaration */'], + 'class_constant_declaration' => ['/* testNoMatchClassConstantDeclarationLower */'], + 'class_method_declaration' => ['/* testNoMatchClassMethodDeclaration */'], + 'property_assigment' => ['/* testNoMatchPropertyAssignment */'], + 'class_instantiation' => [ + '/* testNoMatchClassInstantiation */', + 'Match', + ], + 'anon_class_method_declaration' => [ + '/* testNoMatchAnonClassMethodDeclaration */', + 'maTCH', + ], + 'class_declaration' => [ + '/* testNoMatchClassDeclaration */', + 'Match', + ], + 'interface_declaration' => [ + '/* testNoMatchInterfaceDeclaration */', + 'Match', + ], + 'trait_declaration' => [ + '/* testNoMatchTraitDeclaration */', + 'Match', + ], + 'constant_declaration' => [ + '/* testNoMatchConstantDeclaration */', + 'MATCH', + ], + 'function_declaration' => ['/* testNoMatchFunctionDeclaration */'], + 'namespace_declaration' => [ + '/* testNoMatchNamespaceDeclaration */', + 'Match', + ], + 'unsupported_inline_control_structure' => ['/* testNoMatchMissingCurlies */'], + 'unsupported_alternative_syntax' => ['/* testNoMatchAlternativeSyntax */'], + 'live_coding' => ['/* testLiveCoding */'], + ]; + + }//end dataNotAMatchStructure() + + + /** + * Verify that the tokenization of switch structures is not affected by the backfill. + * + * @param string $testMarker The comment prefacing the target token. + * @param int $openerOffset The expected offset of the scope opener in relation to the testMarker. + * @param int $closerOffset The expected offset of the scope closer in relation to the testMarker. + * + * @dataProvider dataSwitchExpression + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testSwitchExpression($testMarker, $openerOffset, $closerOffset) + { + $token = $this->getTargetToken($testMarker, T_SWITCH); + + $this->scopeTestHelper($token, $openerOffset, $closerOffset); + $this->parenthesisTestHelper($token); + + }//end testSwitchExpression() + + + /** + * Data provider. + * + * @see testSwitchExpression() + * + * @return array + */ + public function dataSwitchExpression() + { + return [ + 'switch_containing_match' => [ + '/* testSwitchContainingMatch */', + 6, + 174, + ], + 'match_containing_switch_1' => [ + '/* testSwitchNestedInMatch1 */', + 5, + 63, + ], + 'match_containing_switch_2' => [ + '/* testSwitchNestedInMatch2 */', + 5, + 63, + ], + ]; + + }//end dataSwitchExpression() + + + /** + * Verify that the tokenization of a switch case/default structure containing a match structure + * or contained *in* a match structure is not affected by the backfill. + * + * @param string $testMarker The comment prefacing the target token. + * @param int $openerOffset The expected offset of the scope opener in relation to the testMarker. + * @param int $closerOffset The expected offset of the scope closer in relation to the testMarker. + * + * @dataProvider dataSwitchCaseVersusMatch + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testSwitchCaseVersusMatch($testMarker, $openerOffset, $closerOffset) + { + $token = $this->getTargetToken($testMarker, [T_CASE, T_DEFAULT]); + + $this->scopeTestHelper($token, $openerOffset, $closerOffset); + + }//end testSwitchCaseVersusMatch() + + + /** + * Data provider. + * + * @see testSwitchCaseVersusMatch() + * + * @return array + */ + public function dataSwitchCaseVersusMatch() + { + return [ + 'switch_with_nested_match_case_1' => [ + '/* testMatchWithDefaultNestedInSwitchCase1 */', + 3, + 55, + ], + 'switch_with_nested_match_case_2' => [ + '/* testMatchWithDefaultNestedInSwitchCase2 */', + 4, + 21, + ], + 'switch_with_nested_match_default_case' => [ + '/* testMatchWithDefaultNestedInSwitchDefault */', + 1, + 38, + ], + 'match_with_nested_switch_case' => [ + '/* testSwitchDefaultNestedInMatchCase */', + 1, + 18, + ], + 'match_with_nested_switch_default_case' => [ + '/* testSwitchDefaultNestedInMatchDefault */', + 1, + 20, + ], + ]; + + }//end dataSwitchCaseVersusMatch() + + + /** + * Helper function to verify that all scope related array indexes for a control structure + * are set correctly. + * + * @param string $token The control structure token to check. + * @param int $openerOffset The expected offset of the scope opener in relation to + * the control structure token. + * @param int $closerOffset The expected offset of the scope closer in relation to + * the control structure token. + * @param bool $skipScopeCloserCheck Whether to skip the scope closer check. + * This should be set to "true" when testing nested arrow functions, + * where the "inner" arrow function shares a scope closer with the + * "outer" arrow function, as the 'scope_condition' for the scope closer + * of the "inner" arrow function will point to the "outer" arrow function. + * + * @return void + */ + private function scopeTestHelper($token, $openerOffset, $closerOffset, $skipScopeCloserCheck=false) + { + $tokens = self::$phpcsFile->getTokens(); + $tokenArray = $tokens[$token]; + $tokenType = $tokenArray['type']; + $expectedScopeOpener = ($token + $openerOffset); + $expectedScopeCloser = ($token + $closerOffset); + + $this->assertArrayHasKey('scope_condition', $tokenArray, 'Scope condition is not set'); + $this->assertArrayHasKey('scope_opener', $tokenArray, 'Scope opener is not set'); + $this->assertArrayHasKey('scope_closer', $tokenArray, 'Scope closer is not set'); + $this->assertSame($token, $tokenArray['scope_condition'], 'Scope condition is not the '.$tokenType.' token'); + $this->assertSame($expectedScopeOpener, $tokenArray['scope_opener'], 'Scope opener of the '.$tokenType.' token incorrect'); + $this->assertSame($expectedScopeCloser, $tokenArray['scope_closer'], 'Scope closer of the '.$tokenType.' token incorrect'); + + $opener = $tokenArray['scope_opener']; + $this->assertArrayHasKey('scope_condition', $tokens[$opener], 'Opener scope condition is not set'); + $this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Opener scope opener is not set'); + $this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Opener scope closer is not set'); + $this->assertSame($token, $tokens[$opener]['scope_condition'], 'Opener scope condition is not the '.$tokenType.' token'); + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], $tokenType.' opener scope opener token incorrect'); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], $tokenType.' opener scope closer token incorrect'); + + $closer = $tokenArray['scope_closer']; + $this->assertArrayHasKey('scope_condition', $tokens[$closer], 'Closer scope condition is not set'); + $this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Closer scope opener is not set'); + $this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Closer scope closer is not set'); + if ($skipScopeCloserCheck === false) { + $this->assertSame($token, $tokens[$closer]['scope_condition'], 'Closer scope condition is not the '.$tokenType.' token'); + } + + $this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], $tokenType.' closer scope opener token incorrect'); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], $tokenType.' closer scope closer token incorrect'); + + if (($opener + 1) !== $closer) { + for ($i = ($opener + 1); $i < $closer; $i++) { + $this->assertArrayHasKey( + $token, + $tokens[$i]['conditions'], + $tokenType.' condition not added for token belonging to the '.$tokenType.' structure' + ); + } + } + + }//end scopeTestHelper() + + + /** + * Helper function to verify that all parenthesis related array indexes for a control structure + * token are set correctly. + * + * @param int $token The position of the control structure token. + * + * @return void + */ + private function parenthesisTestHelper($token) + { + $tokens = self::$phpcsFile->getTokens(); + $tokenArray = $tokens[$token]; + $tokenType = $tokenArray['type']; + + $this->assertArrayHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is not set'); + $this->assertArrayHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is not set'); + $this->assertArrayHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is not set'); + $this->assertSame($token, $tokenArray['parenthesis_owner'], 'Parenthesis owner is not the '.$tokenType.' token'); + + $opener = $tokenArray['parenthesis_opener']; + $this->assertArrayHasKey('parenthesis_owner', $tokens[$opener], 'Opening parenthesis owner is not set'); + $this->assertSame($token, $tokens[$opener]['parenthesis_owner'], 'Opening parenthesis owner is not the '.$tokenType.' token'); + + $closer = $tokenArray['parenthesis_closer']; + $this->assertArrayHasKey('parenthesis_owner', $tokens[$closer], 'Closing parenthesis owner is not set'); + $this->assertSame($token, $tokens[$closer]['parenthesis_owner'], 'Closing parenthesis owner is not the '.$tokenType.' token'); + + }//end parenthesisTestHelper() + + +}//end class From 694c5696d1b962303e5288d57b46d9d173ea1606 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 21 Feb 2021 17:47:21 +0100 Subject: [PATCH 226/733] PHP 8.0 | Tokenizer/PHP: retokenize `default` keywords within match expressions as `T_MATCH_DEFAULT` The `default` keyword in match expressions is tokenized as `T_DEFAULT` by PHP. However, for `switch` control structures, `default` (and `case`) are treated as scope owners/openers by PHPCS. If the `default` keyword tokenization in match expressions was left as-is, the `Tokenizer::recurseScopeMap()` would search for the wrong scope opener/closer, throwing the scope setting for scope owners in the rest of the file off. Adding the typical opener/closers for match expression `default` cases to the `PHP::$scopeOpeners` array would not prevent this and once the scope setting is out of kilter, a lot of sniffs start failing, including the `ScopeIndent` sniffs, `ScopeCloserBrace` sniffs etc. The only stable solution I could find to prevent the scope setting potentially getting borked and existing sniffs breaking badly, was to retokenize the `default` keyword when used in a `match` expression to `T_MATCH_DEFAULT`. Includes a separate set of unit tests which specifically tests the tokenization of the `default` keyword and verifies both the retokenization to `T_MATCH_DEFAULT` when used in `match` expressions, as well as the tokenization of the `default` keyword within `switch` control structures, including the scope setting for the `default` case. --- package.xml | 6 + src/Tokenizers/PHP.php | 43 +++++ src/Util/Tokens.php | 1 + tests/Core/Tokenizer/DefaultKeywordTest.inc | 93 ++++++++++ tests/Core/Tokenizer/DefaultKeywordTest.php | 185 ++++++++++++++++++++ 5 files changed, 328 insertions(+) create mode 100644 tests/Core/Tokenizer/DefaultKeywordTest.inc create mode 100644 tests/Core/Tokenizer/DefaultKeywordTest.php diff --git a/package.xml b/package.xml index efb13e6d48..d46ce4c7ff 100644 --- a/package.xml +++ b/package.xml @@ -202,6 +202,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2108,6 +2110,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2186,6 +2190,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 92e7d94ab1..24be2b09ef 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -373,6 +373,7 @@ class PHP extends Tokenizer T_LOGICAL_OR => 2, T_LOGICAL_XOR => 3, T_MATCH => 5, + T_MATCH_DEFAULT => 7, T_METHOD_C => 10, T_MINUS_EQUAL => 2, T_POW_EQUAL => 3, @@ -1343,6 +1344,48 @@ protected function tokenize($string) }//end if }//end if + /* + Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT + to prevent scope being set and the scope for switch default statements + breaking. + */ + + if ($tokenIsArray === true + && $token[0] === T_DEFAULT + ) { + 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; + } + } + + if (isset($tokens[$x]) === true + && is_array($tokens[$x]) === true + && $tokens[$x][0] === T_DOUBLE_ARROW + ) { + $newToken = []; + $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_MATCH_DEFAULT".PHP_EOL; + } + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + continue; + }//end if + }//end if + /* Convert ? to T_NULLABLE OR T_INLINE_THEN */ diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index d2b81ee895..ae47bc86f1 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -77,6 +77,7 @@ define('T_FN_ARROW', 'PHPCS_T_FN_ARROW'); define('T_TYPE_UNION', 'PHPCS_T_TYPE_UNION'); define('T_PARAM_NAME', 'PHPCS_T_PARAM_NAME'); +define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT'); // Some PHP 5.5 tokens, replicated for lower versions. if (defined('T_FINALLY') === false) { diff --git a/tests/Core/Tokenizer/DefaultKeywordTest.inc b/tests/Core/Tokenizer/DefaultKeywordTest.inc new file mode 100644 index 0000000000..dca7a811ef --- /dev/null +++ b/tests/Core/Tokenizer/DefaultKeywordTest.inc @@ -0,0 +1,93 @@ + 1, + 2 => 2, + /* testSimpleMatchDefault */ + default => 'default', + }; +} + +function switchWithDefault($i) { + switch ($i) { + case 1: + return 1; + case 2: + return 2; + /* testSimpleSwitchDefault */ + default: + return 'default'; + } +} + +function switchWithDefaultAndCurlies($i) { + switch ($i) { + case 1: + return 1; + case 2: + return 2; + /* testSimpleSwitchDefaultWithCurlies */ + default: { + return 'default'; + } + } +} + +function matchWithDefaultInSwitch() { + switch ($something) { + case 'foo': + $var = [1, 2, 3]; + $var = match ($i) { + 1 => 1, + /* testMatchDefaultNestedInSwitchCase1 */ + default => 'default', + }; + continue; + + case 'bar' : + $i = callMe($a, $b); + return match ($i) { + 1 => 1, + /* testMatchDefaultNestedInSwitchCase2 */ + default => 'default', + }; + + /* testSwitchDefault */ + default; + echo 'something', match ($i) { + 1, => 1, + /* testMatchDefaultNestedInSwitchDefault */ + default, => 'default', + }; + break; + } +} + +function switchWithDefaultInMatch() { + $x = match ($y) { + 5, 8 => function($z) { + switch($z) { + case 'a'; + $var = [1, 2, 3]; + return 'a'; + /* testSwitchDefaultNestedInMatchCase */ + default: + $var = [1, 2, 3]; + return 'default1'; + } + }, + /* testMatchDefault */ + default => function($z) { + switch($z) { + case 'a': + $i = callMe($a, $b); + return 'b'; + /* testSwitchDefaultNestedInMatchDefault */ + default: + $i = callMe($a, $b); + return 'default2'; + } + } + }; +} diff --git a/tests/Core/Tokenizer/DefaultKeywordTest.php b/tests/Core/Tokenizer/DefaultKeywordTest.php new file mode 100644 index 0000000000..e49204d99d --- /dev/null +++ b/tests/Core/Tokenizer/DefaultKeywordTest.php @@ -0,0 +1,185 @@ + + * @copyright 2020-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 DefaultKeywordTest extends AbstractMethodUnitTest +{ + + + /** + * Test the retokenization of the `default` keyword for match structure to `T_MATCH_DEFAULT`. + * + * Note: Cases and default structures within a match structure do *NOT* get case/default scope + * conditions, in contrast to case and default structures in switch control structures. + * + * @param string $testMarker The comment prefacing the target token. + * + * @dataProvider dataMatchDefault + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap + * + * @return void + */ + public function testMatchDefault($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT]); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_MATCH_DEFAULT, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_DEFAULT (code)'); + $this->assertSame('T_MATCH_DEFAULT', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_DEFAULT (type)'); + + $this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set'); + $this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set'); + $this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set'); + + }//end testMatchDefault() + + + /** + * Data provider. + * + * @see testMatchDefault() + * + * @return array + */ + public function dataMatchDefault() + { + return [ + 'simple_match_default' => ['/* testSimpleMatchDefault */'], + 'match_default_in_switch_case_1' => ['/* testMatchDefaultNestedInSwitchCase1 */'], + 'match_default_in_switch_case_2' => ['/* testMatchDefaultNestedInSwitchCase2 */'], + 'match_default_in_switch_default' => ['/* testMatchDefaultNestedInSwitchDefault */'], + 'match_default_containing_switch' => ['/* testMatchDefault */'], + ]; + + }//end dataMatchDefault() + + + /** + * Verify that the retokenization of `T_DEFAULT` tokens in match constructs, doesn't negatively + * impact the tokenization of `T_DEFAULT` tokens in switch control structures. + * + * Note: Cases and default structures within a switch control structure *do* get case/default scope + * conditions. + * + * @param string $testMarker The comment prefacing the target token. + * @param int $openerOffset The expected offset of the scope opener in relation to the testMarker. + * @param int $closerOffset The expected offset of the scope closer in relation to the testMarker. + * @param int|null $conditionStop The expected offset at which tokens stop having T_DEFAULT as a scope condition. + * + * @dataProvider dataSwitchDefault + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap + * + * @return void + */ + public function testSwitchDefault($testMarker, $openerOffset, $closerOffset, $conditionStop=null) + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT]); + $tokenArray = $tokens[$token]; + $expectedScopeOpener = ($token + $openerOffset); + $expectedScopeCloser = ($token + $closerOffset); + + $this->assertSame(T_DEFAULT, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_DEFAULT (code)'); + $this->assertSame('T_DEFAULT', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_DEFAULT (type)'); + + $this->assertArrayHasKey('scope_condition', $tokenArray, 'Scope condition is not set'); + $this->assertArrayHasKey('scope_opener', $tokenArray, 'Scope opener is not set'); + $this->assertArrayHasKey('scope_closer', $tokenArray, 'Scope closer is not set'); + $this->assertSame($token, $tokenArray['scope_condition'], 'Scope condition is not the T_DEFAULT token'); + $this->assertSame($expectedScopeOpener, $tokenArray['scope_opener'], 'Scope opener of the T_DEFAULT token incorrect'); + $this->assertSame($expectedScopeCloser, $tokenArray['scope_closer'], 'Scope closer of the T_DEFAULT token incorrect'); + + $opener = $tokenArray['scope_opener']; + $this->assertArrayHasKey('scope_condition', $tokens[$opener], 'Opener scope condition is not set'); + $this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Opener scope opener is not set'); + $this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Opener scope closer is not set'); + $this->assertSame($token, $tokens[$opener]['scope_condition'], 'Opener scope condition is not the T_DEFAULT token'); + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'T_DEFAULT opener scope opener token incorrect'); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'T_DEFAULT opener scope closer token incorrect'); + + $closer = $tokenArray['scope_closer']; + $this->assertArrayHasKey('scope_condition', $tokens[$closer], 'Closer scope condition is not set'); + $this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Closer scope opener is not set'); + $this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Closer scope closer is not set'); + $this->assertSame($token, $tokens[$closer]['scope_condition'], 'Closer scope condition is not the T_DEFAULT token'); + $this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], 'T_DEFAULT closer scope opener token incorrect'); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'T_DEFAULT closer scope closer token incorrect'); + + if (($opener + 1) !== $closer) { + $end = $closer; + if (isset($conditionStop) === true) { + $end = $conditionStop; + } + + for ($i = ($opener + 1); $i < $end; $i++) { + $this->assertArrayHasKey( + $token, + $tokens[$i]['conditions'], + 'T_DEFAULT condition not added for token belonging to the T_DEFAULT structure' + ); + } + } + + }//end testSwitchDefault() + + + /** + * Data provider. + * + * @see testSwitchDefault() + * + * @return array + */ + public function dataSwitchDefault() + { + return [ + 'simple_switch_default' => [ + '/* testSimpleSwitchDefault */', + 1, + 4, + ], + 'simple_switch_default_with_curlies' => [ + // For a default structure with curly braces, the scope opener + // will be the open curly and the closer the close curly. + // However, scope conditions will not be set for open to close, + // but only for the open token up to the "break/return/continue" etc. + '/* testSimpleSwitchDefaultWithCurlies */', + 3, + 12, + 6, + ], + 'switch_default_toplevel' => [ + '/* testSwitchDefault */', + 1, + 43, + ], + 'switch_default_nested_in_match_case' => [ + '/* testSwitchDefaultNestedInMatchCase */', + 1, + 20, + ], + 'switch_default_nested_in_match_default' => [ + '/* testSwitchDefaultNestedInMatchDefault */', + 1, + 18, + ], + ]; + + }//end dataSwitchDefault() + + +}//end class From 0fa0e018dc638e585fa04d08fa9280fc1b758097 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 20 Feb 2021 15:57:25 +0100 Subject: [PATCH 227/733] PHP 8.0 | Tokenizer/PHP: add support for match expressions in combination with arrow functions As a match expression can be nested in an arrow function and visa versa, setting the scope openers/closers becomes _interesting_. If the arrow function would be allowed to take over, it would break the scope setting for match expression, making the `scope_closer` and `scope_condition` indexes next to useless. So in the interest of making things easier for sniff writers and keeping in mind that scope setting for arrow functions isn't perfect anyway, preference is given to keeping the scope setting for match structures intact. This means that if a match expression is in the return value of an arrow function, like so: ```php $fn = fn($x) => match($y) {...}; ``` ... the token _after_ the closing curly belonging to the `match` will be considered the scope closer for the arrow function. In this example, that is the `;` (semicolon). While, if an arrow function is the return value of one of the match expression cases, generally speaking the comma at the end of the case will be the scope closer for the arrow function. There is one exception to this: the comma after each case is optional for the last case in a match expression. In that situation, the last non-empty token will be considered the scope closer for the arrow function. Example: ```php $match = match($y) { default => fn($x) => $y * $x }; ``` In this example, the `$x` at the end of the arrow expression would be considered the scope closer for the arrow function. ```php $match = match($y) { default => fn($x) => ($y * $x) }; ``` And in this example, the parenthesis closer at the end of the arrow expression would be considered the scope closer for the arrow function. --- src/Tokenizers/PHP.php | 25 +++++ tests/Core/Tokenizer/BackfillFnTokenTest.inc | 40 +++++++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 103 +++++++++++++++++++ 3 files changed, 168 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 24be2b09ef..01171ed434 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2242,6 +2242,31 @@ protected function processAdditional() $lastEndToken = null; for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) { + // Arrow function closer should never be shared with the closer of a match + // control structure. + if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true + && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer'] + && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH + ) { + if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) { + // Match in return value of arrow function. Move on to the next token. + continue; + } + + // Arrow function as return value for the last match case without trailing comma. + if ($lastEndToken !== null) { + $scopeCloser = $lastEndToken; + break; + } + + for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) { + $scopeCloser = $lastNonEmpty; + break 2; + } + } + } + if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) { if ($lastEndToken !== null && $this->tokens[$scopeCloser]['code'] === T_CLOSE_PARENTHESIS diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index f470bb0c67..d9a03aaa66 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -90,6 +90,46 @@ $arrowWithUnionReturn = fn($param) : int|float => $param | 10; /* testTernary */ $fn = fn($a) => $a ? /* testTernaryThen */ fn() : string => 'a' : /* testTernaryElse */ fn() : string => 'b'; +function matchInArrow($x) { + /* testWithMatchValue */ + $fn = fn($x) => match(true) { + 1, 2, 3, 4, 5 => 'foo', + default => 'bar', + }; +} + +function matchInArrowAndMore($x) { + /* testWithMatchValueAndMore */ + $fn = fn($x) => match(true) { + 1, 2, 3, 4, 5 => 'foo', + default => 'bar', + } . 'suffix'; +} + +function arrowFunctionInMatchWithTrailingComma($x) { + return match ($x) { + /* testInMatchNotLastValue */ + 1 => fn($y) => callMe($y), + /* testInMatchLastValueWithTrailingComma */ + default => fn($y) => callThem($y), + }; +} + +function arrowFunctionInMatchNoTrailingComma1($x) { + return match ($x) { + 1 => fn($y) => callMe($y), + /* testInMatchLastValueNoTrailingComma1 */ + default => fn($y) => callThem($y) + }; +} + +function arrowFunctionInMatchNoTrailingComma2($x) { + return match ($x) { + /* testInMatchLastValueNoTrailingComma2 */ + default => fn($y) => 5 * $y + }; +} + /* testConstantDeclaration */ const FN = 'a'; diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index b3fb50878d..2836f85920 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -465,6 +465,109 @@ public function testTernary() }//end testTernary() + /** + * Test arrow function returning a match control structure. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testWithMatchValue() + { + $token = $this->getTargetToken('/* testWithMatchValue */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 5, 44); + + }//end testWithMatchValue() + + + /** + * Test arrow function returning a match control structure with something behind it. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testWithMatchValueAndMore() + { + $token = $this->getTargetToken('/* testWithMatchValueAndMore */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 5, 48); + + }//end testWithMatchValueAndMore() + + + /** + * Test match control structure returning arrow functions. + * + * @param string $testMarker The comment prefacing the target token. + * @param int $openerOffset The expected offset of the scope opener in relation to the testMarker. + * @param int $closerOffset The expected offset of the scope closer in relation to the testMarker. + * @param string $expectedCloserType The type of token expected for the scope closer. + * @param string $expectedCloserFriendlyName A friendly name for the type of token expected for the scope closer + * to be used in the error message for failing tests. + * + * @dataProvider dataInMatchValue + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testInMatchValue($testMarker, $openerOffset, $closerOffset, $expectedCloserType, $expectedCloserFriendlyName) + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, $openerOffset, $closerOffset, $expectedCloserFriendlyName); + + $this->assertSame($expectedCloserType, $tokens[($token + $closerOffset)]['type'], 'Mismatched scope closer type'); + + }//end testInMatchValue() + + + /** + * Data provider. + * + * @see testInMatchValue() + * + * @return array + */ + public function dataInMatchValue() + { + return [ + 'not_last_value' => [ + '/* testInMatchNotLastValue */', + 5, + 11, + 'T_COMMA', + 'comma', + ], + 'last_value_with_trailing_comma' => [ + '/* testInMatchLastValueWithTrailingComma */', + 5, + 11, + 'T_COMMA', + 'comma', + ], + 'last_value_without_trailing_comma_1' => [ + '/* testInMatchLastValueNoTrailingComma1 */', + 5, + 10, + 'T_CLOSE_PARENTHESIS', + 'close parenthesis', + ], + 'last_value_without_trailing_comma_2' => [ + '/* testInMatchLastValueNoTrailingComma2 */', + 5, + 11, + 'T_VARIABLE', + '$y variable', + ], + ]; + + }//end dataInMatchValue() + + /** * Test arrow function nested within a method declaration. * From 835343649c1cba2c2880afe314bf50408aa50b62 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 20 Feb 2021 16:11:00 +0100 Subject: [PATCH 228/733] Tokenizer/PHP: retokenize the match double arrow to T_MATCH_ARROW The double arrow in PHP is used in a number of contexts: * Long/short arrays with keys; * Long/short lists with keys; * In the `as` part of `foreach()` statements with keys; * For `yield` statements with keys; * For arrow functions as the scope opener; * And now for `match` expressions to separate the cases from the return value (body). As most of these constructs can be nested in each other - an arrow function in an array value, a match expression in a list key -, every sniff handling any of these constructs has to take a lot of care when searching for the double arrow for the construct they are handling, to prevent matching a double arrow belonging to another type of construct nested in the target construct. This type of detection and special handling has to be done in each individual sniff which in one way or another has to deal with the `T_DOUBLE_ARROW` token and can cause quite some processing overhead. With that in mind, the double arrow as a scope opener for arrow functions has previously already been retokenized to `T_FN_ARROW`. Following the same reasoning, I'm proposing to retokenize the double arrow which separates `match` case expressions from the body expression to `T_MATCH_ARROW`. This should make life easier for any sniff dealing with any of the above constructs and will prevent potential false positives being introduced for sniffs currently handling any of these constructs, but not yet updated to allow for match expressions. Includes a set of dedicated unit tests verifying the tokenization of the double arrow operator in all currently supported contexts, including in combined (nested) contexts. --- package.xml | 6 + src/Tokenizers/PHP.php | 51 ++++++ src/Util/Tokens.php | 1 + tests/Core/Tokenizer/DoubleArrowTest.inc | 221 ++++++++++++++++++++++ tests/Core/Tokenizer/DoubleArrowTest.php | 223 +++++++++++++++++++++++ 5 files changed, 502 insertions(+) create mode 100644 tests/Core/Tokenizer/DoubleArrowTest.inc create mode 100644 tests/Core/Tokenizer/DoubleArrowTest.php diff --git a/package.xml b/package.xml index d46ce4c7ff..794f7463db 100644 --- a/package.xml +++ b/package.xml @@ -204,6 +204,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2112,6 +2114,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2192,6 +2196,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 01171ed434..92427059b7 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -373,6 +373,7 @@ class PHP extends Tokenizer T_LOGICAL_OR => 2, T_LOGICAL_XOR => 3, T_MATCH => 5, + T_MATCH_ARROW => 2, T_MATCH_DEFAULT => 7, T_METHOD_C => 10, T_MINUS_EQUAL => 2, @@ -1371,6 +1372,15 @@ protected function tokenize($string) && 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; + } + $newToken = []; $newToken['code'] = T_MATCH_DEFAULT; $newToken['type'] = 'T_MATCH_DEFAULT'; @@ -2450,6 +2460,47 @@ protected function processAdditional() echo "\t\t* cleaned parenthesis of token $i *".PHP_EOL; } } + } else { + // Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`. + $searchFor = [ + T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET, + T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET, + T_OPEN_PARENTHESIS => T_OPEN_PARENTHESIS, + T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY, + T_DOUBLE_ARROW => T_DOUBLE_ARROW, + ]; + $searchFor += Util\Tokens::$scopeOpeners; + + for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) { + if (isset($searchFor[$this->tokens[$x]['code']]) === false) { + continue; + } + + if (isset($this->tokens[$x]['scope_closer']) === true) { + $x = $this->tokens[$x]['scope_closer']; + continue; + } + + if (isset($this->tokens[$x]['parenthesis_closer']) === true) { + $x = $this->tokens[$x]['parenthesis_closer']; + continue; + } + + if (isset($this->tokens[$x]['bracket_closer']) === true) { + $x = $this->tokens[$x]['bracket_closer']; + continue; + } + + // This must be a double arrow, but make sure anyhow. + if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) { + $this->tokens[$x]['code'] = T_MATCH_ARROW; + $this->tokens[$x]['type'] = 'T_MATCH_ARROW'; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL; + } + } + }//end for }//end if continue; diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index ae47bc86f1..7071259b71 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -77,6 +77,7 @@ define('T_FN_ARROW', 'PHPCS_T_FN_ARROW'); define('T_TYPE_UNION', 'PHPCS_T_TYPE_UNION'); define('T_PARAM_NAME', 'PHPCS_T_PARAM_NAME'); +define('T_MATCH_ARROW', 'PHPCS_T_MATCH_ARROW'); define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT'); // Some PHP 5.5 tokens, replicated for lower versions. diff --git a/tests/Core/Tokenizer/DoubleArrowTest.inc b/tests/Core/Tokenizer/DoubleArrowTest.inc new file mode 100644 index 0000000000..ad5e5798d9 --- /dev/null +++ b/tests/Core/Tokenizer/DoubleArrowTest.inc @@ -0,0 +1,221 @@ + 'Zero', + ); +} + +function simpleShortArray($x) { + return [ + /* testShortArrayArrowSimple */ + 0 => 'Zero', + ]; +} + +function simpleLongList($x) { + list( + /* testLongListArrowSimple */ + 0 => $a, + ) = $x; +} + +function simpleShortList($x) { + [ + /* testShortListArrowSimple */ + 0 => $a, + ] = $x; +} + +function simpleYield($x) { + $i = 0; + foreach (explode("\n", $x) as $line) { + /* testYieldArrowSimple */ + yield ++$i => $line; + } +} + +function simpleForeach($x) { + /* testForeachArrowSimple */ + foreach ($x as $k => $value) {} +} + +function simpleMatch($x) { + return match ($x) { + /* testMatchArrowSimpleSingleCase */ + 0 => 'Zero', + /* testMatchArrowSimpleMultiCase */ + 2, 4, 6 => 'Zero', + /* testMatchArrowSimpleSingleCaseWithTrailingComma */ + 1, => 'Zero', + /* testMatchArrowSimpleMultiCaseWithTrailingComma */ + 3, 5, => 'Zero', + }; +} + +function simpleArrowFunction($y) { + /* testFnArrowSimple */ + return fn ($y) => callMe($y); +} + +function matchNestedInMatch() { + $x = match ($y) { + /* testMatchArrowNestedMatchOuter */ + default, => match ($z) { + /* testMatchArrowNestedMatchInner */ + 1 => 1 + }, + }; +} + +function matchNestedInLongArrayValue() { + $array = array( + /* testLongArrayArrowWithNestedMatchValue1 */ + 'a' => match ($test) { + /* testMatchArrowInLongArrayValue1 */ + 1 => 'a', + /* testMatchArrowInLongArrayValue2 */ + 2 => 'b' + }, + /* testLongArrayArrowWithNestedMatchValue2 */ + $i => match ($test) { + /* testMatchArrowInLongArrayValue3 */ + 1 => 'a', + }, + ); +} + +function matchNestedInShortArrayValue() { + $array = [ + /* testShortArrayArrowWithNestedMatchValue1 */ + 'a' => match ($test) { + /* testMatchArrowInShortArrayValue1 */ + 1 => 'a', + /* testMatchArrowInShortArrayValue2 */ + 2 => 'b' + }, + /* testShortArrayArrowWithNestedMatchValue2 */ + $i => match ($test) { + /* testMatchArrowInShortArrayValue3 */ + 1 => 'a', + }, + ]; +} + +function matchNestedInLongArrayKey() { + $array = array( + match ($test) { /* testMatchArrowInLongArrayKey1 */ 1 => 'a', /* testMatchArrowInLongArrayKey2 */ 2 => 'b' } + /* testLongArrayArrowWithMatchKey */ + => 'dynamic keys, woho!', + ); +} + +function matchNestedInShortArrayKey() { + $array = [ + match ($test) { /* testMatchArrowInShortArrayKey1 */ 1 => 'a', /* testMatchArrowInShortArrayKey2 */ 2 => 'b' } + /* testShortArrayArrowWithMatchKey */ + => 'dynamic keys, woho!', + ]; +} + +function arraysNestedInMatch() { + $matcher = match ($x) { + /* testMatchArrowWithLongArrayBodyWithKeys */ + 0 => array( + /* testLongArrayArrowInMatchBody1 */ + 0 => 1, + /* testLongArrayArrowInMatchBody2 */ + 'a' => 2, + /* testLongArrayArrowInMatchBody3 */ + 'b' => 3 + ), + /* testMatchArrowWithShortArrayBodyWithoutKeys */ + 1 => [1, 2, 3], + /* testMatchArrowWithLongArrayBodyWithoutKeys */ + 2 => array( 1, [1, 2, 3], 2, 3), + /* testMatchArrowWithShortArrayBodyWithKeys */ + 3 => [ + /* testShortArrayArrowInMatchBody1 */ + 0 => 1, + /* testShortArrayArrowInMatchBody2 */ + 'a' => array(1, 2, 3), + /* testShortArrayArrowInMatchBody3 */ + 'b' => 2, + 3 + ], + /* testShortArrayArrowinMatchCase1 */ + [4 => 'a', /* testShortArrayArrowinMatchCase2 */ 5 => 6] + /* testMatchArrowWithShortArrayWithKeysAsCase */ + => 'match with array as case value', + /* testShortArrayArrowinMatchCase3 */ + [4 => 'a'], /* testLongArrayArrowinMatchCase4 */ array(5 => 6), + /* testMatchArrowWithMultipleArraysWithKeysAsCase */ + => 'match with multiple arrays as case value', + }; +} + +function matchNestedInArrowFunction($x) { + /* testFnArrowWithMatchInValue */ + $fn = fn($x) => match(true) { + /* testMatchArrowInFnBody1 */ + 1, 2, 3, 4, 5 => 'foo', + /* testMatchArrowInFnBody2 */ + default => 'bar', + }; +} + +function arrowFunctionsNestedInMatch($x) { + return match ($x) { + /* testMatchArrowWithFnBody1 */ + 1 => /* testFnArrowInMatchBody1 */ fn($y) => callMe($y), + /* testMatchArrowWithFnBody2 */ + default => /* testFnArrowInMatchBody2 */ fn($y) => callThem($y) + }; +} + +function matchShortArrayMismash() { + $array = [ + match ($test) { + /* testMatchArrowInComplexShortArrayKey1 */ + 1 => [ /* testShortArrayArrowInComplexMatchValueinShortArrayKey */ 1 => 'a'], + /* testMatchArrowInComplexShortArrayKey2 */ + 2 => 'b' + /* testShortArrayArrowInComplexMatchArrayMismash */ + } => match ($test) { + /* testMatchArrowInComplexShortArrayValue1 */ + 1 => [ /* testShortArrayArrowInComplexMatchValueinShortArrayValue */ 1 => 'a'], + /* testMatchArrowInComplexShortArrayValue1 */ + 2 => /* testFnArrowInComplexMatchValueInShortArrayValue */ fn($y) => callMe($y) + }, + ]; +} + + +function longListInMatch($x, $y) { + return match($x) { + /* testMatchArrowWithLongListBody */ + 1 => list('a' => $a, /* testLongListArrowInMatchBody */ 'b' => $b, 'c' => list('d' => $c)) = $y, + /* testLongListArrowInMatchCase */ + list('a' => $a, 'b' => $b) = $y /* testMatchArrowWithLongListInCase */ => 'something' + }; +} + +function shortListInMatch($x, $y) { + return match($x) { + /* testMatchArrowWithShortListBody */ + 1 => ['a' => $a, 'b' => $b, 'c' => /* testShortListArrowInMatchBody */ ['d' => $c]] = $y, + /* testShortListArrowInMatchCase */ + ['a' => $a, 'b' => $b] = $y /* testMatchArrowWithShortListInCase */ => 'something' + }; +} + +function matchInLongList() { + /* testMatchArrowInLongListKey */ + list(match($x) {1 => 1, 2 => 2} /* testLongListArrowWithMatchInKey */ => $a) = $array; +} + +function matchInShortList() { + /* testMatchArrowInShortListKey */ + [match($x) {1 => 1, 2 => 2} /* testShortListArrowWithMatchInKey */ => $a] = $array; +} diff --git a/tests/Core/Tokenizer/DoubleArrowTest.php b/tests/Core/Tokenizer/DoubleArrowTest.php new file mode 100644 index 0000000000..c5c7b04cc8 --- /dev/null +++ b/tests/Core/Tokenizer/DoubleArrowTest.php @@ -0,0 +1,223 @@ + + * @copyright 2020-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 DoubleArrowTest extends AbstractMethodUnitTest +{ + + + /** + * Test that "normal" double arrows are correctly tokenized as `T_DOUBLE_ARROW`. + * + * @param string $testMarker The comment prefacing the target token. + * + * @dataProvider dataDoubleArrow + * @coversNothing + * + * @return void + */ + public function testDoubleArrow($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_DOUBLE_ARROW, T_MATCH_ARROW, T_FN_ARROW]); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_DOUBLE_ARROW, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_DOUBLE_ARROW (code)'); + $this->assertSame('T_DOUBLE_ARROW', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_DOUBLE_ARROW (type)'); + + }//end testDoubleArrow() + + + /** + * Data provider. + * + * @see testDoubleArrow() + * + * @return array + */ + public function dataDoubleArrow() + { + return [ + 'simple_long_array' => ['/* testLongArrayArrowSimple */'], + 'simple_short_array' => ['/* testShortArrayArrowSimple */'], + 'simple_long_list' => ['/* testLongListArrowSimple */'], + 'simple_short_list' => ['/* testShortListArrowSimple */'], + 'simple_yield' => ['/* testYieldArrowSimple */'], + 'simple_foreach' => ['/* testForeachArrowSimple */'], + + 'long_array_with_match_value_1' => ['/* testLongArrayArrowWithNestedMatchValue1 */'], + 'long_array_with_match_value_2' => ['/* testLongArrayArrowWithNestedMatchValue2 */'], + 'short_array_with_match_value_1' => ['/* testShortArrayArrowWithNestedMatchValue1 */'], + 'short_array_with_match_value_2' => ['/* testShortArrayArrowWithNestedMatchValue2 */'], + + 'long_array_with_match_key' => ['/* testLongArrayArrowWithMatchKey */'], + 'short_array_with_match_key' => ['/* testShortArrayArrowWithMatchKey */'], + + 'long_array_in_match_body_1' => ['/* testLongArrayArrowInMatchBody1 */'], + 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody2 */'], + 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody3 */'], + 'short_array_in_match_body_1' => ['/* testShortArrayArrowInMatchBody1 */'], + 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody2 */'], + 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody3 */'], + + 'short_array_in_match_case_1' => ['/* testShortArrayArrowinMatchCase1 */'], + 'short_array_in_match_case_2' => ['/* testShortArrayArrowinMatchCase2 */'], + 'short_array_in_match_case_3' => ['/* testShortArrayArrowinMatchCase3 */'], + 'long_array_in_match_case_4' => ['/* testLongArrayArrowinMatchCase4 */'], + + 'in_complex_short_array_key_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayKey */'], + 'in_complex_short_array_toplevel' => ['/* testShortArrayArrowInComplexMatchArrayMismash */'], + 'in_complex_short_array_value_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayValue */'], + + 'long_list_in_match_body' => ['/* testLongListArrowInMatchBody */'], + 'long_list_in_match_case' => ['/* testLongListArrowInMatchCase */'], + 'short_list_in_match_body' => ['/* testShortListArrowInMatchBody */'], + 'short_list_in_match_case' => ['/* testShortListArrowInMatchCase */'], + 'long_list_with_match_in_key' => ['/* testLongListArrowWithMatchInKey */'], + 'short_list_with_match_in_key' => ['/* testShortListArrowWithMatchInKey */'], + ]; + + }//end dataDoubleArrow() + + + /** + * Test that double arrows in match expressions which are the demarkation between a case and the return value + * are correctly tokenized as `T_MATCH_ARROW`. + * + * @param string $testMarker The comment prefacing the target token. + * + * @dataProvider dataMatchArrow + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testMatchArrow($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_DOUBLE_ARROW, T_MATCH_ARROW, T_FN_ARROW]); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_MATCH_ARROW, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_ARROW (code)'); + $this->assertSame('T_MATCH_ARROW', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_ARROW (type)'); + + }//end testMatchArrow() + + + /** + * Data provider. + * + * @see testMatchArrow() + * + * @return array + */ + public function dataMatchArrow() + { + return [ + 'single_case' => ['/* testMatchArrowSimpleSingleCase */'], + 'multi_case' => ['/* testMatchArrowSimpleMultiCase */'], + 'single_case_with_trailing_comma' => ['/* testMatchArrowSimpleSingleCaseWithTrailingComma */'], + 'multi_case_with_trailing_comma' => ['/* testMatchArrowSimpleMultiCaseWithTrailingComma */'], + 'match_nested_outer' => ['/* testMatchArrowNestedMatchOuter */'], + 'match_nested_inner' => ['/* testMatchArrowNestedMatchInner */'], + + 'in_long_array_value_1' => ['/* testMatchArrowInLongArrayValue1 */'], + 'in_long_array_value_2' => ['/* testMatchArrowInLongArrayValue2 */'], + 'in_long_array_value_3' => ['/* testMatchArrowInLongArrayValue3 */'], + 'in_short_array_value_1' => ['/* testMatchArrowInShortArrayValue1 */'], + 'in_short_array_value_2' => ['/* testMatchArrowInShortArrayValue2 */'], + 'in_short_array_value_3' => ['/* testMatchArrowInShortArrayValue3 */'], + + 'in_long_array_key_1' => ['/* testMatchArrowInLongArrayKey1 */'], + 'in_long_array_key_2' => ['/* testMatchArrowInLongArrayKey2 */'], + 'in_short_array_key_1' => ['/* testMatchArrowInShortArrayKey1 */'], + 'in_short_array_key_2' => ['/* testMatchArrowInShortArrayKey2 */'], + + 'with_long_array_value_with_keys' => ['/* testMatchArrowWithLongArrayBodyWithKeys */'], + 'with_short_array_value_without_keys' => ['/* testMatchArrowWithShortArrayBodyWithoutKeys */'], + 'with_long_array_value_without_keys' => ['/* testMatchArrowWithLongArrayBodyWithoutKeys */'], + 'with_short_array_value_with_keys' => ['/* testMatchArrowWithShortArrayBodyWithKeys */'], + + 'with_short_array_with_keys_as_case' => ['/* testMatchArrowWithShortArrayWithKeysAsCase */'], + 'with_multiple_arrays_with_keys_as_case' => ['/* testMatchArrowWithMultipleArraysWithKeysAsCase */'], + + 'in_fn_body_case' => ['/* testMatchArrowInFnBody1 */'], + 'in_fn_body_default' => ['/* testMatchArrowInFnBody2 */'], + 'with_fn_body_case' => ['/* testMatchArrowWithFnBody1 */'], + 'with_fn_body_default' => ['/* testMatchArrowWithFnBody2 */'], + + 'in_complex_short_array_key_1' => ['/* testMatchArrowInComplexShortArrayKey1 */'], + 'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayKey2 */'], + 'in_complex_short_array_value_1' => ['/* testMatchArrowInComplexShortArrayValue1 */'], + 'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayValue1 */'], + + 'with_long_list_in_body' => ['/* testMatchArrowWithLongListBody */'], + 'with_long_list_in_case' => ['/* testMatchArrowWithLongListInCase */'], + 'with_short_list_in_body' => ['/* testMatchArrowWithShortListBody */'], + 'with_short_list_in_case' => ['/* testMatchArrowWithShortListInCase */'], + 'in_long_list_key' => ['/* testMatchArrowInLongListKey */'], + 'in_short_list_key' => ['/* testMatchArrowInShortListKey */'], + ]; + + }//end dataMatchArrow() + + + /** + * Test that double arrows used as the scope opener for an arrow function + * are correctly tokenized as `T_FN_ARROW`. + * + * @param string $testMarker The comment prefacing the target token. + * + * @dataProvider dataFnArrow + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testFnArrow($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_DOUBLE_ARROW, T_MATCH_ARROW, T_FN_ARROW]); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_FN_ARROW, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_FN_ARROW (code)'); + $this->assertSame('T_FN_ARROW', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_FN_ARROW (type)'); + + }//end testFnArrow() + + + /** + * Data provider. + * + * @see testFnArrow() + * + * @return array + */ + public function dataFnArrow() + { + return [ + 'simple_fn' => ['/* testFnArrowSimple */'], + + 'with_match_as_value' => ['/* testFnArrowWithMatchInValue */'], + 'in_match_value_case' => ['/* testFnArrowInMatchBody1 */'], + 'in_match_value_default' => ['/* testFnArrowInMatchBody2 */'], + + 'in_complex_match_value_in_short_array' => ['/* testFnArrowInComplexMatchValueInShortArrayValue */'], + ]; + + }//end dataFnArrow() + + +}//end class From 8b0137a78cfae9b8fe1c8afdf5396c8a8d6a25af Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 23 Feb 2021 11:45:17 +1100 Subject: [PATCH 229/733] Changelog for #3226 --- package.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/package.xml b/package.xml index 794f7463db..c22f61893f 100644 --- a/package.xml +++ b/package.xml @@ -35,6 +35,14 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Added support for PHP 8.0 dereferencing of text strings with interpolated variables -- Thanks to Juliette Reinders Folmer for the patch + - Added support for PHP 8.0 match expressions + -- Match expressions are now tokenised with parenthesis and scope openers and closers + --- Sniffs can listen for the T_MATCH token to process match expressions + --- Note that the case and default statements inside match expressions do not have scopes set + -- A new T_MATCH_ARROW token is available to represent the arrows in match expressions + -- A new T_MATCH_DEFAULT token is available to represent the default keyword in match expressions + -- All tokenizing of match expressions has been backfilled for older PHP versions + -- Thanks to Juliette Reinders Folmer for the patch - The value of the T_FN_ARROW token has changed from "T_FN_ARROW" to "PHPCS_T_FN_ARROW" to avoid package conflicts -- This will have no impact on custom sniffs unless they are specifically looking at the value of the T_FN_ARROW constant -- If sniffs are just using constant to find arrow functions, they will continue to work without modification From 580e9debde5ddf958a2a9be30843198e764f8695 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 03:25:30 +0200 Subject: [PATCH 230/733] PHP 8.0 | Generic/AssignmentInCondition: include match expressions Allows the sniff to also check for assignments in condition in the condition part of a match expression. Includes unit test. --- .../Generic/Sniffs/CodeAnalysis/AssignmentInConditionSniff.php | 1 + .../Tests/CodeAnalysis/AssignmentInConditionUnitTest.inc | 2 ++ .../Tests/CodeAnalysis/AssignmentInConditionUnitTest.php | 1 + 3 files changed, 4 insertions(+) diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/AssignmentInConditionSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/AssignmentInConditionSniff.php index 63e831949e..8788a912ea 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/AssignmentInConditionSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/AssignmentInConditionSniff.php @@ -60,6 +60,7 @@ public function register() T_SWITCH, T_CASE, T_WHILE, + T_MATCH, ]; }//end register() diff --git a/src/Standards/Generic/Tests/CodeAnalysis/AssignmentInConditionUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/AssignmentInConditionUnitTest.inc index 49c6cfa77e..9560abbbb1 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/AssignmentInConditionUnitTest.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/AssignmentInConditionUnitTest.inc @@ -91,3 +91,5 @@ while ( $sample = false ) {} if ($a = 123) : endif; + +match ($a[0] = 123) {}; diff --git a/src/Standards/Generic/Tests/CodeAnalysis/AssignmentInConditionUnitTest.php b/src/Standards/Generic/Tests/CodeAnalysis/AssignmentInConditionUnitTest.php index e489f1c35e..3ed57d2207 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/AssignmentInConditionUnitTest.php +++ b/src/Standards/Generic/Tests/CodeAnalysis/AssignmentInConditionUnitTest.php @@ -74,6 +74,7 @@ public function getWarningList() 88 => 1, 90 => 1, 92 => 1, + 95 => 1, ]; }//end getWarningList() From 9c7ada4614cf0921b51856eda704775720a39aa2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 03:28:31 +0200 Subject: [PATCH 231/733] PHP 8.0 | Generic/EmptyStatement: include match expressions Allows the sniff to also check for empty match expressions. Includes unit test. --- .../Generic/Sniffs/CodeAnalysis/EmptyStatementSniff.php | 1 + .../Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.inc | 4 +++- .../Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.php | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyStatementSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyStatementSniff.php index d9248af39c..574bb0ba5a 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyStatementSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyStatementSniff.php @@ -50,6 +50,7 @@ public function register() T_IF, T_SWITCH, T_WHILE, + T_MATCH, ]; }//end register() diff --git a/src/Standards/Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.inc index 83780bce24..210788a1de 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.inc @@ -69,4 +69,6 @@ try { // TODO: Handle this exception later :-) } -if (true) {} elseif (false) {} \ No newline at end of file +if (true) {} elseif (false) {} + +match($foo) {}; diff --git a/src/Standards/Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.php b/src/Standards/Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.php index 9d89ce2456..4e00ada1ca 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.php +++ b/src/Standards/Generic/Tests/CodeAnalysis/EmptyStatementUnitTest.php @@ -39,6 +39,7 @@ public function getErrorList() 64 => 1, 68 => 1, 72 => 2, + 74 => 1, ]; }//end getErrorList() From 0b40b13d79cd150ed117ff93bc1dbe4d2293f9de Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 03:03:06 +0200 Subject: [PATCH 232/733] PHP 8.0 | Generic/LowerCaseKeyword: add match to keyword list This adds the PHP 8.0 `match` keyword to the list handled by this sniff, as well as the `default` keyword when used in a match control structure. Includes unit tests. --- src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php | 2 ++ .../Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc | 6 ++++++ .../Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed | 6 ++++++ .../Generic/Tests/PHP/LowerCaseKeywordUnitTest.php | 2 ++ 4 files changed, 16 insertions(+) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php index 26522b9a2a..e10047b64e 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php @@ -73,6 +73,8 @@ public function register() T_LOGICAL_AND, T_LOGICAL_OR, T_LOGICAL_XOR, + T_MATCH, + T_MATCH_DEFAULT, T_NAMESPACE, T_NEW, T_PARENT, diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc index 96c2062256..8d003c3bcc 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc @@ -29,5 +29,11 @@ class X extends Y { } } FN ($x) => $x; +$r = Match ($x) { + 1 => 1, + 2 => 2, + DEFAULT, => 3, +}; + __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 f7d3b397fe..bbe76b9e18 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed @@ -29,5 +29,11 @@ class X extends Y { } } fn ($x) => $x; +$r = match ($x) { + 1 => 1, + 2 => 2, + default, => 3, +}; + __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 2b9239062e..b272196b8d 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php @@ -38,6 +38,8 @@ public function getErrorList() 25 => 1, 28 => 1, 31 => 1, + 32 => 1, + 35 => 1, ]; }//end getErrorList() From f8fd8aeaa6dab9461cf74abdaf4ce47892b6c1ff Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 03:05:05 +0200 Subject: [PATCH 233/733] PHP 8.0 | PEAR/ControlSignature: check signature of match expressions This adds support for checking the signature of `match` expressions to this sniff. Includes unit test. --- .../Sniffs/ControlStructures/ControlSignatureSniff.php | 1 + .../Tests/ControlStructures/ControlSignatureUnitTest.inc | 8 +++++++- .../Tests/ControlStructures/ControlSignatureUnitTest.php | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Standards/PEAR/Sniffs/ControlStructures/ControlSignatureSniff.php b/src/Standards/PEAR/Sniffs/ControlStructures/ControlSignatureSniff.php index 5724592c69..edd2d6beb9 100644 --- a/src/Standards/PEAR/Sniffs/ControlStructures/ControlSignatureSniff.php +++ b/src/Standards/PEAR/Sniffs/ControlStructures/ControlSignatureSniff.php @@ -39,6 +39,7 @@ protected function getPatterns() '} elseif (...) {EOL', '} else {EOL', 'do {EOL', + 'match (...) {EOL', ]; }//end getPatterns() diff --git a/src/Standards/PEAR/Tests/ControlStructures/ControlSignatureUnitTest.inc b/src/Standards/PEAR/Tests/ControlStructures/ControlSignatureUnitTest.inc index 66ab925535..cc9903aa13 100644 --- a/src/Standards/PEAR/Tests/ControlStructures/ControlSignatureUnitTest.inc +++ b/src/Standards/PEAR/Tests/ControlStructures/ControlSignatureUnitTest.inc @@ -156,4 +156,10 @@ if ($i == 0) { } else { } -?> \ No newline at end of file + +// match +$r = match ($x) { + 1 => 1, +}; + +$r = match( $x ){ 1 => 1 }; diff --git a/src/Standards/PEAR/Tests/ControlStructures/ControlSignatureUnitTest.php b/src/Standards/PEAR/Tests/ControlStructures/ControlSignatureUnitTest.php index 5cb267672b..98c3463b7b 100644 --- a/src/Standards/PEAR/Tests/ControlStructures/ControlSignatureUnitTest.php +++ b/src/Standards/PEAR/Tests/ControlStructures/ControlSignatureUnitTest.php @@ -48,6 +48,7 @@ public function getErrorList() 133 => 2, 147 => 1, 157 => 1, + 165 => 1, ]; }//end getErrorList() From ad97d0cfa9285b469acd0021f2ff906edee2b118 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 14 Feb 2021 13:43:16 +0100 Subject: [PATCH 234/733] PHP 8.0 | PEAR/ScopeClosingBrace: add tests with match control structure --- .../PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc | 8 ++++++++ .../Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed | 9 +++++++++ .../PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php | 2 ++ 3 files changed, 19 insertions(+) diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc index ce211da453..c52023781f 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc +++ b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc @@ -144,3 +144,11 @@ switch ( $a ) { ?> getSummaryCount(); ?>
class="empty"> + + 'a', 2 => 'b' }; + +$match = match ($test) { + 1 => 'a', + 2 => 'b' + }; diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed index 05c8e8a258..23156e4100 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed @@ -148,3 +148,12 @@ switch ( $a ) { getSummaryCount(); ?>
class="empty"> + + 'a', 2 => 'b' +}; + +$match = match ($test) { + 1 => 'a', + 2 => 'b' +}; diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php index 9de3a69ea7..1b01ee9d4e 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php +++ b/src/Standards/PEAR/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php @@ -40,6 +40,8 @@ public function getErrorList() 135 => 1, 141 => 1, 146 => 1, + 149 => 1, + 154 => 1, ]; }//end getErrorList() From a5f9784b2906e1784022c2ac550aefe059aad681 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 23 Feb 2021 02:09:57 +0100 Subject: [PATCH 235/733] Tokenizer/PHP: minor bugfix for match tokenization Oops.. still missed that one, even though I'd run the tests on various PHP versions locally so many times.... luckily CI caught it anyhow with one of the sniffs. --- src/Tokenizers/PHP.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 92427059b7..383e974526 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1290,7 +1290,9 @@ protected function tokenize($string) // Next was an open parenthesis, now check what is before the match keyword. for ($y = ($stackPtr - 1); $y >= 0; $y--) { - if (isset(Util\Tokens::$emptyTokens[$tokens[$y][0]]) === true) { + if (is_array($tokens[$y]) === true + && isset(Util\Tokens::$emptyTokens[$tokens[$y][0]]) === true + ) { continue; } From 8bfae075c06d539dadddf625dd3fd691f2db8e50 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 03:51:33 +0200 Subject: [PATCH 236/733] PHP 8.0 | Squiz/LongConditionClosingComment: include match expressions Include match expressions in the control structures for which an end comment is expected and make sure the comment is after the semi-colon/comma. Includes unit test. --- .../LongConditionClosingCommentSniff.php | 12 ++++ .../LongConditionClosingCommentUnitTest.inc | 71 +++++++++++++++++++ ...gConditionClosingCommentUnitTest.inc.fixed | 71 +++++++++++++++++++ .../LongConditionClosingCommentUnitTest.php | 55 +++++++------- 4 files changed, 183 insertions(+), 26 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/LongConditionClosingCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/LongConditionClosingCommentSniff.php index 8d8c4f898a..103d90ffba 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/LongConditionClosingCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/LongConditionClosingCommentSniff.php @@ -38,6 +38,7 @@ class LongConditionClosingCommentSniff implements Sniff T_WHILE, T_TRY, T_CASE, + T_MATCH, ]; /** @@ -154,6 +155,17 @@ public function process(File $phpcsFile, $stackPtr) } while (isset($tokens[$nextToken]['scope_closer']) === true); } + if ($startCondition['code'] === T_MATCH) { + // Move the stackPtr to after the semi-colon/comma if there is one. + $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($nextToken !== false + && ($tokens[$nextToken]['code'] === T_SEMICOLON + || $tokens[$nextToken]['code'] === T_COMMA) + ) { + $stackPtr = $nextToken; + } + } + $lineDifference = ($endBrace['line'] - $startBrace['line']); $expected = sprintf($this->commentFormat, $startCondition['content']); diff --git a/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.inc index 732003b70a..d6c4cf6c87 100644 --- a/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.inc @@ -960,3 +960,74 @@ try { } finally { // some code here. } + +$expr = match ($foo) { + // Line 1 + // Line 2 + // Line 3 + // Line 4 + // Line 5 + // Line 6 + // Line 7 + // Line 8 + // Line 9 + // Line 10 + // Line 11 + // Line 12 + // Line 13 + // Line 14 + // Line 15 + // Line 16 + // Line 17 + // Line 18 + // Line 19 + // Line 20 +}; //end switch + +$expr = match ($foo) { + // Line 1 + // Line 2 + // Line 3 + // Line 4 + // Line 5 + // Line 6 + // Line 7 + // Line 8 + // Line 9 + // Line 10 + // Line 11 + // Line 12 + // Line 13 + // Line 14 + // Line 15 + // Line 16 + // Line 17 + // Line 18 + // Line 19 + // Line 20 +}; + +$array = [ + 'match' => match ($foo) { + // Line 1 + // Line 2 + // Line 3 + // Line 4 + // Line 5 + // Line 6 + // Line 7 + // Line 8 + // Line 9 + // Line 10 + // Line 11 + // Line 12 + // Line 13 + // Line 14 + // Line 15 + // Line 16 + // Line 17 + // Line 18 + // Line 19 + // Line 20 + }, +]; diff --git a/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.inc.fixed index 6edc34dfc4..176cfe2486 100644 --- a/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.inc.fixed @@ -960,3 +960,74 @@ try { } finally { // some code here. }//end try + +$expr = match ($foo) { + // Line 1 + // Line 2 + // Line 3 + // Line 4 + // Line 5 + // Line 6 + // Line 7 + // Line 8 + // Line 9 + // Line 10 + // Line 11 + // Line 12 + // Line 13 + // Line 14 + // Line 15 + // Line 16 + // Line 17 + // Line 18 + // Line 19 + // Line 20 +}; //end match + +$expr = match ($foo) { + // Line 1 + // Line 2 + // Line 3 + // Line 4 + // Line 5 + // Line 6 + // Line 7 + // Line 8 + // Line 9 + // Line 10 + // Line 11 + // Line 12 + // Line 13 + // Line 14 + // Line 15 + // Line 16 + // Line 17 + // Line 18 + // Line 19 + // Line 20 +};//end match + +$array = [ + 'match' => match ($foo) { + // Line 1 + // Line 2 + // Line 3 + // Line 4 + // Line 5 + // Line 6 + // Line 7 + // Line 8 + // Line 9 + // Line 10 + // Line 11 + // Line 12 + // Line 13 + // Line 14 + // Line 15 + // Line 16 + // Line 17 + // Line 18 + // Line 19 + // Line 20 + },//end match +]; diff --git a/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.php index 60d0292d23..5b61612c50 100644 --- a/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/LongConditionClosingCommentUnitTest.php @@ -30,32 +30,35 @@ public function getErrorList($testFile='LongConditionClosingCommentUnitTest.inc' switch ($testFile) { case 'LongConditionClosingCommentUnitTest.inc': return [ - 49 => 1, - 99 => 1, - 146 => 1, - 192 => 1, - 215 => 1, - 238 => 1, - 261 => 1, - 286 => 1, - 309 => 1, - 332 => 1, - 355 => 1, - 378 => 1, - 493 => 1, - 531 => 1, - 536 => 1, - 540 => 1, - 562 => 1, - 601 => 1, - 629 => 1, - 663 => 1, - 765 => 1, - 798 => 1, - 811 => 1, - 897 => 1, - 931 => 1, - 962 => 1, + 49 => 1, + 99 => 1, + 146 => 1, + 192 => 1, + 215 => 1, + 238 => 1, + 261 => 1, + 286 => 1, + 309 => 1, + 332 => 1, + 355 => 1, + 378 => 1, + 493 => 1, + 531 => 1, + 536 => 1, + 540 => 1, + 562 => 1, + 601 => 1, + 629 => 1, + 663 => 1, + 765 => 1, + 798 => 1, + 811 => 1, + 897 => 1, + 931 => 1, + 962 => 1, + 985 => 2, + 1008 => 1, + 1032 => 1, ]; break; case 'LongConditionClosingCommentUnitTest.js': From c222faefe6053768a43a59b6f4a3e7ed4ed71607 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 03:43:22 +0200 Subject: [PATCH 237/733] PHP 8.0 | Squiz/PostStatementComment: include match expressions Allows the sniff to make an exception for comments within the condition of a match expression, just like it does for other control structures. Includes unit test. --- .../Squiz/Sniffs/Commenting/PostStatementCommentSniff.php | 1 + .../Tests/Commenting/PostStatementCommentUnitTest.inc | 6 ++++++ .../Commenting/PostStatementCommentUnitTest.inc.fixed | 7 +++++++ .../Tests/Commenting/PostStatementCommentUnitTest.php | 1 + 4 files changed, 15 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php index 1164c6f234..ce19d5c1d9 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php @@ -40,6 +40,7 @@ class PostStatementCommentSniff implements Sniff T_WHILE => true, T_FOR => true, T_FOREACH => true, + T_MATCH => true, ]; diff --git a/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.inc index 5650e19b38..b83469613f 100644 --- a/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.inc @@ -46,3 +46,9 @@ for ( if ( $condition === true // comment && $anotherCondition === false ) {} + +$match = match($foo // comment + && $bar +) { + 1 => 1, // comment +}; diff --git a/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.inc.fixed index 99c9e92a29..21a4bbe032 100644 --- a/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.inc.fixed @@ -50,3 +50,10 @@ for ( if ( $condition === true // comment && $anotherCondition === false ) {} + +$match = match($foo // comment + && $bar +) { + 1 => 1, +// comment +}; diff --git a/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.php index fbc08007c9..a04426f15a 100644 --- a/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/PostStatementCommentUnitTest.php @@ -34,6 +34,7 @@ public function getErrorList($testFile='PostStatementCommentUnitTest.inc') 10 => 1, 18 => 1, 35 => 1, + 53 => 1, ]; case 'PostStatementCommentUnitTest.1.js': From b2ed11788275c376481ddee922c0c8899ca75d45 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 03:03:54 +0200 Subject: [PATCH 238/733] PHP 8.0 | Squiz/LowercaseDeclaration: add match to keyword list This adds the PHP 8.0 `match` keyword to the list handled by this sniff. Includes unit test. --- .../Sniffs/ControlStructures/LowercaseDeclarationSniff.php | 1 + .../Tests/ControlStructures/LowercaseDeclarationUnitTest.inc | 2 ++ .../ControlStructures/LowercaseDeclarationUnitTest.inc.fixed | 2 ++ .../Tests/ControlStructures/LowercaseDeclarationUnitTest.php | 1 + 4 files changed, 6 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/ControlStructures/LowercaseDeclarationSniff.php b/src/Standards/Squiz/Sniffs/ControlStructures/LowercaseDeclarationSniff.php index ae4a51e7a3..cba076840a 100644 --- a/src/Standards/Squiz/Sniffs/ControlStructures/LowercaseDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/ControlStructures/LowercaseDeclarationSniff.php @@ -34,6 +34,7 @@ public function register() T_WHILE, T_TRY, T_CATCH, + T_MATCH, ]; }//end register() diff --git a/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.inc b/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.inc index c82f35aee4..1acf3eac43 100644 --- a/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.inc +++ b/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.inc @@ -20,3 +20,5 @@ If ($condition) { TRY { } Catch (Exception $e) { } + +$r = MATCH ($x) {}; diff --git a/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.inc.fixed b/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.inc.fixed index 973781d77f..3c0d3c6378 100644 --- a/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.inc.fixed @@ -20,3 +20,5 @@ if ($condition) { try { } catch (Exception $e) { } + +$r = match ($x) {}; diff --git a/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.php b/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.php index 758119084f..dc8a3ac6c0 100644 --- a/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.php +++ b/src/Standards/Squiz/Tests/ControlStructures/LowercaseDeclarationUnitTest.php @@ -37,6 +37,7 @@ public function getErrorList() 16 => 1, 20 => 1, 21 => 1, + 24 => 1, ]; }//end getErrorList() From dffc59e984a2f4f6df151225854250ba6cdb034f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 03:06:56 +0200 Subject: [PATCH 239/733] PHP 8.0 | Squiz/ControlSignature: check signature of match expressions This adds support for checking the signature of `match` expressions to this sniff. Includes unit test. --- .../Sniffs/ControlStructures/ControlSignatureSniff.php | 1 + .../Tests/ControlStructures/ControlSignatureUnitTest.inc | 6 ++++++ .../ControlStructures/ControlSignatureUnitTest.inc.fixed | 7 +++++++ .../Tests/ControlStructures/ControlSignatureUnitTest.php | 1 + 4 files changed, 15 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/ControlStructures/ControlSignatureSniff.php b/src/Standards/Squiz/Sniffs/ControlStructures/ControlSignatureSniff.php index 032c59ace8..8a56b23fcb 100644 --- a/src/Standards/Squiz/Sniffs/ControlStructures/ControlSignatureSniff.php +++ b/src/Standards/Squiz/Sniffs/ControlStructures/ControlSignatureSniff.php @@ -53,6 +53,7 @@ public function register() T_ELSE, T_ELSEIF, T_SWITCH, + T_MATCH, ]; }//end register() diff --git a/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.inc b/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.inc index 249ae15847..8eaf1b0373 100644 --- a/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.inc +++ b/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.inc @@ -299,6 +299,12 @@ while ( $level-- ): ob_end_clean(); endwhile; +$r = match ($x) { + 1 => 1, +}; + +$r = match($x){1 => 1}; + // Intentional parse error. This should be the last test in the file. foreach // Some unrelated comment. diff --git a/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.inc.fixed b/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.inc.fixed index f0cdde7d78..dc5233d917 100644 --- a/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.inc.fixed @@ -302,6 +302,13 @@ while ( $level-- ): ob_end_clean(); endwhile; +$r = match ($x) { + 1 => 1, +}; + +$r = match ($x) { +1 => 1}; + // Intentional parse error. This should be the last test in the file. foreach // Some unrelated comment. diff --git a/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.php b/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.php index 07e57b4661..92427f0da1 100644 --- a/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.php +++ b/src/Standards/Squiz/Tests/ControlStructures/ControlSignatureUnitTest.php @@ -76,6 +76,7 @@ public function getErrorList($testFile='ControlSignatureUnitTest.inc') $errors[276] = 1; $errors[279] = 1; $errors[283] = 1; + $errors[306] = 3; }//end if return $errors; From 779de9fad57907521fccab770e0a3fa6338d265e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 05:13:43 +0200 Subject: [PATCH 240/733] PHP 8.0 | Squiz/OperatorBracket: make exception for match expressions Make the same exception for match expressions as was already in place for switch control structures. While at the same time safeguarding that the sniff will still apply itself correctly for code within a `match` expression, whether in a "case condition" or when in the value. Includes unit tests. --- .../Squiz/Sniffs/Formatting/OperatorBracketSniff.php | 6 +++--- .../Tests/Formatting/OperatorBracketUnitTest.inc | 12 ++++++++++++ .../Formatting/OperatorBracketUnitTest.inc.fixed | 12 ++++++++++++ .../Tests/Formatting/OperatorBracketUnitTest.php | 2 ++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index 2c650983ff..289bbfebbb 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -165,7 +165,7 @@ public function process(File $phpcsFile, $stackPtr) break; } - if ($prevCode === T_STRING || $prevCode === T_SWITCH) { + if ($prevCode === T_STRING || $prevCode === T_SWITCH || $prevCode === T_MATCH) { // We allow simple operations to not be bracketed. // For example, ceil($one / $two). for ($prev = ($stackPtr - 1); $prev > $bracket; $prev--) { @@ -204,8 +204,8 @@ public function process(File $phpcsFile, $stackPtr) if (in_array($prevCode, Tokens::$scopeOpeners, true) === true) { // This operation is inside a control structure like FOREACH // or IF, but has no bracket of it's own. - // The only control structure allowed to do this is SWITCH. - if ($prevCode !== T_SWITCH) { + // The only control structures allowed to do this are SWITCH and MATCH. + if ($prevCode !== T_SWITCH && $prevCode !== T_MATCH) { break; } } diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc index 6021889cec..18ad808ecf 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc @@ -176,3 +176,15 @@ $errorPos = $params[$x]?->getLine() + $commentStart; $foo = $this->gmail ?? $this->gmail = new Google_Service_Gmail($this->google); exit -1; + +$expr = match ($number - 10) { + -1 => 0, +}; + +$expr = match ($number % 10) { + 1 => 2 * $num, +}; + +$expr = match (true) { + $num * 100 > 500 => 'expression in key', +}; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed index 1215605e61..b031db886d 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed @@ -176,3 +176,15 @@ $errorPos = ($params[$x]?->getLine() + $commentStart); $foo = ($this->gmail ?? $this->gmail = new Google_Service_Gmail($this->google)); exit -1; + +$expr = match ($number - 10) { + -1 => 0, +}; + +$expr = match ($number % 10) { + 1 => (2 * $num), +}; + +$expr = match (true) { + ($num * 100) > 500 => 'expression in key', +}; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php index 1621cc9ea8..79b3e7dd19 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php @@ -68,6 +68,8 @@ public function getErrorList($testFile='OperatorBracketUnitTest.inc') 169 => 1, 174 => 1, 176 => 1, + 185 => 1, + 189 => 1, ]; break; case 'OperatorBracketUnitTest.js': From 87bc198573f4a1d260d3acb23a901df8d802be92 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 03:10:54 +0200 Subject: [PATCH 241/733] PHP 8.0 | Squiz/DisallowMultipleAssignments: correct errorcode for assignment in match expression The `Squiz.PHP.DisallowMultipleAssignments` sniff differentiates in the error code between "normal" assignments which aren't the only thing on a line and assignments in control structure conditions. This adds `match` to the list of control structures, so it gets the correct error code. Includes unit test. --- .../Sniffs/PHP/DisallowMultipleAssignmentsSniff.php | 1 + .../PHP/DisallowMultipleAssignmentsUnitTest.inc | 12 ++++++++++++ .../PHP/DisallowMultipleAssignmentsUnitTest.php | 2 ++ 3 files changed, 15 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php index 84032fec01..b0372837fc 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php @@ -165,6 +165,7 @@ public function process(File $phpcsFile, $stackPtr) T_SWITCH => T_SWITCH, T_CASE => T_CASE, T_FOR => T_FOR, + T_MATCH => T_MATCH, ]; foreach ($nested as $opener => $closer) { if (isset($tokens[$opener]['parenthesis_owner']) === true diff --git a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc index 98c1bb5661..6d86dc54b8 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc @@ -75,3 +75,15 @@ class Bar public bool $c = false, $d = true; protected int $e = 123, $f = 987; } + +switch ($b < 10 && $a = 10) { + case true: + break; +} + +$array = [ + match ($b < 10 && $a = 10) { + true => 10, + false => 0 + }, +]; diff --git a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.php b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.php index 919d4570d0..618d76efa4 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.php @@ -33,6 +33,8 @@ public function getErrorList() 12 => 1, 14 => 1, 15 => 1, + 79 => 1, + 85 => 1, ]; }//end getErrorList() From 3148f1b71c7f31374c0b4ccde66f3891d86c89bb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 14 Feb 2021 14:49:52 +0100 Subject: [PATCH 242/733] PHP 8.0 | Squiz/ObjectInstantiation: prevent false positives on match/fn expressions This change ensures that when `new Class` is returned by a match control structure or an arrow function, the sniff will not throw a false positive. Includes unit tests. --- .../Squiz/Sniffs/Objects/ObjectInstantiationSniff.php | 2 ++ .../Tests/Objects/ObjectInstantiationUnitTest.inc | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php b/src/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php index fb714e16cf..ea4970dbf8 100644 --- a/src/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php +++ b/src/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php @@ -50,6 +50,8 @@ public function process(File $phpcsFile, $stackPtr) $allowedTokens = [ T_EQUAL => true, T_DOUBLE_ARROW => true, + T_FN_ARROW => true, + T_MATCH_ARROW => true, T_THROW => true, T_RETURN => true, T_INLINE_THEN => true, diff --git a/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc b/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc index 8a5c232319..f58af275cd 100644 --- a/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc +++ b/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc @@ -13,5 +13,16 @@ function foo() { return new MyClass(); } $doodad = $x ? new Foo : new Bar; +function returnFn() { + $fn = fn($x) => new MyClass(); +} + +function returnMatch() { + $match = match($x) { + 0 => new MyClass() + } +} + +// Intentional parse error. This must be the last test in the file. function new ?> From a1bee01b26f12c97cae6d632b953419da646623e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 05:18:09 +0200 Subject: [PATCH 243/733] PHP 8.0 | Squiz/ControlStructureSpacing: check match expressions This adds support for checking the spacing for `match` expressions to the Squiz sniff. Includes unit test. --- .../WhiteSpace/ControlStructureSpacingSniff.php | 11 +++++++++++ .../WhiteSpace/ControlStructureSpacingUnitTest.inc | 10 ++++++++++ .../ControlStructureSpacingUnitTest.inc.fixed | 9 +++++++++ .../WhiteSpace/ControlStructureSpacingUnitTest.php | 3 +++ 4 files changed, 33 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php index cfeb657e39..f38fd0e89b 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php @@ -46,6 +46,7 @@ public function register() T_TRY, T_CATCH, T_FINALLY, + T_MATCH, ]; }//end register() @@ -232,6 +233,16 @@ public function process(File $phpcsFile, $stackPtr) }//end if }//end if + if ($tokens[$stackPtr]['code'] === T_MATCH) { + // Move the scope closer to the semicolon/comma. + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($scopeCloser + 1), null, true); + if ($next !== false + && ($tokens[$next]['code'] === T_SEMICOLON || $tokens[$next]['code'] === T_COMMA) + ) { + $scopeCloser = $next; + } + } + $trailingContent = $phpcsFile->findNext( T_WHITESPACE, ($scopeCloser + 1), diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc index 785a4a8cc3..0371ce49b4 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc @@ -251,3 +251,13 @@ echo 'hi'; ?> + + 1, + 2 => 2, + +}; +echo $expr; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed index 1674b3dbfa..ad4505c3e1 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed @@ -244,3 +244,12 @@ echo 'hi'; ?> + + 1, + 2 => 2, +}; + +echo $expr; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php index eeef2b0635..ac3f5d6ff5 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php @@ -57,6 +57,9 @@ public function getErrorList($testFile='ControlStructureSpacingUnitTest.inc') 242 => 1, 246 => 1, 248 => 1, + 257 => 3, + 261 => 1, + 262 => 1, ]; break; case 'ControlStructureSpacingUnitTest.js': From d2872c1ed35ad4077ec820171f895a11c0ccd34f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 14 Feb 2021 13:50:16 +0100 Subject: [PATCH 244/733] PHP 8.0 | Squiz/ScopeClosingBrace: add tests with match control structure --- .../Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc | 7 +++++++ .../Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed | 8 ++++++++ .../Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php | 2 ++ 3 files changed, 17 insertions(+) diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc index 95cd371706..b71c0be729 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc @@ -107,3 +107,10 @@ public function foo() } $fn1 = fn($x) => $x + $y; + +$match = match ($test) { 1 => 'a', 2 => 'b' }; + +$match = match ($test) { + 1 => 'a', + 2 => 'b' + }; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed index 1cdd313d53..b5d87d3d56 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed @@ -108,3 +108,11 @@ public function foo() } $fn1 = fn($x) => $x + $y; + +$match = match ($test) { 1 => 'a', 2 => 'b' +}; + +$match = match ($test) { + 1 => 'a', + 2 => 'b' +}; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php index 53b473bdef..668332562e 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php @@ -31,6 +31,8 @@ public function getErrorList() 24 => 1, 80 => 1, 102 => 1, + 111 => 1, + 116 => 1, ]; }//end getErrorList() From ab7af46b5cf32329804f927e425976d816c6b571 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Aug 2020 03:35:23 +0200 Subject: [PATCH 245/733] PHP 8.0 | PSR12/BooleanOperatorPlacement: include match expressions Allows the sniff to also check the placement of boolean operators in match expressions. Includes unit test. --- .../BooleanOperatorPlacementSniff.php | 1 + .../BooleanOperatorPlacementUnitTest.inc | 11 +++++++++++ .../BooleanOperatorPlacementUnitTest.inc.fixed | 11 +++++++++++ .../BooleanOperatorPlacementUnitTest.php | 1 + 4 files changed, 24 insertions(+) diff --git a/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php b/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php index 98f7f84930..b87c33918d 100644 --- a/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php +++ b/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php @@ -37,6 +37,7 @@ public function register() T_WHILE, T_SWITCH, T_ELSEIF, + T_MATCH, ]; }//end register() diff --git a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc index cc2ae92d78..4bc2eceba8 100644 --- a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc +++ b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc @@ -118,3 +118,14 @@ if ( ) { return 5; } + +// Reset to default. +// phpcs:set PSR12.ControlStructures.BooleanOperatorPlacement allowOnly + +match ( + $expr1 + && $expr2 && + $expr3 +) { + // structure body +}; diff --git a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc.fixed b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc.fixed index 19792a7642..5f4d223e84 100644 --- a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.inc.fixed @@ -128,3 +128,14 @@ if ( ) { return 5; } + +// Reset to default. +// phpcs:set PSR12.ControlStructures.BooleanOperatorPlacement allowOnly + +match ( + $expr1 + && $expr2 + && $expr3 +) { + // structure body +}; diff --git a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.php b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.php index ffd4d68574..3eeaeebc42 100644 --- a/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.php +++ b/src/Standards/PSR12/Tests/ControlStructures/BooleanOperatorPlacementUnitTest.php @@ -35,6 +35,7 @@ public function getErrorList() 90 => 1, 98 => 1, 104 => 1, + 125 => 1, ]; }//end getErrorList() From 6023ed6736c192df96e175d0757c64f6adc1c43d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 26 Jul 2020 03:06:27 +0200 Subject: [PATCH 246/733] PHP 8.0 | PSR2/PSR12/ControlStructureSpacing: check match expressions This adds support for checking the spacing inside single line conditions for `match` expressions to the PSR2 sniff and by extension to the PSR12 sniff, as well as explicitly for multiline conditions to the PSR12 sniff. Includes unit tests. --- .../ControlStructureSpacingSniff.php | 1 + .../ControlStructureSpacingUnitTest.inc | 14 ++++++++++++++ .../ControlStructureSpacingUnitTest.inc.fixed | 16 ++++++++++++++++ .../ControlStructureSpacingUnitTest.php | 4 ++++ .../ControlStructureSpacingSniff.php | 1 + .../ControlStructureSpacingUnitTest.inc | 11 +++++++++++ .../ControlStructureSpacingUnitTest.inc.fixed | 11 +++++++++++ .../ControlStructureSpacingUnitTest.php | 3 +++ 8 files changed, 61 insertions(+) diff --git a/src/Standards/PSR12/Sniffs/ControlStructures/ControlStructureSpacingSniff.php b/src/Standards/PSR12/Sniffs/ControlStructures/ControlStructureSpacingSniff.php index fb19f5694b..3d29c4ace1 100644 --- a/src/Standards/PSR12/Sniffs/ControlStructures/ControlStructureSpacingSniff.php +++ b/src/Standards/PSR12/Sniffs/ControlStructures/ControlStructureSpacingSniff.php @@ -41,6 +41,7 @@ public function register() T_ELSE, T_ELSEIF, T_CATCH, + T_MATCH, ]; }//end register() diff --git a/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc b/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc index 2cdb535650..9c037d6cd3 100644 --- a/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc +++ b/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc @@ -84,3 +84,17 @@ EOD ) { break; } + +match ( + $expr1 && + $expr2 && + $expr3 + ) { + // structure body +}; + +match ($expr1 && +$expr2 && + $expr3) { + // structure body +}; diff --git a/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc.fixed b/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc.fixed index 7f6fbf9b06..7ea61b1e2a 100644 --- a/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc.fixed @@ -85,3 +85,19 @@ EOD ) { break; } + +match ( + $expr1 && + $expr2 && + $expr3 +) { + // structure body +}; + +match ( + $expr1 && + $expr2 && + $expr3 +) { + // structure body +}; diff --git a/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.php b/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.php index c2c0ea9251..6ee076f2ea 100644 --- a/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.php +++ b/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.php @@ -41,6 +41,10 @@ public function getErrorList() 48 => 2, 58 => 1, 59 => 1, + 92 => 1, + 96 => 1, + 97 => 1, + 98 => 2, ]; }//end getErrorList() diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/ControlStructureSpacingSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/ControlStructureSpacingSniff.php index 7fba165acc..09d2c14a07 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/ControlStructureSpacingSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/ControlStructureSpacingSniff.php @@ -47,6 +47,7 @@ public function register() T_ELSE, T_ELSEIF, T_CATCH, + T_MATCH, ]; }//end register() diff --git a/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc index 24de0dc1ea..542ab3cf2c 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc +++ b/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc @@ -68,3 +68,14 @@ if ($expr1 && $expr2 /* comment */ ) { } + +$r = match ($x) {}; +$r = match ( $x ) {}; + +// phpcs:set PSR2.ControlStructures.ControlStructureSpacing requiredSpacesAfterOpen 1 +// phpcs:set PSR2.ControlStructures.ControlStructureSpacing requiredSpacesBeforeClose 1 +$r = match ($x) {}; +$r = match ( $x ) {}; +$r = match ( $x ) {}; +// phpcs:set PSR2.ControlStructures.ControlStructureSpacing requiredSpacesAfterOpen 0 +// phpcs:set PSR2.ControlStructures.ControlStructureSpacing requiredSpacesBeforeClose 0 diff --git a/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc.fixed index f57e792d1d..a29534beed 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc.fixed @@ -67,3 +67,14 @@ if ($expr1 && $expr2 /* comment */) { } + +$r = match ($x) {}; +$r = match ($x) {}; + +// phpcs:set PSR2.ControlStructures.ControlStructureSpacing requiredSpacesAfterOpen 1 +// phpcs:set PSR2.ControlStructures.ControlStructureSpacing requiredSpacesBeforeClose 1 +$r = match ( $x ) {}; +$r = match ( $x ) {}; +$r = match ( $x ) {}; +// phpcs:set PSR2.ControlStructures.ControlStructureSpacing requiredSpacesAfterOpen 0 +// phpcs:set PSR2.ControlStructures.ControlStructureSpacing requiredSpacesBeforeClose 0 diff --git a/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.php b/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.php index b7b251992d..35d5e51508 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.php +++ b/src/Standards/PSR2/Tests/ControlStructures/ControlStructureSpacingUnitTest.php @@ -36,6 +36,9 @@ public function getErrorList() 60 => 1, 64 => 1, 69 => 1, + 73 => 2, + 77 => 2, + 79 => 2, ]; }//end getErrorList() From 7def79ea788bbe6b16f673d1c74bebb78e4f2985 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 22 Feb 2021 05:06:26 +0100 Subject: [PATCH 247/733] PHP 8.0 | Generic/DisallowYodaConditions: add tests with match control structure --- .../ControlStructures/DisallowYodaConditionsUnitTest.inc | 9 +++++++++ .../ControlStructures/DisallowYodaConditionsUnitTest.php | 3 +++ 2 files changed, 12 insertions(+) diff --git a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc index 725e15613f..ce458d8429 100644 --- a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc +++ b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.inc @@ -164,3 +164,12 @@ if ([function() { echo 'hi'; }] === [$foo] ) { } +echo match (5 == $num) { + true => "true\n", + false => "false\n" +}; + +echo match ($text) { + 'foo' => 10 === $y, + 10 === $y => 'bar', +}; diff --git a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php index e69f3b88b1..c558ae8f6f 100644 --- a/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php +++ b/src/Standards/Generic/Tests/ControlStructures/DisallowYodaConditionsUnitTest.php @@ -59,6 +59,9 @@ public function getErrorList() 142 => 1, 156 => 1, 160 => 1, + 167 => 1, + 173 => 1, + 174 => 1, ]; }//end getErrorList() From 48b3d88bf6aae9b0613e9d46b422d118711d2dfe Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 21 Feb 2021 13:16:27 +0100 Subject: [PATCH 248/733] PHP 8.0 | Generic/ArrayIndent: add tests with match control structure --- .../Generic/Tests/Arrays/ArrayIndentUnitTest.inc | 12 ++++++++++++ .../Tests/Arrays/ArrayIndentUnitTest.inc.fixed | 12 ++++++++++++ .../Generic/Tests/Arrays/ArrayIndentUnitTest.php | 1 + 3 files changed, 25 insertions(+) diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc index da2e7c9c92..af56fd497d 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc @@ -86,3 +86,15 @@ $var = [ 2 => 'two', /* three */ 3 => 'three', ]; + +// phpcs:set Generic.Arrays.ArrayIndent indent 4 + +$array = array( + match ($test) { 1 => 'a', 2 => 'b' } + => 'dynamic keys, woho!', +); + +$array = [ + match ($test) { 1 => 'a', 2 => 'b' } + => 'dynamic keys, woho!', +]; diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed index 503b3c6d15..495145956b 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.inc.fixed @@ -87,3 +87,15 @@ $var = [ 2 => 'two', /* three */ 3 => 'three', ]; + +// phpcs:set Generic.Arrays.ArrayIndent indent 4 + +$array = array( + match ($test) { 1 => 'a', 2 => 'b' } + => 'dynamic keys, woho!', +); + +$array = [ + match ($test) { 1 => 'a', 2 => 'b' } + => 'dynamic keys, woho!', +]; diff --git a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php index 77a1014d3e..0b99f2d68f 100644 --- a/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php +++ b/src/Standards/Generic/Tests/Arrays/ArrayIndentUnitTest.php @@ -43,6 +43,7 @@ public function getErrorList() 86 => 1, 87 => 1, 88 => 1, + 98 => 1, ]; }//end getErrorList() From 19b401a0f5cd4d84bc9ece7b0799e9adfebddaa6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 22 Feb 2021 04:33:56 +0100 Subject: [PATCH 249/733] PHP 8.0 | Generic/ScopeIndent: add tests with match control structure --- .../WhiteSpace/ScopeIndentUnitTest.1.inc | 27 +++++++++++++++++++ .../ScopeIndentUnitTest.1.inc.fixed | 27 +++++++++++++++++++ .../WhiteSpace/ScopeIndentUnitTest.2.inc | 27 +++++++++++++++++++ .../ScopeIndentUnitTest.2.inc.fixed | 27 +++++++++++++++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 ++++-- 5 files changed, 114 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index 291d00f683..098b2bdeb0 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1476,6 +1476,33 @@ class Foo } } +$value = match ($value) { + '' => null, + false => false, + 1, 2, 3 => true, + default => $value, +}; + +$value = match ($value) { + '' => null, +false => false, + 1, 2, 3 => true, + default => $value, +}; + +$value = match ( + $value + ) { + '' => null, + false + => false, + 1, + 2, + 3 => true, + default => +$value, +}; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 36dcadb790..67aef35ad4 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1476,6 +1476,33 @@ class Foo } } +$value = match ($value) { + '' => null, + false => false, + 1, 2, 3 => true, + default => $value, +}; + +$value = match ($value) { + '' => null, + false => false, + 1, 2, 3 => true, + default => $value, +}; + +$value = match ( + $value + ) { + '' => null, + false + => false, + 1, + 2, + 3 => true, + default => + $value, +}; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index acffeb1e3a..2dac5ce552 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1476,6 +1476,33 @@ class Foo } } +$value = match ($value) { + '' => null, + false => false, + 1, 2, 3 => true, + default => $value, +}; + +$value = match ($value) { + '' => null, +false => false, + 1, 2, 3 => true, + default => $value, +}; + +$value = match ( + $value + ) { + '' => null, + false + => false, + 1, + 2, + 3 => true, + default => +$value, +}; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index 390b2aa7e6..66eec4568b 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1476,6 +1476,33 @@ class Foo } } +$value = match ($value) { + '' => null, + false => false, + 1, 2, 3 => true, + default => $value, +}; + +$value = match ($value) { + '' => null, + false => false, + 1, 2, 3 => true, + default => $value, +}; + +$value = match ( + $value + ) { + '' => null, + false + => false, + 1, + 2, + 3 => true, + default => + $value, +}; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index 66e82c91e3..f5adc3ac7c 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -178,10 +178,14 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1340 => 1, 1342 => 1, 1345 => 1, - 1487 => 1, 1488 => 1, 1489 => 1, - 1490 => 1, + 1500 => 1, + 1503 => 1, + 1514 => 1, + 1515 => 1, + 1516 => 1, + 1517 => 1, ]; }//end getErrorList() From 07ec92c86122a5e201a78859003d9cd4fd2321a2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 28 Feb 2021 04:34:26 +0100 Subject: [PATCH 250/733] GH Actions: simplify the PHPStan workflow * No need to install via Composer, the `setup-php` action can make PHPStan globally available. --- .github/workflows/phpstan.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 293fdb30bb..91fd6e320f 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -25,14 +25,13 @@ jobs: with: php-version: '7.4' coverage: none - - - name: 'Composer: require PHPStan' - run: composer require --no-update --dev phpstan/phpstan + tools: phpstan # Install dependencies and handle caching in one go. + # 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" - name: Run PHPStan - run: vendor/bin/phpstan analyse --configuration=phpstan.neon + run: phpstan analyse --configuration=phpstan.neon From 6dc06c014d84a8977d5ec65b7a40f1c07144b24f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 28 Feb 2021 04:50:11 +0100 Subject: [PATCH 251/733] GH Actions: fix faulty condition --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bac9f7ee07..760ffebb93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: # 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.custom_ini == false }} + if: ${{ matrix.php >= 8.0 }} uses: "ramsey/composer-install@v1" with: composer-options: --ignore-platform-reqs From a330bf2825fe5a9076791d0fea42135fa16f8649 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 28 Feb 2021 05:16:17 +0100 Subject: [PATCH 252/733] PHP 8.1: fix deprecation notice The `Util\Standards::getInstalledStandardPath()` method returns either a string or `null`, however the `strpos()` method which is used in both the `Util\Common::isPharFile()` method, as well as in the follow-on condition, only accepts a `string` as `$haystack`. As of PHP 8.1, this will generate a deprecation notice `strpos(): Passing null to parameter #1 ($haystack) of type string is deprecated`. Discovered while testing an external standard against PHPCS `master` on PHP 8.1. Fixed now. --- src/Ruleset.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ruleset.php b/src/Ruleset.php index 085d18b4de..7e659706c8 100644 --- a/src/Ruleset.php +++ b/src/Ruleset.php @@ -726,7 +726,7 @@ private function expandRulesetReference($ref, $rulesetDir, $depth=0) } else { // See if this is a whole standard being referenced. $path = Util\Standards::getInstalledStandardPath($ref); - if (Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) { + if ($path !== null && Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) { // If the ruleset exists inside the phar file, use it. if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { $path .= DIRECTORY_SEPARATOR.'ruleset.xml'; From cc678d16fc5c2c98292ad78c1e2b6acb87b93c89 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 28 Feb 2021 06:06:57 +0100 Subject: [PATCH 253/733] GH Actions: improve fix to run the tests on PHP 8.1 There is no need to remove the config file and have all settings on the command-line as PHPUnit offers a `--no-configuration` option to ignore the config file. Enabling that simplifies the action steps a little and isolates the customization for PHP 8.1 to PHP 8.1. --- .github/workflows/test.yml | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 760ffebb93..299e1e3283 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,21 +85,16 @@ jobs: - name: 'PHPCS: set the path to PHP' run: php bin/phpcs --config-set php_path php - # We need to remove the config file so that PHPUnit doesn't try to read it. - # This causes an error on PHP 8.1+ with PHPunit 7, but it's not needed here anyway as the - # phpunit command (below) specifies all required settings. - - name: 'PHPUnit: remove config file' - run: rm phpunit.xml.dist - - # Useless tests were not reported for before PHPUnit 6, so it doesn't - # understand the CLI argument. - name: 'PHPUnit: run the tests' - if: ${{ matrix.php < 7.0 }} - run: vendor/bin/phpunit tests/AllTests.php --bootstrap=tests/bootstrap.php - - - name: 'PHPUnit: run the tests, dont report useless' - if: ${{ matrix.php >= 7.0 }} - run: vendor/bin/phpunit tests/AllTests.php --bootstrap=tests/bootstrap.php --dont-report-useless-tests + if: ${{ matrix.php != 8.1 }} + run: vendor/bin/phpunit tests/AllTests.php + + # We need to ignore the config file so that PHPUnit doesn't try to read it. + # The config file causes an error on PHP 8.1+ with PHPunit 7, but it's not needed here anyway + # as we can pass all required settings in the phpunit command. + - name: 'PHPUnit: run the tests on PHP nightly' + if: ${{ matrix.php == 8.1 }} + run: vendor/bin/phpunit tests/AllTests.php --no-configuration --bootstrap=tests/bootstrap.php --dont-report-useless-tests - name: 'PHPCS: check code style without cache, no parallel' if: ${{ matrix.custom_ini == false }} From a7b2b955cba2b35773b349b809b7fd6c0fa98768 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 28 Feb 2021 06:17:05 +0100 Subject: [PATCH 254/733] GH Actions: report CS violations in the PR The cs2pr tool will allow to display the results from an action run in checkstyle format in-line in the PR code view, which should improve usability of the workflow results. As the CS run is run in every test run in the matrix, passing the results on to CS2PR would result in violations being displayed multiple times, so using conditions to only pass the CS violations on to the PR for one of the runs. Ref: https://github.com/staabm/annotate-pull-request-from-checkstyle --- .github/workflows/test.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 299e1e3283..0cf69cc2f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,6 +66,7 @@ jobs: php-version: ${{ matrix.php }} ini-values: ${{ steps.set_ini.outputs.PHP_INI }} coverage: none + tools: cs2pr # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies @@ -97,9 +98,18 @@ jobs: run: vendor/bin/phpunit tests/AllTests.php --no-configuration --bootstrap=tests/bootstrap.php --dont-report-useless-tests - name: 'PHPCS: check code style without cache, no parallel' - if: ${{ matrix.custom_ini == false }} + if: ${{ matrix.custom_ini == false && matrix.php != 7.4 }} run: php bin/phpcs --no-cache --parallel=1 + - name: 'PHPCS: check code style to show results in PR' + if: ${{ matrix.custom_ini == false && matrix.php == 7.4 }} + continue-on-error: true + run: php bin/phpcs --no-cache --parallel=1 --report-full --report-checkstyle=./phpcs-report.xml + + - name: Show PHPCS results in PR + if: ${{ matrix.custom_ini == false && matrix.php == 7.4 }} + run: cs2pr ./phpcs-report.xml + - name: 'Composer: validate config' if: ${{ matrix.custom_ini == false }} run: composer validate --no-check-all --strict From 5ee08ea5b6623c54c5fd9c72678cc8e7c2d34169 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 28 Feb 2021 07:08:35 +0100 Subject: [PATCH 255/733] GH Actions: allow for manually triggering a workflow Triggering a workflow for a branch manually is not supported by default in GH Actions, but has to be explicitly allowed. This is useful if, for instance, an external action script or composer dependency has broken. Once a fix is available, failing builds for open PRs can be retriggered manually instead of having to be re-pushed to retrigger the workflow. Ref: https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/ --- .github/workflows/phpstan.yml | 2 ++ .github/workflows/test.yml | 2 ++ .github/workflows/validate.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 91fd6e320f..0d33a0d8ca 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -9,6 +9,8 @@ on: pull_request: paths-ignore: - '**.md' + # Allow manually triggering the workflow. + workflow_dispatch: jobs: phpstan: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0cf69cc2f0..66a1b80761 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,8 @@ on: pull_request: paths-ignore: - '**.md' + # Allow manually triggering the workflow. + workflow_dispatch: jobs: test: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 3eb24fb7c8..9fb5685d32 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -9,6 +9,8 @@ on: pull_request: paths-ignore: - '**.md' + # Allow manually triggering the workflow. + workflow_dispatch: jobs: checkxml: From 236fffc17a19ac69de42caa0863674f951bafc3b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 4 Mar 2021 04:41:41 +0100 Subject: [PATCH 256/733] PHP 8.0 | Pear/FunctionCallSignature: support named parameters The `File::findEndOfStatement()` method regards a `T_COLON` as the end of a statement, while for function call arguments using named parameters, the colon is part of the parameter name declaration and should be disregarded when determining the end of the statement. Fixed now. Includes unit tests. --- .../Functions/FunctionCallSignatureSniff.php | 4 +-- .../FunctionCallSignatureUnitTest.inc | 23 ++++++++++++++++ .../FunctionCallSignatureUnitTest.inc.fixed | 26 +++++++++++++++++++ .../FunctionCallSignatureUnitTest.php | 5 ++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php index db86e6d301..3a339abe4c 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php @@ -581,7 +581,7 @@ public function processMultiLineCall(File $phpcsFile, $stackPtr, $openBracket, $ if ($inArg === false) { $argStart = $nextCode; - $argEnd = $phpcsFile->findEndOfStatement($nextCode); + $argEnd = $phpcsFile->findEndOfStatement($nextCode, [T_COLON]); } }//end if @@ -618,7 +618,7 @@ public function processMultiLineCall(File $phpcsFile, $stackPtr, $openBracket, $ }//end if $argStart = $next; - $argEnd = $phpcsFile->findEndOfStatement($next); + $argEnd = $phpcsFile->findEndOfStatement($next, [T_COLON]); }//end if }//end for diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc index 6b2c898c42..ed3d2c43e1 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc @@ -525,3 +525,26 @@ return trim(preg_replace_callback( $a = ['a' => function ($b) { return $b; }]; $a['a']( 1 ); + +// PHP 8.0 named parameters. +array_fill_keys( + keys: range( + 1, + 12, + ), + value: true, +); + +array_fill_keys( + keys: range( 1, + 12, + ), value: true, +); + +// phpcs:set PEAR.Functions.FunctionCallSignature allowMultipleArguments false +array_fill_keys( + keys: range( 1, + 12, + ), value: true, +); +// phpcs:set PEAR.Functions.FunctionCallSignature allowMultipleArguments true diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed index 7396441878..8d02e7467d 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.inc.fixed @@ -537,3 +537,29 @@ return trim( $a = ['a' => function ($b) { return $b; }]; $a['a'](1); + +// PHP 8.0 named parameters. +array_fill_keys( + keys: range( + 1, + 12, + ), + value: true, +); + +array_fill_keys( + keys: range( + 1, + 12, + ), value: true, +); + +// phpcs:set PEAR.Functions.FunctionCallSignature allowMultipleArguments false +array_fill_keys( + keys: range( + 1, + 12, + ), + value: true, +); +// phpcs:set PEAR.Functions.FunctionCallSignature allowMultipleArguments true diff --git a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.php b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.php index c6db5b08e5..1dd59188f3 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.php +++ b/src/Standards/PEAR/Tests/Functions/FunctionCallSignatureUnitTest.php @@ -126,6 +126,11 @@ public function getErrorList($testFile='FunctionCallSignatureUnitTest.inc') 523 => 1, 524 => 3, 527 => 2, + 539 => 1, + 540 => 1, + 546 => 1, + 547 => 1, + 548 => 1, ]; }//end getErrorList() From e890cc3e9d50dfc0e66dfe27a0fa1002e19824a5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 4 Mar 2021 04:23:25 +0100 Subject: [PATCH 257/733] PHP 8.0 | PSR2/FunctionCallSignature: support named parameters The `File::findEndOfStatement()` method regards a `T_COLON` as the end of a statement, while for function call arguments using named parameters, the colon is part of the parameter name declaration and should be disregarded when determining the end of the statement. Fixed now. Includes unit tests. --- .../Methods/FunctionCallSignatureSniff.php | 4 +-- .../Methods/FunctionCallSignatureUnitTest.inc | 23 ++++++++++++++++ .../FunctionCallSignatureUnitTest.inc.fixed | 26 +++++++++++++++++++ .../Methods/FunctionCallSignatureUnitTest.php | 5 ++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/Methods/FunctionCallSignatureSniff.php b/src/Standards/PSR2/Sniffs/Methods/FunctionCallSignatureSniff.php index d7a6522781..406f27948a 100644 --- a/src/Standards/PSR2/Sniffs/Methods/FunctionCallSignatureSniff.php +++ b/src/Standards/PSR2/Sniffs/Methods/FunctionCallSignatureSniff.php @@ -48,7 +48,7 @@ public function isMultiLineCall(File $phpcsFile, $stackPtr, $openBracket, $token $closeBracket = $tokens[$openBracket]['parenthesis_closer']; - $end = $phpcsFile->findEndOfStatement($openBracket + 1); + $end = $phpcsFile->findEndOfStatement(($openBracket + 1), [T_COLON]); while ($tokens[$end]['code'] === T_COMMA) { // If the next bit of code is not on the same line, this is a // multi-line function call. @@ -61,7 +61,7 @@ public function isMultiLineCall(File $phpcsFile, $stackPtr, $openBracket, $token return true; } - $end = $phpcsFile->findEndOfStatement($next); + $end = $phpcsFile->findEndOfStatement($next, [T_COLON]); } // We've reached the last argument, so see if the next content diff --git a/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.inc b/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.inc index dcaf00bd6a..1ca477d054 100644 --- a/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.inc +++ b/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.inc @@ -242,3 +242,26 @@ function (array $term) use ($mode): string { }, $search )); + +// PHP 8.0 named parameters. +array_fill_keys( + keys: range( + 1, + 12, + ), + value: true, +); + +array_fill_keys( + keys: range( 1, + 12, + ), value: true, +); + +// phpcs:set PSR2.Methods.FunctionCallSignature allowMultipleArguments true +array_fill_keys( + keys: range( 1, + 12, + ), value: true, +); +// phpcs:set PSR2.Methods.FunctionCallSignature allowMultipleArguments false diff --git a/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.inc.fixed b/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.inc.fixed index f61b479c50..dc383ed2a7 100644 --- a/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.inc.fixed @@ -255,3 +255,29 @@ return trim(preg_replace_callback( }, $search )); + +// PHP 8.0 named parameters. +array_fill_keys( + keys: range( + 1, + 12, + ), + value: true, +); + +array_fill_keys( + keys: range( + 1, + 12, + ), + value: true, +); + +// phpcs:set PSR2.Methods.FunctionCallSignature allowMultipleArguments true +array_fill_keys( + keys: range( + 1, + 12, + ), value: true, +); +// phpcs:set PSR2.Methods.FunctionCallSignature allowMultipleArguments false diff --git a/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.php b/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.php index fb5532219d..1d87825891 100644 --- a/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.php +++ b/src/Standards/PSR2/Tests/Methods/FunctionCallSignatureUnitTest.php @@ -66,6 +66,11 @@ public function getErrorList() 234 => 1, 242 => 1, 243 => 1, + 256 => 1, + 257 => 1, + 258 => 1, + 263 => 1, + 264 => 1, ]; }//end getErrorList() From 377a4674333937e4134998310dadb30972fabfcc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 5 Mar 2021 02:22:24 +0100 Subject: [PATCH 258/733] PHP 8.1: fix deprecation notice [2] The `Fixer::generateDiff()` method calls the `shell_exec()` method to retrieve the `diff` between two files. In the unit tests, this is used to compare the expected content in a `.fixed` file with the generate fixed file contents. If the expected content matches the generated content, the diff command will produce no output and `shell_exec()` will return `null`. Ref: https://www.php.net/manual/en/function.shell-exec.php This result is subsequently passed to `explode()` as the second parameter, but `explode()` only excepts strings as the second parameter. Ref: https://www.php.net/manual/en/function.explode As of PHP 8.1, this will generate a deprecation notice `explode(): Passing null to parameter #2 ($string) of type string is deprecated`. Discovered while testing an external standard against PHPCS `master` on PHP 8.1. Fixed now. --- src/Fixer.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Fixer.php b/src/Fixer.php index 1e1f2561d4..ed6adb15dc 100644 --- a/src/Fixer.php +++ b/src/Fixer.php @@ -255,6 +255,10 @@ public function generateDiff($filePath=null, $colors=true) unlink($tempName); } + if ($diff === null) { + return ''; + } + if ($colors === false) { return $diff; } From 94c94488a9b47826c47ce212035edcb76bd695cd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 5 Mar 2021 02:56:12 +0100 Subject: [PATCH 259/733] PHP 8.0 | Squiz/OperatorBracket: add tests with named function call parameters ... to confirm that the sniff does not break on these. --- .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc | 5 +++++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed | 5 +++++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.php | 2 ++ 3 files changed, 12 insertions(+) diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc index 18ad808ecf..2a480bb926 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc @@ -188,3 +188,8 @@ $expr = match ($number % 10) { $expr = match (true) { $num * 100 > 500 => 'expression in key', }; + +// PHP 8.0 named parameters. +if ($pos === count(value: $this->tokens) - 1) { + $file = '...'.substr(string: $file, offset: $padding * -1 + 3); +} diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed index b031db886d..669b16b2bc 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed @@ -188,3 +188,8 @@ $expr = match ($number % 10) { $expr = match (true) { ($num * 100) > 500 => 'expression in key', }; + +// PHP 8.0 named parameters. +if ($pos === (count(value: $this->tokens) - 1)) { + $file = '...'.substr(string: $file, offset: ($padding * -1 + 3)); +} diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php index 79b3e7dd19..54e76b9798 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php @@ -70,6 +70,8 @@ public function getErrorList($testFile='OperatorBracketUnitTest.inc') 176 => 1, 185 => 1, 189 => 1, + 193 => 1, + 194 => 3, ]; break; case 'OperatorBracketUnitTest.js': From e5197101a7bc1d2347cdf41f31d2c7d4b10e095d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 5 Mar 2021 03:08:49 +0100 Subject: [PATCH 260/733] Squiz/OperatorBracket: bug fix - improve recognition of unary minus The new test was giving _three_ instead of _two_ errors on line 194 for this snippet `$padding * -1 + 3`, with the errors being thrown on the `*`, `-` and the `+` operators. The `-` operator, however, is a unary operator and should not trigger this error. Fixed now. --- src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php | 3 ++- .../Squiz/Tests/Formatting/OperatorBracketUnitTest.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index 289bbfebbb..60b113d3a9 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -80,7 +80,8 @@ public function process(File $phpcsFile, $stackPtr) $isAssignment = isset(Tokens::$assignmentTokens[$tokens[$previous]['code']]); $isEquality = isset(Tokens::$equalityTokens[$tokens[$previous]['code']]); $isComparison = isset(Tokens::$comparisonTokens[$tokens[$previous]['code']]); - if ($isAssignment === true || $isEquality === true || $isComparison === true) { + $isUnary = isset(Tokens::$operators[$tokens[$previous]['code']]); + if ($isAssignment === true || $isEquality === true || $isComparison === true || $isUnary === true) { // This is a negative assignment or comparison. // We need to check that the minus and the number are // adjacent. diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php index 54e76b9798..4f04a6ebd0 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.php @@ -71,7 +71,7 @@ public function getErrorList($testFile='OperatorBracketUnitTest.inc') 185 => 1, 189 => 1, 193 => 1, - 194 => 3, + 194 => 2, ]; break; case 'OperatorBracketUnitTest.js': From f49df21c7fb15638ed9cc5edba2336b19d3bcb9f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 5 Mar 2021 03:36:42 +0100 Subject: [PATCH 261/733] PHP 7.4 | Squiz/ScopeKeywordSpacing: add tests with typed properties --- .../ScopeKeywordSpacingUnitTest.inc | 13 ++++++++- .../ScopeKeywordSpacingUnitTest.inc.fixed | 12 +++++++- .../ScopeKeywordSpacingUnitTest.php | 28 ++++++++++--------- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc index 50a7a5cca8..8295d1a9de 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc @@ -94,5 +94,16 @@ public function fCreate($attributes = []): object|static { } -// Ensure that static as a scope keyword when preceeded by a colon which is not for a type dclaration is still handled. +// Ensure that static as a scope keyword when preceeded by a colon which is not for a type declaration is still handled. $callback = $cond ? get_fn_name() : static function ($a) { return $a * 10; }; + +class TypedProperties { + public + int $var; + + protected string $stringA, $stringB; + + private bool + $boolA, + $boolB; +} diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed index 5a29fb7c4d..f5ed55bd54 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed @@ -89,5 +89,15 @@ public function fCreate($attributes = []): object|static { } -// Ensure that static as a scope keyword when preceeded by a colon which is not for a type dclaration is still handled. +// Ensure that static as a scope keyword when preceeded by a colon which is not for a type declaration is still handled. $callback = $cond ? get_fn_name() : static function ($a) { return $a * 10; }; + +class TypedProperties { + public int $var; + + protected string $stringA, $stringB; + + private bool + $boolA, + $boolB; +} diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php index d94e53fbcb..bbccf1c88b 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php @@ -26,19 +26,21 @@ class ScopeKeywordSpacingUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 7 => 2, - 8 => 1, - 13 => 1, - 14 => 1, - 15 => 1, - 17 => 2, - 26 => 1, - 28 => 1, - 29 => 1, - 64 => 1, - 67 => 1, - 71 => 1, - 98 => 1, + 7 => 2, + 8 => 1, + 13 => 1, + 14 => 1, + 15 => 1, + 17 => 2, + 26 => 1, + 28 => 1, + 29 => 1, + 64 => 1, + 67 => 1, + 71 => 1, + 98 => 1, + 101 => 1, + 106 => 1, ]; }//end getErrorList() From d6f960b20c022dea76f3e216e650f707fc82efe4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 5 Mar 2021 03:50:37 +0100 Subject: [PATCH 262/733] PHP 8.0 | Squiz/ScopeKeywordSpacing: add support for constructor property promotion Prevent the sniff from ignoring the spacing after the scope keyword in case of PHP 8.0 constructor property promotion. As it was, the sniff would presume "normal" property syntax, which meant that in the case of constructor property promotion in a multi-line constructor declaration, the sniff would look for a semicolon to end the statement and bow out when the semicolon wasn't found. By explicitly checking for constructor property promotion and skipping the next above mentioned check in that case, we ensure that scope keywords in constructors are still handled correctly. Includes tests. --- .../WhiteSpace/ScopeKeywordSpacingSniff.php | 18 +++++++++++++++++- .../WhiteSpace/ScopeKeywordSpacingUnitTest.inc | 14 ++++++++++++++ .../ScopeKeywordSpacingUnitTest.inc.fixed | 14 ++++++++++++++ .../WhiteSpace/ScopeKeywordSpacingUnitTest.php | 3 +++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php index de92697db0..8c5b89446f 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php @@ -84,7 +84,23 @@ public function process(File $phpcsFile, $stackPtr) return; } - if ($nextToken !== false && $tokens[$nextToken]['code'] === T_VARIABLE) { + $isInFunctionDeclaration = false; + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Check if this is PHP 8.0 constructor property promotion. + // In that case, we can't have multi-property definitions. + $nestedParens = $tokens[$stackPtr]['nested_parenthesis']; + $lastCloseParens = end($nestedParens); + if (isset($tokens[$lastCloseParens]['parenthesis_owner']) === true + && $tokens[$tokens[$lastCloseParens]['parenthesis_owner']]['code'] === T_FUNCTION + ) { + $isInFunctionDeclaration = true; + } + } + + if ($nextToken !== false + && $tokens[$nextToken]['code'] === T_VARIABLE + && $isInFunctionDeclaration === false + ) { $endOfStatement = $phpcsFile->findNext(T_SEMICOLON, ($nextToken + 1)); if ($endOfStatement === false) { // Live coding. diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc index 8295d1a9de..d8f21c4e1d 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc @@ -107,3 +107,17 @@ class TypedProperties { $boolA, $boolB; } + +// PHP 8.0 constructor property promotion. +class ConstructorPropertyPromotionTest { + public function __construct( + public $x = 0.0, + protected $y = '', + private $z = null, + $normalParam, + ) {} +} + +class ConstructorPropertyPromotionWithTypesTest { + public function __construct(protected float|int $x, public?string &$y = 'test', private mixed $z) {} +} diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed index f5ed55bd54..daae2bca38 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed @@ -101,3 +101,17 @@ class TypedProperties { $boolA, $boolB; } + +// PHP 8.0 constructor property promotion. +class ConstructorPropertyPromotionTest { + public function __construct( + public $x = 0.0, + protected $y = '', + private $z = null, + $normalParam, + ) {} +} + +class ConstructorPropertyPromotionWithTypesTest { + public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {} +} diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php index bbccf1c88b..9b91298755 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php @@ -41,6 +41,9 @@ public function getErrorList() 98 => 1, 101 => 1, 106 => 1, + 114 => 1, + 116 => 1, + 122 => 2, ]; }//end getErrorList() From 4cb3d16bd614e4d8a244329dfb4affbae5423b40 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 4 Mar 2021 05:34:52 +0100 Subject: [PATCH 263/733] PHP 8.0 | Generic/NoSilencedErrors: add tests with named function call parameters ... to confirm that the code snippet in the error message will be correct (set the `$contextLength` to a higher value to confirm). --- src/Standards/Generic/Sniffs/PHP/NoSilencedErrorsSniff.php | 2 +- src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.inc | 3 ++- src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.php | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/NoSilencedErrorsSniff.php b/src/Standards/Generic/Sniffs/PHP/NoSilencedErrorsSniff.php index de1912d221..14dfaa7216 100644 --- a/src/Standards/Generic/Sniffs/PHP/NoSilencedErrorsSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/NoSilencedErrorsSniff.php @@ -55,7 +55,7 @@ public function process(File $phpcsFile, $stackPtr) { // Prepare the "Found" string to display. $contextLength = 4; - $endOfStatement = $phpcsFile->findEndOfStatement($stackPtr, T_COMMA); + $endOfStatement = $phpcsFile->findEndOfStatement($stackPtr, [T_COMMA, T_COLON]); if (($endOfStatement - $stackPtr) < $contextLength) { $contextLength = ($endOfStatement - $stackPtr); } diff --git a/src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.inc b/src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.inc index 7ff31ba704..72bffe2c4c 100644 --- a/src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.inc @@ -6,4 +6,5 @@ if (@in_array($array, $needle)) { echo '@'; } -?> + +$hasValue = @in_array(haystack: $array, needle: $needle); diff --git a/src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.php b/src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.php index 40b509bced..0e7a4eaa40 100644 --- a/src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/NoSilencedErrorsUnitTest.php @@ -40,7 +40,10 @@ public function getErrorList() */ public function getWarningList() { - return [5 => 1]; + return [ + 5 => 1, + 10 => 1, + ]; }//end getWarningList() From 041af89a16c65b90df25a3569affd5139cb2a6e1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 4 Mar 2021 05:20:21 +0100 Subject: [PATCH 264/733] PHP 8.0 | PEAR/ObjectOperatorIndent: add tests with named function call parameters ... to confirm that the sniff does not break on these. --- .../PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc | 6 ++++++ .../Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed | 6 ++++++ .../PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php | 3 +++ 3 files changed, 15 insertions(+) diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc index 2f3f991b0a..b1b09d9323 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc +++ b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc @@ -134,3 +134,9 @@ $someObject?->someFunction("some", "parameter") ->someOtherFunc3(23, 42) ?->andAThirdFunction(); // phpcs:set PEAR.WhiteSpace.ObjectOperatorIndent multilevel false + +$someObject + ->startSomething(paramName: $value) + ->someOtherFunc(nameA: 23, nameB: 42) +->endSomething($value, name: $value) +->endEverything(); diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed index 1073c9c01e..5d5b77bef6 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.inc.fixed @@ -134,3 +134,9 @@ $someObject?->someFunction("some", "parameter") ->someOtherFunc3(23, 42) ?->andAThirdFunction(); // phpcs:set PEAR.WhiteSpace.ObjectOperatorIndent multilevel false + +$someObject + ->startSomething(paramName: $value) + ->someOtherFunc(nameA: 23, nameB: 42) + ->endSomething($value, name: $value) + ->endEverything(); diff --git a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php index 8289b0f564..0cad3efc15 100644 --- a/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php +++ b/src/Standards/PEAR/Tests/WhiteSpace/ObjectOperatorIndentUnitTest.php @@ -48,6 +48,9 @@ public function getErrorList() 122 => 1, 131 => 1, 134 => 1, + 140 => 1, + 141 => 1, + 142 => 1, ]; }//end getErrorList() From 77cd2c8932fe4119b2b3fe39e6fe8114f7dabfd2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 4 Mar 2021 05:30:38 +0100 Subject: [PATCH 265/733] PHP 8.0 | Generic/ArrayIndent: add tests with named function call parameters ... to confirm that the sniff does not break on these. --- .../Tests/Arrays/ArrayIndentUnitTest.inc | 13 +++++++ .../Arrays/ArrayIndentUnitTest.inc.fixed | 13 +++++++ .../Tests/Arrays/ArrayIndentUnitTest.php | 37 ++++++++++--------- 3 files changed, 45 insertions(+), 18 deletions(-) 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() From d02296fe420483999d2ad8cd8bdceb43014dc83e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 5 Mar 2021 18:46:22 +1100 Subject: [PATCH 266/733] Resolved conflict between Generic.WhiteSpace.ScopeIndent and PSR2.Methods.FunctionCallSignature in a match block (ref #3255) --- .../Sniffs/WhiteSpace/ScopeIndentSniff.php | 5 ++++ .../WhiteSpace/ScopeIndentUnitTest.1.inc | 30 +++++++++++++++++++ .../ScopeIndentUnitTest.1.inc.fixed | 30 +++++++++++++++++++ .../WhiteSpace/ScopeIndentUnitTest.2.inc | 30 +++++++++++++++++++ .../ScopeIndentUnitTest.2.inc.fixed | 30 +++++++++++++++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 13 +++++--- 6 files changed, 134 insertions(+), 4 deletions(-) diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index 7cb7b39e85..3d9ea59faf 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -802,6 +802,11 @@ 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; + } + } $lastOpener = null; if (empty($openScopes) === false) { diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index 098b2bdeb0..c3d7d80913 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1503,6 +1503,36 @@ $value = match ( $value, }; +function toString(): string +{ + return sprintf( + '%s', + match ($type) { + 'foo' => 'bar', + }, + ); +} + +$list = [ + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + } + }, + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + $list2 = [ + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + } + } + ]; + } + } +]; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 67aef35ad4..9c6d4878a9 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1503,6 +1503,36 @@ $value = match ( $value, }; +function toString(): string +{ + return sprintf( + '%s', + match ($type) { + 'foo' => 'bar', + }, + ); +} + +$list = [ + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + } + }, + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + $list2 = [ + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + } + } + ]; + } + } +]; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index 2dac5ce552..b839df7dc2 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1503,6 +1503,36 @@ $value = match ( $value, }; +function toString(): string +{ + return sprintf( + '%s', + match ($type) { + 'foo' => 'bar', + }, + ); +} + +$list = [ + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + } + }, + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + $list2 = [ + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + } + } + ]; + } + } +]; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index 66eec4568b..a373cb837e 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1503,6 +1503,36 @@ $value = match ( $value, }; +function toString(): string +{ + return sprintf( + '%s', + match ($type) { + 'foo' => 'bar', + }, + ); +} + +$list = [ + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + } + }, + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + $list2 = [ + 'fn' => function ($a) { + if ($a === true) { + echo 'hi'; + } + } + ]; + } + } +]; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index f5adc3ac7c..d21dc3f3b2 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -182,10 +182,15 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1489 => 1, 1500 => 1, 1503 => 1, - 1514 => 1, - 1515 => 1, - 1516 => 1, - 1517 => 1, + 1518 => 1, + 1520 => 1, + 1527 => 1, + 1529 => 1, + 1530 => 1, + 1544 => 1, + 1545 => 1, + 1546 => 1, + 1547 => 1, ]; }//end getErrorList() From 6972a567a63cd3e5cf296848368fc037eb348920 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 12 Mar 2021 13:30:42 +1100 Subject: [PATCH 267/733] Changelog for #3258 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index c22f61893f..db92860685 100644 --- a/package.xml +++ b/package.xml @@ -121,6 +121,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Vincent Langlet for the patch - Fixed bug #3197 : Squiz.NamingConventions.ValidVariableName does not use correct error code for all member vars - Fixed bug #3219 : Generic.Formatting.MultipleStatementAlignment false positive for empty anonymous classes and closures + - Fixed bug #3258 : Squiz.Formatting.OperatorBracket duplicate error messages for unary minus + -- Thanks to Juliette Reinders Folmer for the patch From e754fab4d3e0ecd7c1f4912d58b11b48f9584871 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 12 Mar 2021 13:39:35 +1100 Subject: [PATCH 268/733] Changelog for #3259 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index db92860685..a8249e5467 100644 --- a/package.xml +++ b/package.xml @@ -97,6 +97,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Jess Myrbo for the patch - Squiz.PHP.NonExecutableCode now has improved handling of syntax errors -- Thanks to Thiemo Kreuz for the patch + - Squiz.WhiteSpace.ScopeKeywordSpacing now checks spacing when using PHP 8.0 constructor property promotion + -- Thanks to Juliette Reinders Folmer for the patch - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 -- This works around a long-standing PHP bug with is_readable() -- Thanks to Michael S for the patch From 46715ed9eb3842096581a9d88c6b766696951b19 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 12 Mar 2021 14:41:05 +1100 Subject: [PATCH 269/733] Added list of sniffs that now suport match expressions to changelog (ref #3037) --- package.xml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index a8249e5467..6ce690bb14 100644 --- a/package.xml +++ b/package.xml @@ -37,11 +37,25 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Added support for PHP 8.0 match expressions -- Match expressions are now tokenised with parenthesis and scope openers and closers - --- Sniffs can listen for the T_MATCH token to process match expressions - --- Note that the case and default statements inside match expressions do not have scopes set + --- Sniffs can listen for the T_MATCH token to process match expressions + --- Note that the case and default statements inside match expressions do not have scopes set -- A new T_MATCH_ARROW token is available to represent the arrows in match expressions -- A new T_MATCH_DEFAULT token is available to represent the default keyword in match expressions -- All tokenizing of match expressions has been backfilled for older PHP versions + -- The following sniffs have been updated to support match expressions: + --- Generic.CodeAnalysis.AssignmentInCondition + --- Generic.CodeAnalysis.EmptyStatement + --- Generic.PHP.LowerCaseKeyword + --- PEAR.ControlStructures.ControlSignature + --- PSR12.ControlStructures.BooleanOperatorPlacement + --- Squiz.Commenting.LongConditionClosingComment + --- Squiz.Commenting.PostStatementComment + --- Squiz.ControlStructures.LowercaseDeclaration + --- Squiz.ControlStructures.ControlSignature + --- Squiz.Formatting.OperatorBracket + --- Squiz.PHP.DisallowMultipleAssignments + --- Squiz.Objects.ObjectInstantiation + --- Squiz.WhiteSpace.ControlStructureSpacing -- Thanks to Juliette Reinders Folmer for the patch - The value of the T_FN_ARROW token has changed from "T_FN_ARROW" to "PHPCS_T_FN_ARROW" to avoid package conflicts -- This will have no impact on custom sniffs unless they are specifically looking at the value of the T_FN_ARROW constant From c7f7013c12bf02aae9704b91555c9c9cddd77559 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 15 Mar 2021 20:13:54 +0100 Subject: [PATCH 270/733] PHP 8.0 | Tokenizer/PHP: bugfix for union type operator tokenization In `PHP::processAdditional()`, in the part which retokenizes `T_BITWISE_OR` tokens to `T_TYPE_UNION`, the code already took reference operators and spread operators between the type declaration and the variable into account, but would also increment the current position in the loop, while this is already done in the `for()`. My bad. Fixed now. Includes additional unit tests for all relevant functions within the chain, as well as for the sniff for which the issue was reported. Fixes 3267 --- .../Operators/OperatorSpacingUnitTest.inc | 2 ++ .../OperatorSpacingUnitTest.inc.fixed | 2 ++ src/Tokenizers/PHP.php | 1 - tests/Core/File/GetMethodParametersTest.inc | 3 ++ tests/Core/File/GetMethodParametersTest.php | 30 +++++++++++++++++++ tests/Core/Tokenizer/BitwiseOrTest.inc | 7 +++++ tests/Core/Tokenizer/BitwiseOrTest.php | 2 ++ 7 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc index 6cc482af60..9ae9432118 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc @@ -61,3 +61,5 @@ $fn = fn(array &$one) => 1; $fn = fn(array & $one) => 1; $fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); + +function issue3267(string|int ...$values) {} diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed index c90fb9a753..504ae43e5f 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed @@ -61,3 +61,5 @@ $fn = fn(array &$one) => 1; $fn = fn(array & $one) => 1; $fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); + +function issue3267(string|int ...$values) {} diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 383e974526..06263166e9 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2540,7 +2540,6 @@ protected function processAdditional() || $this->tokens[$x]['code'] === T_ELLIPSIS) ) { // Skip past reference and variadic indicators for parameter types. - ++$x; continue; } diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index 0b7a71fc6d..b78301d309 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -45,6 +45,9 @@ function namespaceOperatorTypeHint(?namespace\Name $var1) {} /* testPHP8UnionTypesSimple */ function unionTypeSimple(int|float $number, self|parent &...$obj) {} +/* testPHP8UnionTypesWithSpreadOperatorAndReference */ +function globalFunctionWithSpreadAndReference(float|null &$paramA, string|int ...$paramB) {} + /* testPHP8UnionTypesSimpleWithBitwiseOrInDefault */ $fn = fn(int|float $var = CONSTANT_A | CONSTANT_B) => $var; diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index 9c88fcb684..253b806215 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -370,6 +370,36 @@ public function testPHP8UnionTypesSimple() }//end testPHP8UnionTypesSimple() + /** + * Verify recognition of PHP8 union type declaration when the variable has either a spread operator or a reference. + * + * @return void + */ + public function testPHP8UnionTypesWithSpreadOperatorAndReference() + { + $expected = []; + $expected[0] = [ + 'name' => '$paramA', + 'content' => 'float|null &$paramA', + 'pass_by_reference' => true, + 'variable_length' => false, + 'type_hint' => 'float|null', + 'nullable_type' => false, + ]; + $expected[1] = [ + 'name' => '$paramB', + 'content' => 'string|int ...$paramB', + 'pass_by_reference' => false, + 'variable_length' => true, + 'type_hint' => 'string|int', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8UnionTypesWithSpreadOperatorAndReference() + + /** * Verify recognition of PHP8 union type declaration with a bitwise or in the default value. * diff --git a/tests/Core/Tokenizer/BitwiseOrTest.inc b/tests/Core/Tokenizer/BitwiseOrTest.inc index 921ef7f5ae..ec1801d569 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.inc +++ b/tests/Core/Tokenizer/BitwiseOrTest.inc @@ -48,6 +48,13 @@ class TypeUnion /* testTypeUnionClosureParamIllegalNullable */ $closureWithParamType = function (?string|null $string) {}; +function globalFunctionWithSpreadAndReference( + /* testTypeUnionWithReference */ + float|null &$paramA, + /* testTypeUnionWithSpreadOperator */ + string|int ...$paramB +) {} + /* testBitwiseOrClosureParamDefault */ $closureWithReturnType = function ($string = NONSENSE | FAKE)/* testTypeUnionClosureReturn */ : \Package\MyA|PackageB {}; diff --git a/tests/Core/Tokenizer/BitwiseOrTest.php b/tests/Core/Tokenizer/BitwiseOrTest.php index 88d8e587a0..f6288d817e 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.php +++ b/tests/Core/Tokenizer/BitwiseOrTest.php @@ -110,6 +110,8 @@ public function dataTypeUnion() ['/* testTypeUnionAbstractMethodReturnType1 */'], ['/* testTypeUnionAbstractMethodReturnType2 */'], ['/* testTypeUnionClosureParamIllegalNullable */'], + ['/* testTypeUnionWithReference */'], + ['/* testTypeUnionWithSpreadOperator */'], ['/* testTypeUnionClosureReturn */'], ['/* testTypeUnionArrowParam */'], ['/* testTypeUnionArrowReturnType */'], From 529043f784813a5f013eff9773bc76869c48a9b5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 16 Mar 2021 18:15:46 +0100 Subject: [PATCH 271/733] PHP 8.0 | Generic/UnusedFunctionParameter: add support for constructor property promotion When constructor property promotion is used, the properties declared in the class constructor should never be marked as unused by this sniff. Includes tests. Fixes 3269 --- .../CodeAnalysis/UnusedFunctionParameterSniff.php | 5 +++++ .../UnusedFunctionParameterUnitTest.inc | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php index cf1a5ebc96..1cf7661707 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php @@ -90,6 +90,11 @@ public function process(File $phpcsFile, $stackPtr) } foreach ($methodParams as $param) { + if (isset($param['property_visibility']) === true) { + // Ignore. Constructor property promotion. + continue; + } + $params[$param['name']] = $stackPtr; } diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc index 0645ab4821..bc6ab15689 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc @@ -137,3 +137,16 @@ function moreParamFirst(Exception $foo, LogicException $bar) { function moreParamSecond(LogicException $bar, Exception $foo) { return 'foobar' . $bar; } +// phpcs:set Generic.CodeAnalysis.UnusedFunctionParameter ignoreTypeHints[] + +class ConstructorPropertyPromotionNoContentInMethod { + public function __construct(protected int $id) {} +} + +class ConstructorPropertyPromotionWithContentInMethod { + public function __construct(protected int $id, $toggle = true) { + if ($toggle === true) { + doSomething(); + } + } +} From 805723a6a1a7e794ea42aa029d81e3d4030dc1a0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 16 Mar 2021 18:31:24 +0100 Subject: [PATCH 272/733] PHP 8.0 | Generic/LowerCaseType: add tests with Constructor Property Promotion The sniff already handles this correctly. This just adds some tests to confirm it and safeguard it for the future. --- src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc | 8 ++++++++ .../Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 8 ++++++++ src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php | 2 ++ 3 files changed, 18 insertions(+) diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index 28e696ccc9..8e2dca17e0 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -73,3 +73,11 @@ class TypedProperties private ClassName|/*comment*/Float|STRING|False $unionTypeC; public sTRing | aRRaY | FaLSe $unionTypeD; } + +class ConstructorPropertyPromotionWithTypes { + public function __construct(protected Float|Int $x, public ?STRING &$y = 'test', private mixed $z) {} +} + +class ConstructorPropertyPromotionAndNormalParams { + public function __construct(public Int $promotedProp, ?Int $normalArg) {} +} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed index 302c0faf0a..6e03f7c2d5 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -73,3 +73,11 @@ class TypedProperties private ClassName|/*comment*/float|string|false $unionTypeC; public string | array | false $unionTypeD; } + +class ConstructorPropertyPromotionWithTypes { + public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {} +} + +class ConstructorPropertyPromotionAndNormalParams { + public function __construct(public int $promotedProp, ?int $normalArg) {} +} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php index 005c8280d9..d447e8f47e 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php @@ -63,6 +63,8 @@ public function getErrorList() 72 => 2, 73 => 3, 74 => 3, + 78 => 3, + 82 => 2, ]; }//end getErrorList() From 6e0df17a7275725e908c4cc37f2fa1fb788ec6a5 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 17 Mar 2021 10:04:48 +1100 Subject: [PATCH 273/733] Fixed bug #3273 : Squiz.Functions.FunctionDeclarationArgumentSpacing reports line break as 0 spaces between parenthesis --- package.xml | 1 + .../FunctionDeclarationArgumentSpacingSniff.php | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 6ce690bb14..19a04cea13 100644 --- a/package.xml +++ b/package.xml @@ -139,6 +139,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3219 : Generic.Formatting.MultipleStatementAlignment false positive for empty anonymous classes and closures - Fixed bug #3258 : Squiz.Formatting.OperatorBracket duplicate error messages for unary minus -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3273 : Squiz.Functions.FunctionDeclarationArgumentSpacing reports line break as 0 spaces between parenthesis diff --git a/src/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php b/src/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php index 2f0d3ab5b7..e696d8001d 100644 --- a/src/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php @@ -120,8 +120,14 @@ public function processBracket($phpcsFile, $openBracket) $next = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), $closeBracket, true); if ($next === false) { if (($closeBracket - $openBracket) !== 1) { + if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) { + $found = 'newline'; + } else { + $found = $tokens[($openBracket + 1)]['length']; + } + $error = 'Expected 0 spaces between parenthesis of function declaration; %s found'; - $data = [$tokens[($openBracket + 1)]['length']]; + $data = [$found]; $fix = $phpcsFile->addFixableError($error, $openBracket, 'SpacingBetween', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken(($openBracket + 1), ''); @@ -131,7 +137,7 @@ public function processBracket($phpcsFile, $openBracket) // No params, so we don't check normal spacing rules. return; } - } + }//end if foreach ($params as $paramNumber => $param) { if ($param['pass_by_reference'] === true) { From 43feac151166fe32cce3f2808c8506e191bb5cf3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 16 Mar 2021 18:41:15 +0100 Subject: [PATCH 274/733] PHP 8.0 | PEAR/ValidDefaultValue: add tests with constructor property promotion The sniff already handles this correctly. This just adds some tests to confirm it and safeguard it for the future. --- .../Tests/Functions/ValidDefaultValueUnitTest.inc | 15 +++++++++++++++ .../Tests/Functions/ValidDefaultValueUnitTest.php | 2 ++ 2 files changed, 17 insertions(+) diff --git a/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.inc b/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.inc index c9233734fc..8f8d64ab73 100644 --- a/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.inc @@ -100,5 +100,20 @@ $closure = function(array $arg2=array(), array $arg1) {} $fn = fn($a = [], $b) => $a[] = $b; +class OnlyConstructorPropertyPromotion { + public function __construct( + public string $name = '', + protected $bar + ) {} +} + +class ConstructorPropertyPromotionMixedWithNormalParams { + public function __construct( + public string $name = '', + ?int $optionalParam = 0, + mixed $requiredParam, + ) {} +} + // Intentional syntax error. Must be last thing in the file. function diff --git a/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.php b/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.php index 73489a35c0..60d261cbbb 100644 --- a/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.php +++ b/src/Standards/PEAR/Tests/Functions/ValidDefaultValueUnitTest.php @@ -35,6 +35,8 @@ public function getErrorList() 91 => 1, 99 => 1, 101 => 1, + 106 => 1, + 114 => 1, ]; }//end getErrorList() From ef80e53de0e3c78f1a5599310d2b9803061ae9bd Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 17 Mar 2021 16:17:59 +1100 Subject: [PATCH 275/733] Added match expression support for findStart/EndOfStatement This also adds unit tests for findStartOfStatement and fixes some edge cases where passing the last token in an expression return the same token back again --- package.xml | 1 + src/Files/File.php | 128 ++++- tests/Core/File/FindEndOfStatementTest.inc | 50 ++ tests/Core/File/FindEndOfStatementTest.php | 172 +++++++ tests/Core/File/FindStartOfStatementTest.inc | 112 +++++ tests/Core/File/FindStartOfStatementTest.php | 462 +++++++++++++++++++ 6 files changed, 914 insertions(+), 11 deletions(-) create mode 100644 tests/Core/File/FindStartOfStatementTest.inc create mode 100644 tests/Core/File/FindStartOfStatementTest.php diff --git a/package.xml b/package.xml index 19a04cea13..773ff20993 100644 --- a/package.xml +++ b/package.xml @@ -61,6 +61,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- This will have no impact on custom sniffs unless they are specifically looking at the value of the T_FN_ARROW constant -- If sniffs are just using constant to find arrow functions, they will continue to work without modification -- Thanks to Juliette Reinders Folmer for the patch + - File::findStartOfStatement() now works correctly when passed the last token in a statement - File::getMethodParameters() now supports PHP 8.0 constructor property promotion -- Returned method params now include a "property_visibility" and "visibility_token" index if property promotion is detected -- Thanks to Juliette Reinders Folmer for the patch diff --git a/src/Files/File.php b/src/Files/File.php index a14bd0bdd8..e930a0d099 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2283,27 +2283,103 @@ public function findNext( */ public function findStartOfStatement($start, $ignore=null) { - $endTokens = Util\Tokens::$blockOpeners; + $startTokens = Util\Tokens::$blockOpeners; + $startTokens[T_OPEN_SHORT_ARRAY] = true; + $startTokens[T_OPEN_TAG] = true; - $endTokens[T_COLON] = true; - $endTokens[T_COMMA] = true; - $endTokens[T_DOUBLE_ARROW] = true; - $endTokens[T_SEMICOLON] = true; - $endTokens[T_OPEN_TAG] = true; - $endTokens[T_CLOSE_TAG] = true; - $endTokens[T_OPEN_SHORT_ARRAY] = true; + $endTokens = [ + T_CLOSE_TAG => true, + T_COLON => true, + T_COMMA => true, + T_DOUBLE_ARROW => true, + T_MATCH_ARROW => true, + T_SEMICOLON => true, + ]; if ($ignore !== null) { $ignore = (array) $ignore; foreach ($ignore as $code) { - unset($endTokens[$code]); + if (isset($startTokens[$code]) === true) { + unset($startTokens[$code]); + } + + if (isset($endTokens[$code]) === true) { + unset($endTokens[$code]); + } } } + // If the start token is inside the case part of a match expression, + // find the start of the condition. If it's in the statement part, find + // the token that comes after the match arrow. + $matchExpression = $this->getCondition($start, T_MATCH); + if ($matchExpression !== false) { + for ($prevMatch = $start; $prevMatch > $this->tokens[$matchExpression]['scope_opener']; $prevMatch--) { + if ($prevMatch !== $start + && ($this->tokens[$prevMatch]['code'] === T_MATCH_ARROW + || $this->tokens[$prevMatch]['code'] === T_COMMA) + ) { + break; + } + + // Skip nested statements. + if (isset($this->tokens[$prevMatch]['bracket_opener']) === true + && $prevMatch === $this->tokens[$prevMatch]['bracket_closer'] + ) { + $prevMatch = $this->tokens[$prevMatch]['bracket_opener']; + } else if (isset($this->tokens[$prevMatch]['parenthesis_opener']) === true + && $prevMatch === $this->tokens[$prevMatch]['parenthesis_closer'] + ) { + $prevMatch = $this->tokens[$prevMatch]['parenthesis_opener']; + } + } + + if ($prevMatch <= $this->tokens[$matchExpression]['scope_opener']) { + // We're before the arrow in the first case. + $next = $this->findNext(Util\Tokens::$emptyTokens, ($this->tokens[$matchExpression]['scope_opener'] + 1), null, true); + if ($next === false) { + return $start; + } + + return $next; + } + + if ($this->tokens[$prevMatch]['code'] === T_COMMA) { + // We're before the arrow, but not in the first case. + $prevMatchArrow = $this->findPrevious(T_MATCH_ARROW, ($prevMatch - 1), $this->tokens[$matchExpression]['scope_opener']); + if ($prevMatchArrow === false) { + // We're before the arrow in the first case. + $next = $this->findNext(Util\Tokens::$emptyTokens, ($this->tokens[$matchExpression]['scope_opener'] + 1), null, true); + return $next; + } + + $nextComma = $this->findNext(T_COMMA, ($prevMatchArrow + 1)); + $next = $this->findNext(Util\Tokens::$emptyTokens, ($nextComma + 1), null, true); + return $next; + } + }//end if + $lastNotEmpty = $start; + // If we are starting at a token that ends a scope block, skip to + // the start and continue from there. + // If we are starting at a token that ends a statement, skip this + // token so we find the true start of the statement. + while (isset($endTokens[$this->tokens[$start]['code']]) === true + || (isset($this->tokens[$start]['scope_condition']) === true + && $start === $this->tokens[$start]['scope_closer']) + ) { + if (isset($this->tokens[$start]['scope_condition']) === true) { + $start = $this->tokens[$start]['scope_condition']; + } else { + $start--; + } + } + for ($i = $start; $i >= 0; $i--) { - if (isset($endTokens[$this->tokens[$i]['code']]) === true) { + if (isset($startTokens[$this->tokens[$i]['code']]) === true + || isset($endTokens[$this->tokens[$i]['code']]) === true + ) { // Found the end of the previous statement. return $lastNotEmpty; } @@ -2332,7 +2408,12 @@ public function findStartOfStatement($start, $ignore=null) && $i === $this->tokens[$i]['parenthesis_closer'] ) { $i = $this->tokens[$i]['parenthesis_opener']; - } + } else if ($this->tokens[$i]['code'] === T_CLOSE_USE_GROUP) { + $start = $this->findPrevious(T_OPEN_USE_GROUP, ($i - 1)); + if ($start !== false) { + $i = $start; + } + }//end if if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) { $lastNotEmpty = $i; @@ -2374,6 +2455,31 @@ public function findEndOfStatement($start, $ignore=null) } } + // If the start token is inside the case part of a match expression, + // advance to the match arrow and continue looking for the + // end of the statement from there so that we skip over commas. + $matchExpression = $this->getCondition($start, T_MATCH); + if ($matchExpression !== false) { + $beforeArrow = true; + $prevMatchArrow = $this->findPrevious(T_MATCH_ARROW, ($start - 1), $this->tokens[$matchExpression]['scope_opener']); + if ($prevMatchArrow !== false) { + $prevComma = $this->findNext(T_COMMA, ($prevMatchArrow + 1), $start); + if ($prevComma === false) { + // No comma between this token and the last match arrow, + // so this token exists after the arrow and we can continue + // checking as normal. + $beforeArrow = false; + } + } + + if ($beforeArrow === true) { + $nextMatchArrow = $this->findNext(T_MATCH_ARROW, ($start + 1), $this->tokens[$matchExpression]['scope_closer']); + if ($nextMatchArrow !== false) { + $start = $nextMatchArrow; + } + } + }//end if + $lastNotEmpty = $start; for ($i = $start; $i < $this->numTokens; $i++) { if ($i !== $start && isset($endTokens[$this->tokens[$i]['code']]) === true) { diff --git a/tests/Core/File/FindEndOfStatementTest.inc b/tests/Core/File/FindEndOfStatementTest.inc index 1d72d7d741..6dfd0a2807 100644 --- a/tests/Core/File/FindEndOfStatementTest.inc +++ b/tests/Core/File/FindEndOfStatementTest.inc @@ -52,4 +52,54 @@ $foo = foo( fn() => [$row[0], $row[3]] ); +$match = match ($a) { + /* testMatchCase */ + 1 => 'foo', + /* testMatchDefault */ + default => 'bar' +}; + +$match = match ($a) { + /* testMatchMultipleCase */ + 1, 2, => $a * $b, + /* testMatchDefaultComma */ + default, => 'something' +}; + +match ($pressedKey) { + /* testMatchFunctionCall */ + Key::RETURN_ => save($value, $user) +}; + +$result = match (true) { + /* testMatchFunctionCallArm */ + str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en', + str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr', + default => 'pl' +}; + +/* testMatchClosure */ +$result = match ($key) { + 1 => function($a, $b) {}, + 2 => function($b, $c) {}, +}; + +/* testMatchArray */ +$result = match ($key) { + 1 => [1,2,3], + 2 => [1 => one(), 2 => two()], +}; + +/* testNestedMatch */ +$result = match ($key) { + 1 => match ($key) { + 1 => 'one', + 2 => 'two', + }, + 2 => match ($key) { + 1 => 'two', + 2 => 'one', + }, +}; + return 0; diff --git a/tests/Core/File/FindEndOfStatementTest.php b/tests/Core/File/FindEndOfStatementTest.php index 1fbc8a6871..6b9f6215ef 100644 --- a/tests/Core/File/FindEndOfStatementTest.php +++ b/tests/Core/File/FindEndOfStatementTest.php @@ -237,4 +237,176 @@ public function testArrowFunctionWithArrayAsArgument() }//end testArrowFunctionWithArrayAsArgument() + /** + * Test simple match expression case. + * + * @return void + */ + public function testMatchCase() + { + $start = $this->getTargetToken('/* testMatchCase */', T_LNUMBER); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 5), $found); + + $start = $this->getTargetToken('/* testMatchCase */', T_CONSTANT_ENCAPSED_STRING); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 1), $found); + + }//end testMatchCase() + + + /** + * Test simple match expression default case. + * + * @return void + */ + public function testMatchDefault() + { + $start = $this->getTargetToken('/* testMatchDefault */', T_MATCH_DEFAULT); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 4), $found); + + $start = $this->getTargetToken('/* testMatchDefault */', T_CONSTANT_ENCAPSED_STRING); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame($start, $found); + + }//end testMatchDefault() + + + /** + * Test multiple comma-seperated match expression case values. + * + * @return void + */ + public function testMatchMultipleCase() + { + $start = $this->getTargetToken('/* testMatchMultipleCase */', T_LNUMBER); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 13), $found); + + }//end testMatchMultipleCase() + + + /** + * Test match expression default case with trailing comma. + * + * @return void + */ + public function testMatchDefaultComma() + { + $start = $this->getTargetToken('/* testMatchDefaultComma */', T_MATCH_DEFAULT); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 5), $found); + + }//end testMatchDefaultComma() + + + /** + * Test match expression with function call. + * + * @return void + */ + public function testMatchFunctionCall() + { + $start = $this->getTargetToken('/* testMatchFunctionCall */', T_STRING); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 12), $found); + + $start += 8; + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 1), $found); + + }//end testMatchFunctionCall() + + + /** + * Test match expression with function call in the arm. + * + * @return void + */ + public function testMatchFunctionCallArm() + { + // Check the first case. + $start = $this->getTargetToken('/* testMatchFunctionCallArm */', T_STRING); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 21), $found); + + // Check the second case. + $start += 24; + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 21), $found); + + }//end testMatchFunctionCallArm() + + + /** + * Test match expression with closure. + * + * @return void + */ + public function testMatchClosure() + { + $start = $this->getTargetToken('/* testMatchClosure */', T_LNUMBER); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 14), $found); + + $start += 17; + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 14), $found); + + }//end testMatchClosure() + + + /** + * Test match expression with array declaration. + * + * @return void + */ + public function testMatchArray() + { + $start = $this->getTargetToken('/* testMatchArray */', T_LNUMBER); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 11), $found); + + $start += 14; + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 22), $found); + + }//end testMatchArray() + + + /** + * Test nested match expressions. + * + * @return void + */ + public function testNestedMatch() + { + $start = $this->getTargetToken('/* testNestedMatch */', T_LNUMBER); + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 30), $found); + + $start += 21; + $found = self::$phpcsFile->findEndOfStatement($start); + + $this->assertSame(($start + 5), $found); + + }//end testNestedMatch() + + }//end class diff --git a/tests/Core/File/FindStartOfStatementTest.inc b/tests/Core/File/FindStartOfStatementTest.inc new file mode 100644 index 0000000000..eed940471f --- /dev/null +++ b/tests/Core/File/FindStartOfStatementTest.inc @@ -0,0 +1,112 @@ + $foo + $bar, 'b' => true]; + +/* testUseGroup */ +use Vendor\Package\{ClassA as A, ClassB, ClassC as C}; + +$a = [ + /* testArrowFunctionArrayValue */ + 'a' => fn() => return 1, + 'b' => fn() => return 1, +]; + +/* testStaticArrowFunction */ +static fn ($a) => $a; + +/* testArrowFunctionReturnValue */ +fn(): array => [a($a, $b)]; + +/* testArrowFunctionAsArgument */ +$foo = foo( + fn() => bar() +); + +/* testArrowFunctionWithArrayAsArgument */ +$foo = foo( + fn() => [$row[0], $row[3]] +); + +$match = match ($a) { + /* testMatchCase */ + 1 => 'foo', + /* testMatchDefault */ + default => 'bar' +}; + +$match = match ($a) { + /* testMatchMultipleCase */ + 1, 2, => $a * $b, + /* testMatchDefaultComma */ + default, => 'something' +}; + +match ($pressedKey) { + /* testMatchFunctionCall */ + Key::RETURN_ => save($value, $user) +}; + +$result = match (true) { + /* testMatchFunctionCallArm */ + str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en', + str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr', + default => 'pl' +}; + +/* testMatchClosure */ +$result = match ($key) { + 1 => function($a, $b) {}, + 2 => function($b, $c) {}, +}; + +/* testMatchArray */ +$result = match ($key) { + 1 => [1,2,3], + 2 => [1 => one(), 2 => two()], +}; + +/* testNestedMatch */ +$result = match ($key) { + 1 => match ($key) { + 1 => 'one', + 2 => 'two', + }, + 2 => match ($key) { + 1 => 'two', + 2 => 'one', + }, +}; + +return 0; diff --git a/tests/Core/File/FindStartOfStatementTest.php b/tests/Core/File/FindStartOfStatementTest.php new file mode 100644 index 0000000000..50b475e059 --- /dev/null +++ b/tests/Core/File/FindStartOfStatementTest.php @@ -0,0 +1,462 @@ + + * @copyright 2006-2015 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\File; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; + +class FindStartOfStatementTest extends AbstractMethodUnitTest +{ + + + /** + * Test a simple assignment. + * + * @return void + */ + public function testSimpleAssignment() + { + $start = $this->getTargetToken('/* testSimpleAssignment */', T_SEMICOLON); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 5), $found); + + }//end testSimpleAssignment() + + + /** + * Test a function call. + * + * @return void + */ + public function testFunctionCall() + { + $start = $this->getTargetToken('/* testFunctionCall */', T_CLOSE_PARENTHESIS); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 6), $found); + + }//end testFunctionCall() + + + /** + * Test a function call. + * + * @return void + */ + public function testFunctionCallArgument() + { + $start = $this->getTargetToken('/* testFunctionCallArgument */', T_VARIABLE, '$b'); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame($start, $found); + + }//end testFunctionCallArgument() + + + /** + * Test a direct call to a control structure. + * + * @return void + */ + public function testControlStructure() + { + $start = $this->getTargetToken('/* testControlStructure */', T_CLOSE_CURLY_BRACKET); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 6), $found); + + }//end testControlStructure() + + + /** + * Test the assignment of a closure. + * + * @return void + */ + public function testClosureAssignment() + { + $start = $this->getTargetToken('/* testClosureAssignment */', T_CLOSE_CURLY_BRACKET); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 12), $found); + + }//end testClosureAssignment() + + + /** + * Test using a heredoc in a function argument. + * + * @return void + */ + public function testHeredocFunctionArg() + { + // Find the start of the function. + $start = $this->getTargetToken('/* testHeredocFunctionArg */', T_SEMICOLON); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 10), $found); + + // Find the start of the heredoc. + $start -= 4; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 4), $found); + + // Find the start of the last arg. + $start += 2; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame($start, $found); + + }//end testHeredocFunctionArg() + + + /** + * Test parts of a switch statement. + * + * @return void + */ + public function testSwitch() + { + // Find the start of the switch. + $start = $this->getTargetToken('/* testSwitch */', T_CLOSE_CURLY_BRACKET); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 47), $found); + + // Find the start of default case. + $start -= 5; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 6), $found); + + // Find the start of the second case. + $start -= 12; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 5), $found); + + // Find the start of the first case. + $start -= 13; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 8), $found); + + // Test inside the first case. + $start--; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 1), $found); + + }//end testSwitch() + + + /** + * Test statements that are array values. + * + * @return void + */ + public function testStatementAsArrayValue() + { + // Test short array syntax. + $start = $this->getTargetToken('/* testStatementAsArrayValue */', T_STRING, 'Datetime'); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 2), $found); + + // Test long array syntax. + $start += 12; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 2), $found); + + // Test same statement outside of array. + $start++; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 9), $found); + + // Test with an array index. + $start += 17; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 5), $found); + + }//end testStatementAsArrayValue() + + + /** + * Test a use group. + * + * @return void + */ + public function testUseGroup() + { + $start = $this->getTargetToken('/* testUseGroup */', T_SEMICOLON); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 23), $found); + + }//end testUseGroup() + + + /** + * Test arrow function as array value. + * + * @return void + */ + public function testArrowFunctionArrayValue() + { + $start = $this->getTargetToken('/* testArrowFunctionArrayValue */', T_COMMA); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 9), $found); + + }//end testArrowFunctionArrayValue() + + + /** + * Test static arrow function. + * + * @return void + */ + public function testStaticArrowFunction() + { + $start = $this->getTargetToken('/* testStaticArrowFunction */', T_SEMICOLON); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 11), $found); + + }//end testStaticArrowFunction() + + + /** + * Test arrow function with return value. + * + * @return void + */ + public function testArrowFunctionReturnValue() + { + $start = $this->getTargetToken('/* testArrowFunctionReturnValue */', T_SEMICOLON); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 18), $found); + + }//end testArrowFunctionReturnValue() + + + /** + * Test arrow function used as a function argument. + * + * @return void + */ + public function testArrowFunctionAsArgument() + { + $start = $this->getTargetToken('/* testArrowFunctionAsArgument */', T_FN); + $start += 8; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 8), $found); + + }//end testArrowFunctionAsArgument() + + + /** + * Test arrow function with arrays used as a function argument. + * + * @return void + */ + public function testArrowFunctionWithArrayAsArgument() + { + $start = $this->getTargetToken('/* testArrowFunctionWithArrayAsArgument */', T_FN); + $start += 17; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 17), $found); + + }//end testArrowFunctionWithArrayAsArgument() + + + /** + * Test simple match expression case. + * + * @return void + */ + public function testMatchCase() + { + $start = $this->getTargetToken('/* testMatchCase */', T_COMMA); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 1), $found); + + }//end testMatchCase() + + + /** + * Test simple match expression default case. + * + * @return void + */ + public function testMatchDefault() + { + $start = $this->getTargetToken('/* testMatchDefault */', T_CONSTANT_ENCAPSED_STRING, "'bar'"); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame($start, $found); + + }//end testMatchDefault() + + + /** + * Test multiple comma-seperated match expression case values. + * + * @return void + */ + public function testMatchMultipleCase() + { + $start = $this->getTargetToken('/* testMatchMultipleCase */', T_MATCH_ARROW); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 6), $found); + + $start += 6; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 4), $found); + + }//end testMatchMultipleCase() + + + /** + * Test match expression default case with trailing comma. + * + * @return void + */ + public function testMatchDefaultComma() + { + $start = $this->getTargetToken('/* testMatchDefaultComma */', T_MATCH_ARROW); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 3), $found); + + $start += 2; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame($start, $found); + + }//end testMatchDefaultComma() + + + /** + * Test match expression with function call. + * + * @return void + */ + public function testMatchFunctionCall() + { + $start = $this->getTargetToken('/* testMatchFunctionCall */', T_CLOSE_PARENTHESIS); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 6), $found); + + }//end testMatchFunctionCall() + + + /** + * Test match expression with function call in the arm. + * + * @return void + */ + public function testMatchFunctionCallArm() + { + // Check the first case. + $start = $this->getTargetToken('/* testMatchFunctionCallArm */', T_MATCH_ARROW); + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 18), $found); + + // Check the second case. + $start += 24; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 18), $found); + + }//end testMatchFunctionCallArm() + + + /** + * Test match expression with closure. + * + * @return void + */ + public function testMatchClosure() + { + $start = $this->getTargetToken('/* testMatchClosure */', T_LNUMBER); + $start += 14; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 10), $found); + + $start += 17; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 10), $found); + + }//end testMatchClosure() + + + /** + * Test match expression with array declaration. + * + * @return void + */ + public function testMatchArray() + { + $start = $this->getTargetToken('/* testMatchArray */', T_LNUMBER); + $start += 11; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 7), $found); + + $start += 25; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 18), $found); + + }//end testMatchArray() + + + /** + * Test nested match expressions. + * + * @return void + */ + public function testNestedMatch() + { + $start = $this->getTargetToken('/* testNestedMatch */', T_LNUMBER); + $start += 30; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 26), $found); + + $start -= 4; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 1), $found); + + $start -= 3; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 2), $found); + + }//end testNestedMatch() + + +}//end class From 1b4ce1ab4c887aedf03f4d8afe820816324d9ed2 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 17 Mar 2021 16:27:32 +1100 Subject: [PATCH 276/733] Added missing PEAR file refs --- package.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.xml b/package.xml index 773ff20993..590a2a012f 100644 --- a/package.xml +++ b/package.xml @@ -190,6 +190,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2110,6 +2112,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2192,6 +2196,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + From b05f7692bbe2203ea819efbe96ffca39ae056100 Mon Sep 17 00:00:00 2001 From: javer Date: Wed, 17 Mar 2021 22:16:31 +0200 Subject: [PATCH 277/733] PHP 8.0 | Generic.CodeAnalysis.EmptyPHPStatement: add support for match --- .../Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php | 2 +- .../Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc | 5 +++++ .../Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc.fixed | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php index a37d6cc68f..1c9e400019 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/EmptyPHPStatementSniff.php @@ -72,7 +72,7 @@ public function process(File $phpcsFile, $stackPtr) } $scopeOwner = $tokens[$tokens[$prevNonEmpty]['scope_condition']]['code']; - if ($scopeOwner === T_CLOSURE || $scopeOwner === T_ANON_CLASS) { + if ($scopeOwner === T_CLOSURE || $scopeOwner === T_ANON_CLASS || $scopeOwner === T_MATCH) { return; } diff --git a/src/Standards/Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc index e627fd3edf..464b0e3d8c 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc @@ -79,3 +79,8 @@ echo $a{0}; if ($foo) { ; } + +// Do not remove semicolon after match +$c = match ($a) { + 1 => true, +}; diff --git a/src/Standards/Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc.fixed b/src/Standards/Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc.fixed index 2773f97b85..cc0f85e067 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/CodeAnalysis/EmptyPHPStatementUnitTest.inc.fixed @@ -73,3 +73,8 @@ echo $a{0}; if ($foo) { } + +// Do not remove semicolon after match +$c = match ($a) { + 1 => true, +}; From 18c27ede91c600e40a292a2d8d6a521a3ae6d2a2 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 18 Mar 2021 11:39:21 +1100 Subject: [PATCH 278/733] Changelog for #3275 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 590a2a012f..f9575986dd 100644 --- a/package.xml +++ b/package.xml @@ -44,6 +44,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- All tokenizing of match expressions has been backfilled for older PHP versions -- The following sniffs have been updated to support match expressions: --- Generic.CodeAnalysis.AssignmentInCondition + --- Generic.CodeAnalysis.EmptyPHPStatement + ---- Thanks to Vadim Borodavko for the patch --- Generic.CodeAnalysis.EmptyStatement --- Generic.PHP.LowerCaseKeyword --- PEAR.ControlStructures.ControlSignature From e0152a7308f41c82682072c1bc9c6a707ad2f242 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Sat, 30 Jan 2021 01:07:12 +0100 Subject: [PATCH 279/733] tokenizer: add support for php8 attributes --- package.xml | 6 + src/Files/File.php | 1 + src/Tokenizers/PHP.php | 113 +++++++ src/Tokenizers/Tokenizer.php | 17 + src/Util/Tokens.php | 5 + tests/Core/File/GetMemberPropertiesTest.inc | 16 + tests/Core/File/GetMemberPropertiesTest.php | 30 ++ tests/Core/Tokenizer/AttributesTest.inc | 69 ++++ tests/Core/Tokenizer/AttributesTest.php | 336 ++++++++++++++++++++ 9 files changed, 593 insertions(+) create mode 100644 tests/Core/Tokenizer/AttributesTest.inc create mode 100644 tests/Core/Tokenizer/AttributesTest.php diff --git a/package.xml b/package.xml index f9575986dd..7cc12743d3 100644 --- a/package.xml +++ b/package.xml @@ -226,6 +226,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2138,6 +2140,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2222,6 +2226,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Files/File.php b/src/Files/File.php index e930a0d099..39e62b6d10 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1813,6 +1813,7 @@ public function getMemberProperties($stackPtr) T_SEMICOLON, T_OPEN_CURLY_BRACKET, T_CLOSE_CURLY_BRACKET, + T_ATTRIBUTE_END, ], ($stackPtr - 1) ); diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 06263166e9..d6bb2cbe5a 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -903,6 +903,50 @@ protected function tokenize($string) continue; }//end if + /* + PHP 8.0 Attributes + */ + + if (PHP_VERSION_ID < 80000 + && $token[0] === T_COMMENT + && strpos($token[1], '#[') === 0 + ) { + $subTokens = $this->parsePhpAttribute($tokens, $stackPtr); + if ($subTokens !== null) { + array_splice($tokens, $stackPtr, 1, $subTokens); + $numTokens = count($tokens); + + $tokenIsArray = true; + $token = $tokens[$stackPtr]; + } else { + $token[0] = T_ATTRIBUTE; + } + } + + if ($tokenIsArray === true + && $token[0] === T_ATTRIBUTE + ) { + // Go looking for the close bracket. + $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), '[', ']'); + + $newToken = []; + $newToken['code'] = T_ATTRIBUTE; + $newToken['type'] = 'T_ATTRIBUTE'; + $newToken['content'] = '#['; + $finalTokens[$newStackPtr] = $newToken; + + $tokens[$bracketCloser] = []; + $tokens[$bracketCloser][0] = T_ATTRIBUTE_END; + $tokens[$bracketCloser][1] = ']'; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo "\t\t* token $bracketCloser changed from T_CLOSE_SQUARE_BRACKET to T_ATTRIBUTE_END".PHP_EOL; + } + + $newStackPtr++; + continue; + }//end if + /* 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. @@ -1845,6 +1889,7 @@ function return types. We want to keep the parenthesis map clean, T_CLASS => true, T_EXTENDS => true, T_IMPLEMENTS => true, + T_ATTRIBUTE => true, T_NEW => true, T_CONST => true, T_NS_SEPARATOR => true, @@ -3077,4 +3122,72 @@ public static function resolveSimpleToken($token) }//end resolveSimpleToken() + /** + * Finds a "closer" token (closing parenthesis or square bracket for example) + * Handle parenthesis balancing while searching for closing token + * + * @param array $tokens The list of tokens to iterate searching the closing token (as returned by token_get_all) + * @param int $start The starting position + * @param string $openerChar The opening character + * @param string $closerChar The closing character + * + * @return int|null The position of the closing token, if found. NULL otherwise. + */ + private function findCloser(array &$tokens, $start, $openerChar, $closerChar) + { + $numTokens = count($tokens); + $stack = [0]; + $closer = null; + for ($x = $start; $x < $numTokens; $x++) { + if ($tokens[$x] === $openerChar) { + $stack[] = $x; + } else if ($tokens[$x] === $closerChar) { + array_pop($stack); + if (empty($stack) === true) { + $closer = $x; + break; + } + } + } + + return $closer; + + }//end findCloser() + + + /** + * PHP 8 attributes parser for PHP < 8 + * Handles single-line and multiline attributes. + * + * @param array $tokens The original array of tokens (as returned by token_get_all) + * @param int $stackPtr The current position in token array + * + * @return array|null The array of parsed attribute tokens + */ + private function parsePhpAttribute(array &$tokens, $stackPtr) + { + + $token = $tokens[$stackPtr]; + + $commentBody = substr($token[1], 2); + $subTokens = @token_get_all('findCloser($subTokens, 1, '[', ']'); + if ($bracketCloser === null) { + $bracketCloser = $this->findCloser($tokens, $stackPtr, '[', ']'); + if ($bracketCloser === null) { + return null; + } + + array_splice($subTokens, count($subTokens), 0, array_slice($tokens, ($stackPtr + 1), ($bracketCloser - $stackPtr))); + array_splice($tokens, ($stackPtr + 1), ($bracketCloser - $stackPtr)); + } + + return $subTokens; + + }//end parsePhpAttribute() + + }//end class diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index ac9aa20254..8317691988 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -740,6 +740,23 @@ private function createTokenMap() $this->tokens[$i]['parenthesis_closer'] = $i; $this->tokens[$opener]['parenthesis_closer'] = $i; }//end if + } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE) { + $found = null; + $numTokens = count($this->tokens); + for ($x = ($i + 1); $x < $numTokens; $x++) { + if ($this->tokens[$x]['code'] === T_ATTRIBUTE_END) { + $found = $x; + break; + } + } + + $this->tokens[$i]['attribute_opener'] = $i; + $this->tokens[$i]['attribute_closer'] = $found; + + if ($found !== null) { + $this->tokens[$found]['attribute_opener'] = $i; + $this->tokens[$found]['attribute_closer'] = $found; + } }//end if /* diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 7071259b71..56afd27919 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -79,6 +79,7 @@ define('T_PARAM_NAME', 'PHPCS_T_PARAM_NAME'); define('T_MATCH_ARROW', 'PHPCS_T_MATCH_ARROW'); define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT'); +define('T_ATTRIBUTE_END', 'PHPCS_T_ATTRIBUTE_END'); // Some PHP 5.5 tokens, replicated for lower versions. if (defined('T_FINALLY') === false) { @@ -149,6 +150,10 @@ define('T_MATCH', 'PHPCS_T_MATCH'); } +if (defined('T_ATTRIBUTE') === false) { + define('T_ATTRIBUTE', 'PHPCS_T_ATTRIBUTE'); +} + // 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'); diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index 9cc56bd53a..ea47e9fc57 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -240,3 +240,19 @@ $anon = class() { // 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; }; + +$anon = class { + /* testPHP8PropertySingleAttribute */ + #[PropertyWithAttribute] + public string $foo; + + /* testPHP8PropertyMultipleAttributes */ + #[PropertyWithAttribute(foo: 'bar'), MyAttribute] + protected ?int|float $bar; + + /* testPHP8PropertyMultilineAttribute */ + #[ + PropertyWithAttribute(/* comment */ 'baz') + ] + private mixed $baz; +}; diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index a59effc752..0af0437932 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -610,6 +610,36 @@ public function dataGetMemberProperties() 'nullable_type' => false, ], ], + [ + '/* testPHP8PropertySingleAttribute */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'string', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP8PropertyMultipleAttributes */', + [ + 'scope' => 'protected', + 'scope_specified' => true, + 'is_static' => false, + 'type' => '?int|float', + 'nullable_type' => true, + ], + ], + [ + '/* testPHP8PropertyMultilineAttribute */', + [ + 'scope' => 'private', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'mixed', + 'nullable_type' => false, + ], + ], ]; }//end dataGetMemberProperties() diff --git a/tests/Core/Tokenizer/AttributesTest.inc b/tests/Core/Tokenizer/AttributesTest.inc new file mode 100644 index 0000000000..f0c117c658 --- /dev/null +++ b/tests/Core/Tokenizer/AttributesTest.inc @@ -0,0 +1,69 @@ + 'foobar'])] +function attribute_with_params_on_function_test() {} + +/* testTwoAttributeOnTheSameLine */ +#[CustomAttribute] #[AttributeWithParams('foo')] +function two_attribute_on_same_line_test() {} + +/* testAttributeAndCommentOnTheSameLine */ +#[CustomAttribute] // This is a comment +function attribute_and_line_comment_on_same_line_test() {} + +/* testAttributeGrouping */ +#[CustomAttribute, AttributeWithParams('foo'), AttributeWithParams('foo', bar: ['bar' => 'foobar'])] +function attribute_grouping_test() {} + +/* testAttributeMultiline */ +#[ + CustomAttribute, + AttributeWithParams('foo'), + AttributeWithParams('foo', bar: ['bar' => 'foobar']) +] +function attribute_multiline_test() {} + +/* testAttributeMultilineWithComment */ +#[ + CustomAttribute, // comment + AttributeWithParams(/* another comment */ 'foo'), + AttributeWithParams('foo', bar: ['bar' => 'foobar']) +] +function attribute_multiline_with_comment_test() {} + +/* testSingleAttributeOnParameter */ +function single_attribute_on_parameter_test(#[ParamAttribute] int $param) {} + +/* testMultipleAttributesOnParameter */ +function multiple_attributes_on_parameter_test(#[ParamAttribute, AttributeWithParams(/* another comment */ 'foo')] int $param) {} + +/* testMultilineAttributesOnParameter */ +function multiline_attributes_on_parameter_test(#[ + AttributeWithParams( + 'foo' + ) + ] int $param) {} + +/* testInvalidAttribute */ +#[ThisIsNotAnAttribute +function invalid_attribute_test() {} + diff --git a/tests/Core/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php new file mode 100644 index 0000000000..96f6b4a2a6 --- /dev/null +++ b/tests/Core/Tokenizer/AttributesTest.php @@ -0,0 +1,336 @@ + + * @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; +use PHP_CodeSniffer\Util\Tokens; + +class AttributesTest extends AbstractMethodUnitTest +{ + + + /** + * Test that attributes are parsed correctly. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int $length The number of tokens between opener and closer. + * @param array $tokenCodes The codes of tokens inside the attributes. + * + * @dataProvider dataAttribute + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser + * @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute + * + * @return void + */ + public function testAttribute($testMarker, $length, $tokenCodes) + { + $tokens = self::$phpcsFile->getTokens(); + + $attribute = $this->getTargetToken($testMarker, T_ATTRIBUTE); + $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); + + $closer = $tokens[$attribute]['attribute_closer']; + $this->assertSame(($attribute + $length), $closer); + + $this->assertSame(T_ATTRIBUTE_END, $tokens[$closer]['code']); + + $this->assertSame($tokens[$attribute]['attribute_opener'], $tokens[$closer]['attribute_opener']); + $this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']); + + $map = array_map( + static function ($token) { + return $token['code']; + }, + array_slice($tokens, ($attribute + 1), ($length - 1)) + ); + + $this->assertSame($tokenCodes, $map); + + }//end testAttribute() + + + /** + * Data provider. + * + * @see testAttribute() + * + * @return array + */ + public function dataAttribute() + { + return [ + [ + '/* testAttribute */', + 2, + [ T_STRING ], + ], + [ + '/* testAttributeWithParams */', + 7, + [ + T_STRING, + T_OPEN_PARENTHESIS, + T_STRING, + T_DOUBLE_COLON, + T_STRING, + T_CLOSE_PARENTHESIS, + ], + ], + [ + '/* testAttributeWithNamedParam */', + 10, + [ + T_STRING, + T_OPEN_PARENTHESIS, + T_PARAM_NAME, + T_COLON, + T_WHITESPACE, + T_STRING, + T_DOUBLE_COLON, + T_STRING, + T_CLOSE_PARENTHESIS, + ], + ], + [ + '/* testAttributeOnFunction */', + 2, + [ T_STRING ], + ], + [ + '/* testAttributeOnFunctionWithParams */', + 17, + [ + T_STRING, + T_OPEN_PARENTHESIS, + T_CONSTANT_ENCAPSED_STRING, + T_COMMA, + T_WHITESPACE, + T_PARAM_NAME, + T_COLON, + T_WHITESPACE, + T_OPEN_SHORT_ARRAY, + T_CONSTANT_ENCAPSED_STRING, + T_WHITESPACE, + T_DOUBLE_ARROW, + T_WHITESPACE, + T_CONSTANT_ENCAPSED_STRING, + T_CLOSE_SHORT_ARRAY, + T_CLOSE_PARENTHESIS, + ], + ], + [ + '/* testAttributeGrouping */', + 26, + [ + T_STRING, + T_COMMA, + T_WHITESPACE, + T_STRING, + T_OPEN_PARENTHESIS, + T_CONSTANT_ENCAPSED_STRING, + T_CLOSE_PARENTHESIS, + T_COMMA, + T_WHITESPACE, + T_STRING, + T_OPEN_PARENTHESIS, + T_CONSTANT_ENCAPSED_STRING, + T_COMMA, + T_WHITESPACE, + T_PARAM_NAME, + T_COLON, + T_WHITESPACE, + T_OPEN_SHORT_ARRAY, + T_CONSTANT_ENCAPSED_STRING, + T_WHITESPACE, + T_DOUBLE_ARROW, + T_WHITESPACE, + T_CONSTANT_ENCAPSED_STRING, + T_CLOSE_SHORT_ARRAY, + T_CLOSE_PARENTHESIS, + ], + ], + [ + '/* testAttributeMultiline */', + 31, + [ + T_WHITESPACE, + T_WHITESPACE, + T_STRING, + T_COMMA, + T_WHITESPACE, + T_WHITESPACE, + T_STRING, + T_OPEN_PARENTHESIS, + T_CONSTANT_ENCAPSED_STRING, + T_CLOSE_PARENTHESIS, + T_COMMA, + T_WHITESPACE, + T_WHITESPACE, + T_STRING, + T_OPEN_PARENTHESIS, + T_CONSTANT_ENCAPSED_STRING, + T_COMMA, + T_WHITESPACE, + T_PARAM_NAME, + T_COLON, + T_WHITESPACE, + T_OPEN_SHORT_ARRAY, + T_CONSTANT_ENCAPSED_STRING, + T_WHITESPACE, + T_DOUBLE_ARROW, + T_WHITESPACE, + T_CONSTANT_ENCAPSED_STRING, + T_CLOSE_SHORT_ARRAY, + T_CLOSE_PARENTHESIS, + T_WHITESPACE, + ], + ], + ]; + + }//end dataAttribute() + + + /** + * Test that multiple attributes on the same line are parsed correctly. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser + * @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute + * + * @return void + */ + public function testTwoAttributesOnTheSameLine() + { + $tokens = self::$phpcsFile->getTokens(); + + $attribute = $this->getTargetToken('/* testTwoAttributeOnTheSameLine */', T_ATTRIBUTE); + $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); + + $closer = $tokens[$attribute]['attribute_closer']; + $this->assertSame(T_WHITESPACE, $tokens[($closer + 1)]['code']); + $this->assertSame(T_ATTRIBUTE, $tokens[($closer + 2)]['code']); + $this->assertArrayHasKey('attribute_closer', $tokens[($closer + 2)]); + + }//end testTwoAttributesOnTheSameLine() + + + /** + * Test that attribute followed by a line comment is parsed correctly. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser + * @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute + * + * @return void + */ + public function testAttributeAndLineComment() + { + $tokens = self::$phpcsFile->getTokens(); + + $attribute = $this->getTargetToken('/* testAttributeAndCommentOnTheSameLine */', T_ATTRIBUTE); + $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); + + $closer = $tokens[$attribute]['attribute_closer']; + $this->assertSame(T_WHITESPACE, $tokens[($closer + 1)]['code']); + $this->assertSame(T_COMMENT, $tokens[($closer + 2)]['code']); + + }//end testAttributeAndLineComment() + + + /** + * Test that attribute followed by a line comment is 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. + * @param int $length The number of tokens between opener and closer. + * + * @dataProvider dataAttributeOnParameters + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser + * @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute + * + * @return void + */ + public function testAttributeOnParameters($testMarker, $position, $length) + { + $tokens = self::$phpcsFile->getTokens(); + + $function = $this->getTargetToken($testMarker, T_FUNCTION); + $attribute = ($function + $position); + + $this->assertSame(T_ATTRIBUTE, $tokens[$attribute]['code']); + $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); + $this->assertSame(($attribute + $length), $tokens[$attribute]['attribute_closer']); + + $closer = $tokens[$attribute]['attribute_closer']; + $this->assertSame(T_WHITESPACE, $tokens[($closer + 1)]['code']); + $this->assertSame(T_STRING, $tokens[($closer + 2)]['code']); + $this->assertSame('int', $tokens[($closer + 2)]['content']); + + $this->assertSame(T_VARIABLE, $tokens[($closer + 4)]['code']); + $this->assertSame('$param', $tokens[($closer + 4)]['content']); + + }//end testAttributeOnParameters() + + + /** + * Data provider. + * + * @see testAttributeOnParameters() + * + * @return array + */ + public function dataAttributeOnParameters() + { + return [ + [ + '/* testSingleAttributeOnParameter */', + 4, + 2, + ], + [ + '/* testMultipleAttributesOnParameter */', + 4, + 10, + ], + [ + '/* testMultilineAttributesOnParameter */', + 4, + 13, + ], + ]; + + }//end dataAttributeOnParameters() + + + /** + * Test that invalid attribute (or comment starting with #[ and without ]) are parsed correctly. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser + * @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute + * + * @return void + */ + public function testInvalidAttribute() + { + $tokens = self::$phpcsFile->getTokens(); + + $attribute = $this->getTargetToken('/* testInvalidAttribute */', T_ATTRIBUTE); + + $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); + $this->assertNull($tokens[$attribute]['attribute_closer']); + + }//end testInvalidAttribute() + + +}//end class From 9baa998306e9172132795f5eeef255ca7b7f6e26 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Sat, 30 Jan 2021 02:25:43 +0100 Subject: [PATCH 280/733] convert an array_splice to array_merge for clarity --- src/Tokenizers/PHP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index d6bb2cbe5a..c46d8ab4ef 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -3181,7 +3181,7 @@ private function parsePhpAttribute(array &$tokens, $stackPtr) return null; } - array_splice($subTokens, count($subTokens), 0, array_slice($tokens, ($stackPtr + 1), ($bracketCloser - $stackPtr))); + $subTokens = array_merge($subTokens, array_slice($tokens, ($stackPtr + 1), ($bracketCloser - $stackPtr))); array_splice($tokens, ($stackPtr + 1), ($bracketCloser - $stackPtr)); } From 71bed8b3a643901fd964e27cda21378a4ea97441 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Mon, 1 Feb 2021 12:01:13 +0100 Subject: [PATCH 281/733] add attribute_opener and attribute_closer properties to all tokens inside an attribute --- src/Tokenizers/Tokenizer.php | 6 ++++-- tests/Core/Tokenizer/AttributesTest.php | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 8317691988..30156f22c1 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -754,8 +754,10 @@ private function createTokenMap() $this->tokens[$i]['attribute_closer'] = $found; if ($found !== null) { - $this->tokens[$found]['attribute_opener'] = $i; - $this->tokens[$found]['attribute_closer'] = $found; + for ($x = ($i + 1); $x <= $found; ++$x) { + $this->tokens[$x]['attribute_opener'] = $i; + $this->tokens[$x]['attribute_closer'] = $found; + } } }//end if diff --git a/tests/Core/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php index 96f6b4a2a6..49a39b03d9 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 { @@ -46,7 +45,10 @@ public function testAttribute($testMarker, $length, $tokenCodes) $this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']); $map = array_map( - static function ($token) { + function ($token) use ($attribute, $length) { + $this->assertArrayHasKey('attribute_closer', $token); + $this->assertSame(($attribute + $length), $token['attribute_closer']); + return $token['code']; }, array_slice($tokens, ($attribute + 1), ($length - 1)) From 128a9976476b368f9ba4cfa35c3cb9e10e3d7ef9 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Mon, 1 Feb 2021 12:12:47 +0100 Subject: [PATCH 282/733] add test to ensure T_FN backfill is applied correctly --- tests/Core/Tokenizer/AttributesTest.inc | 4 ++++ tests/Core/Tokenizer/AttributesTest.php | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tests/Core/Tokenizer/AttributesTest.inc b/tests/Core/Tokenizer/AttributesTest.inc index f0c117c658..213ff5c1ae 100644 --- a/tests/Core/Tokenizer/AttributesTest.inc +++ b/tests/Core/Tokenizer/AttributesTest.inc @@ -22,6 +22,10 @@ function attribute_on_function_test() {} #[AttributeWithParams('foo', bar: ['bar' => 'foobar'])] function attribute_with_params_on_function_test() {} +/* testAttributeWithShortClosureParameter */ +#[AttributeWithParams(static fn ($value) => ! $value)] +function attribute_with_short_closure_param_test() {} + /* testTwoAttributeOnTheSameLine */ #[CustomAttribute] #[AttributeWithParams('foo')] function two_attribute_on_same_line_test() {} diff --git a/tests/Core/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php index 49a39b03d9..48260b1d09 100644 --- a/tests/Core/Tokenizer/AttributesTest.php +++ b/tests/Core/Tokenizer/AttributesTest.php @@ -128,6 +128,28 @@ public function dataAttribute() T_CLOSE_PARENTHESIS, ], ], + [ + '/* testAttributeWithShortClosureParameter */', + 17, + [ + T_STRING, + T_OPEN_PARENTHESIS, + T_STATIC, + T_WHITESPACE, + T_FN, + T_WHITESPACE, + T_OPEN_PARENTHESIS, + T_VARIABLE, + T_CLOSE_PARENTHESIS, + T_WHITESPACE, + T_FN_ARROW, + T_WHITESPACE, + T_BOOLEAN_NOT, + T_WHITESPACE, + T_VARIABLE, + T_CLOSE_PARENTHESIS, + ], + ], [ '/* testAttributeGrouping */', 26, From 2f92a4d830a520ed661433e6c01d3a6b251d53e8 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Tue, 2 Feb 2021 22:41:20 +0100 Subject: [PATCH 283/733] add test for fqcn attribute tokenization --- tests/Core/Tokenizer/AttributesTest.inc | 4 ++++ tests/Core/Tokenizer/AttributesTest.php | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/tests/Core/Tokenizer/AttributesTest.inc b/tests/Core/Tokenizer/AttributesTest.inc index 213ff5c1ae..e620ec40cb 100644 --- a/tests/Core/Tokenizer/AttributesTest.inc +++ b/tests/Core/Tokenizer/AttributesTest.inc @@ -60,6 +60,10 @@ function single_attribute_on_parameter_test(#[ParamAttribute] int $param) {} /* testMultipleAttributesOnParameter */ function multiple_attributes_on_parameter_test(#[ParamAttribute, AttributeWithParams(/* another comment */ 'foo')] int $param) {} +/* testFqcnAttribute */ +#[Boo\QualifiedName, \Foo\FullyQualifiedName('foo')] +function fqcn_attrebute_test() {} + /* testMultilineAttributesOnParameter */ function multiline_attributes_on_parameter_test(#[ AttributeWithParams( diff --git a/tests/Core/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php index 48260b1d09..5da1ed523f 100644 --- a/tests/Core/Tokenizer/AttributesTest.php +++ b/tests/Core/Tokenizer/AttributesTest.php @@ -10,6 +10,7 @@ namespace PHP_CodeSniffer\Tests\Core\Tokenizer; use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; +use PHP_CodeSniffer\Util\Tokens; class AttributesTest extends AbstractMethodUnitTest { @@ -217,6 +218,24 @@ public function dataAttribute() T_WHITESPACE, ], ], + [ + '/* testFqcnAttribute */', + 13, + [ + T_STRING, + T_NS_SEPARATOR, + T_STRING, + T_COMMA, + T_WHITESPACE, + T_NS_SEPARATOR, + T_STRING, + T_NS_SEPARATOR, + T_STRING, + T_OPEN_PARENTHESIS, + T_CONSTANT_ENCAPSED_STRING, + T_CLOSE_PARENTHESIS, + ], + ], ]; }//end dataAttribute() From 868be7e76267567084017daee49dadff7e9d3b40 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Wed, 3 Feb 2021 09:43:37 +0100 Subject: [PATCH 284/733] handled nested attributes tokenization --- src/Tokenizers/PHP.php | 39 ++++++-- src/Tokenizers/Tokenizer.php | 41 ++++++--- tests/Core/Tokenizer/AttributesTest.inc | 4 + tests/Core/Tokenizer/AttributesTest.php | 117 +++++++++++++++++++++++- 4 files changed, 177 insertions(+), 24 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index c46d8ab4ef..f5b6cdea01 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -927,7 +927,7 @@ protected function tokenize($string) && $token[0] === T_ATTRIBUTE ) { // Go looking for the close bracket. - $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), '[', ']'); + $bracketCloser = $this->findCloser($tokens, ($stackPtr + 1), ['[', '#['], ']'); $newToken = []; $newToken['code'] = T_ATTRIBUTE; @@ -3126,20 +3126,24 @@ public static function resolveSimpleToken($token) * Finds a "closer" token (closing parenthesis or square bracket for example) * Handle parenthesis balancing while searching for closing token * - * @param array $tokens The list of tokens to iterate searching the closing token (as returned by token_get_all) - * @param int $start The starting position - * @param string $openerChar The opening character - * @param string $closerChar The closing character + * @param array $tokens The list of tokens to iterate searching the closing token (as returned by token_get_all) + * @param int $start The starting position + * @param string|string[] $openerTokens The opening character + * @param string $closerChar The closing character * * @return int|null The position of the closing token, if found. NULL otherwise. */ - private function findCloser(array &$tokens, $start, $openerChar, $closerChar) + private function findCloser(array &$tokens, $start, $openerTokens, $closerChar) { - $numTokens = count($tokens); - $stack = [0]; - $closer = null; + $numTokens = count($tokens); + $stack = [0]; + $closer = null; + $openerTokens = (array) $openerTokens; + for ($x = $start; $x < $numTokens; $x++) { - if ($tokens[$x] === $openerChar) { + if (in_array($tokens[$x], $openerTokens, true) === true + || (is_array($tokens[$x]) === true && in_array($tokens[$x][1], $openerTokens, true) === true) + ) { $stack[] = $x; } else if ($tokens[$x] === $closerChar) { array_pop($stack); @@ -3171,6 +3175,21 @@ private function parsePhpAttribute(array &$tokens, $stackPtr) $commentBody = substr($token[1], 2); $subTokens = @token_get_all(' $subToken) { + if (is_array($subToken) === true + && $subToken[0] === T_COMMENT + && strpos($subToken[1], '#[') === 0 + ) { + $reparsed = $this->parsePhpAttribute($subTokens, $i); + if ($reparsed !== null) { + array_splice($subTokens, $i, 1, $reparsed); + } else { + $subToken[0] = T_ATTRIBUTE; + } + } + } + array_splice($subTokens, 0, 1, [[T_ATTRIBUTE, '#[']]); // Go looking for the close bracket. diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 30156f22c1..a229d6979e 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -741,24 +741,39 @@ private function createTokenMap() $this->tokens[$opener]['parenthesis_closer'] = $i; }//end if } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE) { - $found = null; - $numTokens = count($this->tokens); - for ($x = ($i + 1); $x < $numTokens; $x++) { - if ($this->tokens[$x]['code'] === T_ATTRIBUTE_END) { - $found = $x; - break; - } + $openers[] = $i; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", count($openers)); + echo "=> Found attribute opener at $i".PHP_EOL; } $this->tokens[$i]['attribute_opener'] = $i; - $this->tokens[$i]['attribute_closer'] = $found; + $this->tokens[$i]['attribute_closer'] = null; + } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE_END) { + $numOpeners = count($openers); + if ($numOpeners !== 0) { + $opener = array_pop($openers); + if (isset($this->tokens[$opener]['attribute_opener']) === true) { + $this->tokens[$opener]['attribute_closer'] = $i; - if ($found !== null) { - for ($x = ($i + 1); $x <= $found; ++$x) { - $this->tokens[$x]['attribute_opener'] = $i; - $this->tokens[$x]['attribute_closer'] = $found; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", (count($openers) + 1)); + echo "=> Found attribute closer at $i for $opener".PHP_EOL; + } + + for ($x = ($opener + 1); $x <= $i; ++$x) { + if (isset($this->tokens[$x]['attribute_closer']) === true) { + continue; + } + + $this->tokens[$x]['attribute_opener'] = $opener; + $this->tokens[$x]['attribute_closer'] = $i; + } + } else if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo str_repeat("\t", (count($openers) + 1)); + echo "=> Found unowned attribute closer at $i for $opener".PHP_EOL; } - } + }//end if }//end if /* diff --git a/tests/Core/Tokenizer/AttributesTest.inc b/tests/Core/Tokenizer/AttributesTest.inc index e620ec40cb..9b7b869d13 100644 --- a/tests/Core/Tokenizer/AttributesTest.inc +++ b/tests/Core/Tokenizer/AttributesTest.inc @@ -64,6 +64,10 @@ function multiple_attributes_on_parameter_test(#[ParamAttribute, AttributeWithPa #[Boo\QualifiedName, \Foo\FullyQualifiedName('foo')] function fqcn_attrebute_test() {} +/* testNestedAttributes */ +#[Boo\QualifiedName(fn (#[AttributeOne('boo')] $value) => (string) $value)] +function nested_attributes_test() {} + /* testMultilineAttributesOnParameter */ function multiline_attributes_on_parameter_test(#[ AttributeWithParams( diff --git a/tests/Core/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php index 5da1ed523f..0358d63965 100644 --- a/tests/Core/Tokenizer/AttributesTest.php +++ b/tests/Core/Tokenizer/AttributesTest.php @@ -294,6 +294,7 @@ public function testAttributeAndLineComment() * @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. * @param int $length The number of tokens between opener and closer. + * @param array $tokenCodes The codes of tokens inside the attributes. * * @dataProvider dataAttributeOnParameters * @@ -303,7 +304,7 @@ public function testAttributeAndLineComment() * * @return void */ - public function testAttributeOnParameters($testMarker, $position, $length) + public function testAttributeOnParameters($testMarker, $position, $length, array $tokenCodes) { $tokens = self::$phpcsFile->getTokens(); @@ -312,6 +313,7 @@ public function testAttributeOnParameters($testMarker, $position, $length) $this->assertSame(T_ATTRIBUTE, $tokens[$attribute]['code']); $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); + $this->assertSame(($attribute + $length), $tokens[$attribute]['attribute_closer']); $closer = $tokens[$attribute]['attribute_closer']; @@ -322,6 +324,18 @@ public function testAttributeOnParameters($testMarker, $position, $length) $this->assertSame(T_VARIABLE, $tokens[($closer + 4)]['code']); $this->assertSame('$param', $tokens[($closer + 4)]['content']); + $map = array_map( + function ($token) use ($attribute, $length) { + $this->assertArrayHasKey('attribute_closer', $token); + $this->assertSame(($attribute + $length), $token['attribute_closer']); + + return $token['code']; + }, + array_slice($tokens, ($attribute + 1), ($length - 1)) + ); + + $this->assertSame($tokenCodes, $map); + }//end testAttributeOnParameters() @@ -339,16 +353,42 @@ public function dataAttributeOnParameters() '/* testSingleAttributeOnParameter */', 4, 2, + [T_STRING], ], [ '/* testMultipleAttributesOnParameter */', 4, 10, + [ + T_STRING, + T_COMMA, + T_WHITESPACE, + T_STRING, + T_OPEN_PARENTHESIS, + T_COMMENT, + T_WHITESPACE, + T_CONSTANT_ENCAPSED_STRING, + T_CLOSE_PARENTHESIS, + ], ], [ '/* testMultilineAttributesOnParameter */', 4, 13, + [ + T_WHITESPACE, + T_WHITESPACE, + T_STRING, + T_OPEN_PARENTHESIS, + T_WHITESPACE, + T_WHITESPACE, + T_CONSTANT_ENCAPSED_STRING, + T_WHITESPACE, + T_WHITESPACE, + T_CLOSE_PARENTHESIS, + T_WHITESPACE, + T_WHITESPACE, + ], ], ]; @@ -376,4 +416,79 @@ public function testInvalidAttribute() }//end testInvalidAttribute() + /** + * Test that nested attributes are parsed correctly. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser + * @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute + * + * @return void + */ + public function testNestedAttributes() + { + $tokens = self::$phpcsFile->getTokens(); + $tokenCodes = [ + T_STRING, + T_NS_SEPARATOR, + T_STRING, + T_OPEN_PARENTHESIS, + T_FN, + T_WHITESPACE, + T_OPEN_PARENTHESIS, + T_ATTRIBUTE, + T_STRING, + T_OPEN_PARENTHESIS, + T_CONSTANT_ENCAPSED_STRING, + T_CLOSE_PARENTHESIS, + T_ATTRIBUTE_END, + T_WHITESPACE, + T_VARIABLE, + T_CLOSE_PARENTHESIS, + T_WHITESPACE, + T_FN_ARROW, + T_WHITESPACE, + T_STRING_CAST, + T_WHITESPACE, + T_VARIABLE, + T_CLOSE_PARENTHESIS, + ]; + + $attribute = $this->getTargetToken('/* testNestedAttributes */', T_ATTRIBUTE); + $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); + + $closer = $tokens[$attribute]['attribute_closer']; + $this->assertSame(($attribute + 24), $closer); + + $this->assertSame(T_ATTRIBUTE_END, $tokens[$closer]['code']); + + $this->assertSame($tokens[$attribute]['attribute_opener'], $tokens[$closer]['attribute_opener']); + $this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']); + + $test = function (array $tokens, $length) use ($attribute) { + foreach ($tokens as $token) { + $this->assertArrayHasKey('attribute_closer', $token); + $this->assertSame(($attribute + $length), $token['attribute_closer']); + } + }; + + $test(array_slice($tokens, ($attribute + 1), 7), 24); + + // Length here is 8 (nested attribute offset) + 5 (real length). + $test(array_slice($tokens, ($attribute + 8), 6), 8 + 5); + + $test(array_slice($tokens, ($attribute + 14), 11), 24); + + $map = array_map( + static function ($token) { + return $token['code']; + }, + array_slice($tokens, ($attribute + 1), 23) + ); + + $this->assertSame($tokenCodes, $map); + + }//end testNestedAttributes() + + }//end class From 6367e71b2da928baa52aee6708521bb4d580df96 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Wed, 3 Feb 2021 11:59:23 +0100 Subject: [PATCH 285/733] add nested_attributes property on tokens inside an attribute --- src/Tokenizers/PHP.php | 39 +++++++++++++++++++++++++ tests/Core/Tokenizer/AttributesTest.php | 22 +++++++++++--- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index f5b6cdea01..1f2686b914 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2147,6 +2147,8 @@ protected function processAdditional() echo "\t*** START ADDITIONAL PHP PROCESSING ***".PHP_EOL; } + $this->createAttributesNestingMap(); + $numTokens = count($this->tokens); for ($i = ($numTokens - 1); $i >= 0; $i--) { // Check for any unset scope conditions due to alternate IF/ENDIF syntax. @@ -3209,4 +3211,41 @@ private function parsePhpAttribute(array &$tokens, $stackPtr) }//end parsePhpAttribute() + /** + * Creates a map for the attributes tokens that surround other tokens. + * + * @return void + */ + private function createAttributesNestingMap() + { + $map = []; + for ($i = 0; $i < $this->numTokens; $i++) { + if (isset($this->tokens[$i]['attribute_opener']) === true + && $i === $this->tokens[$i]['attribute_opener'] + ) { + if (empty($map) === false) { + $this->tokens[$i]['nested_attributes'] = $map; + } + + if (isset($this->tokens[$i]['attribute_closer']) === true) { + $map[$this->tokens[$i]['attribute_opener']] + = $this->tokens[$i]['attribute_closer']; + } + } else if (isset($this->tokens[$i]['attribute_closer']) === true + && $i === $this->tokens[$i]['attribute_closer'] + ) { + array_pop($map); + if (empty($map) === false) { + $this->tokens[$i]['nested_attributes'] = $map; + } + } else { + if (empty($map) === false) { + $this->tokens[$i]['nested_attributes'] = $map; + } + }//end if + }//end for + + }//end createAttributesNestingMap() + + }//end class diff --git a/tests/Core/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php index 0358d63965..46d8365431 100644 --- a/tests/Core/Tokenizer/AttributesTest.php +++ b/tests/Core/Tokenizer/AttributesTest.php @@ -465,19 +465,33 @@ public function testNestedAttributes() $this->assertSame($tokens[$attribute]['attribute_opener'], $tokens[$closer]['attribute_opener']); $this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']); - $test = function (array $tokens, $length) use ($attribute) { + $this->assertArrayNotHasKey('nested_attributes', $tokens[$attribute]); + $this->assertArrayHasKey('nested_attributes', $tokens[($attribute + 8)]); + $this->assertSame([$attribute => ($attribute + 24)], $tokens[($attribute + 8)]['nested_attributes']); + + $test = function (array $tokens, $length, $nestedMap) use ($attribute) { foreach ($tokens as $token) { $this->assertArrayHasKey('attribute_closer', $token); $this->assertSame(($attribute + $length), $token['attribute_closer']); + $this->assertSame($nestedMap, $token['nested_attributes']); } }; - $test(array_slice($tokens, ($attribute + 1), 7), 24); + $test(array_slice($tokens, ($attribute + 1), 7), 24, [$attribute => $attribute + 24]); + $test(array_slice($tokens, ($attribute + 8), 1), 8 + 5, [$attribute => $attribute + 24]); // Length here is 8 (nested attribute offset) + 5 (real length). - $test(array_slice($tokens, ($attribute + 8), 6), 8 + 5); + $test( + array_slice($tokens, ($attribute + 9), 4), + 8 + 5, + [ + $attribute => $attribute + 24, + $attribute + 8 => $attribute + 13, + ] + ); - $test(array_slice($tokens, ($attribute + 14), 11), 24); + $test(array_slice($tokens, ($attribute + 13), 1), 8 + 5, [$attribute => $attribute + 24]); + $test(array_slice($tokens, ($attribute + 14), 10), 24, [$attribute => $attribute + 24]); $map = array_map( static function ($token) { From 2bd19c2e6edb918ab14d87f1c4b37d1f112d8f00 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 22 Mar 2021 08:51:39 +1100 Subject: [PATCH 286/733] Fixed bug #3277 : Nullable static return typehint causes whitespace error --- package.xml | 1 + .../Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php | 7 +++++++ .../Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc | 5 +++++ .../WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed | 5 +++++ .../Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php | 10 +++++----- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/package.xml b/package.xml index f9575986dd..28c55d4579 100644 --- a/package.xml +++ b/package.xml @@ -143,6 +143,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3258 : Squiz.Formatting.OperatorBracket duplicate error messages for unary minus -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3273 : Squiz.Functions.FunctionDeclarationArgumentSpacing reports line break as 0 spaces between parenthesis + - Fixed bug #3277 : Nullable static return typehint causes whitespace error diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php index 8c5b89446f..ad995dc45b 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php @@ -66,6 +66,13 @@ public function process(File $phpcsFile, $stackPtr) return; } + if ($prevToken !== false + && $tokens[$prevToken]['code'] === T_NULLABLE + ) { + // Not a scope keyword, but a return type. + return; + } + if ($prevToken !== false && $tokens[$prevToken]['code'] === T_COLON ) { diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc index d8f21c4e1d..2213817116 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc @@ -89,6 +89,11 @@ public static function fCreate($attributes = []): static return static::factory()->create($attributes); } +public static function fCreate($attributes = []): ?static +{ + return static::factory()->create($attributes); +} + // Also account for static used within union types. public function fCreate($attributes = []): object|static { diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed index daae2bca38..e642f0c7b3 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.inc.fixed @@ -84,6 +84,11 @@ public static function fCreate($attributes = []): static return static::factory()->create($attributes); } +public static function fCreate($attributes = []): ?static +{ + return static::factory()->create($attributes); +} + // Also account for static used within union types. public function fCreate($attributes = []): object|static { diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php index 9b91298755..de4697c0cb 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeKeywordSpacingUnitTest.php @@ -38,12 +38,12 @@ public function getErrorList() 64 => 1, 67 => 1, 71 => 1, - 98 => 1, - 101 => 1, + 103 => 1, 106 => 1, - 114 => 1, - 116 => 1, - 122 => 2, + 111 => 1, + 119 => 1, + 121 => 1, + 127 => 2, ]; }//end getErrorList() From b4ad1ad8fa39fdbea7a47e7977e9f8672640cdf7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 25 Mar 2021 14:01:42 +0100 Subject: [PATCH 287/733] PHP 8.0 | Tokenizer/PHP: efficiency tweaks This PR contains two separate, small efficiency tweaks for the `match` expression retokenization. 1. The tokenizer was walking backwards in the `match` backfill, while it could have just used `$finalTokens[$lastNotEmptyToken]` to get the last non-empty token. 2. There was some redundancy in the tokens which were being checked for the token "before". For tokens like, `T_CLASS` and `T_CONST`, the next token after the `match` keyword could never be an open parenthesis, so they would already be marked as "not match". Includes additional tests. Additionally, when I initially started the work on backfilling `match` expressions, the nullsafe object operator had not yet been backfilled, so there was not test covering that case. Added now just to be on the safe side. Note: all extra tests would already pass prior to this change. These are just extra safeguards. --- src/Tokenizers/PHP.php | 39 +++++++------------ .../Core/Tokenizer/BackfillMatchTokenTest.inc | 14 ++++++- .../Core/Tokenizer/BackfillMatchTokenTest.php | 12 ++++++ 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 06263166e9..0b7ebd2976 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1288,33 +1288,22 @@ protected function tokenize($string) break; } - // Next was an open parenthesis, now check what is before the match keyword. - for ($y = ($stackPtr - 1); $y >= 0; $y--) { - if (is_array($tokens[$y]) === true - && isset(Util\Tokens::$emptyTokens[$tokens[$y][0]]) === true - ) { - continue; - } + $notMatchContext = [ + T_PAAMAYIM_NEKUDOTAYIM => true, + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_NS_SEPARATOR => true, + T_NEW => true, + T_FUNCTION => true, + ]; - if (is_array($tokens[$y]) === true - && ($tokens[$y][0] === T_PAAMAYIM_NEKUDOTAYIM - || $tokens[$y][0] === T_OBJECT_OPERATOR - || $tokens[$y][0] === T_NS_SEPARATOR - || $tokens[$y][0] === T_NEW - || $tokens[$y][0] === T_FUNCTION - || $tokens[$y][0] === T_CLASS - || $tokens[$y][0] === T_INTERFACE - || $tokens[$y][0] === T_TRAIT - || $tokens[$y][0] === T_NAMESPACE - || $tokens[$y][0] === T_CONST) - ) { - // This is not a match expression. - break 2; - } + if (isset($notMatchContext[$finalTokens[$lastNotEmptyToken]['code']]) === true) { + // Also not a match expression. + break; + } - $isMatch = true; - break 2; - }//end for + $isMatch = true; + break; }//end for if ($isMatch === true && $token[0] === T_STRING) { diff --git a/tests/Core/Tokenizer/BackfillMatchTokenTest.inc b/tests/Core/Tokenizer/BackfillMatchTokenTest.inc index 3edc9cd5b6..095a4d7dfc 100644 --- a/tests/Core/Tokenizer/BackfillMatchTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillMatchTokenTest.inc @@ -220,7 +220,7 @@ $a = MyClass::Match[$a]; $a = $obj->match($param); /* testNoMatchMethodCallUpper */ -$a = $obj->MATCH()->chain($param); +$a = $obj??->MATCH()->chain($param); /* testNoMatchPropertyAccess */ $a = $obj->match; @@ -282,6 +282,18 @@ function match() {} // Intentional fatal error. Match is now a reserved keyword. namespace Match {} +/* testNoMatchExtendedClassDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +class Foo extends Match {} + +/* testNoMatchImplementedClassDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +class Bar implements Match {} + +/* testNoMatchInUseStatement */ +// Intentional fatal error in PHP < 8. Match is now a reserved keyword. +use Match\me; + function brokenMatchNoCurlies($x) { /* testNoMatchMissingCurlies */ // Intentional fatal error. New control structure is not supported without curly braces. diff --git a/tests/Core/Tokenizer/BackfillMatchTokenTest.php b/tests/Core/Tokenizer/BackfillMatchTokenTest.php index c9f7fad89d..80f909acdf 100644 --- a/tests/Core/Tokenizer/BackfillMatchTokenTest.php +++ b/tests/Core/Tokenizer/BackfillMatchTokenTest.php @@ -296,6 +296,18 @@ public function dataNotAMatchStructure() '/* testNoMatchNamespaceDeclaration */', 'Match', ], + 'class_extends_declaration' => [ + '/* testNoMatchExtendedClassDeclaration */', + 'Match', + ], + 'class_implements_declaration' => [ + '/* testNoMatchImplementedClassDeclaration */', + 'Match', + ], + 'use_statement' => [ + '/* testNoMatchInUseStatement */', + 'Match', + ], 'unsupported_inline_control_structure' => ['/* testNoMatchMissingCurlies */'], 'unsupported_alternative_syntax' => ['/* testNoMatchAlternativeSyntax */'], 'live_coding' => ['/* testLiveCoding */'], From 14ac7ebffc478a82a3a2e095c9b22bb3690430bc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 25 Mar 2021 11:50:13 +0100 Subject: [PATCH 288/733] Tokenizer/PHP: add more tests for default keyword and double arrow In particular, this adds tests where the `default` keyword is not used as the keyword and should be tokenized as `T_STRING`. --- tests/Core/Tokenizer/DefaultKeywordTest.inc | 102 ++++++++++++ tests/Core/Tokenizer/DefaultKeywordTest.php | 110 +++++++++++-- tests/Core/Tokenizer/DoubleArrowTest.inc | 60 +++++++ tests/Core/Tokenizer/DoubleArrowTest.php | 174 +++++++++++--------- 4 files changed, 356 insertions(+), 90 deletions(-) diff --git a/tests/Core/Tokenizer/DefaultKeywordTest.inc b/tests/Core/Tokenizer/DefaultKeywordTest.inc index dca7a811ef..6de4485730 100644 --- a/tests/Core/Tokenizer/DefaultKeywordTest.inc +++ b/tests/Core/Tokenizer/DefaultKeywordTest.inc @@ -91,3 +91,105 @@ function switchWithDefaultInMatch() { } }; } + +function shortArrayWithConstantKey() { + $arr = [ + /* testClassConstantAsShortArrayKey */ + SomeClass::DEFAULT => 1, + /* testClassPropertyAsShortArrayKey */ + SomeClass->DEFAULT => 1, + /* testNamespacedConstantAsShortArrayKey */ + // Intentional parse error PHP < 8.0. Reserved keyword used as namespaced constant. + SomeNamespace\DEFAULT => 1, + /* testFQNGlobalConstantAsShortArrayKey */ + // Intentional parse error in PHP < 8.0. Reserved keyword used as global constant. + \DEFAULT => 1, + ]; +} + +function longArrayWithConstantKey() { + $arr = array( + /* testClassConstantAsLongArrayKey */ + SomeClass::DEFAULT => 1, + ); +} + +function yieldWithConstantKey() { + /* testClassConstantAsYieldKey */ + yield SomeClass::DEFAULT => 1; +} + +function longArrayWithConstantKeyNestedInMatch() { + return match($x) { + /* testMatchDefaultWithNestedLongArrayWithClassConstantKey */ + DEFAULT => array( + /* testClassConstantAsLongArrayKeyNestedInMatch */ + SomeClass::DEFAULT => match($x) { + /* testMatchDefaultWithNestedLongArrayWithClassConstantKeyLevelDown */ + DEFAULT => array( + /* testClassConstantAsLongArrayKeyNestedInMatchLevelDown */ + SomeClass::DEFAULT => 1, + ), + }, + ), + }; +} + +function shortArrayWithConstantKeyNestedInMatch() { + return match($x) { + /* testMatchDefaultWithNestedShortArrayWithClassConstantKey */ + DEFAULT => [ + /* testClassConstantAsShortArrayKeyNestedInMatch */ + SomeClass::DEFAULT => match($x) { + /* testMatchDefaultWithNestedShortArrayWithClassConstantKeyLevelDown */ + DEFAULT => [ + /* testClassConstantAsShortArrayKeyNestedInMatchLevelDown */ + SomeClass::DEFAULT => 1, + ], + }, + ], + }; +} + + +function longArrayWithConstantKeyWithNestedMatch() { + return array( + /* testClassConstantAsLongArrayKeyWithNestedMatch */ + SomeClass::DEFAULT => match($x) { + /* testMatchDefaultNestedInLongArray */ + DEFAULT => 'foo' + }, + ); +} + +function shortArrayWithConstantKeyWithNestedMatch() { + return [ + /* testClassConstantAsShortArrayKeyWithNestedMatch */ + SomeClass::DEFAULT => match($x) { + /* testMatchDefaultNestedInShortArray */ + DEFAULT => 'foo' + }, + ]; +} + +function switchWithConstantNonDefault($i) { + switch ($i) { + /* testClassConstantInSwitchCase */ + case SomeClass::DEFAULT: + return 1; + + /* testClassPropertyInSwitchCase */ + case SomeClass->DEFAULT: + return 2; + + /* testNamespacedConstantInSwitchCase */ + // Intentional parse error PHP < 8.0. Reserved keyword used as constant. + case SomeNamespace\DEFAULT: + return 2; + + /* testNamespaceRelativeConstantInSwitchCase */ + // Intentional parse error PHP < 8.0. Reserved keyword used as global constant. + case namespace\DEFAULT: + return 2; + } +} diff --git a/tests/Core/Tokenizer/DefaultKeywordTest.php b/tests/Core/Tokenizer/DefaultKeywordTest.php index e49204d99d..2d1e68cb6c 100644 --- a/tests/Core/Tokenizer/DefaultKeywordTest.php +++ b/tests/Core/Tokenizer/DefaultKeywordTest.php @@ -22,7 +22,8 @@ class DefaultKeywordTest extends AbstractMethodUnitTest * Note: Cases and default structures within a match structure do *NOT* get case/default scope * conditions, in contrast to case and default structures in switch control structures. * - * @param string $testMarker The comment prefacing the target token. + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. * * @dataProvider dataMatchDefault * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize @@ -30,11 +31,11 @@ class DefaultKeywordTest extends AbstractMethodUnitTest * * @return void */ - public function testMatchDefault($testMarker) + public function testMatchDefault($testMarker, $testContent='default') { $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT]); + $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT, T_STRING], $testContent); $tokenArray = $tokens[$token]; $this->assertSame(T_MATCH_DEFAULT, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_DEFAULT (code)'); @@ -57,11 +58,36 @@ public function testMatchDefault($testMarker) public function dataMatchDefault() { return [ - 'simple_match_default' => ['/* testSimpleMatchDefault */'], - 'match_default_in_switch_case_1' => ['/* testMatchDefaultNestedInSwitchCase1 */'], - 'match_default_in_switch_case_2' => ['/* testMatchDefaultNestedInSwitchCase2 */'], - 'match_default_in_switch_default' => ['/* testMatchDefaultNestedInSwitchDefault */'], - 'match_default_containing_switch' => ['/* testMatchDefault */'], + 'simple_match_default' => ['/* testSimpleMatchDefault */'], + 'match_default_in_switch_case_1' => ['/* testMatchDefaultNestedInSwitchCase1 */'], + 'match_default_in_switch_case_2' => ['/* testMatchDefaultNestedInSwitchCase2 */'], + 'match_default_in_switch_default' => ['/* testMatchDefaultNestedInSwitchDefault */'], + 'match_default_containing_switch' => ['/* testMatchDefault */'], + + 'match_default_with_nested_long_array_and_default_key' => [ + '/* testMatchDefaultWithNestedLongArrayWithClassConstantKey */', + 'DEFAULT', + ], + 'match_default_with_nested_long_array_and_default_key_2' => [ + '/* testMatchDefaultWithNestedLongArrayWithClassConstantKeyLevelDown */', + 'DEFAULT', + ], + 'match_default_with_nested_short_array_and_default_key' => [ + '/* testMatchDefaultWithNestedShortArrayWithClassConstantKey */', + 'DEFAULT', + ], + 'match_default_with_nested_short_array_and_default_key_2' => [ + '/* testMatchDefaultWithNestedShortArrayWithClassConstantKeyLevelDown */', + 'DEFAULT', + ], + 'match_default_in_long_array' => [ + '/* testMatchDefaultNestedInLongArray */', + 'DEFAULT', + ], + 'match_default_in_short_array' => [ + '/* testMatchDefaultNestedInShortArray */', + 'DEFAULT', + ], ]; }//end dataMatchDefault() @@ -78,17 +104,18 @@ public function dataMatchDefault() * @param int $openerOffset The expected offset of the scope opener in relation to the testMarker. * @param int $closerOffset The expected offset of the scope closer in relation to the testMarker. * @param int|null $conditionStop The expected offset at which tokens stop having T_DEFAULT as a scope condition. + * @param string $testContent The token content to look for. * * @dataProvider dataSwitchDefault * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap * * @return void */ - public function testSwitchDefault($testMarker, $openerOffset, $closerOffset, $conditionStop=null) + public function testSwitchDefault($testMarker, $openerOffset, $closerOffset, $conditionStop=null, $testContent='default') { $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT]); + $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT, T_STRING], $testContent); $tokenArray = $tokens[$token]; $expectedScopeOpener = ($token + $openerOffset); $expectedScopeCloser = ($token + $closerOffset); @@ -182,4 +209,67 @@ public function dataSwitchDefault() }//end dataSwitchDefault() + /** + * Verify that the retokenization of `T_DEFAULT` tokens in match constructs, doesn't negatively + * impact the tokenization of `T_STRING` tokens with the contents 'default' which aren't in + * actual fact the default keyword. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. + * + * @dataProvider dataNotDefaultKeyword + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testNotDefaultKeyword($testMarker, $testContent='DEFAULT') + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT, T_STRING], $testContent); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)'); + $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)'); + + $this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set'); + $this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set'); + $this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set'); + + }//end testNotDefaultKeyword() + + + /** + * Data provider. + * + * @see testNotDefaultKeyword() + * + * @return array + */ + public function dataNotDefaultKeyword() + { + return [ + 'class-constant-as-short-array-key' => ['/* testClassConstantAsShortArrayKey */'], + 'class-property-as-short-array-key' => ['/* testClassPropertyAsShortArrayKey */'], + 'namespaced-constant-as-short-array-key' => ['/* testNamespacedConstantAsShortArrayKey */'], + 'fqn-global-constant-as-short-array-key' => ['/* testFQNGlobalConstantAsShortArrayKey */'], + 'class-constant-as-long-array-key' => ['/* testClassConstantAsLongArrayKey */'], + 'class-constant-as-yield-key' => ['/* testClassConstantAsYieldKey */'], + + 'class-constant-as-long-array-key-nested-in-match' => ['/* testClassConstantAsLongArrayKeyNestedInMatch */'], + 'class-constant-as-long-array-key-nested-in-match-2' => ['/* testClassConstantAsLongArrayKeyNestedInMatchLevelDown */'], + 'class-constant-as-short-array-key-nested-in-match' => ['/* testClassConstantAsShortArrayKeyNestedInMatch */'], + 'class-constant-as-short-array-key-nested-in-match-2' => ['/* testClassConstantAsShortArrayKeyNestedInMatchLevelDown */'], + 'class-constant-as-long-array-key-with-nested-match' => ['/* testClassConstantAsLongArrayKeyWithNestedMatch */'], + 'class-constant-as-short-array-key-with-nested-match' => ['/* testClassConstantAsShortArrayKeyWithNestedMatch */'], + + 'class-constant-in-switch-case' => ['/* testClassConstantInSwitchCase */'], + 'class-property-in-switch-case' => ['/* testClassPropertyInSwitchCase */'], + 'namespaced-constant-in-switch-case' => ['/* testNamespacedConstantInSwitchCase */'], + 'namespace-relative-constant-in-switch-case' => ['/* testNamespaceRelativeConstantInSwitchCase */'], + ]; + + }//end dataNotDefaultKeyword() + + }//end class diff --git a/tests/Core/Tokenizer/DoubleArrowTest.inc b/tests/Core/Tokenizer/DoubleArrowTest.inc index ad5e5798d9..3ca1025730 100644 --- a/tests/Core/Tokenizer/DoubleArrowTest.inc +++ b/tests/Core/Tokenizer/DoubleArrowTest.inc @@ -219,3 +219,63 @@ function matchInShortList() { /* testMatchArrowInShortListKey */ [match($x) {1 => 1, 2 => 2} /* testShortListArrowWithMatchInKey */ => $a] = $array; } + +function longArrayWithConstantKey() { + $arr = array( + /* testLongArrayArrowWithClassConstantKey */ + SomeClass::DEFAULT => 1, + ); +} + +function shortArrayWithConstantKey() { + $arr = [ + /* testShortArrayArrowWithClassConstantKey */ + SomeClass::DEFAULT => 1, + ]; +} + +function yieldWithConstantKey() { + /* testYieldArrowWithClassConstantKey */ + yield SomeClass::DEFAULT => 1; +} + +function longArrayWithConstantKeyNestedInMatch() { + return match($x) { + /* testMatchArrowWithNestedLongArrayWithClassConstantKey */ + default => array( + /* testLongArrayArrowWithClassConstantKeyNestedInMatch */ + SomeClass::DEFAULT => 1, + ), + }; +} + +function shortArrayWithConstantKeyNestedInMatch() { + return match($x) { + /* testMatchArrowWithNestedShortArrayWithClassConstantKey */ + default => [ + /* testShortArrayArrowWithClassConstantKeyNestedInMatch */ + SomeClass::DEFAULT => 1, + ], + }; +} + + +function longArrayWithConstantKeyWithNestedMatch() { + return array( + /* testLongArrayArrowWithClassConstantKeyWithNestedMatch */ + SomeClass::DEFAULT => match($x) { + /* testMatchArrowNestedInLongArrayWithClassConstantKey */ + default => 'foo' + }, + ); +} + +function shortArrayWithConstantKeyWithNestedMatch() { + return [ + /* testShortArrayArrowWithClassConstantKeyWithNestedMatch */ + SomeClass::DEFAULT => match($x) { + /* testMatchArrowNestedInShortArrayWithClassConstantKey */ + default => 'foo' + }, + ]; +} diff --git a/tests/Core/Tokenizer/DoubleArrowTest.php b/tests/Core/Tokenizer/DoubleArrowTest.php index c5c7b04cc8..9a0b174c3c 100644 --- a/tests/Core/Tokenizer/DoubleArrowTest.php +++ b/tests/Core/Tokenizer/DoubleArrowTest.php @@ -50,43 +50,52 @@ public function testDoubleArrow($testMarker) public function dataDoubleArrow() { return [ - 'simple_long_array' => ['/* testLongArrayArrowSimple */'], - 'simple_short_array' => ['/* testShortArrayArrowSimple */'], - 'simple_long_list' => ['/* testLongListArrowSimple */'], - 'simple_short_list' => ['/* testShortListArrowSimple */'], - 'simple_yield' => ['/* testYieldArrowSimple */'], - 'simple_foreach' => ['/* testForeachArrowSimple */'], - - 'long_array_with_match_value_1' => ['/* testLongArrayArrowWithNestedMatchValue1 */'], - 'long_array_with_match_value_2' => ['/* testLongArrayArrowWithNestedMatchValue2 */'], - 'short_array_with_match_value_1' => ['/* testShortArrayArrowWithNestedMatchValue1 */'], - 'short_array_with_match_value_2' => ['/* testShortArrayArrowWithNestedMatchValue2 */'], - - 'long_array_with_match_key' => ['/* testLongArrayArrowWithMatchKey */'], - 'short_array_with_match_key' => ['/* testShortArrayArrowWithMatchKey */'], - - 'long_array_in_match_body_1' => ['/* testLongArrayArrowInMatchBody1 */'], - 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody2 */'], - 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody3 */'], - 'short_array_in_match_body_1' => ['/* testShortArrayArrowInMatchBody1 */'], - 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody2 */'], - 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody3 */'], - - 'short_array_in_match_case_1' => ['/* testShortArrayArrowinMatchCase1 */'], - 'short_array_in_match_case_2' => ['/* testShortArrayArrowinMatchCase2 */'], - 'short_array_in_match_case_3' => ['/* testShortArrayArrowinMatchCase3 */'], - 'long_array_in_match_case_4' => ['/* testLongArrayArrowinMatchCase4 */'], - - 'in_complex_short_array_key_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayKey */'], - 'in_complex_short_array_toplevel' => ['/* testShortArrayArrowInComplexMatchArrayMismash */'], - 'in_complex_short_array_value_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayValue */'], - - 'long_list_in_match_body' => ['/* testLongListArrowInMatchBody */'], - 'long_list_in_match_case' => ['/* testLongListArrowInMatchCase */'], - 'short_list_in_match_body' => ['/* testShortListArrowInMatchBody */'], - 'short_list_in_match_case' => ['/* testShortListArrowInMatchCase */'], - 'long_list_with_match_in_key' => ['/* testLongListArrowWithMatchInKey */'], - 'short_list_with_match_in_key' => ['/* testShortListArrowWithMatchInKey */'], + 'simple_long_array' => ['/* testLongArrayArrowSimple */'], + 'simple_short_array' => ['/* testShortArrayArrowSimple */'], + 'simple_long_list' => ['/* testLongListArrowSimple */'], + 'simple_short_list' => ['/* testShortListArrowSimple */'], + 'simple_yield' => ['/* testYieldArrowSimple */'], + 'simple_foreach' => ['/* testForeachArrowSimple */'], + + 'long_array_with_match_value_1' => ['/* testLongArrayArrowWithNestedMatchValue1 */'], + 'long_array_with_match_value_2' => ['/* testLongArrayArrowWithNestedMatchValue2 */'], + 'short_array_with_match_value_1' => ['/* testShortArrayArrowWithNestedMatchValue1 */'], + 'short_array_with_match_value_2' => ['/* testShortArrayArrowWithNestedMatchValue2 */'], + + 'long_array_with_match_key' => ['/* testLongArrayArrowWithMatchKey */'], + 'short_array_with_match_key' => ['/* testShortArrayArrowWithMatchKey */'], + + 'long_array_in_match_body_1' => ['/* testLongArrayArrowInMatchBody1 */'], + 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody2 */'], + 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody3 */'], + 'short_array_in_match_body_1' => ['/* testShortArrayArrowInMatchBody1 */'], + 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody2 */'], + 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody3 */'], + + 'short_array_in_match_case_1' => ['/* testShortArrayArrowinMatchCase1 */'], + 'short_array_in_match_case_2' => ['/* testShortArrayArrowinMatchCase2 */'], + 'short_array_in_match_case_3' => ['/* testShortArrayArrowinMatchCase3 */'], + 'long_array_in_match_case_4' => ['/* testLongArrayArrowinMatchCase4 */'], + + 'in_complex_short_array_key_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayKey */'], + 'in_complex_short_array_toplevel' => ['/* testShortArrayArrowInComplexMatchArrayMismash */'], + 'in_complex_short_array_value_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayValue */'], + + 'long_list_in_match_body' => ['/* testLongListArrowInMatchBody */'], + 'long_list_in_match_case' => ['/* testLongListArrowInMatchCase */'], + 'short_list_in_match_body' => ['/* testShortListArrowInMatchBody */'], + 'short_list_in_match_case' => ['/* testShortListArrowInMatchCase */'], + 'long_list_with_match_in_key' => ['/* testLongListArrowWithMatchInKey */'], + 'short_list_with_match_in_key' => ['/* testShortListArrowWithMatchInKey */'], + + 'long_array_with_constant_default_in_key' => ['/* testLongArrayArrowWithClassConstantKey */'], + 'short_array_with_constant_default_in_key' => ['/* testShortArrayArrowWithClassConstantKey */'], + 'yield_with_constant_default_in_key' => ['/* testYieldArrowWithClassConstantKey */'], + + 'long_array_with_default_in_key_in_match' => ['/* testLongArrayArrowWithClassConstantKeyNestedInMatch */'], + 'short_array_with_default_in_key_in_match' => ['/* testShortArrayArrowWithClassConstantKeyNestedInMatch */'], + 'long_array_with_default_in_key_with_match' => ['/* testLongArrayArrowWithClassConstantKeyWithNestedMatch */'], + 'long_array_with_default_in_key_with_match' => ['/* testShortArrayArrowWithClassConstantKeyWithNestedMatch */'], ]; }//end dataDoubleArrow() @@ -126,49 +135,54 @@ public function testMatchArrow($testMarker) public function dataMatchArrow() { return [ - 'single_case' => ['/* testMatchArrowSimpleSingleCase */'], - 'multi_case' => ['/* testMatchArrowSimpleMultiCase */'], - 'single_case_with_trailing_comma' => ['/* testMatchArrowSimpleSingleCaseWithTrailingComma */'], - 'multi_case_with_trailing_comma' => ['/* testMatchArrowSimpleMultiCaseWithTrailingComma */'], - 'match_nested_outer' => ['/* testMatchArrowNestedMatchOuter */'], - 'match_nested_inner' => ['/* testMatchArrowNestedMatchInner */'], - - 'in_long_array_value_1' => ['/* testMatchArrowInLongArrayValue1 */'], - 'in_long_array_value_2' => ['/* testMatchArrowInLongArrayValue2 */'], - 'in_long_array_value_3' => ['/* testMatchArrowInLongArrayValue3 */'], - 'in_short_array_value_1' => ['/* testMatchArrowInShortArrayValue1 */'], - 'in_short_array_value_2' => ['/* testMatchArrowInShortArrayValue2 */'], - 'in_short_array_value_3' => ['/* testMatchArrowInShortArrayValue3 */'], - - 'in_long_array_key_1' => ['/* testMatchArrowInLongArrayKey1 */'], - 'in_long_array_key_2' => ['/* testMatchArrowInLongArrayKey2 */'], - 'in_short_array_key_1' => ['/* testMatchArrowInShortArrayKey1 */'], - 'in_short_array_key_2' => ['/* testMatchArrowInShortArrayKey2 */'], - - 'with_long_array_value_with_keys' => ['/* testMatchArrowWithLongArrayBodyWithKeys */'], - 'with_short_array_value_without_keys' => ['/* testMatchArrowWithShortArrayBodyWithoutKeys */'], - 'with_long_array_value_without_keys' => ['/* testMatchArrowWithLongArrayBodyWithoutKeys */'], - 'with_short_array_value_with_keys' => ['/* testMatchArrowWithShortArrayBodyWithKeys */'], - - 'with_short_array_with_keys_as_case' => ['/* testMatchArrowWithShortArrayWithKeysAsCase */'], - 'with_multiple_arrays_with_keys_as_case' => ['/* testMatchArrowWithMultipleArraysWithKeysAsCase */'], - - 'in_fn_body_case' => ['/* testMatchArrowInFnBody1 */'], - 'in_fn_body_default' => ['/* testMatchArrowInFnBody2 */'], - 'with_fn_body_case' => ['/* testMatchArrowWithFnBody1 */'], - 'with_fn_body_default' => ['/* testMatchArrowWithFnBody2 */'], - - 'in_complex_short_array_key_1' => ['/* testMatchArrowInComplexShortArrayKey1 */'], - 'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayKey2 */'], - 'in_complex_short_array_value_1' => ['/* testMatchArrowInComplexShortArrayValue1 */'], - 'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayValue1 */'], - - 'with_long_list_in_body' => ['/* testMatchArrowWithLongListBody */'], - 'with_long_list_in_case' => ['/* testMatchArrowWithLongListInCase */'], - 'with_short_list_in_body' => ['/* testMatchArrowWithShortListBody */'], - 'with_short_list_in_case' => ['/* testMatchArrowWithShortListInCase */'], - 'in_long_list_key' => ['/* testMatchArrowInLongListKey */'], - 'in_short_list_key' => ['/* testMatchArrowInShortListKey */'], + 'single_case' => ['/* testMatchArrowSimpleSingleCase */'], + 'multi_case' => ['/* testMatchArrowSimpleMultiCase */'], + 'single_case_with_trailing_comma' => ['/* testMatchArrowSimpleSingleCaseWithTrailingComma */'], + 'multi_case_with_trailing_comma' => ['/* testMatchArrowSimpleMultiCaseWithTrailingComma */'], + 'match_nested_outer' => ['/* testMatchArrowNestedMatchOuter */'], + 'match_nested_inner' => ['/* testMatchArrowNestedMatchInner */'], + + 'in_long_array_value_1' => ['/* testMatchArrowInLongArrayValue1 */'], + 'in_long_array_value_2' => ['/* testMatchArrowInLongArrayValue2 */'], + 'in_long_array_value_3' => ['/* testMatchArrowInLongArrayValue3 */'], + 'in_short_array_value_1' => ['/* testMatchArrowInShortArrayValue1 */'], + 'in_short_array_value_2' => ['/* testMatchArrowInShortArrayValue2 */'], + 'in_short_array_value_3' => ['/* testMatchArrowInShortArrayValue3 */'], + + 'in_long_array_key_1' => ['/* testMatchArrowInLongArrayKey1 */'], + 'in_long_array_key_2' => ['/* testMatchArrowInLongArrayKey2 */'], + 'in_short_array_key_1' => ['/* testMatchArrowInShortArrayKey1 */'], + 'in_short_array_key_2' => ['/* testMatchArrowInShortArrayKey2 */'], + + 'with_long_array_value_with_keys' => ['/* testMatchArrowWithLongArrayBodyWithKeys */'], + 'with_short_array_value_without_keys' => ['/* testMatchArrowWithShortArrayBodyWithoutKeys */'], + 'with_long_array_value_without_keys' => ['/* testMatchArrowWithLongArrayBodyWithoutKeys */'], + 'with_short_array_value_with_keys' => ['/* testMatchArrowWithShortArrayBodyWithKeys */'], + + 'with_short_array_with_keys_as_case' => ['/* testMatchArrowWithShortArrayWithKeysAsCase */'], + 'with_multiple_arrays_with_keys_as_case' => ['/* testMatchArrowWithMultipleArraysWithKeysAsCase */'], + + 'in_fn_body_case' => ['/* testMatchArrowInFnBody1 */'], + 'in_fn_body_default' => ['/* testMatchArrowInFnBody2 */'], + 'with_fn_body_case' => ['/* testMatchArrowWithFnBody1 */'], + 'with_fn_body_default' => ['/* testMatchArrowWithFnBody2 */'], + + 'in_complex_short_array_key_1' => ['/* testMatchArrowInComplexShortArrayKey1 */'], + 'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayKey2 */'], + 'in_complex_short_array_value_1' => ['/* testMatchArrowInComplexShortArrayValue1 */'], + 'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayValue1 */'], + + 'with_long_list_in_body' => ['/* testMatchArrowWithLongListBody */'], + 'with_long_list_in_case' => ['/* testMatchArrowWithLongListInCase */'], + 'with_short_list_in_body' => ['/* testMatchArrowWithShortListBody */'], + 'with_short_list_in_case' => ['/* testMatchArrowWithShortListInCase */'], + 'in_long_list_key' => ['/* testMatchArrowInLongListKey */'], + 'in_short_list_key' => ['/* testMatchArrowInShortListKey */'], + + 'with_long_array_value_with_default_key' => ['/* testMatchArrowWithNestedLongArrayWithClassConstantKey */'], + 'with_short_array_value_with_default_key' => ['/* testMatchArrowWithNestedShortArrayWithClassConstantKey */'], + 'in_long_array_value_with_default_key' => ['/* testMatchArrowNestedInLongArrayWithClassConstantKey */'], + 'in_short_array_value_with_default_key' => ['/* testMatchArrowNestedInShortArrayWithClassConstantKey */'], ]; }//end dataMatchArrow() From fc4970b75e912827d4ab54e2e7c0c3b6fc5f6f9d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 25 Mar 2021 14:34:21 +0100 Subject: [PATCH 289/733] PHP 8.0 | Tokenizer/PHP: improve retokenization of T_DEFAULT This commit improves the tokenization of "default" keywords, by making sure that the keyword is not used as a constant or property. If it is, it will never be either `T_DEFAULT` or `T_MATCH_DEFAULT`, so it will be retokenized to `T_STRING` early. Fixes 3280 --- src/Tokenizers/PHP.php | 73 +++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 06263166e9..31ab77613a 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1356,40 +1356,63 @@ protected function tokenize($string) if ($tokenIsArray === true && $token[0] === T_DEFAULT ) { - for ($x = ($stackPtr + 1); $x < $numTokens; $x++) { - if ($tokens[$x] === ',') { - // Skip over potential trailing comma (supported in PHP). - continue; + $ignoreContext = [ + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_NS_SEPARATOR => true, + T_PAAMAYIM_NEKUDOTAYIM => true, + ]; + + if (isset($ignoreContext[$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; + } } - if (is_array($tokens[$x]) === false - || isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false + if (isset($tokens[$x]) === true + && is_array($tokens[$x]) === true + && $tokens[$x][0] === T_DOUBLE_ARROW ) { - // Non-empty, non-comma content. - break; - } - } + // 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; + } - 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; - } + $newToken = []; + $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_MATCH_DEFAULT".PHP_EOL; + } + + $finalTokens[$newStackPtr] = $newToken; + $newStackPtr++; + continue; + }//end if + } else { + // Definitely not the "default" keyword. $newToken = []; - $newToken['code'] = T_MATCH_DEFAULT; - $newToken['type'] = 'T_MATCH_DEFAULT'; + $newToken['code'] = T_STRING; + $newToken['type'] = 'T_STRING'; $newToken['content'] = $token[1]; if (PHP_CODESNIFFER_VERBOSITY > 1) { - echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL; + echo "\t\t* token $stackPtr changed from T_DEFAULT to T_STRING".PHP_EOL; } $finalTokens[$newStackPtr] = $newToken; From b6267972f5766891a6cd2cc7099b574ef6de363f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 30 Mar 2021 09:18:05 +1100 Subject: [PATCH 290/733] Changelog for #3203 --- package.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.xml b/package.xml index deb3f7ec47..587ffa06fc 100644 --- a/package.xml +++ b/package.xml @@ -33,6 +33,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Added support for PHP 8.0 named function call arguments -- A new T_PARAM_NAME token is available to represent the label with the name of the function argument in it -- Thanks to Juliette Reinders Folmer for the patch + - Added support for PHP 8.0 attributes + -- The PHP-supplied T_ATTRIBUTE token marks the start of an attribute + -- A new T_ATTRIBUTE_END token is available to mark the end of an attribute + -- New attribute_owner and attribute_closer indexes are available in the tokens array for all tokens inside an attribute + -- Tokenizing of attributes has been backfilled for older PHP versions + -- Thanks to Alessandro Chitolina for the patch - Added support for PHP 8.0 dereferencing of text strings with interpolated variables -- Thanks to Juliette Reinders Folmer for the patch - Added support for PHP 8.0 match expressions From 9ae6d1a2123ea025195756878d04597c2846aa80 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 30 Mar 2021 09:57:14 +1100 Subject: [PATCH 291/733] Updated sniff to ignore attributes (ref #3283) --- src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php | 8 ++++++++ src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc | 1 + 2 files changed, 9 insertions(+) diff --git a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php index 985e645ed3..27454cc1d7 100644 --- a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php +++ b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php @@ -178,6 +178,14 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens) continue; } + // Ignore attributes. + if ($tokens[$i]['code'] === T_ATTRIBUTE + && isset($tokens[$i]['attribute_closer']) === true + ) { + $i = $tokens[$i]['attribute_closer']; + continue; + } + // Detect and skip over symbols. if (isset($symbols[$tokens[$i]['code']]) === true && isset($tokens[$i]['scope_closer']) === true diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc index fdfd9b0985..596d6cf9b0 100644 --- a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc @@ -63,6 +63,7 @@ if (!interface_exists('MyInterface')) { interface MyInterface {} } +#[\Attribute] namespace { class A {} } From 3decd5ef0eb3a9c688f0dc29bd832b8bc3af4406 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 30 Mar 2021 11:02:48 +1100 Subject: [PATCH 292/733] Fixed bug #3284 : Unused parameter false positive when using array index in arrow function --- package.xml | 1 + .../UnusedFunctionParameterSniff.php | 9 ++++++++- .../UnusedFunctionParameterUnitTest.inc | 2 ++ src/Tokenizers/PHP.php | 7 ++++++- tests/Core/Tokenizer/BackfillFnTokenTest.inc | 3 +++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 16 ++++++++++++++++ 6 files changed, 36 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 587ffa06fc..4cc0ba7f3d 100644 --- a/package.xml +++ b/package.xml @@ -150,6 +150,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3273 : Squiz.Functions.FunctionDeclarationArgumentSpacing reports line break as 0 spaces between parenthesis - Fixed bug #3277 : Nullable static return typehint causes whitespace error + - Fixed bug #3284 : Unused parameter false positive when using array index in arrow function diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php index 1cf7661707..58aa29fe85 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php @@ -91,7 +91,7 @@ public function process(File $phpcsFile, $stackPtr) foreach ($methodParams as $param) { if (isset($param['property_visibility']) === true) { - // Ignore. Constructor property promotion. + // Ignore constructor property promotion. continue; } @@ -101,6 +101,13 @@ public function process(File $phpcsFile, $stackPtr) $next = ++$token['scope_opener']; $end = --$token['scope_closer']; + // Check the end token for arrow functions as + // they can end at a content token due to not having + // a clearly defined closing token. + if ($token['code'] === T_FN) { + ++$end; + } + $foundContent = false; $validTokens = [ T_HEREDOC => T_HEREDOC, diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc index bc6ab15689..d800d690fd 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnusedFunctionParameterUnitTest.inc @@ -150,3 +150,5 @@ class ConstructorPropertyPromotionWithContentInMethod { } } } + +$found = in_array_cb($needle, $haystack, fn($array, $needle) => $array[2] === $needle); diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 07651ecfd0..1924cf0745 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2343,7 +2343,12 @@ protected function processAdditional() && $this->tokens[$scopeCloser]['code'] === T_CLOSE_PARENTHESIS && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow ) { - $scopeCloser = $lastEndToken; + for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) { + $scopeCloser = $lastNonEmpty; + break; + } + } } break; diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index d9a03aaa66..72cb244200 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -40,6 +40,9 @@ $extended = fn($c) => $callable(function() { echo 'done'; }, $c); +/* testArrayIndex */ +$found = in_array_cb($needle, $haystack, fn($array, $needle) => $array[2] === $needle); + $result = array_map( /* testReturnType */ static fn(int $number) : int => $number + 1, diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 60b951dfa5..6404026c34 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -192,6 +192,22 @@ public function testClosure() }//end testClosure() + /** + * Test arrow functions using an array index. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testArrayIndex() + { + $token = $this->getTargetToken('/* testArrayIndex */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 8, 17, 'comma'); + + }//end testArrayIndex() + + /** * Test arrow functions with a return type. * From d2812fd40ef05ac45b158c41bf5e6d0407eeb996 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 31 Mar 2021 09:24:34 +1100 Subject: [PATCH 293/733] Ignore attributes when figuring out if the docblock is file level or code level (ref #3286) --- package.xml | 1 + .../PSR12/Sniffs/Files/FileHeaderSniff.php | 21 +++++++++++++++++-- .../Tests/Files/FileHeaderUnitTest.17.inc | 13 ++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.17.inc diff --git a/package.xml b/package.xml index 4cc0ba7f3d..355221694e 100644 --- a/package.xml +++ b/package.xml @@ -1275,6 +1275,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php b/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php index c9a5955006..8a8255cf47 100644 --- a/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php +++ b/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php @@ -166,8 +166,25 @@ public function getHeaderLines(File $phpcsFile, $stackPtr) } // Make sure this is not a code-level docblock. - $end = $tokens[$next]['comment_closer']; - $docToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true); + $end = $tokens[$next]['comment_closer']; + for ($docToken = ($end + 1); $docToken < $phpcsFile->numTokens; $docToken++) { + if (isset(Tokens::$emptyTokens[$tokens[$docToken]['code']]) === true) { + continue; + } + + if ($tokens[$docToken]['code'] === T_ATTRIBUTE + && isset($tokens[$docToken]['attribute_closer']) === true + ) { + $docToken = $tokens[$docToken]['attribute_closer']; + continue; + } + + break; + } + + if ($docToken === $phpcsFile->numTokens) { + $docToken--; + } if (isset($commentOpeners[$tokens[$docToken]['code']]) === false && isset(Tokens::$methodPrefixes[$tokens[$docToken]['code']]) === false diff --git a/src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.17.inc b/src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.17.inc new file mode 100644 index 0000000000..0b1ebe2315 --- /dev/null +++ b/src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.17.inc @@ -0,0 +1,13 @@ + Date: Wed, 31 Mar 2021 10:13:24 +1100 Subject: [PATCH 294/733] Added attibute support --- package.xml | 3 +- .../Sniffs/Commenting/FileCommentSniff.php | 24 +++++-- ...UnitTest.inc => FileCommentUnitTest.1.inc} | 0 .../Commenting/FileCommentUnitTest.2.inc | 9 +++ .../Tests/Commenting/FileCommentUnitTest.php | 69 ++++++++++++------- 5 files changed, 73 insertions(+), 32 deletions(-) rename src/Standards/PEAR/Tests/Commenting/{FileCommentUnitTest.inc => FileCommentUnitTest.1.inc} (100%) create mode 100644 src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.2.inc diff --git a/package.xml b/package.xml index 355221694e..0e7896fdca 100644 --- a/package.xml +++ b/package.xml @@ -1059,7 +1059,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + + diff --git a/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php index 86f5b0044b..e0672ffe3a 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php @@ -138,12 +138,24 @@ public function process(File $phpcsFile, $stackPtr) $commentEnd = $tokens[$commentStart]['comment_closer']; - $nextToken = $phpcsFile->findNext( - T_WHITESPACE, - ($commentEnd + 1), - null, - true - ); + for ($nextToken = ($commentEnd + 1); $nextToken < $phpcsFile->numTokens; $nextToken++) { + if ($tokens[$nextToken]['code'] === T_WHITESPACE) { + continue; + } + + if ($tokens[$nextToken]['code'] === T_ATTRIBUTE + && isset($tokens[$nextToken]['attribute_closer']) === true + ) { + $nextToken = $tokens[$nextToken]['attribute_closer']; + continue; + } + + break; + } + + if ($nextToken === $phpcsFile->numTokens) { + $nextToken--; + } $ignore = [ T_CLASS, diff --git a/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.1.inc similarity index 100% rename from src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.inc rename to src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.1.inc diff --git a/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.2.inc b/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.2.inc new file mode 100644 index 0000000000..8845eb1906 --- /dev/null +++ b/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.2.inc @@ -0,0 +1,9 @@ + */ - public function getErrorList() + public function getErrorList($testFile='FileCommentUnitTest.inc') { - return [ - 21 => 1, - 23 => 2, - 24 => 1, - 26 => 1, - 28 => 1, - 29 => 1, - 30 => 1, - 31 => 1, - 32 => 2, - 33 => 1, - 34 => 1, - 35 => 1, - 40 => 2, - 41 => 2, - 43 => 1, - ]; + switch ($testFile) { + case 'FileCommentUnitTest.1.inc': + return [ + 21 => 1, + 23 => 2, + 24 => 1, + 26 => 1, + 28 => 1, + 29 => 1, + 30 => 1, + 31 => 1, + 32 => 2, + 33 => 1, + 34 => 1, + 35 => 1, + 40 => 2, + 41 => 2, + 43 => 1, + ]; + + case 'FileCommentUnitTest.2.inc': + return [1 => 1]; + + default: + return []; + }//end switch }//end getErrorList() @@ -52,16 +63,24 @@ public function getErrorList() * The key of the array should represent the line number and the value * should represent the number of warnings that should occur on that line. * + * @param string $testFile The name of the file being tested. + * * @return array */ - public function getWarningList() + public function getWarningList($testFile='FileCommentUnitTest.inc') { - return [ - 29 => 1, - 30 => 1, - 34 => 1, - 43 => 1, - ]; + switch ($testFile) { + case 'FileCommentUnitTest.1.inc': + return [ + 29 => 1, + 30 => 1, + 34 => 1, + 43 => 1, + ]; + + default: + return []; + }//end switch }//end getWarningList() From 6da6d82e485902a27558eab1be5275a21654ebfe Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 31 Mar 2021 10:14:16 +1100 Subject: [PATCH 295/733] Added attribute support --- package.xml | 1 + .../Sniffs/Commenting/FileCommentSniff.php | 24 ++++++++++++++----- .../Commenting/FileCommentUnitTest.7.inc | 12 ++++++++++ .../Tests/Commenting/FileCommentUnitTest.php | 1 + 4 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.7.inc diff --git a/package.xml b/package.xml index 0e7896fdca..ce352c57fd 100644 --- a/package.xml +++ b/package.xml @@ -1687,6 +1687,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php index 66fc5e4db1..73eb31b71f 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php @@ -72,12 +72,24 @@ public function process(File $phpcsFile, $stackPtr) $commentEnd = $tokens[$commentStart]['comment_closer']; - $nextToken = $phpcsFile->findNext( - T_WHITESPACE, - ($commentEnd + 1), - null, - true - ); + for ($nextToken = ($commentEnd + 1); $nextToken < $phpcsFile->numTokens; $nextToken++) { + if ($tokens[$nextToken]['code'] === T_WHITESPACE) { + continue; + } + + if ($tokens[$nextToken]['code'] === T_ATTRIBUTE + && isset($tokens[$nextToken]['attribute_closer']) === true + ) { + $nextToken = $tokens[$nextToken]['attribute_closer']; + continue; + } + + break; + } + + if ($nextToken === $phpcsFile->numTokens) { + $nextToken--; + } $ignore = [ T_CLASS, diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.7.inc b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.7.inc new file mode 100644 index 0000000000..7074dac234 --- /dev/null +++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.7.inc @@ -0,0 +1,12 @@ + + * @copyright 2010-2014 Squiz Pty Ltd (ABN 77 084 670 600) + */ +#[Attribute] +class Foo { +} diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php index b3708e80cf..080df3aafc 100644 --- a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php @@ -44,6 +44,7 @@ public function getErrorList($testFile='FileCommentUnitTest.inc') case 'FileCommentUnitTest.4.inc': case 'FileCommentUnitTest.6.inc': + case 'FileCommentUnitTest.7.inc': return [1 => 1]; case 'FileCommentUnitTest.5.inc': From c132dc510b6f05c819404b2e43e2722409de605a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 31 Mar 2021 10:14:33 +1100 Subject: [PATCH 296/733] Added attribute support --- .../Sniffs/Commenting/ClassCommentSniff.php | 20 ++++++++++++++++--- .../Tests/Commenting/ClassCommentUnitTest.inc | 15 ++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php index 90e9eb8cab..ac3351e02b 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php @@ -47,10 +47,24 @@ public function process(File $phpcsFile, $stackPtr) $type = strtolower($tokens[$stackPtr]['content']); $errorData = [$type]; - $find = Tokens::$methodPrefixes; - $find[] = T_WHITESPACE; + $find = Tokens::$methodPrefixes; + $find[T_WHITESPACE] = T_WHITESPACE; + + for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) { + if (isset($find[$tokens[$commentEnd]['code']]) === true) { + continue; + } + + if ($tokens[$commentEnd]['code'] === T_ATTRIBUTE_END + && isset($tokens[$commentEnd]['attribute_opener']) === true + ) { + $commentEnd = $tokens[$commentEnd]['attribute_opener']; + continue; + } + + break; + } - $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG && $tokens[$commentEnd]['code'] !== T_COMMENT ) { diff --git a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc index 0ca87512f8..8414efbedb 100644 --- a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc +++ b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc @@ -118,3 +118,18 @@ trait Empty_Trait_Doc { }//end trait + + +/** + * Sample class comment + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +#[Authenticate('admin_logged_in')] +class TodoController extends AbstractController implements MustBeLoggedInInterface +{ +} From bc882d47b42bd625b05cc3fa6637b84533be682d Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 31 Mar 2021 10:14:39 +1100 Subject: [PATCH 297/733] Added attribute support --- .../Sniffs/Commenting/ClassCommentSniff.php | 25 ++++++++++++++++--- .../Tests/Commenting/ClassCommentUnitTest.inc | 8 ++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php index 3aa969dc78..0da4ef2450 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php @@ -50,9 +50,28 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $find = Tokens::$methodPrefixes; - $find[] = T_WHITESPACE; + $find[T_WHITESPACE] = T_WHITESPACE; + + $previousContent = null; + for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) { + if (isset($find[$tokens[$commentEnd]['code']]) === true) { + continue; + } + + if ($previousContent === null) { + $previousContent = $commentEnd; + } + + if ($tokens[$commentEnd]['code'] === T_ATTRIBUTE_END + && isset($tokens[$commentEnd]['attribute_opener']) === true + ) { + $commentEnd = $tokens[$commentEnd]['attribute_opener']; + continue; + } + + break; + } - $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG && $tokens[$commentEnd]['code'] !== T_COMMENT ) { @@ -69,7 +88,7 @@ public function process(File $phpcsFile, $stackPtr) return; } - if ($tokens[$commentEnd]['line'] !== ($tokens[$stackPtr]['line'] - 1)) { + if ($tokens[$previousContent]['line'] !== ($tokens[$stackPtr]['line'] - 1)) { $error = 'There must be no blank lines after the class comment'; $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); } diff --git a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc index eefa44c9bb..28aff226b0 100644 --- a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc @@ -123,3 +123,11 @@ class Space_At_end { }//end class + +/** + * Sample class comment + */ +#[Attribute] +class AllGood +{ +} From 103004077b81a29ed9766bb7c738bbe5632becf6 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 31 Mar 2021 10:21:26 +1100 Subject: [PATCH 298/733] Listed updated attribute sniffs in changelog --- package.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package.xml b/package.xml index ce352c57fd..649836daf1 100644 --- a/package.xml +++ b/package.xml @@ -38,6 +38,13 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- A new T_ATTRIBUTE_END token is available to mark the end of an attribute -- New attribute_owner and attribute_closer indexes are available in the tokens array for all tokens inside an attribute -- Tokenizing of attributes has been backfilled for older PHP versions + -- The following sniffs have been updated to support attributes: + --- PEAR.Commenting.ClassComment + --- PEAR.Commenting.FileComment + --- PSR1.Files.SideEffects + --- PSR12.Files.FileHeader + --- Squiz.Commenting.ClassComment + --- Squiz.Commenting.FileComment -- Thanks to Alessandro Chitolina for the patch - Added support for PHP 8.0 dereferencing of text strings with interpolated variables -- Thanks to Juliette Reinders Folmer for the patch From cf0a1953268c5191e3d86136e73c26ad7cc3ce7a Mon Sep 17 00:00:00 2001 From: javer Date: Wed, 31 Mar 2021 18:07:53 +0300 Subject: [PATCH 299/733] PHP 8.0 | Squiz.WhiteSpace.FunctionSpacing: add support for attributes --- .../WhiteSpace/FunctionSpacingSniff.php | 14 +++++++++ .../WhiteSpace/FunctionSpacingUnitTest.1.inc | 31 +++++++++++++++++++ .../FunctionSpacingUnitTest.1.inc.fixed | 29 +++++++++++++++++ .../WhiteSpace/FunctionSpacingUnitTest.php | 5 +++ 4 files changed, 79 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/FunctionSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/FunctionSpacingSniff.php index 83a62d3542..1f4704b91f 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/FunctionSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/FunctionSpacingSniff.php @@ -115,6 +115,12 @@ public function process(File $phpcsFile, $stackPtr) $ignore = ([T_WHITESPACE => T_WHITESPACE] + Tokens::$methodPrefixes); $prev = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); + + while ($tokens[$prev]['code'] === T_ATTRIBUTE_END) { + // Skip past function attributes. + $prev = $phpcsFile->findPrevious($ignore, ($tokens[$prev]['attribute_opener'] - 1), null, true); + } + if ($tokens[$prev]['code'] === T_DOC_COMMENT_CLOSE_TAG) { // Skip past function docblocks. $prev = $phpcsFile->findPrevious($ignore, ($tokens[$prev]['comment_opener'] - 1), null, true); @@ -241,6 +247,14 @@ public function process(File $phpcsFile, $stackPtr) return; } + while ($tokens[$prevContent]['code'] === T_ATTRIBUTE_END + && $tokens[$prevContent]['line'] === ($currentLine - 1) + ) { + // Account for function attributes. + $currentLine = $tokens[$tokens[$prevContent]['attribute_opener']]['line']; + $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, ($tokens[$prevContent]['attribute_opener'] - 1), null, true); + } + if ($tokens[$prevContent]['code'] === T_DOC_COMMENT_CLOSE_TAG && $tokens[$prevContent]['line'] === ($currentLine - 1) ) { diff --git a/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.1.inc b/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.1.inc index 2e058050d8..36a287f077 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.1.inc @@ -540,6 +540,37 @@ class MyClass { // function d() {} } +class ClassWithAttributes { + + #[Attribute1] + #[Attribute2] + function a(){} + + + #[Attribute3] + function b(){} + #[Attribute4] + function c(){} + + + /** + * Description. + */ + #[Attribute5] + function d(){} + /** + * Description. + */ + #[Attribute6] + #[Attribute7] + function e(){} + + + #[Attribute8] + #[Attribute9] + function f(){} +} + // phpcs:set Squiz.WhiteSpace.FunctionSpacing spacing 2 // phpcs:set Squiz.WhiteSpace.FunctionSpacing spacingBeforeFirst 2 // phpcs:set Squiz.WhiteSpace.FunctionSpacing spacingAfterLast 2 diff --git a/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.1.inc.fixed index eec26c6913..ac2df1cb7b 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.1.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.1.inc.fixed @@ -624,6 +624,35 @@ class MyClass { // function d() {} } +class ClassWithAttributes { + #[Attribute1] + #[Attribute2] + function a(){} + + #[Attribute3] + function b(){} + + #[Attribute4] + function c(){} + + /** + * Description. + */ + #[Attribute5] + function d(){} + + /** + * Description. + */ + #[Attribute6] + #[Attribute7] + function e(){} + + #[Attribute8] + #[Attribute9] + function f(){} +} + // phpcs:set Squiz.WhiteSpace.FunctionSpacing spacing 2 // phpcs:set Squiz.WhiteSpace.FunctionSpacing spacingBeforeFirst 2 // phpcs:set Squiz.WhiteSpace.FunctionSpacing spacingAfterLast 2 diff --git a/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.php index 9f202888a9..fabb6adb92 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/FunctionSpacingUnitTest.php @@ -90,6 +90,11 @@ public function getErrorList($testFile='') 495 => 1, 529 => 1, 539 => 1, + 547 => 2, + 551 => 1, + 553 => 1, + 560 => 1, + 566 => 1, ]; case 'FunctionSpacingUnitTest.2.inc': From cf92acd14fa56f92e4e00f7a1daed0155455eea7 Mon Sep 17 00:00:00 2001 From: javer Date: Wed, 31 Mar 2021 21:43:25 +0300 Subject: [PATCH 300/733] PHP 8.0 | Squiz.Commenting.FunctionComment: add support for mixed param --- .../Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 6 ++++++ .../Squiz/Tests/Commenting/FunctionCommentUnitTest.inc | 9 +++++++++ .../Tests/Commenting/FunctionCommentUnitTest.inc.fixed | 9 +++++++++ .../Squiz/Tests/Commenting/FunctionCommentUnitTest.php | 9 +++++++++ 4 files changed, 33 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index a5403337a0..e75633c5d8 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -444,6 +444,12 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) } } + if ($this->phpVersion >= 80000) { + if ($suggestedName === 'mixed') { + $suggestedTypeHint = 'mixed'; + } + } + if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true) { $typeHint = $realParams[$pos]['type_hint']; diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc index 2f79a6218d..aaaa490706 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc @@ -1012,3 +1012,12 @@ public function foo($a, $b) {} * {@inheritDoc} */ public function foo($a, $b) {} + +/** + * Foo. + * + * @param mixed $a Comment. + * + * @return mixed + */ +public function foo(mixed $a): mixed {} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index be5811281b..468710cb8e 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -1012,3 +1012,12 @@ public function foo($a, $b) {} * {@inheritDoc} */ public function foo($a, $b) {} + +/** + * Foo. + * + * @param mixed $a Comment. + * + * @return mixed + */ +public function foo(mixed $a): mixed {} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php index 69628243c1..7d7ab71928 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php @@ -144,6 +144,15 @@ public function getErrorList() $errors[992] = 2; } + // Mixed type hints only work from PHP 8.0 onwards. + if (PHP_VERSION_ID >= 80000) { + $errors[265] = 1; + $errors[459] = 1; + $errors[893] = 3; + } else { + $errors[1023] = 1; + } + return $errors; }//end getErrorList() From 2c8e5a31e088195021d6587609697dd4f7506727 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 1 Apr 2021 09:25:47 +1100 Subject: [PATCH 301/733] Changelog for #3288 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 649836daf1..109091ed0b 100644 --- a/package.xml +++ b/package.xml @@ -45,6 +45,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> --- PSR12.Files.FileHeader --- Squiz.Commenting.ClassComment --- Squiz.Commenting.FileComment + --- Squiz.WhiteSpace.FunctionSpacing + ---- Thanks to Vadim Borodavko for the patch -- Thanks to Alessandro Chitolina for the patch - Added support for PHP 8.0 dereferencing of text strings with interpolated variables -- Thanks to Juliette Reinders Folmer for the patch From d427d75a155195970133974162e7fa9dbf3069da Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 1 Apr 2021 09:36:57 +1100 Subject: [PATCH 302/733] Changelog for #3289 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 109091ed0b..0a71b163b1 100644 --- a/package.xml +++ b/package.xml @@ -127,6 +127,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Set the skipIfInheritdoc sniff property to "true" to skip checking function comments if the content is only {@inhertidoc} -- The default remains at "false", so these comments will continue to report errors -- Thanks to Jess Myrbo for the patch + - Squiz.Commenting.FunctionComment now supports the PHP 8 mixed type + -- Thanks to Vadim Borodavko for the patch - Squiz.PHP.NonExecutableCode now has improved handling of syntax errors -- Thanks to Thiemo Kreuz for the patch - Squiz.WhiteSpace.ScopeKeywordSpacing now checks spacing when using PHP 8.0 constructor property promotion From f5d8a62405ca2903f3fc3d03f71e12c3d971aa7d Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 6 Apr 2021 09:05:59 +1000 Subject: [PATCH 303/733] Added some extra debug output --- .../Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index 3d9ea59faf..c75843e4b2 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -1017,13 +1017,14 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { $disableExactEnd = max($disableExactEnd, $tokens[$i]['bracket_closer']); if ($this->debug === true) { - $line = $tokens[$i]['line']; - $type = $tokens[$disableExactEnd]['type']; + $line = $tokens[$i]['line']; + $type = $tokens[$disableExactEnd]['type']; + $endLine = $tokens[$disableExactEnd]['line']; echo "Opening short array bracket found on line $line".PHP_EOL; if ($disableExactEnd === $tokens[$i]['bracket_closer']) { - echo "\t=> disabling exact indent checking until $disableExactEnd ($type)".PHP_EOL; + echo "\t=> disabling exact indent checking until $disableExactEnd ($type) on line $endLine".PHP_EOL; } else { - echo "\t=> continuing to disable exact indent checking until $disableExactEnd ($type)".PHP_EOL; + echo "\t=> continuing to disable exact indent checking until $disableExactEnd ($type) on line $endLine".PHP_EOL; } } } From 97f78982711bdf122200dc37be6f33dfbd4aa4dd Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 6 Apr 2021 09:07:47 +1000 Subject: [PATCH 304/733] Fixed bug #3287 : Wrongly assumed indentation with match and arrays Match checking in findStartOfStatement needed to skip over short arrays and function calls etc, so now using findEndOfStatement for that. That exposed a bug in findEndOfStatement when checking a match arrow directly, which is now also fixed. --- src/Files/File.php | 40 ++++++++++--------- .../WhiteSpace/ScopeIndentUnitTest.1.inc | 14 +++++++ .../ScopeIndentUnitTest.1.inc.fixed | 14 +++++++ .../WhiteSpace/ScopeIndentUnitTest.2.inc | 14 +++++++ .../ScopeIndentUnitTest.2.inc.fixed | 14 +++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 ++-- tests/Core/File/FindEndOfStatementTest.php | 5 ++- tests/Core/File/FindStartOfStatementTest.inc | 3 +- tests/Core/File/FindStartOfStatementTest.php | 17 ++++++-- 9 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 39e62b6d10..aef4fbf27b 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2354,8 +2354,8 @@ public function findStartOfStatement($start, $ignore=null) return $next; } - $nextComma = $this->findNext(T_COMMA, ($prevMatchArrow + 1)); - $next = $this->findNext(Util\Tokens::$emptyTokens, ($nextComma + 1), null, true); + $end = $this->findEndOfStatement($prevMatchArrow); + $next = $this->findNext(Util\Tokens::$emptyTokens, ($end + 1), null, true); return $next; } }//end if @@ -2459,26 +2459,28 @@ public function findEndOfStatement($start, $ignore=null) // If the start token is inside the case part of a match expression, // advance to the match arrow and continue looking for the // end of the statement from there so that we skip over commas. - $matchExpression = $this->getCondition($start, T_MATCH); - if ($matchExpression !== false) { - $beforeArrow = true; - $prevMatchArrow = $this->findPrevious(T_MATCH_ARROW, ($start - 1), $this->tokens[$matchExpression]['scope_opener']); - if ($prevMatchArrow !== false) { - $prevComma = $this->findNext(T_COMMA, ($prevMatchArrow + 1), $start); - if ($prevComma === false) { - // No comma between this token and the last match arrow, - // so this token exists after the arrow and we can continue - // checking as normal. - $beforeArrow = false; + if ($this->tokens[$start]['code'] !== T_MATCH_ARROW) { + $matchExpression = $this->getCondition($start, T_MATCH); + if ($matchExpression !== false) { + $beforeArrow = true; + $prevMatchArrow = $this->findPrevious(T_MATCH_ARROW, ($start - 1), $this->tokens[$matchExpression]['scope_opener']); + if ($prevMatchArrow !== false) { + $prevComma = $this->findNext(T_COMMA, ($prevMatchArrow + 1), $start); + if ($prevComma === false) { + // No comma between this token and the last match arrow, + // so this token exists after the arrow and we can continue + // checking as normal. + $beforeArrow = false; + } } - } - if ($beforeArrow === true) { - $nextMatchArrow = $this->findNext(T_MATCH_ARROW, ($start + 1), $this->tokens[$matchExpression]['scope_closer']); - if ($nextMatchArrow !== false) { - $start = $nextMatchArrow; + if ($beforeArrow === true) { + $nextMatchArrow = $this->findNext(T_MATCH_ARROW, ($start + 1), $this->tokens[$matchExpression]['scope_closer']); + if ($nextMatchArrow !== false) { + $start = $nextMatchArrow; + } } - } + }//end if }//end if $lastNotEmpty = $start; diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index c3d7d80913..6d63a64a34 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1533,6 +1533,20 @@ $list = [ } ]; +$foo = match ($type) { + 'a' => [ + 'aa' => 'DESC', + 'ab' => 'DESC', + ], + 'b' => [ + 'ba' => 'DESC', + 'bb' => 'DESC', + ], + default => [ + 'da' => 'DESC', + ], +}; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 9c6d4878a9..51000a01f7 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1533,6 +1533,20 @@ $list = [ } ]; +$foo = match ($type) { + 'a' => [ + 'aa' => 'DESC', + 'ab' => 'DESC', + ], + 'b' => [ + 'ba' => 'DESC', + 'bb' => 'DESC', + ], + default => [ + 'da' => 'DESC', + ], +}; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index b839df7dc2..9e40384b2f 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1533,6 +1533,20 @@ $list = [ } ]; +$foo = match ($type) { + 'a' => [ + 'aa' => 'DESC', + 'ab' => 'DESC', + ], + 'b' => [ + 'ba' => 'DESC', + 'bb' => 'DESC', + ], + default => [ + 'da' => 'DESC', + ], +}; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index a373cb837e..3aed0fcf13 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1533,6 +1533,20 @@ $list = [ } ]; +$foo = match ($type) { + 'a' => [ + 'aa' => 'DESC', + 'ab' => 'DESC', + ], + 'b' => [ + 'ba' => 'DESC', + 'bb' => 'DESC', + ], + default => [ + 'da' => 'DESC', + ], +}; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index d21dc3f3b2..03ade71b44 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -187,10 +187,10 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1527 => 1, 1529 => 1, 1530 => 1, - 1544 => 1, - 1545 => 1, - 1546 => 1, - 1547 => 1, + 1558 => 1, + 1559 => 1, + 1560 => 1, + 1561 => 1, ]; }//end getErrorList() diff --git a/tests/Core/File/FindEndOfStatementTest.php b/tests/Core/File/FindEndOfStatementTest.php index 6b9f6215ef..7b4b6db9cd 100644 --- a/tests/Core/File/FindEndOfStatementTest.php +++ b/tests/Core/File/FindEndOfStatementTest.php @@ -286,9 +286,12 @@ public function testMatchMultipleCase() { $start = $this->getTargetToken('/* testMatchMultipleCase */', T_LNUMBER); $found = self::$phpcsFile->findEndOfStatement($start); - $this->assertSame(($start + 13), $found); + $start += 6; + $found = self::$phpcsFile->findEndOfStatement($start); + $this->assertSame(($start + 7), $found); + }//end testMatchMultipleCase() diff --git a/tests/Core/File/FindStartOfStatementTest.inc b/tests/Core/File/FindStartOfStatementTest.inc index eed940471f..a759a1aa6d 100644 --- a/tests/Core/File/FindStartOfStatementTest.inc +++ b/tests/Core/File/FindStartOfStatementTest.inc @@ -94,7 +94,8 @@ $result = match ($key) { /* testMatchArray */ $result = match ($key) { 1 => [1,2,3], - 2 => [1 => one(), 2 => two()], + 2 => [1 => one($a, $b), 2 => two($b, $c)], + 3 => [], }; /* testNestedMatch */ diff --git a/tests/Core/File/FindStartOfStatementTest.php b/tests/Core/File/FindStartOfStatementTest.php index 50b475e059..9ff0680caa 100644 --- a/tests/Core/File/FindStartOfStatementTest.php +++ b/tests/Core/File/FindStartOfStatementTest.php @@ -419,16 +419,25 @@ public function testMatchClosure() */ public function testMatchArray() { - $start = $this->getTargetToken('/* testMatchArray */', T_LNUMBER); + // Start of first case statement. + $start = $this->getTargetToken('/* testMatchArray */', T_LNUMBER); + $found = self::$phpcsFile->findStartOfStatement($start); + $this->assertSame($start, $found); + + // Comma after first statement. $start += 11; $found = self::$phpcsFile->findStartOfStatement($start); - $this->assertSame(($start - 7), $found); - $start += 25; + // Start of second case statement. + $start += 3; $found = self::$phpcsFile->findStartOfStatement($start); + $this->assertSame($start, $found); - $this->assertSame(($start - 18), $found); + // Comma after first statement. + $start += 30; + $found = self::$phpcsFile->findStartOfStatement($start); + $this->assertSame(($start - 26), $found); }//end testMatchArray() From b245bb315dae6dc7347681e4f5a55dd7bdfcd045 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 8 Apr 2021 11:26:22 +1000 Subject: [PATCH 305/733] Fixed bug #3195 : Generic.WhiteSpace.ScopeIndent confusing message when combination of tabs and spaces found --- package.xml | 1 + .../Sniffs/WhiteSpace/ScopeIndentSniff.php | 32 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/package.xml b/package.xml index 0a71b163b1..464e815c0d 100644 --- a/package.xml +++ b/package.xml @@ -155,6 +155,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3192 : findStartOfStatement doesn't work correctly inside switch -- Thanks to Vincent Langlet for the patch + - Fixed bug #3195 : Generic.WhiteSpace.ScopeIndent confusing message when combination of tabs and spaces found - Fixed bug #3197 : Squiz.NamingConventions.ValidVariableName does not use correct error code for all member vars - Fixed bug #3219 : Generic.Formatting.MultipleStatementAlignment false positive for empty anonymous classes and closures - Fixed bug #3258 : Squiz.Formatting.OperatorBracket duplicate error messages for unary minus diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index c75843e4b2..a01449197a 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -973,18 +973,38 @@ public function process(File $phpcsFile, $stackPtr) } if ($this->tabIndent === true) { - $error .= '%s tabs, found %s'; - $data = [ - floor($checkIndent / $this->tabWidth), - floor($tokenIndent / $this->tabWidth), - ]; + $expectedTabs = floor($checkIndent / $this->tabWidth); + $foundTabs = floor($tokenIndent / $this->tabWidth); + $foundSpaces = ($tokenIndent - ($foundTabs * $this->tabWidth)); + if ($foundSpaces > 0) { + if ($foundTabs > 0) { + $error .= '%s tabs, found %s tabs and %s spaces'; + $data = [ + $expectedTabs, + $foundTabs, + $foundSpaces, + ]; + } else { + $error .= '%s tabs, found %s spaces'; + $data = [ + $expectedTabs, + $foundSpaces, + ]; + } + } else { + $error .= '%s tabs, found %s'; + $data = [ + $expectedTabs, + $foundTabs, + ]; + }//end if } else { $error .= '%s spaces, found %s'; $data = [ $checkIndent, $tokenIndent, ]; - } + }//end if if ($this->debug === true) { $line = $tokens[$checkToken]['line']; From 9ca9a54e814724cb22cfc27ad3a639e0236fdccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Thu, 8 Apr 2021 10:53:05 +0200 Subject: [PATCH 306/733] Squiz.PHP.DisallowMultipleAssignmentsSniff: Ignore default value assignments in arrow functions --- .../Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php | 2 +- .../Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php index b0372837fc..cf29e7b064 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php @@ -43,7 +43,7 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); // Ignore default value assignments in function definitions. - $function = $phpcsFile->findPrevious([T_FUNCTION, T_CLOSURE], ($stackPtr - 1), null, false, null, true); + $function = $phpcsFile->findPrevious([T_FUNCTION, T_CLOSURE, T_FN], ($stackPtr - 1), null, false, null, true); if ($function !== false) { $opener = $tokens[$function]['parenthesis_opener']; $closer = $tokens[$function]['parenthesis_closer']; diff --git a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc index 6d86dc54b8..43069ed182 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc @@ -87,3 +87,5 @@ $array = [ false => 0 }, ]; + +$arrow_function = fn ($a = null) => $a; From 2c4199086fc8a420b1a6d5a4c5a80857bfb4d0de Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Thu, 8 Apr 2021 11:08:05 +0100 Subject: [PATCH 307/733] Spelling - seperator should be separator --- tests/Core/File/FindEndOfStatementTest.php | 2 +- tests/Core/File/FindStartOfStatementTest.php | 2 +- tests/Core/Tokenizer/BackfillNumericSeparatorTest.php | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Core/File/FindEndOfStatementTest.php b/tests/Core/File/FindEndOfStatementTest.php index 7b4b6db9cd..7bff26b566 100644 --- a/tests/Core/File/FindEndOfStatementTest.php +++ b/tests/Core/File/FindEndOfStatementTest.php @@ -278,7 +278,7 @@ public function testMatchDefault() /** - * Test multiple comma-seperated match expression case values. + * Test multiple comma-separated match expression case values. * * @return void */ diff --git a/tests/Core/File/FindStartOfStatementTest.php b/tests/Core/File/FindStartOfStatementTest.php index 9ff0680caa..33c0686ea4 100644 --- a/tests/Core/File/FindStartOfStatementTest.php +++ b/tests/Core/File/FindStartOfStatementTest.php @@ -315,7 +315,7 @@ public function testMatchDefault() /** - * Test multiple comma-seperated match expression case values. + * Test multiple comma-separated match expression case values. * * @return void */ diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php index 443cc30ef1..ee4275a214 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php @@ -1,6 +1,6 @@ * @copyright 2019 Squiz Pty Ltd (ABN 77 084 670 600) @@ -16,7 +16,7 @@ class BackfillNumericSeparatorTest extends AbstractMethodUnitTest /** - * Test that numbers using numeric seperators are tokenized correctly. + * Test that numbers using numeric separators are tokenized correctly. * * @param array $testData The data required for the specific test case. * @@ -145,7 +145,7 @@ public function dataTestBackfill() /** - * Test that numbers using numeric seperators which are considered parse errors and/or + * Test that numbers using numeric separators which are considered parse errors and/or * which aren't relevant to the backfill, do not incorrectly trigger the backfill anyway. * * @param string $testMarker The comment which prefaces the target token in the test file. From c19a0f58f1a15287190a09211a0a715a28212d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Thu, 8 Apr 2021 13:20:29 +0200 Subject: [PATCH 308/733] Squiz.PHP.DisallowMultipleAssignmentsSniff: Fixed false positive when assigment is on first line in closure --- .../Sniffs/PHP/DisallowMultipleAssignmentsSniff.php | 12 ++++++------ .../PHP/DisallowMultipleAssignmentsUnitTest.inc | 9 +++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php index cf29e7b064..13525df673 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php @@ -83,6 +83,12 @@ public function process(File $phpcsFile, $stackPtr) */ for ($varToken = ($stackPtr - 1); $varToken >= 0; $varToken--) { + if (in_array($tokens[$varToken]['code'], [T_SEMICOLON, T_OPEN_CURLY_BRACKET], true) === true) { + // We've reached the next statement, so we + // didn't find a variable. + return; + } + // Skip brackets. if (isset($tokens[$varToken]['parenthesis_opener']) === true && $tokens[$varToken]['parenthesis_opener'] < $varToken) { $varToken = $tokens[$varToken]['parenthesis_opener']; @@ -94,12 +100,6 @@ public function process(File $phpcsFile, $stackPtr) continue; } - if ($tokens[$varToken]['code'] === T_SEMICOLON) { - // We've reached the next statement, so we - // didn't find a variable. - return; - } - if ($tokens[$varToken]['code'] === T_VARIABLE) { // We found our variable. break; diff --git a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc index 43069ed182..d76daa76b7 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc @@ -89,3 +89,12 @@ $array = [ ]; $arrow_function = fn ($a = null) => $a; + +function ($html) { + $regEx = '/regexp/'; + + return preg_replace_callback($regEx, function ($matches) { + [$all] = $matches; + return $all; + }, $html); +}; From 557756cd2b801573f5887b8a8462b89c49b9d35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Thu, 8 Apr 2021 13:38:22 +0200 Subject: [PATCH 309/733] Squiz.PHP.DisallowMultipleAssignmentsSniff: Fixed false positive when assigment is after goto label --- .../Sniffs/PHP/DisallowMultipleAssignmentsSniff.php | 1 + .../Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php index 13525df673..4448d2445b 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php @@ -146,6 +146,7 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$varToken]['code'] === T_VARIABLE || $tokens[$varToken]['code'] === T_OPEN_TAG + || $tokens[$varToken]['code'] === T_GOTO_LABEL || $tokens[$varToken]['code'] === T_INLINE_THEN || $tokens[$varToken]['code'] === T_INLINE_ELSE || $tokens[$varToken]['code'] === T_SEMICOLON diff --git a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc index d76daa76b7..f657fb4a4c 100644 --- a/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DisallowMultipleAssignmentsUnitTest.inc @@ -98,3 +98,13 @@ function ($html) { return $all; }, $html); }; + + +function () { + $a = false; + + some_label: + + $b = getB(); +}; + From ccca3137dca65808c8de168d989341ae5e891ab9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 1 Apr 2020 10:54:02 +0200 Subject: [PATCH 310/733] Improve check for special method and add option --- .../Commenting/FunctionCommentSniff.php | 20 +++++++++--- .../Commenting/FunctionCommentUnitTest.inc | 31 +++++++++++++++++++ .../FunctionCommentUnitTest.inc.fixed | 31 +++++++++++++++++++ .../Commenting/FunctionCommentUnitTest.php | 2 ++ .../Commenting/FunctionCommentSniff.php | 9 +++--- .../Commenting/FunctionCommentUnitTest.inc | 22 ++++++++++++- .../FunctionCommentUnitTest.inc.fixed | 22 ++++++++++++- .../Commenting/FunctionCommentUnitTest.php | 1 + 8 files changed, 127 insertions(+), 11 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php index dd08e84b3d..3c3dbfc408 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php @@ -25,6 +25,16 @@ class FunctionCommentSniff implements Sniff */ public $minimumVisibility = 'private'; + /** + * Array of methods which do not require a return type. + * + * @var array + */ + public $specialMethods = [ + '__construct', + '__destruct', + ]; + /** * Returns an array of tokens this test wants to listen for. @@ -135,7 +145,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) // Skip constructor and destructor. $methodName = $phpcsFile->getDeclarationName($stackPtr); - $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct'); + $isSpecialMethod = in_array($methodName, $this->specialMethods, true); $return = null; foreach ($tokens[$commentStart]['comment_tags'] as $tag) { @@ -150,10 +160,6 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) } } - if ($isSpecialMethod === true) { - return; - } - if ($return !== null) { $content = $tokens[($return + 2)]['content']; if (empty($content) === true || $tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) { @@ -161,6 +167,10 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) $phpcsFile->addError($error, $return, 'MissingReturnType'); } } else { + if ($isSpecialMethod === true) { + return; + } + $error = 'Missing @return tag in function comment'; $phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn'); }//end if diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc index 1d32837b6f..0e935eadf6 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc @@ -398,3 +398,34 @@ private function setTranslator4($a, &$b): void { $this->translator = $translator; } + +class Bar { + /** + * The PHP5 constructor + * + * @return + */ + public function __construct() { + + } +} + +// phpcs:set PEAR.Commenting.FunctionComment specialMethods[] +class Bar { + /** + * The PHP5 constructor + */ + public function __construct() { + + } +} + +// phpcs:set PEAR.Commenting.FunctionComment specialMethods[] ignored +/** + * Should be ok + */ +public function ignored() { + +} + +// phpcs:set PEAR.Commenting.FunctionComment specialMethods[] __construct,__destruct diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 28e8b4bcd4..29588134d6 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -398,3 +398,34 @@ private function setTranslator4($a, &$b): void { $this->translator = $translator; } + +class Bar { + /** + * The PHP5 constructor + * + * @return + */ + public function __construct() { + + } +} + +// phpcs:set PEAR.Commenting.FunctionComment specialMethods[] +class Bar { + /** + * The PHP5 constructor + */ + public function __construct() { + + } +} + +// phpcs:set PEAR.Commenting.FunctionComment specialMethods[] ignored +/** + * Should be ok + */ +public function ignored() { + +} + +// phpcs:set PEAR.Commenting.FunctionComment specialMethods[] __construct,__destruct diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php index 2f27c18710..a7b35e60a1 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php @@ -68,6 +68,8 @@ public function getErrorList() 361 => 1, 363 => 1, 364 => 1, + 406 => 1, + 417 => 1, ]; }//end getErrorList() diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index e75633c5d8..eeed382952 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -67,10 +67,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) // Skip constructor and destructor. $methodName = $phpcsFile->getDeclarationName($stackPtr); - $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct'); - if ($isSpecialMethod === true) { - return; - } + $isSpecialMethod = in_array($methodName, $this->specialMethods, true); if ($return !== null) { $content = $tokens[($return + 2)]['content']; @@ -181,6 +178,10 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) }//end if }//end if } else { + if ($isSpecialMethod === true) { + return; + } + $error = 'Missing @return tag in function comment'; $phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn'); }//end if diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc index aaaa490706..deaa966eae 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc @@ -666,7 +666,7 @@ class Baz { * Test * * @return void - * @throws E + * @throws E */ function myFunction() {} @@ -1021,3 +1021,23 @@ public function foo($a, $b) {} * @return mixed */ public function foo(mixed $a): mixed {} + +// phpcs:set Squiz.Commenting.FunctionComment specialMethods[] +class Bar { + /** + * The PHP5 constructor + */ + public function __construct() { + + } +} + +// phpcs:set Squiz.Commenting.FunctionComment specialMethods[] ignored +/** + * Should be ok + */ +public function ignored() { + +} + +// phpcs:set Squiz.Commenting.FunctionComment specialMethods[] __construct,__destruct diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 468710cb8e..b46df26b54 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -666,7 +666,7 @@ class Baz { * Test * * @return void - * @throws E + * @throws E */ function myFunction() {} @@ -1021,3 +1021,23 @@ public function foo($a, $b) {} * @return mixed */ public function foo(mixed $a): mixed {} + +// phpcs:set Squiz.Commenting.FunctionComment specialMethods[] +class Bar { + /** + * The PHP5 constructor + */ + public function __construct() { + + } +} + +// phpcs:set Squiz.Commenting.FunctionComment specialMethods[] ignored +/** + * Should be ok + */ +public function ignored() { + +} + +// phpcs:set Squiz.Commenting.FunctionComment specialMethods[] __construct,__destruct diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php index 7d7ab71928..632b7c51b2 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php @@ -115,6 +115,7 @@ public function getErrorList() 997 => 1, 1004 => 2, 1006 => 1, + 1029 => 1, ]; // Scalar type hints only work from PHP 7 onwards. From d96b6d8f071894bf5f80574136e6884f6967e88f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 9 Apr 2021 08:55:35 +1000 Subject: [PATCH 311/733] Changelog for #3293 --- package.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.xml b/package.xml index 464e815c0d..53acf535e7 100644 --- a/package.xml +++ b/package.xml @@ -136,6 +136,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 -- This works around a long-standing PHP bug with is_readable() -- Thanks to Michael S for the patch + - Fixed a number of false positives in the Squiz.PHP.DisallowMultipleAssignments sniff + -- Sniff no longer errors for default value assignments in arrow functions + -- Sniff no longer errors for assignments on first line of closure + -- Sniff no longer errors for assignments after a goto label + -- Thanks to Jaroslav Hanslík for the patch - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional - Fixed bug #2992 : Enabling caching using a ruleset produces invalid cache files when using --sniffs and --exclude CLI args - Fixed bug #3003 : Squiz.Formatting.OperatorBracket autofix incorrect when assignment used with null coalescing operator From f14e43926046541ffc555eb62b30b4c293cfaec7 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 9 Apr 2021 09:25:56 +1000 Subject: [PATCH 312/733] Changelog for #2925 --- package.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.xml b/package.xml index 53acf535e7..9329065bfc 100644 --- a/package.xml +++ b/package.xml @@ -117,6 +117,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Set the "minimumVisibility" sniff property to "public" to ignore both private and protected methods -- The default remains at "private", so all methods are checked -- Thanks to Vincent Langlet for the patch + - PEAR.Commenting.FunctionComment and Squiz.Commenting.FunctionComment sniffs can now ignore return tags in any method + -- Previously, only __construct and __destruct were ignored + -- Set the list of method names to ignore in the "specialMethods" sniff property + -- The default remains at "__construct" and "__destruct" only + -- Thanks to Vincent Langlet for the patch - PSR2.ControlStructures.SwitchDeclaration now supports nested switch statements where every branch terminates -- Previously, if a CASE only contained a SWITCH and no direct terminating statement, a fall-through error was displayed -- Now, the error is surpressed if every branch of the SWITCH has a terminating statement From ffced0d2c8fa8e6cdc4d695a743271fab6c38625 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 9 Apr 2021 10:54:41 +1000 Subject: [PATCH 313/733] Prepare for 3.6.0 release --- package.xml | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 9329065bfc..45b17403b1 100644 --- a/package.xml +++ b/package.xml @@ -14,8 +14,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> gsherwood@squiz.net yes - 2020-10-23 - + 2021-04-09 + 3.6.0 3.6.0 @@ -2296,6 +2296,167 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 3.6.0 + 3.6.0 + + + stable + stable + + 2021-04-09 + BSD License + + - Added support for PHP 8.0 union types + -- A new T_TYPE_UNION token is available to represent the pipe character + -- File::getMethodParameters(), getMethodProperties(), and getMemberProperties() will now return union types + -- Thanks to Juliette Reinders Folmer for the patch + - Added support for PHP 8.0 named function call arguments + -- A new T_PARAM_NAME token is available to represent the label with the name of the function argument in it + -- Thanks to Juliette Reinders Folmer for the patch + - Added support for PHP 8.0 attributes + -- The PHP-supplied T_ATTRIBUTE token marks the start of an attribute + -- A new T_ATTRIBUTE_END token is available to mark the end of an attribute + -- New attribute_owner and attribute_closer indexes are available in the tokens array for all tokens inside an attribute + -- Tokenizing of attributes has been backfilled for older PHP versions + -- The following sniffs have been updated to support attributes: + --- PEAR.Commenting.ClassComment + --- PEAR.Commenting.FileComment + --- PSR1.Files.SideEffects + --- PSR12.Files.FileHeader + --- Squiz.Commenting.ClassComment + --- Squiz.Commenting.FileComment + --- Squiz.WhiteSpace.FunctionSpacing + ---- Thanks to Vadim Borodavko for the patch + -- Thanks to Alessandro Chitolina for the patch + - Added support for PHP 8.0 dereferencing of text strings with interpolated variables + -- Thanks to Juliette Reinders Folmer for the patch + - Added support for PHP 8.0 match expressions + -- Match expressions are now tokenised with parenthesis and scope openers and closers + --- Sniffs can listen for the T_MATCH token to process match expressions + --- Note that the case and default statements inside match expressions do not have scopes set + -- A new T_MATCH_ARROW token is available to represent the arrows in match expressions + -- A new T_MATCH_DEFAULT token is available to represent the default keyword in match expressions + -- All tokenizing of match expressions has been backfilled for older PHP versions + -- The following sniffs have been updated to support match expressions: + --- Generic.CodeAnalysis.AssignmentInCondition + --- Generic.CodeAnalysis.EmptyPHPStatement + ---- Thanks to Vadim Borodavko for the patch + --- Generic.CodeAnalysis.EmptyStatement + --- Generic.PHP.LowerCaseKeyword + --- PEAR.ControlStructures.ControlSignature + --- PSR12.ControlStructures.BooleanOperatorPlacement + --- Squiz.Commenting.LongConditionClosingComment + --- Squiz.Commenting.PostStatementComment + --- Squiz.ControlStructures.LowercaseDeclaration + --- Squiz.ControlStructures.ControlSignature + --- Squiz.Formatting.OperatorBracket + --- Squiz.PHP.DisallowMultipleAssignments + --- Squiz.Objects.ObjectInstantiation + --- Squiz.WhiteSpace.ControlStructureSpacing + -- Thanks to Juliette Reinders Folmer for the patch + - The value of the T_FN_ARROW token has changed from "T_FN_ARROW" to "PHPCS_T_FN_ARROW" to avoid package conflicts + -- This will have no impact on custom sniffs unless they are specifically looking at the value of the T_FN_ARROW constant + -- If sniffs are just using constant to find arrow functions, they will continue to work without modification + -- Thanks to Juliette Reinders Folmer for the patch + - File::findStartOfStatement() now works correctly when passed the last token in a statement + - File::getMethodParameters() now supports PHP 8.0 constructor property promotion + -- Returned method params now include a "property_visibility" and "visibility_token" index if property promotion is detected + -- Thanks to Juliette Reinders Folmer for the patch + - File::getMethodProperties() now includes a "return_type_end_token" index in the return value + -- This indicates the last token in the return type, which is helpful when checking union types + -- Thanks to Juliette Reinders Folmer for the patch + - Include patterns are now ignored when processing STDIN + -- Previously, checks using include patterns were excluded when processing STDIN when no file path was provided via --stdin-path + -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run + -- If you want include and exclude rules enforced when checking STDIN, use --stdin-path to set the file path + -- Thanks to Juliette Reinders Folmer for the patch + - Spaces are now correctly escaped in the paths to external on Windows + -- Thanks to Juliette Reinders Folmer for the patch + - Added Generic.NamingConventions.AbstractClassNamePrefix to enforce that class names are prefixed with "Abstract" + -- Thanks to Anna Borzenko for the contribution + - Added Generic.NamingConventions.InterfaceNameSuffix to enforce that interface names are suffixed with "Interface" + -- Thanks to Anna Borzenko for the contribution + - Added Generic.NamingConventions.TraitNameSuffix to enforce that trait names are suffixed with "Trait" + -- Thanks to Anna Borzenko for the contribution + - Generic.CodeAnalysis.UnusedFunctionParameter can now be configured to ignore variable usage for specific type hints + -- This allows you to suppress warnings for some variables that are not required, but leave warnings for others + -- Set the ignoreTypeHints array property to a list of type hints to ignore + -- Thanks to Petr Bugyík for the patch + - Generic.Formatting.MultipleStatementAlignment can now align statements at the start of the assignment token + -- Previously, the sniff enforced that the values were aligned, even if this meant the assignment tokens were not + -- Now, the sniff can enforce that the assignment tokens are aligned, even if this means the values are not + -- Set the "alignAtEnd" sniff property to "false" to align the assignment tokens + -- The default remains at "true", so the assigned values are aligned + -- Thanks to John P. Bloch for the patch + - Generic.PHP.LowerCaseType now supports checking of typed properties + -- Thanks to Juliette Reinders Folmer for the patch + - Generic.PHP.LowerCaseType now supports checking of union types + -- Thanks to Juliette Reinders Folmer for the patch + - PEAR.Commenting.FunctionComment and Squiz.Commenting.FunctionComment sniffs can now ignore private and protected methods + -- Set the "minimumVisibility" sniff property to "protected" to ignore private methods + -- Set the "minimumVisibility" sniff property to "public" to ignore both private and protected methods + -- The default remains at "private", so all methods are checked + -- Thanks to Vincent Langlet for the patch + - PEAR.Commenting.FunctionComment and Squiz.Commenting.FunctionComment sniffs can now ignore return tags in any method + -- Previously, only __construct and __destruct were ignored + -- Set the list of method names to ignore in the "specialMethods" sniff property + -- The default remains at "__construct" and "__destruct" only + -- Thanks to Vincent Langlet for the patch + - PSR2.ControlStructures.SwitchDeclaration now supports nested switch statements where every branch terminates + -- Previously, if a CASE only contained a SWITCH and no direct terminating statement, a fall-through error was displayed + -- Now, the error is surpressed if every branch of the SWITCH has a terminating statement + -- Thanks to Vincent Langlet for the patch + - The PSR2.Methods.FunctionCallSignature.SpaceBeforeCloseBracket error message is now reported on the closing parenthesis token + -- Previously, the error was being reported on the function keyword, leading to confusing line numbers in the error report + - Squiz.Commenting.FunctionComment is now able to ignore function comments that are only inheritdoc statements + -- Set the skipIfInheritdoc sniff property to "true" to skip checking function comments if the content is only {@inhertidoc} + -- The default remains at "false", so these comments will continue to report errors + -- Thanks to Jess Myrbo for the patch + - Squiz.Commenting.FunctionComment now supports the PHP 8 mixed type + -- Thanks to Vadim Borodavko for the patch + - Squiz.PHP.NonExecutableCode now has improved handling of syntax errors + -- Thanks to Thiemo Kreuz for the patch + - Squiz.WhiteSpace.ScopeKeywordSpacing now checks spacing when using PHP 8.0 constructor property promotion + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 + -- This works around a long-standing PHP bug with is_readable() + -- Thanks to Michael S for the patch + - Fixed a number of false positives in the Squiz.PHP.DisallowMultipleAssignments sniff + -- Sniff no longer errors for default value assignments in arrow functions + -- Sniff no longer errors for assignments on first line of closure + -- Sniff no longer errors for assignments after a goto label + -- Thanks to Jaroslav Hanslík for the patch + - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional + - Fixed bug #2992 : Enabling caching using a ruleset produces invalid cache files when using --sniffs and --exclude CLI args + - Fixed bug #3003 : Squiz.Formatting.OperatorBracket autofix incorrect when assignment used with null coalescing operator + - Fixed bug #3145 : Autoloading of sniff fails when multiple classes declared in same file + - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented + - Fixed bug #3163 : Undefined index error with pre-commit hook using husky on PHP 7.4 + -- Thanks to Ismo Vuorinen for the patch + - Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure + - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion + - Fixed bug #3170 : Squiz.WhiteSpace.OperatorSpacing false positive when using negation with string concat + -- This also fixes the same issue in the PSR12.Operators.OperatorSpacing sniff + - Fixed bug #3177 : Incorrect tokenization of GOTO statements in mixed PHP/HTML files + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3184 : PSR2.Namespace.NamespaceDeclaration false positive on namespace operator + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3188 : Squiz.WhiteSpace.ScopeKeywordSpacing false positive for static return type + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3192 : findStartOfStatement doesn't work correctly inside switch + -- Thanks to Vincent Langlet for the patch + - Fixed bug #3195 : Generic.WhiteSpace.ScopeIndent confusing message when combination of tabs and spaces found + - Fixed bug #3197 : Squiz.NamingConventions.ValidVariableName does not use correct error code for all member vars + - Fixed bug #3219 : Generic.Formatting.MultipleStatementAlignment false positive for empty anonymous classes and closures + - Fixed bug #3258 : Squiz.Formatting.OperatorBracket duplicate error messages for unary minus + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3273 : Squiz.Functions.FunctionDeclarationArgumentSpacing reports line break as 0 spaces between parenthesis + - Fixed bug #3277 : Nullable static return typehint causes whitespace error + - Fixed bug #3284 : Unused parameter false positive when using array index in arrow function + + 3.5.8 From 26a545f9f5f163bcde5e495033f78b8ba38299c8 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Mon, 12 Apr 2021 10:44:33 +0200 Subject: [PATCH 314/733] fix bug in attributes tokenization on PHP < 8.0 (#3294) --- src/Tokenizers/PHP.php | 25 +++- tests/Core/Tokenizer/AttributesTest.inc | 11 +- tests/Core/Tokenizer/AttributesTest.php | 151 ++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 7 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 1924cf0745..3d8cee1657 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -3213,14 +3213,27 @@ private function parsePhpAttribute(array &$tokens, $stackPtr) // Go looking for the close bracket. $bracketCloser = $this->findCloser($subTokens, 1, '[', ']'); - if ($bracketCloser === null) { - $bracketCloser = $this->findCloser($tokens, $stackPtr, '[', ']'); - if ($bracketCloser === null) { - return null; + if (PHP_VERSION_ID < 80000 && $bracketCloser === null) { + foreach (array_slice($tokens, ($stackPtr + 1)) as $token) { + if (is_array($token) === true) { + $commentBody .= $token[1]; + } else { + $commentBody .= $token; + } + } + + $subTokens = @token_get_all('findCloser($subTokens, 1, '[', ']'); + if ($bracketCloser !== null) { + array_splice($tokens, ($stackPtr + 1), count($tokens), array_slice($subTokens, ($bracketCloser + 1))); + $subTokens = array_slice($subTokens, 0, ($bracketCloser + 1)); } + } - $subTokens = array_merge($subTokens, array_slice($tokens, ($stackPtr + 1), ($bracketCloser - $stackPtr))); - array_splice($tokens, ($stackPtr + 1), ($bracketCloser - $stackPtr)); + if ($bracketCloser === null) { + return null; } return $subTokens; diff --git a/tests/Core/Tokenizer/AttributesTest.inc b/tests/Core/Tokenizer/AttributesTest.inc index 9b7b869d13..e539adf8a7 100644 --- a/tests/Core/Tokenizer/AttributesTest.inc +++ b/tests/Core/Tokenizer/AttributesTest.inc @@ -75,7 +75,16 @@ function multiline_attributes_on_parameter_test(#[ ) ] int $param) {} +/* testAttributeContainingTextLookingLikeCloseTag */ +#[DeprecationReason('reason: ')] +function attribute_containing_text_looking_like_close_tag() {} + +/* testAttributeContainingMultilineTextLookingLikeCloseTag */ +#[DeprecationReason( + 'reason: ' +)] +function attribute_containing_mulitline_text_looking_like_close_tag() {} + /* testInvalidAttribute */ #[ThisIsNotAnAttribute function invalid_attribute_test() {} - diff --git a/tests/Core/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php index 46d8365431..b10a1efb95 100644 --- a/tests/Core/Tokenizer/AttributesTest.php +++ b/tests/Core/Tokenizer/AttributesTest.php @@ -395,6 +395,157 @@ public function dataAttributeOnParameters() }//end dataAttributeOnParameters() + /** + * Test that an attribute containing text which looks like a PHP close tag is tokenized correctly. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int $length The number of tokens between opener and closer. + * @param array $expectedTokensAttribute The codes of tokens inside the attributes. + * @param array $expectedTokensAfter The codes of tokens after the attributes. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute + * + * @dataProvider dataAttributeOnTextLookingLikeCloseTag + * + * @return void + */ + public function testAttributeContainingTextLookingLikeCloseTag($testMarker, $length, array $expectedTokensAttribute, array $expectedTokensAfter) + { + $tokens = self::$phpcsFile->getTokens(); + + $attribute = $this->getTargetToken($testMarker, T_ATTRIBUTE); + + $this->assertSame('T_ATTRIBUTE', $tokens[$attribute]['type']); + $this->assertArrayHasKey('attribute_closer', $tokens[$attribute]); + + $closer = $tokens[$attribute]['attribute_closer']; + $this->assertSame(($attribute + $length), $closer); + $this->assertSame(T_ATTRIBUTE_END, $tokens[$closer]['code']); + $this->assertSame('T_ATTRIBUTE_END', $tokens[$closer]['type']); + + $this->assertSame($tokens[$attribute]['attribute_opener'], $tokens[$closer]['attribute_opener']); + $this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']); + + $i = ($attribute + 1); + foreach ($expectedTokensAttribute as $item) { + list($expectedType, $expectedContents) = $item; + $this->assertSame($expectedType, $tokens[$i]['type']); + $this->assertSame($expectedContents, $tokens[$i]['content']); + $this->assertArrayHasKey('attribute_opener', $tokens[$i]); + $this->assertArrayHasKey('attribute_closer', $tokens[$i]); + ++$i; + } + + $i = ($closer + 1); + foreach ($expectedTokensAfter as $expectedCode) { + $this->assertSame($expectedCode, $tokens[$i]['code']); + ++$i; + } + + }//end testAttributeContainingTextLookingLikeCloseTag() + + + /** + * Data provider. + * + * @see dataAttributeOnTextLookingLikeCloseTag() + * + * @return array + */ + public function dataAttributeOnTextLookingLikeCloseTag() + { + return [ + [ + '/* testAttributeContainingTextLookingLikeCloseTag */', + 5, + [ + [ + 'T_STRING', + 'DeprecationReason', + ], + [ + 'T_OPEN_PARENTHESIS', + '(', + ], + [ + 'T_CONSTANT_ENCAPSED_STRING', + "'reason: '", + ], + [ + 'T_CLOSE_PARENTHESIS', + ')', + ], + [ + 'T_ATTRIBUTE_END', + ']', + ], + ], + [ + T_WHITESPACE, + T_FUNCTION, + T_WHITESPACE, + T_STRING, + T_OPEN_PARENTHESIS, + T_CLOSE_PARENTHESIS, + T_WHITESPACE, + T_OPEN_CURLY_BRACKET, + T_CLOSE_CURLY_BRACKET, + ], + ], + [ + '/* testAttributeContainingMultilineTextLookingLikeCloseTag */', + 8, + [ + [ + 'T_STRING', + 'DeprecationReason', + ], + [ + 'T_OPEN_PARENTHESIS', + '(', + ], + [ + 'T_WHITESPACE', + "\n", + ], + [ + 'T_WHITESPACE', + " ", + ], + [ + 'T_CONSTANT_ENCAPSED_STRING', + "'reason: '", + ], + [ + 'T_WHITESPACE', + "\n", + ], + [ + 'T_CLOSE_PARENTHESIS', + ')', + ], + [ + 'T_ATTRIBUTE_END', + ']', + ], + ], + [ + T_WHITESPACE, + T_FUNCTION, + T_WHITESPACE, + T_STRING, + T_OPEN_PARENTHESIS, + T_CLOSE_PARENTHESIS, + T_WHITESPACE, + T_OPEN_CURLY_BRACKET, + T_CLOSE_CURLY_BRACKET, + ], + ], + ]; + + }//end dataAttributeOnTextLookingLikeCloseTag() + + /** * Test that invalid attribute (or comment starting with #[ and without ]) are parsed correctly. * From e9a865216c5678d92370c8c56053190b20ab1112 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 15 Apr 2021 09:41:25 +1000 Subject: [PATCH 315/733] Changelog for #3294 (ref #3299) --- package.xml | 152 ++----------------------------------------------- src/Config.php | 2 +- 2 files changed, 5 insertions(+), 149 deletions(-) diff --git a/package.xml b/package.xml index 45b17403b1..fd76b5ced6 100644 --- a/package.xml +++ b/package.xml @@ -17,8 +17,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2021-04-09 - 3.6.0 - 3.6.0 + 3.6.1 + 3.6.1 stable @@ -26,153 +26,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - - Added support for PHP 8.0 union types - -- A new T_TYPE_UNION token is available to represent the pipe character - -- File::getMethodParameters(), getMethodProperties(), and getMemberProperties() will now return union types - -- Thanks to Juliette Reinders Folmer for the patch - - Added support for PHP 8.0 named function call arguments - -- A new T_PARAM_NAME token is available to represent the label with the name of the function argument in it - -- Thanks to Juliette Reinders Folmer for the patch - - Added support for PHP 8.0 attributes - -- The PHP-supplied T_ATTRIBUTE token marks the start of an attribute - -- A new T_ATTRIBUTE_END token is available to mark the end of an attribute - -- New attribute_owner and attribute_closer indexes are available in the tokens array for all tokens inside an attribute - -- Tokenizing of attributes has been backfilled for older PHP versions - -- The following sniffs have been updated to support attributes: - --- PEAR.Commenting.ClassComment - --- PEAR.Commenting.FileComment - --- PSR1.Files.SideEffects - --- PSR12.Files.FileHeader - --- Squiz.Commenting.ClassComment - --- Squiz.Commenting.FileComment - --- Squiz.WhiteSpace.FunctionSpacing - ---- Thanks to Vadim Borodavko for the patch + - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line -- Thanks to Alessandro Chitolina for the patch - - Added support for PHP 8.0 dereferencing of text strings with interpolated variables - -- Thanks to Juliette Reinders Folmer for the patch - - Added support for PHP 8.0 match expressions - -- Match expressions are now tokenised with parenthesis and scope openers and closers - --- Sniffs can listen for the T_MATCH token to process match expressions - --- Note that the case and default statements inside match expressions do not have scopes set - -- A new T_MATCH_ARROW token is available to represent the arrows in match expressions - -- A new T_MATCH_DEFAULT token is available to represent the default keyword in match expressions - -- All tokenizing of match expressions has been backfilled for older PHP versions - -- The following sniffs have been updated to support match expressions: - --- Generic.CodeAnalysis.AssignmentInCondition - --- Generic.CodeAnalysis.EmptyPHPStatement - ---- Thanks to Vadim Borodavko for the patch - --- Generic.CodeAnalysis.EmptyStatement - --- Generic.PHP.LowerCaseKeyword - --- PEAR.ControlStructures.ControlSignature - --- PSR12.ControlStructures.BooleanOperatorPlacement - --- Squiz.Commenting.LongConditionClosingComment - --- Squiz.Commenting.PostStatementComment - --- Squiz.ControlStructures.LowercaseDeclaration - --- Squiz.ControlStructures.ControlSignature - --- Squiz.Formatting.OperatorBracket - --- Squiz.PHP.DisallowMultipleAssignments - --- Squiz.Objects.ObjectInstantiation - --- Squiz.WhiteSpace.ControlStructureSpacing - -- Thanks to Juliette Reinders Folmer for the patch - - The value of the T_FN_ARROW token has changed from "T_FN_ARROW" to "PHPCS_T_FN_ARROW" to avoid package conflicts - -- This will have no impact on custom sniffs unless they are specifically looking at the value of the T_FN_ARROW constant - -- If sniffs are just using constant to find arrow functions, they will continue to work without modification - -- Thanks to Juliette Reinders Folmer for the patch - - File::findStartOfStatement() now works correctly when passed the last token in a statement - - File::getMethodParameters() now supports PHP 8.0 constructor property promotion - -- Returned method params now include a "property_visibility" and "visibility_token" index if property promotion is detected - -- Thanks to Juliette Reinders Folmer for the patch - - File::getMethodProperties() now includes a "return_type_end_token" index in the return value - -- This indicates the last token in the return type, which is helpful when checking union types - -- Thanks to Juliette Reinders Folmer for the patch - - Include patterns are now ignored when processing STDIN - -- Previously, checks using include patterns were excluded when processing STDIN when no file path was provided via --stdin-path - -- Now, all include and exclude rules are ignored when no file path is provided, allowing all checks to run - -- If you want include and exclude rules enforced when checking STDIN, use --stdin-path to set the file path - -- Thanks to Juliette Reinders Folmer for the patch - - Spaces are now correctly escaped in the paths to external on Windows - -- Thanks to Juliette Reinders Folmer for the patch - - Added Generic.NamingConventions.AbstractClassNamePrefix to enforce that class names are prefixed with "Abstract" - -- Thanks to Anna Borzenko for the contribution - - Added Generic.NamingConventions.InterfaceNameSuffix to enforce that interface names are suffixed with "Interface" - -- Thanks to Anna Borzenko for the contribution - - Added Generic.NamingConventions.TraitNameSuffix to enforce that trait names are suffixed with "Trait" - -- Thanks to Anna Borzenko for the contribution - - Generic.CodeAnalysis.UnusedFunctionParameter can now be configured to ignore variable usage for specific type hints - -- This allows you to suppress warnings for some variables that are not required, but leave warnings for others - -- Set the ignoreTypeHints array property to a list of type hints to ignore - -- Thanks to Petr Bugyík for the patch - - Generic.Formatting.MultipleStatementAlignment can now align statements at the start of the assignment token - -- Previously, the sniff enforced that the values were aligned, even if this meant the assignment tokens were not - -- Now, the sniff can enforce that the assignment tokens are aligned, even if this means the values are not - -- Set the "alignAtEnd" sniff property to "false" to align the assignment tokens - -- The default remains at "true", so the assigned values are aligned - -- Thanks to John P. Bloch for the patch - - Generic.PHP.LowerCaseType now supports checking of typed properties - -- Thanks to Juliette Reinders Folmer for the patch - - Generic.PHP.LowerCaseType now supports checking of union types - -- Thanks to Juliette Reinders Folmer for the patch - - PEAR.Commenting.FunctionComment and Squiz.Commenting.FunctionComment sniffs can now ignore private and protected methods - -- Set the "minimumVisibility" sniff property to "protected" to ignore private methods - -- Set the "minimumVisibility" sniff property to "public" to ignore both private and protected methods - -- The default remains at "private", so all methods are checked - -- Thanks to Vincent Langlet for the patch - - PEAR.Commenting.FunctionComment and Squiz.Commenting.FunctionComment sniffs can now ignore return tags in any method - -- Previously, only __construct and __destruct were ignored - -- Set the list of method names to ignore in the "specialMethods" sniff property - -- The default remains at "__construct" and "__destruct" only - -- Thanks to Vincent Langlet for the patch - - PSR2.ControlStructures.SwitchDeclaration now supports nested switch statements where every branch terminates - -- Previously, if a CASE only contained a SWITCH and no direct terminating statement, a fall-through error was displayed - -- Now, the error is surpressed if every branch of the SWITCH has a terminating statement - -- Thanks to Vincent Langlet for the patch - - The PSR2.Methods.FunctionCallSignature.SpaceBeforeCloseBracket error message is now reported on the closing parenthesis token - -- Previously, the error was being reported on the function keyword, leading to confusing line numbers in the error report - - Squiz.Commenting.FunctionComment is now able to ignore function comments that are only inheritdoc statements - -- Set the skipIfInheritdoc sniff property to "true" to skip checking function comments if the content is only {@inhertidoc} - -- The default remains at "false", so these comments will continue to report errors - -- Thanks to Jess Myrbo for the patch - - Squiz.Commenting.FunctionComment now supports the PHP 8 mixed type - -- Thanks to Vadim Borodavko for the patch - - Squiz.PHP.NonExecutableCode now has improved handling of syntax errors - -- Thanks to Thiemo Kreuz for the patch - - Squiz.WhiteSpace.ScopeKeywordSpacing now checks spacing when using PHP 8.0 constructor property promotion - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed an issue that could occurr when checking files on network drives, such as with WSL2 on Windows 10 - -- This works around a long-standing PHP bug with is_readable() - -- Thanks to Michael S for the patch - - Fixed a number of false positives in the Squiz.PHP.DisallowMultipleAssignments sniff - -- Sniff no longer errors for default value assignments in arrow functions - -- Sniff no longer errors for assignments on first line of closure - -- Sniff no longer errors for assignments after a goto label - -- Thanks to Jaroslav Hanslík for the patch - - Fixed bug #2913 : Generic.WhiteSpace.ScopeIndent false positive when opening and closing tag on same line inside conditional - - Fixed bug #2992 : Enabling caching using a ruleset produces invalid cache files when using --sniffs and --exclude CLI args - - Fixed bug #3003 : Squiz.Formatting.OperatorBracket autofix incorrect when assignment used with null coalescing operator - - Fixed bug #3145 : Autoloading of sniff fails when multiple classes declared in same file - - Fixed bug #3157 : PSR2.ControlStructures.SwitchDeclaration.BreakIndent false positive when case keyword is not indented - - Fixed bug #3163 : Undefined index error with pre-commit hook using husky on PHP 7.4 - -- Thanks to Ismo Vuorinen for the patch - - Fixed bug #3165 : Squiz.PHP.DisallowComparisonAssignment false positive when comparison inside closure - - Fixed bug #3167 : Generic.WhiteSpace.ScopeIndent false positive when using PHP 8.0 constructor property promotion - - Fixed bug #3170 : Squiz.WhiteSpace.OperatorSpacing false positive when using negation with string concat - -- This also fixes the same issue in the PSR12.Operators.OperatorSpacing sniff - - Fixed bug #3177 : Incorrect tokenization of GOTO statements in mixed PHP/HTML files - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3184 : PSR2.Namespace.NamespaceDeclaration false positive on namespace operator - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3188 : Squiz.WhiteSpace.ScopeKeywordSpacing false positive for static return type - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3192 : findStartOfStatement doesn't work correctly inside switch - -- Thanks to Vincent Langlet for the patch - - Fixed bug #3195 : Generic.WhiteSpace.ScopeIndent confusing message when combination of tabs and spaces found - - Fixed bug #3197 : Squiz.NamingConventions.ValidVariableName does not use correct error code for all member vars - - Fixed bug #3219 : Generic.Formatting.MultipleStatementAlignment false positive for empty anonymous classes and closures - - Fixed bug #3258 : Squiz.Formatting.OperatorBracket duplicate error messages for unary minus - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3273 : Squiz.Functions.FunctionDeclarationArgumentSpacing reports line break as 0 spaces between parenthesis - - Fixed bug #3277 : Nullable static return typehint causes whitespace error - - Fixed bug #3284 : Unused parameter false positive when using array index in arrow function + -- Thanks to Juliette Reinders Folmer for the tests diff --git a/src/Config.php b/src/Config.php index 22b2049210..106b1c50b9 100644 --- a/src/Config.php +++ b/src/Config.php @@ -79,7 +79,7 @@ class Config * * @var string */ - const VERSION = '3.6.0'; + const VERSION = '3.6.1'; /** * Package stability; either stable, beta or alpha. From 342dbf2481b7a3c0b799fecd7b7c4def3c0408e3 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 16 Apr 2021 14:22:12 +1000 Subject: [PATCH 316/733] Fixed bug #3296 : PSR2.ControlStructures.SwitchDeclaration takes phpcs:ignore as content of case body --- package.xml | 1 + .../Sniffs/ControlStructures/SwitchDeclarationSniff.php | 2 +- .../ControlStructures/SwitchDeclarationUnitTest.inc | 9 +++++++++ .../SwitchDeclarationUnitTest.inc.fixed | 9 +++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index fd76b5ced6..84121d4f7c 100644 --- a/package.xml +++ b/package.xml @@ -29,6 +29,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line -- Thanks to Alessandro Chitolina for the patch -- Thanks to Juliette Reinders Folmer for the tests + - Fixed bug #3296 : PSR2.ControlStructures.SwitchDeclaration takes phpcs:ignore as content of case body diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php index faac9dcbbe..11f6a3f49e 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php @@ -186,7 +186,7 @@ public function process(File $phpcsFile, $stackPtr) $nextCode = $this->findNextCase($phpcsFile, ($opener + 1), $nextCloser); if ($nextCode !== false) { $prevCode = $phpcsFile->findPrevious(T_WHITESPACE, ($nextCode - 1), $nextCase, true); - if ($tokens[$prevCode]['code'] !== T_COMMENT + if (isset(Tokens::$commentTokens[$tokens[$prevCode]['code']]) === false && $this->findNestedTerminator($phpcsFile, ($opener + 1), $nextCode) === false ) { $error = 'There must be a comment when fall-through is intentional in a non-empty case body'; diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc index 8050b636f6..9bf0f6055c 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc @@ -331,3 +331,12 @@ switch ($foo) { case 2: return 2; } + +switch ($foo) { + case 1: + // phpcs:ignore + case 2: + return 1; + case 3: + return 2; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed index bebc66619f..85617836db 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed @@ -334,3 +334,12 @@ switch ($foo) { case 2: return 2; } + +switch ($foo) { + case 1: + // phpcs:ignore + case 2: + return 1; + case 3: + return 2; +} From 52f2e80b56ecdb1b3fd3caf0126e85b7edbd43d5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 20 Apr 2021 01:57:50 +0200 Subject: [PATCH 317/733] PHP 8.0 | Tokenizer/PHP: bugfix for union types using namespace operator Type declarations can use namespace relative Types, i.e. `namespace\Sub\Name`. However, in that case, the `T_BITWISE_OR` token was incorrectly not converted to `T_TYPE_UNION`. Includes unit tests. Includes additional tests with all other "namespaced identifier name" types to safeguard this for the PHPCS 4.x change to the PHP 8.0 tokenization of identifier names. --- src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/BitwiseOrTest.inc | 27 ++++++++++++++++++++++++++ tests/Core/Tokenizer/BitwiseOrTest.php | 9 +++++++++ 3 files changed, 37 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 3d8cee1657..8d2e3779d2 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2583,6 +2583,7 @@ protected function processAdditional() T_STATIC => T_STATIC, T_FALSE => T_FALSE, T_NULL => T_NULL, + T_NAMESPACE => T_NAMESPACE, T_NS_SEPARATOR => T_NS_SEPARATOR, ]; diff --git a/tests/Core/Tokenizer/BitwiseOrTest.inc b/tests/Core/Tokenizer/BitwiseOrTest.inc index ec1801d569..2af0ecae98 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.inc +++ b/tests/Core/Tokenizer/BitwiseOrTest.inc @@ -24,6 +24,15 @@ class TypeUnion /* testTypeUnionPropertyMulti3 */ | null $arrayOrFalse; + /* testTypeUnionPropertyNamespaceRelative */ + public namespace\Sub\NameA|namespace\Sub\NameB $namespaceRelative; + + /* testTypeUnionPropertyPartiallyQualified */ + public Partially\Qualified\NameA|Partially\Qualified\NameB $partiallyQual; + + /* testTypeUnionPropertyFullyQualified */ + public \Fully\Qualified\NameA|\Fully\Qualified\NameB $fullyQual; + public function paramTypes( /* testTypeUnionParam1 */ int|float $paramA /* testBitwiseOrParamDefaultValue */ = CONSTANT_A | CONSTANT_B, @@ -35,6 +44,15 @@ class TypeUnion return (($a1 ^ $b1) |($a2 ^ $b2)) + $c; } + public function identifierNames( + /* testTypeUnionParamNamespaceRelative */ + namespace\Sub\NameA|namespace\Sub\NameB $paramA, + /* testTypeUnionParamPartiallyQualified */ + Partially\Qualified\NameA|Partially\Qualified\NameB $paramB, + /* testTypeUnionParamFullyQualified */ + \Fully\Qualified\NameA|\Fully\Qualified\NameB $paramC, + ) {} + /* testTypeUnionReturnType */ public function returnType() : int|false {} @@ -43,6 +61,15 @@ class TypeUnion /* testTypeUnionAbstractMethodReturnType1 */ abstract public function abstractMethod(): object|array /* testTypeUnionAbstractMethodReturnType2 */ |false; + + /* testTypeUnionReturnTypeNamespaceRelative */ + public function identifierNamesReturnRelative() : namespace\Sub\NameA|namespace\Sub\NameB {} + + /* testTypeUnionReturnPartiallyQualified */ + public function identifierNamesReturnPQ() : Partially\Qualified\NameA|Partially\Qualified\NameB {} + + /* testTypeUnionReturnFullyQualified */ + public function identifierNamesReturnFQ() : \Fully\Qualified\NameA|\Fully\Qualified\NameB {} } /* testTypeUnionClosureParamIllegalNullable */ diff --git a/tests/Core/Tokenizer/BitwiseOrTest.php b/tests/Core/Tokenizer/BitwiseOrTest.php index f6288d817e..d4a27bdc33 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.php +++ b/tests/Core/Tokenizer/BitwiseOrTest.php @@ -102,13 +102,22 @@ public function dataTypeUnion() ['/* testTypeUnionPropertyMulti1 */'], ['/* testTypeUnionPropertyMulti2 */'], ['/* testTypeUnionPropertyMulti3 */'], + ['/* testTypeUnionPropertyNamespaceRelative */'], + ['/* testTypeUnionPropertyPartiallyQualified */'], + ['/* testTypeUnionPropertyFullyQualified */'], ['/* testTypeUnionParam1 */'], ['/* testTypeUnionParam2 */'], ['/* testTypeUnionParam3 */'], + ['/* testTypeUnionParamNamespaceRelative */'], + ['/* testTypeUnionParamPartiallyQualified */'], + ['/* testTypeUnionParamFullyQualified */'], ['/* testTypeUnionReturnType */'], ['/* testTypeUnionConstructorPropertyPromotion */'], ['/* testTypeUnionAbstractMethodReturnType1 */'], ['/* testTypeUnionAbstractMethodReturnType2 */'], + ['/* testTypeUnionReturnTypeNamespaceRelative */'], + ['/* testTypeUnionReturnPartiallyQualified */'], + ['/* testTypeUnionReturnFullyQualified */'], ['/* testTypeUnionClosureParamIllegalNullable */'], ['/* testTypeUnionWithReference */'], ['/* testTypeUnionWithSpreadOperator */'], From ae4f33bc1ba3c779681275a506c44f8dff378c28 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 22 Apr 2021 09:33:45 +1000 Subject: [PATCH 318/733] Fixed bug #3303 : findStartOfStatement() doesn't work with T_OPEN_TAG_WITH_ECHO --- package.xml | 1 + src/Files/File.php | 5 +-- tests/Core/File/FindStartOfStatementTest.inc | 10 ++++++ tests/Core/File/FindStartOfStatementTest.php | 32 ++++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 84121d4f7c..c408f94353 100644 --- a/package.xml +++ b/package.xml @@ -30,6 +30,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Alessandro Chitolina for the patch -- Thanks to Juliette Reinders Folmer for the tests - Fixed bug #3296 : PSR2.ControlStructures.SwitchDeclaration takes phpcs:ignore as content of case body + - Fixed bug #3303 : findStartOfStatement() doesn't work with T_OPEN_TAG_WITH_ECHO diff --git a/src/Files/File.php b/src/Files/File.php index aef4fbf27b..ca01f37e95 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2285,8 +2285,9 @@ public function findNext( public function findStartOfStatement($start, $ignore=null) { $startTokens = Util\Tokens::$blockOpeners; - $startTokens[T_OPEN_SHORT_ARRAY] = true; - $startTokens[T_OPEN_TAG] = true; + $startTokens[T_OPEN_SHORT_ARRAY] = true; + $startTokens[T_OPEN_TAG] = true; + $startTokens[T_OPEN_TAG_WITH_ECHO] = true; $endTokens = [ T_CLOSE_TAG => true, diff --git a/tests/Core/File/FindStartOfStatementTest.inc b/tests/Core/File/FindStartOfStatementTest.inc index a759a1aa6d..ce9dfad3f9 100644 --- a/tests/Core/File/FindStartOfStatementTest.inc +++ b/tests/Core/File/FindStartOfStatementTest.inc @@ -111,3 +111,13 @@ $result = match ($key) { }; return 0; + +/* testOpenTag */ +?> +

Test

+', foo(), ''; + +/* testOpenTagWithEcho */ +?> +

Test

+', foo(), ''; diff --git a/tests/Core/File/FindStartOfStatementTest.php b/tests/Core/File/FindStartOfStatementTest.php index 33c0686ea4..464021f6fc 100644 --- a/tests/Core/File/FindStartOfStatementTest.php +++ b/tests/Core/File/FindStartOfStatementTest.php @@ -468,4 +468,36 @@ public function testNestedMatch() }//end testNestedMatch() + /** + * Test nested match expressions. + * + * @return void + */ + public function testOpenTag() + { + $start = $this->getTargetToken('/* testOpenTag */', T_OPEN_TAG); + $start += 2; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 1), $found); + + }//end testOpenTag() + + + /** + * Test nested match expressions. + * + * @return void + */ + public function testOpenTagWithEcho() + { + $start = $this->getTargetToken('/* testOpenTagWithEcho */', T_OPEN_TAG_WITH_ECHO); + $start += 3; + $found = self::$phpcsFile->findStartOfStatement($start); + + $this->assertSame(($start - 1), $found); + + }//end testOpenTagWithEcho() + + }//end class From f6d40100fe9a1646b070ef86593cc28c6fc49e5d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 21 Apr 2021 22:02:53 +0200 Subject: [PATCH 319/733] Generic/ExecutableFile: check files using short open echo tag Includes unit tests. --- package.xml | 2 ++ src/Standards/Generic/Sniffs/Files/ExecutableFileSniff.php | 5 ++++- .../Generic/Tests/Files/ExecutableFileUnitTest.3.inc | 1 + .../Generic/Tests/Files/ExecutableFileUnitTest.4.inc | 1 + src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.3.inc create mode 100755 src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.4.inc diff --git a/package.xml b/package.xml index c408f94353..be9cef8331 100644 --- a/package.xml +++ b/package.xml @@ -568,6 +568,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Standards/Generic/Sniffs/Files/ExecutableFileSniff.php b/src/Standards/Generic/Sniffs/Files/ExecutableFileSniff.php index 51534bf377..e1213d5f64 100644 --- a/src/Standards/Generic/Sniffs/Files/ExecutableFileSniff.php +++ b/src/Standards/Generic/Sniffs/Files/ExecutableFileSniff.php @@ -23,7 +23,10 @@ class ExecutableFileSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.3.inc b/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.3.inc new file mode 100644 index 0000000000..ee44f5118f --- /dev/null +++ b/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.3.inc @@ -0,0 +1 @@ + diff --git a/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.4.inc b/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.4.inc new file mode 100755 index 0000000000..ee44f5118f --- /dev/null +++ b/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.4.inc @@ -0,0 +1 @@ + diff --git a/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php b/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php index f47fef6d36..b613748414 100644 --- a/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php +++ b/src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php @@ -44,6 +44,7 @@ public function getErrorList($testFile='') { switch ($testFile) { case 'ExecutableFileUnitTest.2.inc': + case 'ExecutableFileUnitTest.4.inc': return [1 => 1]; default: return []; From 297e7d320e377f07997b429895c416ff65b29aa0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 21 Apr 2021 22:03:32 +0200 Subject: [PATCH 320/733] Generic/LowercasedFilename: check files using short open echo tag Includes unit tests. --- package.xml | 3 ++- .../Generic/Sniffs/Files/LowercasedFilenameSniff.php | 5 ++++- ...UnitTest.inc => LowercasedFilenameUnitTest.1.inc} | 0 .../Tests/Files/LowercasedFilenameUnitTest.2.inc | 7 +++++++ .../Tests/Files/LowercasedFilenameUnitTest.php | 12 ++++++++++-- 5 files changed, 23 insertions(+), 4 deletions(-) rename src/Standards/Generic/Tests/Files/{LowercasedFilenameUnitTest.inc => LowercasedFilenameUnitTest.1.inc} (100%) create mode 100644 src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.2.inc diff --git a/package.xml b/package.xml index c408f94353..3c9e59bde2 100644 --- a/package.xml +++ b/package.xml @@ -589,7 +589,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + + diff --git a/src/Standards/Generic/Sniffs/Files/LowercasedFilenameSniff.php b/src/Standards/Generic/Sniffs/Files/LowercasedFilenameSniff.php index 2f442b1123..a9fd4c5d44 100644 --- a/src/Standards/Generic/Sniffs/Files/LowercasedFilenameSniff.php +++ b/src/Standards/Generic/Sniffs/Files/LowercasedFilenameSniff.php @@ -23,7 +23,10 @@ class LowercasedFilenameSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.inc b/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.1.inc similarity index 100% rename from src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.inc rename to src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.1.inc diff --git a/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.2.inc b/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.2.inc new file mode 100644 index 0000000000..708d19c6d6 --- /dev/null +++ b/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.2.inc @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.php b/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.php index 4b4bc97427..f67a40a3be 100644 --- a/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.php +++ b/src/Standards/Generic/Tests/Files/LowercasedFilenameUnitTest.php @@ -21,11 +21,19 @@ class LowercasedFilenameUnitTest extends AbstractSniffUnitTest * The key of the array should represent the line number and the value * should represent the number of errors that should occur on that line. * + * @param string $testFile The name of the file being tested. + * * @return array */ - public function getErrorList() + public function getErrorList($testFile='') { - return [1 => 1]; + switch ($testFile) { + case 'LowercasedFilenameUnitTest.1.inc': + case 'LowercasedFilenameUnitTest.2.inc': + return [1 => 1]; + default: + return []; + } }//end getErrorList() From 002e4ccb9de4e59f3a694a333fb92afe43e0daf3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 21 Apr 2021 22:19:25 +0200 Subject: [PATCH 321/733] Generic/LineEndings: check files using only short open echo tag Includes unit tests. --- .gitattributes | 1 + package.xml | 6 ++-- .../Generic/Sniffs/Files/LineEndingsSniff.php | 5 ++- ...st.inc.fixed => LineEndingsUnitTest.1.inc} | 0 ...st.inc => LineEndingsUnitTest.1.inc.fixed} | 36 +++++++++---------- .../Tests/Files/LineEndingsUnitTest.2.inc | 5 +++ .../Files/LineEndingsUnitTest.2.inc.fixed | 5 +++ 7 files changed, 37 insertions(+), 21 deletions(-) rename src/Standards/Generic/Tests/Files/{LineEndingsUnitTest.inc.fixed => LineEndingsUnitTest.1.inc} (100%) rename src/Standards/Generic/Tests/Files/{LineEndingsUnitTest.inc => LineEndingsUnitTest.1.inc.fixed} (88%) create mode 100644 src/Standards/Generic/Tests/Files/LineEndingsUnitTest.2.inc create mode 100644 src/Standards/Generic/Tests/Files/LineEndingsUnitTest.2.inc.fixed diff --git a/.gitattributes b/.gitattributes index df3ee2b7f9..2807459b5e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,4 @@ scripts/ export-ignore # Declare files that should always have CRLF line endings on checkout. *WinTest.inc text eol=crlf *WinTest.php text eol=crlf +src/Standards/Generic/Tests/Files/LineEndingsUnitTest*.inc text eol=crlf diff --git a/package.xml b/package.xml index c408f94353..d9788071b3 100644 --- a/package.xml +++ b/package.xml @@ -579,8 +579,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - + + + + diff --git a/src/Standards/Generic/Sniffs/Files/LineEndingsSniff.php b/src/Standards/Generic/Sniffs/Files/LineEndingsSniff.php index 763e7863ca..845e1bcfc4 100644 --- a/src/Standards/Generic/Sniffs/Files/LineEndingsSniff.php +++ b/src/Standards/Generic/Sniffs/Files/LineEndingsSniff.php @@ -41,7 +41,10 @@ class LineEndingsSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.inc.fixed b/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.1.inc similarity index 100% rename from src/Standards/Generic/Tests/Files/LineEndingsUnitTest.inc.fixed rename to src/Standards/Generic/Tests/Files/LineEndingsUnitTest.1.inc diff --git a/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.inc b/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.1.inc.fixed similarity index 88% rename from src/Standards/Generic/Tests/Files/LineEndingsUnitTest.inc rename to src/Standards/Generic/Tests/Files/LineEndingsUnitTest.1.inc.fixed index a2d3ed2093..b143fb4cf0 100644 --- a/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.inc +++ b/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.1.inc.fixed @@ -1,18 +1,18 @@ - - - - - -group('a.id, - uc.name, - ag.title, - ua.name' - ); -} + + + + + +group('a.id, + uc.name, + ag.title, + ua.name' + ); +} diff --git a/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.2.inc b/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.2.inc new file mode 100644 index 0000000000..260d7fd2fe --- /dev/null +++ b/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.2.inc @@ -0,0 +1,5 @@ + + + +
...more HTML...
+ diff --git a/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.2.inc.fixed new file mode 100644 index 0000000000..260d7fd2fe --- /dev/null +++ b/src/Standards/Generic/Tests/Files/LineEndingsUnitTest.2.inc.fixed @@ -0,0 +1,5 @@ + + + +
...more HTML...
+ From db1a0a7d276c50f6ea05d6b2b81dab9649387dd0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 00:55:54 +0200 Subject: [PATCH 322/733] Generic/EndFileNewline: check files using only short open echo tag Includes unit tests. --- package.xml | 5 +++++ src/Standards/Generic/Sniffs/Files/EndFileNewlineSniff.php | 5 ++++- .../Generic/Tests/Files/EndFileNewlineUnitTest.6.inc | 1 + .../Generic/Tests/Files/EndFileNewlineUnitTest.6.inc.fixed | 1 + .../Generic/Tests/Files/EndFileNewlineUnitTest.7.inc | 1 + .../Generic/Tests/Files/EndFileNewlineUnitTest.7.inc.fixed | 1 + .../Generic/Tests/Files/EndFileNewlineUnitTest.8.inc | 1 + src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.php | 3 +++ 8 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.6.inc create mode 100644 src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.6.inc.fixed create mode 100644 src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.7.inc create mode 100644 src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.7.inc.fixed create mode 100644 src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.8.inc diff --git a/package.xml b/package.xml index c408f94353..51f4fc3324 100644 --- a/package.xml +++ b/package.xml @@ -544,6 +544,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + diff --git a/src/Standards/Generic/Sniffs/Files/EndFileNewlineSniff.php b/src/Standards/Generic/Sniffs/Files/EndFileNewlineSniff.php index ee52a1b975..d5375dc5c0 100644 --- a/src/Standards/Generic/Sniffs/Files/EndFileNewlineSniff.php +++ b/src/Standards/Generic/Sniffs/Files/EndFileNewlineSniff.php @@ -34,7 +34,10 @@ class EndFileNewlineSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.6.inc b/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.6.inc new file mode 100644 index 0000000000..4f2e47af9c --- /dev/null +++ b/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.6.inc @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.7.inc.fixed b/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.7.inc.fixed new file mode 100644 index 0000000000..d3c19feeb2 --- /dev/null +++ b/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.7.inc.fixed @@ -0,0 +1 @@ + diff --git a/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.8.inc b/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.8.inc new file mode 100644 index 0000000000..d3c19feeb2 --- /dev/null +++ b/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.8.inc @@ -0,0 +1 @@ + diff --git a/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.php b/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.php index b97e8ab295..f5ab9ffa6f 100644 --- a/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.php +++ b/src/Standards/Generic/Tests/Files/EndFileNewlineUnitTest.php @@ -33,6 +33,9 @@ public function getErrorList($testFile='') case 'EndFileNewlineUnitTest.3.css': case 'EndFileNewlineUnitTest.4.inc': return [2 => 1]; + case 'EndFileNewlineUnitTest.6.inc': + case 'EndFileNewlineUnitTest.7.inc': + return [1 => 1]; default: return []; }//end switch From 1d845a116317c2ea4e1ef7d5c164a02a1cc4f8be Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 00:56:14 +0200 Subject: [PATCH 323/733] Generic/EndFileNoNewline: check files using only short open echo tag Includes unit tests. --- package.xml | 5 +++++ src/Standards/Generic/Sniffs/Files/EndFileNoNewlineSniff.php | 5 ++++- .../Generic/Tests/Files/EndFileNoNewlineUnitTest.10.inc | 1 + .../Generic/Tests/Files/EndFileNoNewlineUnitTest.8.inc | 1 + .../Generic/Tests/Files/EndFileNoNewlineUnitTest.8.inc.fixed | 1 + .../Generic/Tests/Files/EndFileNoNewlineUnitTest.9.inc | 1 + .../Generic/Tests/Files/EndFileNoNewlineUnitTest.9.inc.fixed | 1 + .../Generic/Tests/Files/EndFileNoNewlineUnitTest.php | 3 +++ 8 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.10.inc create mode 100644 src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.8.inc create mode 100644 src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.8.inc.fixed create mode 100644 src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.9.inc create mode 100644 src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.9.inc.fixed diff --git a/package.xml b/package.xml index c408f94353..097a088b6c 100644 --- a/package.xml +++ b/package.xml @@ -565,6 +565,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + diff --git a/src/Standards/Generic/Sniffs/Files/EndFileNoNewlineSniff.php b/src/Standards/Generic/Sniffs/Files/EndFileNoNewlineSniff.php index 2c7fcd35bc..41c9c74991 100644 --- a/src/Standards/Generic/Sniffs/Files/EndFileNoNewlineSniff.php +++ b/src/Standards/Generic/Sniffs/Files/EndFileNoNewlineSniff.php @@ -34,7 +34,10 @@ class EndFileNoNewlineSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.10.inc b/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.10.inc new file mode 100644 index 0000000000..884aad1b30 --- /dev/null +++ b/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.10.inc @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.8.inc b/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.8.inc new file mode 100644 index 0000000000..1f87609292 --- /dev/null +++ b/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.8.inc @@ -0,0 +1 @@ + diff --git a/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.9.inc.fixed b/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.9.inc.fixed new file mode 100644 index 0000000000..884aad1b30 --- /dev/null +++ b/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.9.inc.fixed @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.php b/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.php index 23c9980b44..88dc2f88a1 100644 --- a/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.php +++ b/src/Standards/Generic/Tests/Files/EndFileNoNewlineUnitTest.php @@ -37,6 +37,9 @@ public function getErrorList($testFile='') case 'EndFileNoNewlineUnitTest.2.js': case 'EndFileNoNewlineUnitTest.6.inc': return [2 => 1]; + case 'EndFileNoNewlineUnitTest.8.inc': + case 'EndFileNoNewlineUnitTest.9.inc': + return [1 => 1]; default: return []; }//end switch From 92eba1cfabeee80a55f7f45e2f61b2f26323d5ad Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 01:07:35 +0200 Subject: [PATCH 324/733] Generic/ClosingPHPTag: also check for short open echo tag Includes unit tests. --- package.xml | 3 ++- .../Generic/Sniffs/PHP/ClosingPHPTagSniff.php | 5 ++++- ...agUnitTest.inc => ClosingPHPTagUnitTest.1.inc} | 0 .../Generic/Tests/PHP/ClosingPHPTagUnitTest.2.inc | 5 +++++ .../Generic/Tests/PHP/ClosingPHPTagUnitTest.php | 15 +++++++++++++-- 5 files changed, 24 insertions(+), 4 deletions(-) rename src/Standards/Generic/Tests/PHP/{ClosingPHPTagUnitTest.inc => ClosingPHPTagUnitTest.1.inc} (100%) create mode 100644 src/Standards/Generic/Tests/PHP/ClosingPHPTagUnitTest.2.inc diff --git a/package.xml b/package.xml index c408f94353..58c05179c0 100644 --- a/package.xml +++ b/package.xml @@ -664,7 +664,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + + diff --git a/src/Standards/Generic/Sniffs/PHP/ClosingPHPTagSniff.php b/src/Standards/Generic/Sniffs/PHP/ClosingPHPTagSniff.php index e59ed5ffa7..d03bf8abe1 100644 --- a/src/Standards/Generic/Sniffs/PHP/ClosingPHPTagSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/ClosingPHPTagSniff.php @@ -23,7 +23,10 @@ class ClosingPHPTagSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/PHP/ClosingPHPTagUnitTest.inc b/src/Standards/Generic/Tests/PHP/ClosingPHPTagUnitTest.1.inc similarity index 100% rename from src/Standards/Generic/Tests/PHP/ClosingPHPTagUnitTest.inc rename to src/Standards/Generic/Tests/PHP/ClosingPHPTagUnitTest.1.inc diff --git a/src/Standards/Generic/Tests/PHP/ClosingPHPTagUnitTest.2.inc b/src/Standards/Generic/Tests/PHP/ClosingPHPTagUnitTest.2.inc new file mode 100644 index 0000000000..fc896ed197 --- /dev/null +++ b/src/Standards/Generic/Tests/PHP/ClosingPHPTagUnitTest.2.inc @@ -0,0 +1,5 @@ + +Bold text + +Italic text + */ - public function getErrorList() + public function getErrorList($testFile='') { - return [9 => 1]; + switch ($testFile) { + case 'ClosingPHPTagUnitTest.1.inc': + return [9 => 1]; + case 'ClosingPHPTagUnitTest.2.inc': + return [5 => 1]; + break; + default: + return []; + break; + } }//end getErrorList() From 6568bd06380d98ce3e455bd6170c97cc8a434b76 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 01:13:09 +0200 Subject: [PATCH 325/733] Generic/Syntax: check files using only short open echo tag Includes unit tests. --- package.xml | 3 ++- src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php | 5 ++++- .../{SyntaxUnitTest.inc => SyntaxUnitTest.1.inc} | 0 .../Generic/Tests/PHP/SyntaxUnitTest.2.inc | 3 +++ src/Standards/Generic/Tests/PHP/SyntaxUnitTest.php | 14 ++++++++++++-- 5 files changed, 21 insertions(+), 4 deletions(-) rename src/Standards/Generic/Tests/PHP/{SyntaxUnitTest.inc => SyntaxUnitTest.1.inc} (100%) create mode 100644 src/Standards/Generic/Tests/PHP/SyntaxUnitTest.2.inc diff --git a/package.xml b/package.xml index c408f94353..5990335ff5 100644 --- a/package.xml +++ b/package.xml @@ -704,7 +704,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + + diff --git a/src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php b/src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php index 1519aa1376..85e717bc99 100644 --- a/src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/SyntaxSniff.php @@ -33,7 +33,10 @@ class SyntaxSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.inc b/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.1.inc similarity index 100% rename from src/Standards/Generic/Tests/PHP/SyntaxUnitTest.inc rename to src/Standards/Generic/Tests/PHP/SyntaxUnitTest.1.inc diff --git a/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.2.inc b/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.2.inc new file mode 100644 index 0000000000..11f8a58988 --- /dev/null +++ b/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.2.inc @@ -0,0 +1,3 @@ + +
text
+ diff --git a/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.php b/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.php index d8cd7efaab..98d205ce43 100644 --- a/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/SyntaxUnitTest.php @@ -22,11 +22,21 @@ class SyntaxUnitTest extends AbstractSniffUnitTest * The key of the array should represent the line number and the value * should represent the number of errors that should occur on that line. * + * @param string $testFile The name of the file being tested. + * * @return array */ - public function getErrorList() + public function getErrorList($testFile='') { - return [3 => 1]; + switch ($testFile) { + case 'SyntaxUnitTest.1.inc': + case 'SyntaxUnitTest.2.inc': + return [3 => 1]; + break; + default: + return []; + break; + } }//end getErrorList() From a9a1c4c4460694d59de0cb3fbb24b648143d8189 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 01:36:17 +0200 Subject: [PATCH 326/733] Generic/GitMergeConflict: check files using only short open echo tag Includes unit tests. --- package.xml | 1 + .../VersionControl/GitMergeConflictSniff.php | 5 ++++- .../GitMergeConflictUnitTest.7.inc | 19 +++++++++++++++++++ .../GitMergeConflictUnitTest.php | 10 ++++++++++ .../WhiteSpace/SuperfluousWhitespaceSniff.php | 1 + 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/Standards/Generic/Tests/VersionControl/GitMergeConflictUnitTest.7.inc diff --git a/package.xml b/package.xml index c408f94353..e408d1ba89 100644 --- a/package.xml +++ b/package.xml @@ -724,6 +724,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/Generic/Sniffs/VersionControl/GitMergeConflictSniff.php b/src/Standards/Generic/Sniffs/VersionControl/GitMergeConflictSniff.php index e25d556b8b..83265b2e47 100644 --- a/src/Standards/Generic/Sniffs/VersionControl/GitMergeConflictSniff.php +++ b/src/Standards/Generic/Sniffs/VersionControl/GitMergeConflictSniff.php @@ -34,7 +34,10 @@ class GitMergeConflictSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/VersionControl/GitMergeConflictUnitTest.7.inc b/src/Standards/Generic/Tests/VersionControl/GitMergeConflictUnitTest.7.inc new file mode 100644 index 0000000000..85cae1fdc8 --- /dev/null +++ b/src/Standards/Generic/Tests/VersionControl/GitMergeConflictUnitTest.7.inc @@ -0,0 +1,19 @@ + +
+<<<<<<< HEAD +

Testing a merge conflict.

+======= +

Another text string.

+>>>>>>> ref/heads/feature-branch +
+ + +
+<<<<<<< HEAD +

+======= +

+>>>>>>> ref/heads/feature-branch +
+ + diff --git a/src/Standards/Generic/Tests/VersionControl/GitMergeConflictUnitTest.php b/src/Standards/Generic/Tests/VersionControl/GitMergeConflictUnitTest.php index a36caafbd2..50986f48bb 100644 --- a/src/Standards/Generic/Tests/VersionControl/GitMergeConflictUnitTest.php +++ b/src/Standards/Generic/Tests/VersionControl/GitMergeConflictUnitTest.php @@ -99,6 +99,16 @@ public function getErrorList($testFile='GitMergeConflictUnitTest.1.inc') 32 => 1, ]; + case 'GitMergeConflictUnitTest.7.inc': + return [ + 3 => 1, + 5 => 1, + 7 => 1, + 12 => 1, + 14 => 1, + 16 => 1, + ]; + case 'GitMergeConflictUnitTest.1.css': return [ 3 => 1, diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php index 831e832766..e99d829bca 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php @@ -49,6 +49,7 @@ public function register() { return [ T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG, T_WHITESPACE, T_COMMENT, From 3fb8a2d940d2ee144b2a114636958e3754fe40a2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 04:11:20 +0200 Subject: [PATCH 327/733] Generic/DisallowSpaceIndent: check files using only short open echo tag Includes unit tests. --- package.xml | 2 ++ .../WhiteSpace/DisallowSpaceIndentSniff.php | 5 ++++- .../DisallowSpaceIndentUnitTest.3.inc | 19 +++++++++++++++++++ .../DisallowSpaceIndentUnitTest.3.inc.fixed | 19 +++++++++++++++++++ .../DisallowSpaceIndentUnitTest.php | 11 +++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/Standards/Generic/Tests/WhiteSpace/DisallowSpaceIndentUnitTest.3.inc create mode 100644 src/Standards/Generic/Tests/WhiteSpace/DisallowSpaceIndentUnitTest.3.inc.fixed diff --git a/package.xml b/package.xml index c408f94353..87b422723e 100644 --- a/package.xml +++ b/package.xml @@ -739,6 +739,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php index 4fe89e767a..bbdbfe0a9a 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php @@ -41,7 +41,10 @@ class DisallowSpaceIndentSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/WhiteSpace/DisallowSpaceIndentUnitTest.3.inc b/src/Standards/Generic/Tests/WhiteSpace/DisallowSpaceIndentUnitTest.3.inc new file mode 100644 index 0000000000..4df731a3c0 --- /dev/null +++ b/src/Standards/Generic/Tests/WhiteSpace/DisallowSpaceIndentUnitTest.3.inc @@ -0,0 +1,19 @@ + + + + Foo + + +
+
+
+
+
+
+ + + + + + + Foo + + +
+
+
+
+
+
+ + + + 1, ]; break; + case 'DisallowSpaceIndentUnitTest.3.inc': + return [ + 2 => 1, + 5 => 1, + 10 => 1, + 12 => 1, + 13 => 1, + 14 => 1, + 15 => 1, + ]; + break; case 'DisallowSpaceIndentUnitTest.js': return [3 => 1]; break; From 551cdf7328e25d40330c617afa44f4605bf6e07c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 03:52:38 +0200 Subject: [PATCH 328/733] Generic/DisallowTabIndent: check files using only short open echo tag Includes unit tests. --- package.xml | 6 ++++-- .../WhiteSpace/DisallowTabIndentSniff.php | 5 ++++- ...st.inc => DisallowTabIndentUnitTest.1.inc} | 0 ... => DisallowTabIndentUnitTest.1.inc.fixed} | 0 .../DisallowTabIndentUnitTest.2.inc | 19 +++++++++++++++++++ .../DisallowTabIndentUnitTest.2.inc.fixed | 19 +++++++++++++++++++ .../WhiteSpace/DisallowTabIndentUnitTest.php | 17 +++++++++++++++-- 7 files changed, 61 insertions(+), 5 deletions(-) rename src/Standards/Generic/Tests/WhiteSpace/{DisallowTabIndentUnitTest.inc => DisallowTabIndentUnitTest.1.inc} (100%) rename src/Standards/Generic/Tests/WhiteSpace/{DisallowTabIndentUnitTest.inc.fixed => DisallowTabIndentUnitTest.1.inc.fixed} (100%) create mode 100644 src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.2.inc create mode 100644 src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.2.inc.fixed diff --git a/package.xml b/package.xml index c408f94353..7ff6fb5fef 100644 --- a/package.xml +++ b/package.xml @@ -744,8 +744,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - + + + + diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php index a50e9f92bb..3e20cf4964 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php @@ -41,7 +41,10 @@ class DisallowTabIndentSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.inc b/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.1.inc similarity index 100% rename from src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.inc rename to src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.1.inc diff --git a/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.1.inc.fixed similarity index 100% rename from src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.inc.fixed rename to src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.1.inc.fixed diff --git a/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.2.inc new file mode 100644 index 0000000000..4df731a3c0 --- /dev/null +++ b/src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.2.inc @@ -0,0 +1,19 @@ + + + + Foo + + +
+
+
+
+
+
+ + + + + + + Foo + + +
+
+
+
+
+
+ + + + */ - public function getErrorList($testFile='DisallowTabIndentUnitTest.inc') + public function getErrorList($testFile='') { switch ($testFile) { - case 'DisallowTabIndentUnitTest.inc': + case 'DisallowTabIndentUnitTest.1.inc': return [ 5 => 2, 9 => 1, @@ -84,6 +84,19 @@ public function getErrorList($testFile='DisallowTabIndentUnitTest.inc') 93 => 1, ]; break; + case 'DisallowTabIndentUnitTest.2.inc': + return [ + 6 => 1, + 7 => 1, + 8 => 1, + 9 => 1, + 10 => 1, + 11 => 1, + 12 => 1, + 13 => 1, + 19 => 1, + ]; + break; case 'DisallowTabIndentUnitTest.js': return [ 3 => 1, From b094c94f0bcec76cc7c8803355134e36c6aefc18 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 02:18:21 +0200 Subject: [PATCH 329/733] PSR2/EndFileNewline: check files using only short open echo tag As per the standard: > All PHP files MUST end with a single blank line This does not exclude files which are mixed PHP/HTML. Includes unit tests. --- package.xml | 7 +++++++ src/Standards/PSR2/Sniffs/Files/EndFileNewlineSniff.php | 5 ++++- .../PSR2/Tests/Files/EndFileNewlineUnitTest.11.inc | 1 + .../PSR2/Tests/Files/EndFileNewlineUnitTest.11.inc.fixed | 1 + .../PSR2/Tests/Files/EndFileNewlineUnitTest.12.inc | 1 + .../PSR2/Tests/Files/EndFileNewlineUnitTest.12.inc.fixed | 1 + .../PSR2/Tests/Files/EndFileNewlineUnitTest.13.inc | 5 +++++ .../PSR2/Tests/Files/EndFileNewlineUnitTest.13.inc.fixed | 1 + .../PSR2/Tests/Files/EndFileNewlineUnitTest.14.inc | 1 + src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.php | 4 ++++ 10 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.11.inc create mode 100644 src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.11.inc.fixed create mode 100644 src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.12.inc create mode 100644 src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.12.inc.fixed create mode 100644 src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.13.inc create mode 100644 src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.13.inc.fixed create mode 100644 src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.14.inc diff --git a/package.xml b/package.xml index c408f94353..1c1f7f39e6 100644 --- a/package.xml +++ b/package.xml @@ -1299,6 +1299,13 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + +
diff --git a/src/Standards/PSR2/Sniffs/Files/EndFileNewlineSniff.php b/src/Standards/PSR2/Sniffs/Files/EndFileNewlineSniff.php index 241fa5a8f4..aed461f83f 100644 --- a/src/Standards/PSR2/Sniffs/Files/EndFileNewlineSniff.php +++ b/src/Standards/PSR2/Sniffs/Files/EndFileNewlineSniff.php @@ -23,7 +23,10 @@ class EndFileNewlineSniff implements Sniff */ public function register() { - return [T_OPEN_TAG]; + return [ + T_OPEN_TAG, + T_OPEN_TAG_WITH_ECHO, + ]; }//end register() diff --git a/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.11.inc b/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.11.inc new file mode 100644 index 0000000000..4f2e47af9c --- /dev/null +++ b/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.11.inc @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.12.inc.fixed b/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.12.inc.fixed new file mode 100644 index 0000000000..d3c19feeb2 --- /dev/null +++ b/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.12.inc.fixed @@ -0,0 +1 @@ + diff --git a/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.13.inc b/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.13.inc new file mode 100644 index 0000000000..fa2f476a92 --- /dev/null +++ b/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.13.inc @@ -0,0 +1,5 @@ + diff --git a/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.php b/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.php index a80c16dbed..456106fe5a 100644 --- a/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.php +++ b/src/Standards/PSR2/Tests/Files/EndFileNewlineUnitTest.php @@ -35,6 +35,10 @@ public function getErrorList($testFile='') case 'EndFileNewlineUnitTest.9.inc': case 'EndFileNewlineUnitTest.10.inc': return [2 => 1]; + case 'EndFileNewlineUnitTest.11.inc': + case 'EndFileNewlineUnitTest.12.inc': + case 'EndFileNewlineUnitTest.13.inc': + return [1 => 1]; default: return []; }//end switch From 93306f5e1347902d6df69d3fa388e60eab058752 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 22 Apr 2021 03:32:02 +0200 Subject: [PATCH 330/733] Squiz/BlockComment: also check for short open echo tag Includes unit tests. --- .../Sniffs/Commenting/BlockCommentSniff.php | 1 + .../Tests/Commenting/BlockCommentUnitTest.inc | 16 ++++++++++++++++ .../Commenting/BlockCommentUnitTest.inc.fixed | 16 ++++++++++++++++ .../Tests/Commenting/BlockCommentUnitTest.php | 2 ++ 4 files changed, 35 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php index ae9db7944c..93b60adaad 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php @@ -363,6 +363,7 @@ public function process(File $phpcsFile, $stackPtr) if ((isset($tokens[$contentBefore]['scope_closer']) === true && $tokens[$contentBefore]['scope_opener'] === $contentBefore) || $tokens[$contentBefore]['code'] === T_OPEN_TAG + || $tokens[$contentBefore]['code'] === T_OPEN_TAG_WITH_ECHO ) { if (($tokens[$stackPtr]['line'] - $tokens[$contentBefore]['line']) !== 1) { $error = 'Empty line not required before block comment'; diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc index 877bca648e..daf50fa382 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc @@ -256,3 +256,19 @@ $y = 10 + /* test */ -2; /* * No blank line allowed above the comment if it's the first non-empty token after a PHP open tag. */ + +?> + + + + 1, 233 => 1, 256 => 1, + 271 => 1, + 273 => 1, ]; return $errors; From 51b5df9d2785918d4be66df6204560257bc4183e Mon Sep 17 00:00:00 2001 From: Emil Andersson Date: Thu, 22 Apr 2021 10:41:58 +0200 Subject: [PATCH 331/733] Continue to the next file when an ignored file is found when running checks in parallel. This fixes #3317. --- src/Runner.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Runner.php b/src/Runner.php index 320a8f20a0..c6f2c92edf 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -487,6 +487,7 @@ private function run() $file = $todo->current(); if ($file->ignored === true) { + $todo->next(); continue; } From 41a9bef4940cab7d7031b15b709bcd214fef8e2c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 23 Apr 2021 12:55:48 +1000 Subject: [PATCH 332/733] Fixed bug #3316 : Arrow function not tokenized correctly when using null in union type --- package.xml | 1 + src/Tokenizers/PHP.php | 5 +++-- tests/Core/Tokenizer/BackfillFnTokenTest.inc | 3 +++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 18 ++++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index c408f94353..ca6aa28da2 100644 --- a/package.xml +++ b/package.xml @@ -31,6 +31,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the tests - Fixed bug #3296 : PSR2.ControlStructures.SwitchDeclaration takes phpcs:ignore as content of case body - Fixed bug #3303 : findStartOfStatement() doesn't work with T_OPEN_TAG_WITH_ECHO + - Fixed bug #3316 : Arrow function not tokenized correctly when using null in union type diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 3d8cee1657..debef98d13 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2276,16 +2276,17 @@ protected function processAdditional() if (isset($this->tokens[$x]) === true && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) { $ignore = Util\Tokens::$emptyTokens; $ignore += [ - T_STRING => T_STRING, 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_CALLABLE => T_CALLABLE, T_PARENT => T_PARENT, T_SELF => T_SELF, T_STATIC => T_STATIC, + T_STRING => T_STRING, T_TYPE_UNION => T_TYPE_UNION, ]; diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index 72cb244200..56e8c29c3d 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -93,6 +93,9 @@ $arrowWithUnionReturn = fn($param) : int|float => $param | 10; /* testTernary */ $fn = fn($a) => $a ? /* testTernaryThen */ fn() : string => 'a' : /* testTernaryElse */ fn() : string => 'b'; +/* testTernaryWithTypes */ +$fn = fn(int|null $a) : array|null => $a ? null : []; + function matchInArrow($x) { /* testWithMatchValue */ $fn = fn($x) => match(true) { diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 6404026c34..76b39819d2 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -445,6 +445,24 @@ public function testTernary() }//end testTernary() + /** + * Test typed arrow functions used in ternary operators. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testTernaryWithTypes() + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken('/* testTernaryWithTypes */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 15, 27); + + }//end testTernaryWithTypes() + + /** * Test arrow function returning a match control structure. * From ba54cb533b060b7c92c60b21d71717a84fa78fd8 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 23 Apr 2021 13:11:56 +1000 Subject: [PATCH 333/733] Changelog for #3302 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index ca6aa28da2..5e212f6420 100644 --- a/package.xml +++ b/package.xml @@ -30,6 +30,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Alessandro Chitolina for the patch -- Thanks to Juliette Reinders Folmer for the tests - Fixed bug #3296 : PSR2.ControlStructures.SwitchDeclaration takes phpcs:ignore as content of case body + - Fixed bug #3302 : PHP 8.0 | Tokenizer/PHP: bugfix for union types using namespace operator + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3303 : findStartOfStatement() doesn't work with T_OPEN_TAG_WITH_ECHO - Fixed bug #3316 : Arrow function not tokenized correctly when using null in union type From e4af9dc436f566c5ae4bb84a5f5500ec7dd5dc84 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 16 Apr 2021 17:38:04 +0200 Subject: [PATCH 334/733] File::getMethodParameters(): add test documenting behaviour for comments This test demonstrates and documents the existing behaviour of the method when comments are encountered within a parameter declaration. --- tests/Core/File/GetMethodParametersTest.inc | 6 ++++++ tests/Core/File/GetMethodParametersTest.php | 23 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index b78301d309..6b66e0543b 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -120,3 +120,9 @@ abstract class ConstructorPropertyPromotionAbstractMethod { // 3. The callable type is not supported for properties, but that's not the concern of this method. abstract public function __construct(public callable $y, private ...$x); } + +/* testCommentsInParameter */ +function commentsInParams( + // Leading comment. + ?MyClass /*-*/ & /*-*/.../*-*/ $param /*-*/ = /*-*/ 'default value' . /*-*/ 'second part' // Trailing comment. +) {} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index 253b806215..2f8f4791e1 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -818,6 +818,29 @@ public function testPHP8ConstructorPropertyPromotionAbstractMethod() }//end testPHP8ConstructorPropertyPromotionAbstractMethod() + /** + * Verify and document behaviour when there are comments within a parameter declaration. + * + * @return void + */ + public function testCommentsInParameter() + { + $expected = []; + $expected[0] = [ + 'name' => '$param', + 'content' => '// Leading comment. + ?MyClass /*-*/ & /*-*/.../*-*/ $param /*-*/ = /*-*/ \'default value\' . /*-*/ \'second part\' // Trailing comment.', + 'pass_by_reference' => true, + 'variable_length' => true, + 'type_hint' => '?MyClass', + 'nullable_type' => true, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testCommentsInParameter() + + /** * Test helper. * From e3b113f208be5f149e785beb6bd3619f7752dcda Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Apr 2021 07:10:19 +0200 Subject: [PATCH 335/733] File::getMethodParameters(): bug fix for attributes leaking into type hint This commit adds handling of parameter attributes to the `File::getMethodParameters()` method as per option [2] discussed in issue 3298. In practice this means that: * [New] A new `attributes` index is introduced into the returned array which will hold a boolean value indicating whether attributes are attached to the parameter. * [Unchanged] The `content` index in the returned array includes the textual representation of any attributes attached to a parameter. * [Fixed] The `type_hint` and `type_hint_token` indexes will no longer be polluted (set incorrectly) with information belonging to the attribute(s) instead of to the type declaration. Includes minor efficiency fix for handling of parenthesis and brackets in default values. Includes dedicated unit test. Fixes 3298 --- src/Files/File.php | 17 +++- tests/Core/File/GetMethodParametersTest.inc | 14 +++ tests/Core/File/GetMethodParametersTest.php | 107 ++++++++++++++++++++ 3 files changed, 135 insertions(+), 3 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index ca01f37e95..d9e52a7e30 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1283,6 +1283,7 @@ public function getDeclarationName($stackPtr) * 'name' => '$var', // The variable name. * 'token' => integer, // The stack pointer to the variable name. * 'content' => string, // The full content of the variable definition. + * 'attributes' => boolean, // Does the parameter have one or more attributes attached ? * 'pass_by_reference' => boolean, // Is the variable passed by reference? * 'reference_token' => integer, // The stack pointer to the reference operator * // or FALSE if the param is not passed by reference. @@ -1355,6 +1356,7 @@ public function getMethodParameters($stackPtr) $defaultStart = null; $equalToken = null; $paramCount = 0; + $attributes = false; $passByReference = false; $referenceToken = false; $variableLength = false; @@ -1373,18 +1375,25 @@ public function getMethodParameters($stackPtr) if (isset($this->tokens[$i]['parenthesis_opener']) === true) { // Don't do this if it's the close parenthesis for the method. if ($i !== $this->tokens[$i]['parenthesis_closer']) { - $i = ($this->tokens[$i]['parenthesis_closer'] + 1); + $i = $this->tokens[$i]['parenthesis_closer']; + continue; } } if (isset($this->tokens[$i]['bracket_opener']) === true) { - // Don't do this if it's the close parenthesis for the method. if ($i !== $this->tokens[$i]['bracket_closer']) { - $i = ($this->tokens[$i]['bracket_closer'] + 1); + $i = $this->tokens[$i]['bracket_closer']; + continue; } } switch ($this->tokens[$i]['code']) { + case T_ATTRIBUTE: + $attributes = true; + + // Skip to the end of the attribute. + $i = $this->tokens[$i]['attribute_closer']; + break; case T_BITWISE_AND: if ($defaultStart === null) { $passByReference = true; @@ -1501,6 +1510,7 @@ public function getMethodParameters($stackPtr) $vars[$paramCount]['default_equal_token'] = $equalToken; } + $vars[$paramCount]['attributes'] = $attributes; $vars[$paramCount]['pass_by_reference'] = $passByReference; $vars[$paramCount]['reference_token'] = $referenceToken; $vars[$paramCount]['variable_length'] = $variableLength; @@ -1526,6 +1536,7 @@ public function getMethodParameters($stackPtr) $paramStart = ($i + 1); $defaultStart = null; $equalToken = null; + $attributes = false; $passByReference = false; $referenceToken = false; $variableLength = false; diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index 6b66e0543b..ed1762e7d2 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -126,3 +126,17 @@ function commentsInParams( // Leading comment. ?MyClass /*-*/ & /*-*/.../*-*/ $param /*-*/ = /*-*/ 'default value' . /*-*/ 'second part' // Trailing comment. ) {} + +/* testParameterAttributesInFunctionDeclaration */ +class ParametersWithAttributes( + public function __construct( + #[\MyExample\MyAttribute] private string $constructorPropPromTypedParamSingleAttribute, + #[MyAttr([1, 2])] + Type|false + $typedParamSingleAttribute, + #[MyAttribute(1234), MyAttribute(5678)] ?int $nullableTypedParamMultiAttribute, + #[WithoutArgument] #[SingleArgument(0)] $nonTypedParamTwoAttributes, + #[MyAttribute(array("key" => "value"))] + &...$otherParam, + ) {} +} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index 2f8f4791e1..6de4622aed 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -26,6 +26,7 @@ public function testPassByReference() $expected[0] = [ 'name' => '$var', 'content' => '&$var', + 'attributes' => false, 'pass_by_reference' => true, 'variable_length' => false, 'type_hint' => '', @@ -48,6 +49,7 @@ public function testArrayHint() $expected[0] = [ 'name' => '$var', 'content' => 'array $var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'array', @@ -70,6 +72,7 @@ public function testTypeHint() $expected[0] = [ 'name' => '$var1', 'content' => 'foo $var1', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'foo', @@ -79,6 +82,7 @@ public function testTypeHint() $expected[1] = [ 'name' => '$var2', 'content' => 'bar $var2', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'bar', @@ -101,6 +105,7 @@ public function testSelfTypeHint() $expected[0] = [ 'name' => '$var', 'content' => 'self $var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'self', @@ -123,6 +128,7 @@ public function testNullableTypeHint() $expected[0] = [ 'name' => '$var1', 'content' => '?int $var1', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?int', @@ -132,6 +138,7 @@ public function testNullableTypeHint() $expected[1] = [ 'name' => '$var2', 'content' => '?\bar $var2', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?\bar', @@ -154,6 +161,7 @@ public function testVariable() $expected[0] = [ 'name' => '$var', 'content' => '$var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -176,6 +184,7 @@ public function testSingleDefaultValue() $expected[0] = [ 'name' => '$var1', 'content' => '$var1=self::CONSTANT', + 'attributes' => false, 'default' => 'self::CONSTANT', 'pass_by_reference' => false, 'variable_length' => false, @@ -199,6 +208,7 @@ public function testDefaultValues() $expected[0] = [ 'name' => '$var1', 'content' => '$var1=1', + 'attributes' => false, 'default' => '1', 'pass_by_reference' => false, 'variable_length' => false, @@ -208,6 +218,7 @@ public function testDefaultValues() $expected[1] = [ 'name' => '$var2', 'content' => "\$var2='value'", + 'attributes' => false, 'default' => "'value'", 'pass_by_reference' => false, 'variable_length' => false, @@ -232,6 +243,7 @@ public function testBitwiseAndConstantExpressionDefaultValue() 'name' => '$a', 'content' => '$a = 10 & 20', 'default' => '10 & 20', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -254,6 +266,7 @@ public function testArrowFunction() $expected[0] = [ 'name' => '$a', 'content' => 'int $a', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int', @@ -263,6 +276,7 @@ public function testArrowFunction() $expected[1] = [ 'name' => '$b', 'content' => '...$b', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => true, 'type_hint' => '', @@ -285,6 +299,7 @@ public function testPHP8MixedTypeHint() $expected[0] = [ 'name' => '$var1', 'content' => 'mixed &...$var1', + 'attributes' => false, 'pass_by_reference' => true, 'variable_length' => true, 'type_hint' => 'mixed', @@ -307,6 +322,7 @@ public function testPHP8MixedTypeHintNullable() $expected[0] = [ 'name' => '$var1', 'content' => '?Mixed $var1', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?Mixed', @@ -329,6 +345,7 @@ public function testNamespaceOperatorTypeHint() $expected[0] = [ 'name' => '$var1', 'content' => '?namespace\Name $var1', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?namespace\Name', @@ -351,6 +368,7 @@ public function testPHP8UnionTypesSimple() $expected[0] = [ 'name' => '$number', 'content' => 'int|float $number', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int|float', @@ -359,6 +377,7 @@ public function testPHP8UnionTypesSimple() $expected[1] = [ 'name' => '$obj', 'content' => 'self|parent &...$obj', + 'attributes' => false, 'pass_by_reference' => true, 'variable_length' => true, 'type_hint' => 'self|parent', @@ -381,6 +400,7 @@ public function testPHP8UnionTypesWithSpreadOperatorAndReference() $expected[0] = [ 'name' => '$paramA', 'content' => 'float|null &$paramA', + 'attributes' => false, 'pass_by_reference' => true, 'variable_length' => false, 'type_hint' => 'float|null', @@ -389,6 +409,7 @@ public function testPHP8UnionTypesWithSpreadOperatorAndReference() $expected[1] = [ 'name' => '$paramB', 'content' => 'string|int ...$paramB', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => true, 'type_hint' => 'string|int', @@ -412,6 +433,7 @@ public function testPHP8UnionTypesSimpleWithBitwiseOrInDefault() 'name' => '$var', 'content' => 'int|float $var = CONSTANT_A | CONSTANT_B', 'default' => 'CONSTANT_A | CONSTANT_B', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int|float', @@ -434,6 +456,7 @@ public function testPHP8UnionTypesTwoClasses() $expected[0] = [ 'name' => '$var', 'content' => 'MyClassA|\Package\MyClassB $var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'MyClassA|\Package\MyClassB', @@ -456,6 +479,7 @@ public function testPHP8UnionTypesAllBaseTypes() $expected[0] = [ 'name' => '$var', 'content' => 'array|bool|callable|int|float|null|object|string $var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'array|bool|callable|int|float|null|object|string', @@ -478,6 +502,7 @@ public function testPHP8UnionTypesAllPseudoTypes() $expected[0] = [ 'name' => '$var', 'content' => 'false|mixed|self|parent|iterable|Resource $var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'false|mixed|self|parent|iterable|Resource', @@ -500,6 +525,7 @@ public function testPHP8UnionTypesNullable() $expected[0] = [ 'name' => '$number', 'content' => '?int|float $number', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?int|float', @@ -523,6 +549,7 @@ public function testPHP8PseudoTypeNull() 'name' => '$var', 'content' => 'null $var = null', 'default' => 'null', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'null', @@ -546,6 +573,7 @@ public function testPHP8PseudoTypeFalse() 'name' => '$var', 'content' => 'false $var = false', 'default' => 'false', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'false', @@ -569,6 +597,7 @@ public function testPHP8PseudoTypeFalseAndBool() 'name' => '$var', 'content' => 'bool|false $var = false', 'default' => 'false', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'bool|false', @@ -591,6 +620,7 @@ public function testPHP8ObjectAndClass() $expected[0] = [ 'name' => '$var', 'content' => 'object|ClassName $var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'object|ClassName', @@ -613,6 +643,7 @@ public function testPHP8PseudoTypeIterableAndArray() $expected[0] = [ 'name' => '$var', 'content' => 'iterable|array|Traversable $var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'iterable|array|Traversable', @@ -635,6 +666,7 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment() $expected[0] = [ 'name' => '$var', 'content' => 'int | string /*comment*/ | INT $var', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int|string|INT', @@ -658,6 +690,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes() 'name' => '$x', 'content' => 'public $x = 0.0', 'default' => '0.0', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -668,6 +701,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes() 'name' => '$y', 'content' => 'protected $y = \'\'', 'default' => "''", + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -678,6 +712,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes() 'name' => '$z', 'content' => 'private $z = null', 'default' => 'null', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -701,6 +736,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes() $expected[0] = [ 'name' => '$x', 'content' => 'protected float|int $x', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'float|int', @@ -711,6 +747,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes() 'name' => '$y', 'content' => 'public ?string &$y = \'test\'', 'default' => "'test'", + 'attributes' => false, 'pass_by_reference' => true, 'variable_length' => false, 'type_hint' => '?string', @@ -720,6 +757,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes() $expected[2] = [ 'name' => '$z', 'content' => 'private mixed $z', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'mixed', @@ -743,6 +781,7 @@ public function testPHP8ConstructorPropertyPromotionAndNormalParam() $expected[0] = [ 'name' => '$promotedProp', 'content' => 'public int $promotedProp', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int', @@ -752,6 +791,7 @@ public function testPHP8ConstructorPropertyPromotionAndNormalParam() $expected[1] = [ 'name' => '$normalArg', 'content' => '?int $normalArg', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?int', @@ -774,6 +814,7 @@ public function testPHP8ConstructorPropertyPromotionGlobalFunction() $expected[0] = [ 'name' => '$x', 'content' => 'private $x', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -797,6 +838,7 @@ public function testPHP8ConstructorPropertyPromotionAbstractMethod() $expected[0] = [ 'name' => '$y', 'content' => 'public callable $y', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'callable', @@ -806,6 +848,7 @@ public function testPHP8ConstructorPropertyPromotionAbstractMethod() $expected[1] = [ 'name' => '$x', 'content' => 'private ...$x', + 'attributes' => false, 'pass_by_reference' => false, 'variable_length' => true, 'type_hint' => '', @@ -830,6 +873,7 @@ public function testCommentsInParameter() 'name' => '$param', 'content' => '// Leading comment. ?MyClass /*-*/ & /*-*/.../*-*/ $param /*-*/ = /*-*/ \'default value\' . /*-*/ \'second part\' // Trailing comment.', + 'attributes' => false, 'pass_by_reference' => true, 'variable_length' => true, 'type_hint' => '?MyClass', @@ -841,6 +885,69 @@ public function testCommentsInParameter() }//end testCommentsInParameter() + /** + * Verify behaviour when parameters have attributes attached. + * + * @return void + */ + public function testParameterAttributesInFunctionDeclaration() + { + $expected = []; + $expected[0] = [ + 'name' => '$constructorPropPromTypedParamSingleAttribute', + 'content' => '#[\MyExample\MyAttribute] private string $constructorPropPromTypedParamSingleAttribute', + 'attributes' => true, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'string', + 'nullable_type' => false, + 'property_visibility' => 'private', + ]; + $expected[1] = [ + 'name' => '$typedParamSingleAttribute', + 'content' => '#[MyAttr([1, 2])] + Type|false + $typedParamSingleAttribute', + 'attributes' => true, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'Type|false', + 'nullable_type' => false, + ]; + $expected[2] = [ + 'name' => '$nullableTypedParamMultiAttribute', + 'content' => '#[MyAttribute(1234), MyAttribute(5678)] ?int $nullableTypedParamMultiAttribute', + 'attributes' => true, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?int', + 'nullable_type' => true, + ]; + $expected[3] = [ + 'name' => '$nonTypedParamTwoAttributes', + 'content' => '#[WithoutArgument] #[SingleArgument(0)] $nonTypedParamTwoAttributes', + 'attributes' => true, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '', + 'nullable_type' => false, + ]; + $expected[4] = [ + 'name' => '$otherParam', + 'content' => '#[MyAttribute(array("key" => "value"))] + &...$otherParam', + 'attributes' => true, + 'pass_by_reference' => true, + 'variable_length' => true, + 'type_hint' => '', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testParameterAttributesInFunctionDeclaration() + + /** * Test helper. * From 62e6e0696bd03773fd551ec766aad765607303da Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 23 Apr 2021 15:37:51 +1000 Subject: [PATCH 336/733] Changelog for #3317 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 5e212f6420..2ba1ae8b43 100644 --- a/package.xml +++ b/package.xml @@ -34,6 +34,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3303 : findStartOfStatement() doesn't work with T_OPEN_TAG_WITH_ECHO - Fixed bug #3316 : Arrow function not tokenized correctly when using null in union type + - Fixed bug #3317 : Problem with how phpcs handles ignored files when running in parallel + -- Thanks to Emil Andersson for the patch From b84c5414e27f33946adb1a2ed7d481c13973c659 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 26 Apr 2021 15:25:52 +1000 Subject: [PATCH 337/733] Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call --- package.xml | 1 + src/Tokenizers/PHP.php | 10 ++++++ tests/Core/Tokenizer/BackfillFnTokenTest.inc | 3 ++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 35 ++++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/package.xml b/package.xml index 2ba1ae8b43..bfd7d49e1d 100644 --- a/package.xml +++ b/package.xml @@ -36,6 +36,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3316 : Arrow function not tokenized correctly when using null in union type - Fixed bug #3317 : Problem with how phpcs handles ignored files when running in parallel -- Thanks to Emil Andersson for the patch + - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index e2aa2ba45a..60c89c4507 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2355,6 +2355,16 @@ protected function processAdditional() break; } + if ($inTernary === false + && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true + && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer'] + && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN + ) { + // Found a nested arrow function that already has the closer set and is in + // the same scope as us, so we can use its closer. + break; + } + if (isset($this->tokens[$scopeCloser]['scope_closer']) === true && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index 56e8c29c3d..ddd502bb61 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -23,6 +23,9 @@ function fn() {} /* testNestedOuter */ $fn = fn($x) => /* testNestedInner */ fn($y) => $x * $y + $z; +/* testNestedSharedCloserOuter */ +$foo = foo(fn() => /* testNestedSharedCloserInner */ fn() => bar() === true); + /* testFunctionCall */ $extended = fn($c) => $callable($factory($c), $c); diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 76b39819d2..5529d2e96a 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -128,6 +128,41 @@ public function testNestedInner() }//end testNestedInner() + /** + * Test nested arrow functions with a shared closer. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testNestedSharedCloser() + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken('/* testNestedSharedCloserOuter */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 4, 20); + + $token = $this->getTargetToken('/* testNestedSharedCloserInner */', T_FN); + $this->backfillHelper($token, true); + + $expectedScopeOpener = ($token + 4); + $expectedScopeCloser = ($token + 12); + + $this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener for "inner" arrow function is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer for "inner" arrow function is not the TRUE token'); + + $opener = $tokens[$token]['scope_opener']; + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener for "inner" arrow function is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer for "inner" arrow function is not the semicolon token'); + + $closer = $tokens[$token]['scope_closer']; + $this->assertSame(($token - 4), $tokens[$closer]['scope_opener'], 'Closer scope opener for "inner" arrow function is not the arrow token of the "outer" arrow function (shared scope closer)'); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer for "inner" arrow function is not the TRUE token'); + + }//end testNestedSharedCloser() + + /** * Test arrow functions that call functions. * From 807c10cc5690d4fca494ae650ace2748509faa9f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 31 Aug 2020 20:44:07 +0200 Subject: [PATCH 338/733] Tokenizer: support hash comment for ignore annotations Until now, for the PHPCS native ignore annotations, only `//` slash or `/* */` star-style comments were supported. This adds support for PHPCS native ignore annotations using `#` hash-style comments. Includes unit tests and syncing of the `ltrim()` used in `File::process()` with the `ltrim()` used in `Tokenizer::createPositionMap()`. --- src/Files/File.php | 2 +- src/Tokenizers/Tokenizer.php | 2 +- tests/Core/ErrorSuppressionTest.php | 170 +++++++++++++++++++++++++++- 3 files changed, 169 insertions(+), 5 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index ca01f37e95..e4056bc8f8 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -356,7 +356,7 @@ public function process() || $token['code'] === T_DOC_COMMENT_TAG || ($inTests === true && $token['code'] === T_INLINE_HTML)) ) { - $commentText = ltrim($this->tokens[$stackPtr]['content'], ' /*'); + $commentText = ltrim($this->tokens[$stackPtr]['content'], " \t/*#"); $commentTextLower = strtolower($commentText); if (strpos($commentText, '@codingStandards') !== false) { if (strpos($commentText, '@codingStandardsIgnoreFile') !== false) { diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index a229d6979e..4c5d391340 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -250,7 +250,7 @@ private function createPositionMap() || $this->tokens[$i]['code'] === T_DOC_COMMENT_TAG || ($inTests === true && $this->tokens[$i]['code'] === T_INLINE_HTML) ) { - $commentText = ltrim($this->tokens[$i]['content'], " \t/*"); + $commentText = ltrim($this->tokens[$i]['content'], " \t/*#"); $commentText = rtrim($commentText, " */\t\r\n"); $commentTextLower = strtolower($commentText); if (strpos($commentText, '@codingStandards') !== false) { diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index f031b334bb..a11399d6c5 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -81,6 +81,46 @@ public function testSuppressError() $this->assertEquals(0, $numErrors); $this->assertCount(0, $errors); + // Process with inline hash comment suppression. + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(0, $numErrors); + $this->assertCount(0, $errors); + + // Process with multi-line inline hash comment suppression, tab-indented. + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(0, $numErrors); + $this->assertCount(0, $errors); + + // Process with inline hash @ comment suppression. + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(0, $numErrors); + $this->assertCount(0, $errors); + + // Process with inline hash comment suppression mixed case. + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(0, $numErrors); + $this->assertCount(0, $errors); + // Process with inline comment suppression (deprecated syntax). $content = 'assertEquals(1, $numErrors); $this->assertCount(1, $errors); + // Process with suppression (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(1, $numErrors); + $this->assertCount(1, $errors); + + // Process with @ suppression (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(1, $numErrors); + $this->assertCount(1, $errors); + // Process with suppression (deprecated syntax). $content = 'assertEquals(1, $numErrors); $this->assertCount(1, $errors); + // Process with suppression on line before (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(1, $numErrors); + $this->assertCount(1, $errors); + + // Process with @ suppression on line before (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(1, $numErrors); + $this->assertCount(1, $errors); + // Process with suppression on line before. $content = 'assertEquals(1, $numErrors); $this->assertCount(1, $errors); + // Process with suppression on same line (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(1, $numErrors); + $this->assertCount(1, $errors); + + // Process with @ suppression on same line (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(1, $numErrors); + $this->assertCount(1, $errors); + // Process with suppression on same line (deprecated syntax). $content = 'assertEquals(0, $numErrors); $this->assertCount(0, $errors); + // Process with disable/enable suppression and no single line suppression (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(0, $numErrors); + $this->assertCount(0, $errors); + // Process with disable/enable suppression and no single line suppression (deprecated syntax). $content = 'assertEquals(0, $numErrors); $this->assertCount(0, $errors); + // Process with line @ suppression nested within disable/enable @ suppression (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(0, $numErrors); + $this->assertCount(0, $errors); + // Process with line suppression nested within disable/enable suppression (deprecated syntax). $content = 'assertEquals(0, $numErrors); $this->assertCount(0, $errors); + // Process with suppression (hash comment). + $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; + $file = new DummyFile($content, $ruleset, $config); + $file->process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $this->assertEquals(0, $numErrors); + $this->assertCount(0, $errors); + // Process with suppression. $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; $file = new DummyFile($content, $ruleset, $config); @@ -647,6 +777,26 @@ public function testSuppressFile() $this->assertEquals(0, $numWarnings); $this->assertCount(0, $warnings); + // Process with suppression (hash comment). + $content = 'process(); + + $warnings = $file->getWarnings(); + $numWarnings = $file->getWarningCount(); + $this->assertEquals(0, $numWarnings); + $this->assertCount(0, $warnings); + + // Process with @ suppression (hash comment). + $content = 'process(); + + $warnings = $file->getWarnings(); + $numWarnings = $file->getWarningCount(); + $this->assertEquals(0, $numWarnings); + $this->assertCount(0, $warnings); + // Process with suppression (deprecated syntax). $content = 'assertEquals(0, $numWarnings); $this->assertCount(0, $warnings); + // Suppress a single sniff with reason (hash comment). + $content = 'process(); + + $errors = $file->getErrors(); + $numErrors = $file->getErrorCount(); + $warnings = $file->getWarnings(); + $numWarnings = $file->getWarningCount(); + $this->assertEquals(1, $numErrors); + $this->assertCount(1, $errors); + $this->assertEquals(0, $numWarnings); + $this->assertCount(0, $warnings); + // Suppress multiple sniffs. $content = 'assertCount(1, $warnings); // Suppress multiple sniffs and re-enable one. - $content = 'process(); @@ -998,7 +1162,7 @@ public function testEnableSelected() $this->assertCount(1, $warnings); // Suppress a category and re-enable a whole standard. - $content = 'process(); @@ -1143,7 +1307,7 @@ public function testIgnoreSelected() $this->assertCount(0, $warnings); // Suppress a category of sniffs. - $content = 'process(); From fde816c3c3229af6440ed542df00d37a7bc6a261 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 28 Apr 2021 13:23:55 +1000 Subject: [PATCH 339/733] Changelog for #3071 --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index bfd7d49e1d..e38a9bd114 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - PHPCS annotations can now be specified using hash-style comments + -- Previously, only slash-style and block-style comments could be used to do things like disable errors + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line -- Thanks to Alessandro Chitolina for the patch -- Thanks to Juliette Reinders Folmer for the tests From 7364463b9c30b24a7793562500ba37df4a0a906f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 09:27:19 +0200 Subject: [PATCH 340/733] ErrorSuppressionTest::testSuppressError(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 293 +++++++++++----------------- 1 file changed, 115 insertions(+), 178 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index a11399d6c5..cf1dc11bcc 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -21,197 +21,134 @@ class ErrorSuppressionTest extends TestCase /** * Test suppressing a single error. * + * @param string $before Annotation to place before the code. + * @param string $after Annotation to place after the code. + * @param int $expectedErrors Optional. Number of errors expected. + * Defaults to 0. + * + * @dataProvider dataSuppressError + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testSuppressError() + public function testSuppressError($before, $after, $expectedErrors=0) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = ['Generic.PHP.LowerCaseConstant']; + static $config, $ruleset; - $ruleset = new Ruleset($config); - - // Process without suppression. - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = ['Generic.PHP.LowerCaseConstant']; - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + $ruleset = new Ruleset($config); + } - // Process with inline comment suppression. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with multi-line inline comment suppression, tab-indented. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with inline @ comment suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with inline comment suppression mixed case. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with inline hash comment suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with multi-line inline hash comment suppression, tab-indented. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with inline hash @ comment suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with inline hash comment suppression mixed case. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with inline comment suppression (deprecated syntax). - $content = 'process(); + $this->assertSame($expectedErrors, $file->getErrorCount()); + $this->assertCount($expectedErrors, $file->getErrors()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with block comment suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with multi-line block comment suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with multi-line block comment suppression, each line starred. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with multi-line block comment suppression, tab-indented. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with block comment suppression (deprecated syntax). - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with multi-line block comment suppression (deprecated syntax). - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with a docblock suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + }//end testSuppressError() - // Process with a docblock suppression (deprecated syntax). - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + /** + * Data provider. + * + * @see testSuppressError() + * + * @return array + */ + public function dataSuppressError() + { + return [ + 'no suppression' => [ + 'before' => '', + 'after' => '', + 'expectedErrors' => 1, + ], + + // Inline slash comments. + 'disable/enable: slash comment' => [ + 'before' => '// phpcs:disable'.PHP_EOL, + 'after' => '// phpcs:enable', + ], + 'disable/enable: multi-line slash comment, tab indented' => [ + 'before' => "\t".'// For reasons'.PHP_EOL."\t".'// phpcs:disable'.PHP_EOL."\t", + 'after' => "\t".'// phpcs:enable', + ], + 'disable/enable: slash comment, with @' => [ + 'before' => '// @phpcs:disable'.PHP_EOL, + 'after' => '// @phpcs:enable', + ], + 'disable/enable: slash comment, mixed case' => [ + 'before' => '// PHPCS:Disable'.PHP_EOL, + 'after' => '// pHPcs:enabLE', + ], + + // Inline hash comments. + 'disable/enable: hash comment' => [ + 'before' => '# phpcs:disable'.PHP_EOL, + 'after' => '# phpcs:enable', + ], + 'disable/enable: multi-line hash comment, tab indented' => [ + 'before' => "\t".'# For reasons'.PHP_EOL."\t".'# phpcs:disable'.PHP_EOL."\t", + 'after' => "\t".'# phpcs:enable', + ], + 'disable/enable: hash comment, with @' => [ + 'before' => '# @phpcs:disable'.PHP_EOL, + 'after' => '# @phpcs:enable', + ], + 'disable/enable: hash comment, mixed case' => [ + 'before' => '# PHPCS:Disable'.PHP_EOL, + 'after' => '# pHPcs:enabLE', + ], + + // Inline star (block) comments. + 'disable/enable: star comment' => [ + 'before' => '/* phpcs:disable */'.PHP_EOL, + 'after' => '/* phpcs:enable */', + ], + 'disable/enable: multi-line star comment' => [ + 'before' => '/*'.PHP_EOL.' phpcs:disable'.PHP_EOL.' */'.PHP_EOL, + 'after' => '/*'.PHP_EOL.' phpcs:enable'.PHP_EOL.' */', + ], + 'disable/enable: multi-line star comment, each line starred' => [ + 'before' => '/*'.PHP_EOL.' * phpcs:disable'.PHP_EOL.' */'.PHP_EOL, + 'after' => '/*'.PHP_EOL.' * phpcs:enable'.PHP_EOL.' */', + ], + 'disable/enable: multi-line star comment, each line starred, tab indented' => [ + 'before' => "\t".'/*'.PHP_EOL."\t".' * phpcs:disable'.PHP_EOL."\t".' */'.PHP_EOL."\t", + 'after' => "\t".'/*'.PHP_EOL.' * phpcs:enable'.PHP_EOL.' */', + ], + + // Docblock comments. + 'disable/enable: single line docblock comment' => [ + 'before' => '/** phpcs:disable */'.PHP_EOL, + 'after' => '/** phpcs:enable */', + ], + + // Deprecated syntax. + 'old style: slash comment' => [ + 'before' => '// @codingStandardsIgnoreStart'.PHP_EOL, + 'after' => '// @codingStandardsIgnoreEnd', + ], + 'old style: star comment' => [ + 'before' => '/* @codingStandardsIgnoreStart */'.PHP_EOL, + 'after' => '/* @codingStandardsIgnoreEnd */', + ], + 'old style: multi-line star comment' => [ + 'before' => '/*'.PHP_EOL.' @codingStandardsIgnoreStart'.PHP_EOL.' */'.PHP_EOL, + 'after' => '/*'.PHP_EOL.' @codingStandardsIgnoreEnd'.PHP_EOL.' */', + ], + 'old style: single line docblock comment' => [ + 'before' => '/** @codingStandardsIgnoreStart */'.PHP_EOL, + 'after' => '/** @codingStandardsIgnoreEnd */', + ], + ]; - }//end testSuppressError() + }//end dataSuppressError() /** From 0d315215bc5f773aa9d5113514419768d93bceee Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 10:24:25 +0200 Subject: [PATCH 341/733] ErrorSuppressionTest::testSuppressSomeErrors(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 149 ++++++++++++++-------------- 1 file changed, 73 insertions(+), 76 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index cf1dc11bcc..d31c9ff1f3 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -154,97 +154,94 @@ public function dataSuppressError() /** * Test suppressing 1 out of 2 errors. * + * @param string $before Annotation to place before the code. + * @param string $between Annotation to place between the code. + * @param int $expectedErrors Optional. Number of errors expected. + * Defaults to 1. + * + * @dataProvider dataSuppressSomeErrors + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testSuppressSomeErrors() + public function testSuppressSomeErrors($before, $between, $expectedErrors=1) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = ['Generic.PHP.LowerCaseConstant']; - - $ruleset = new Ruleset($config); - - // Process without suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(2, $numErrors); - $this->assertCount(2, $errors); - - // Process with suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with @ suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + static $config, $ruleset; - // Process with suppression (hash comment). - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = ['Generic.PHP.LowerCaseConstant']; - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + $ruleset = new Ruleset($config); + } - // Process with @ suppression (hash comment). - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with suppression (deprecated syntax). - $content = 'process(); + $this->assertSame($expectedErrors, $file->getErrorCount()); + $this->assertCount($expectedErrors, $file->getErrors()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + }//end testSuppressSomeErrors() - // Process with a PHPDoc block suppression. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + /** + * Data provider. + * + * @see testSuppressSomeErrors() + * + * @return array + */ + public function dataSuppressSomeErrors() + { + return [ + 'no suppression' => [ + 'before' => '', + 'between' => '', + 'expectedErrors' => 2, + ], - // Process with a PHPDoc block suppression (deprecated syntax). - $content = 'process(); + // With suppression. + 'disable/enable: slash comment' => [ + 'before' => '// phpcs:disable', + 'between' => '// phpcs:enable', + ], + 'disable/enable: slash comment, with @' => [ + 'before' => '// @phpcs:disable', + 'between' => '// @phpcs:enable', + ], + 'disable/enable: hash comment' => [ + 'before' => '# phpcs:disable', + 'between' => '# phpcs:enable', + ], + 'disable/enable: hash comment, with @' => [ + 'before' => '# @phpcs:disable', + 'between' => '# @phpcs:enable', + ], + 'disable/enable: single line docblock comment' => [ + 'before' => '/** phpcs:disable */', + 'between' => '/** phpcs:enable */', + ], - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + // Deprecated syntax. + 'old style: slash comment' => [ + 'before' => '// @codingStandardsIgnoreStart', + 'between' => '// @codingStandardsIgnoreEnd', + ], + 'old style: single line docblock comment' => [ + 'before' => '/** @codingStandardsIgnoreStart */', + 'between' => '/** @codingStandardsIgnoreEnd */', + ], + ]; - }//end testSuppressSomeErrors() + }//end dataSuppressSomeErrors() /** From 58e8734024ec47a050126fee57ed39e2329ca9e3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 10:35:01 +0200 Subject: [PATCH 342/733] ErrorSuppressionTest::testSuppressWarning(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 120 +++++++++++++++------------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index d31c9ff1f3..cb8fa4417d 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -247,77 +247,85 @@ public function dataSuppressSomeErrors() /** * Test suppressing a single warning. * + * @param string $before Annotation to place before the code. + * @param string $after Annotation to place after the code. + * @param int $expectedWarnings Optional. Number of warnings expected. + * Defaults to 0. + * + * @dataProvider dataSuppressWarning + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testSuppressWarning() + public function testSuppressWarning($before, $after, $expectedWarnings=0) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = ['Generic.Commenting.Todo']; - - $ruleset = new Ruleset($config); - - // Process without suppression. - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); + static $config, $ruleset; - // Process with suppression. - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = ['Generic.Commenting.Todo']; - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + $ruleset = new Ruleset($config); + } - // Process with @ suppression. - $content = 'process(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + $this->assertSame($expectedWarnings, $file->getWarningCount()); + $this->assertCount($expectedWarnings, $file->getWarnings()); - // Process with suppression (deprecated syntax). - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + }//end testSuppressWarning() - // Process with a docblock suppression. - $content = 'process(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + /** + * Data provider. + * + * @see testSuppressWarning() + * + * @return array + */ + public function dataSuppressWarning() + { + return [ + 'no suppression' => [ + 'before' => '', + 'after' => '', + 'expectedWarnings' => 1, + ], - // Process with a docblock suppression (deprecated syntax). - $content = 'process(); + // With suppression. + 'disable/enable: slash comment' => [ + 'before' => '// phpcs:disable', + 'after' => '// phpcs:enable', + ], + 'disable/enable: slash comment, with @' => [ + 'before' => '// @phpcs:disable', + 'after' => '// @phpcs:enable', + ], + 'disable/enable: single line docblock comment' => [ + 'before' => '/** phpcs:disable */', + 'after' => '/** phpcs:enable */', + ], - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + // Deprecated syntax. + 'old style: slash comment' => [ + 'before' => '// @codingStandardsIgnoreStart', + 'after' => '// @codingStandardsIgnoreEnd', + ], + 'old style: single line docblock comment' => [ + 'before' => '/** @codingStandardsIgnoreStart */', + 'after' => '/** @codingStandardsIgnoreEnd */', + ], + ]; - }//end testSuppressWarning() + }//end dataSuppressWarning() /** From 8f2ec28efe00d17649a78bb8c1d642042c3b5d04 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 10:49:13 +0200 Subject: [PATCH 343/733] ErrorSuppressionTest: move one test case out of `testSuppressLine()` ... as it uses a different code pattern, so doesn't fit with the other tests for the (upcoming) data provider. --- tests/Core/ErrorSuppressionTest.php | 44 ++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index cb8fa4417d..6420e19a09 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -424,16 +424,6 @@ public function testSuppressLine() $this->assertEquals(1, $numErrors); $this->assertCount(1, $errors); - // Process with @ suppression on line before inside docblock. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - // Process with suppression on same line. $content = 'standards = ['Generic']; + $config->sniffs = ['Generic.Files.LineLength']; + + $ruleset = new Ruleset($config); + + // Process with @ suppression on line before inside docblock. + $comment = str_repeat('a ', 50); + $content = <<process(); + + $this->assertSame(0, $file->getErrorCount()); + $this->assertCount(0, $file->getErrors()); + + }//end testSuppressLineWithinDocblock() + + /** * Test that using a single line ignore does not interfere with other suppressions. * From d465698f39c3f62a7ae717171b42cbe0f47ebefa Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 11:05:00 +0200 Subject: [PATCH 344/733] ErrorSuppressionTest::testSuppressLine(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 203 ++++++++++------------------ 1 file changed, 74 insertions(+), 129 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 6420e19a09..39c88d0022 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -331,150 +331,95 @@ public function dataSuppressWarning() /** * Test suppressing a single error using a single line ignore. * + * @param string $before Annotation to place before the code. + * @param string $after Optional. Annotation to place after the code. + * Defaults to an empty string. + * @param int $expectedErrors Optional. Number of errors expected. + * Defaults to 1. + * + * @dataProvider dataSuppressLine + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testSuppressLine() + public function testSuppressLine($before, $after='', $expectedErrors=1) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = [ - 'Generic.PHP.LowerCaseConstant', - 'Generic.Files.LineLength', - ]; - - $ruleset = new Ruleset($config); - - // Process without suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(2, $numErrors); - $this->assertCount(2, $errors); - - // Process with suppression on line before. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with @ suppression on line before. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with suppression on line before (hash comment). - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with @ suppression on line before (hash comment). - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with suppression on line before. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with @ suppression on line before. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with suppression on line before (deprecated syntax). - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + static $config, $ruleset; - // Process with suppression on same line. - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = ['Generic.PHP.LowerCaseConstant']; - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + $ruleset = new Ruleset($config); + } - // Process with @ suppression on same line. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with suppression on same line (hash comment). - $content = 'process(); + $this->assertSame($expectedErrors, $file->getErrorCount()); + $this->assertCount($expectedErrors, $file->getErrors()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + }//end testSuppressLine() - // Process with @ suppression on same line (hash comment). - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + /** + * Data provider. + * + * @see testSuppressLine() + * + * @return array + */ + public function dataSuppressLine() + { + return [ + 'no suppression' => [ + 'before' => '', + 'after' => '', + 'expectedErrors' => 2, + ], - // Process with suppression on same line (deprecated syntax). - $content = 'process(); + // With suppression on line before. + 'ignore: line before, slash comment' => ['before' => '// phpcs:ignore'], + 'ignore: line before, slash comment, with @' => ['before' => '// @phpcs:ignore'], + 'ignore: line before, hash comment' => ['before' => '# phpcs:ignore'], + 'ignore: line before, hash comment, with @' => ['before' => '# @phpcs:ignore'], + 'ignore: line before, star comment' => ['before' => '/* phpcs:ignore */'], + 'ignore: line before, star comment, with @' => ['before' => '/* @phpcs:ignore */'], + + // With suppression as trailing comment on code line. + 'ignore: end of line, slash comment' => [ + 'before' => '', + 'after' => ' // phpcs:ignore', + ], + 'ignore: end of line, slash comment, with @' => [ + 'before' => '', + 'after' => ' // @phpcs:ignore', + ], + 'ignore: end of line, hash comment' => [ + 'before' => '', + 'after' => ' # phpcs:ignore', + ], + 'ignore: end of line, hash comment, with @' => [ + 'before' => '', + 'after' => ' # @phpcs:ignore', + ], - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); + // Deprecated syntax. + 'old style: line before, slash comment' => ['before' => '// @codingStandardsIgnoreLine'], + 'old style: end of line, slash comment' => [ + 'before' => '', + 'after' => ' // @codingStandardsIgnoreLine', + ], + ]; - }//end testSuppressLine() + }//end dataSuppressLine() /** From c14b49d1be3a739d583b69f92c3cf044ced5cb75 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 8 May 2021 06:21:04 +0200 Subject: [PATCH 345/733] ErrorSuppressionTest: add test for midline ignore --- tests/Core/ErrorSuppressionTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 39c88d0022..a064fdab41 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -422,6 +422,31 @@ public function dataSuppressLine() }//end dataSuppressLine() + /** + * Test suppressing a single error using a single line ignore in the middle of a line. + * + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * + * @return void + */ + public function testSuppressLineMidLine() + { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = ['Generic.PHP.LowerCaseConstant']; + + $ruleset = new Ruleset($config); + + $content = 'process(); + + $this->assertSame(0, $file->getErrorCount()); + $this->assertCount(0, $file->getErrors()); + + }//end testSuppressLineMidLine() + + /** * Test suppressing a single error using a single line ignore within a docblock. * From f9dfb9b95f8c1c1fdb66cda99a29f1c220ad10b3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 13:57:55 +0200 Subject: [PATCH 346/733] ErrorSuppressionTest::testNestedSuppressLine(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 147 +++++++++++++--------------- 1 file changed, 70 insertions(+), 77 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index a064fdab41..7d392ce55d 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -484,97 +484,90 @@ public function testSuppressLineWithinDocblock() /** * Test that using a single line ignore does not interfere with other suppressions. * + * @param string $before Annotation to place before the code. + * @param string $after Annotation to place after the code. + * + * @dataProvider dataNestedSuppressLine + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testNestedSuppressLine() + public function testNestedSuppressLine($before, $after) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = ['Generic.PHP.LowerCaseConstant']; - - $ruleset = new Ruleset($config); - - // Process with disable/enable suppression and no single line suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with disable/enable @ suppression and no single line suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with disable/enable suppression and no single line suppression (hash comment). - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with disable/enable suppression and no single line suppression (deprecated syntax). - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + static $config, $ruleset; - // Process with line suppression nested within disable/enable suppression. - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = ['Generic.PHP.LowerCaseConstant']; - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + $ruleset = new Ruleset($config); + } - // Process with line @ suppression nested within disable/enable @ suppression. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + $this->assertSame(0, $file->getErrorCount()); + $this->assertCount(0, $file->getErrors()); - // Process with line @ suppression nested within disable/enable @ suppression (hash comment). - $content = 'process(); + }//end testNestedSuppressLine() - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - // Process with line suppression nested within disable/enable suppression (deprecated syntax). - $content = 'process(); + /** + * Data provider. + * + * @see testNestedSuppressLine() + * + * @return array + */ + public function dataNestedSuppressLine() + { + return [ + // Process with disable/enable suppression and no single line suppression. + 'disable/enable: slash comment, no single line suppression' => [ + 'before' => '// phpcs:disable', + 'after' => '// phpcs:enable', + ], + 'disable/enable: slash comment, with @, no single line suppression' => [ + 'before' => '// @phpcs:disable', + 'after' => '// @phpcs:enable', + ], + 'disable/enable: hash comment, no single line suppression' => [ + 'before' => '# phpcs:disable', + 'after' => '# phpcs:enable', + ], + 'old style: slash comment, no single line suppression' => [ + 'before' => '// @codingStandardsIgnoreStart', + 'after' => '// @codingStandardsIgnoreEnd', + ], - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + // Process with line suppression nested within disable/enable suppression. + 'disable/enable: slash comment, next line nested single line suppression' => [ + 'before' => '// phpcs:disable'.PHP_EOL.'// phpcs:ignore', + 'after' => '// phpcs:enable', + ], + 'disable/enable: slash comment, with @, next line nested single line suppression' => [ + 'before' => '// @phpcs:disable'.PHP_EOL.'// @phpcs:ignore', + 'after' => '// @phpcs:enable', + ], + 'disable/enable: hash comment, next line nested single line suppression' => [ + 'before' => '# @phpcs:disable'.PHP_EOL.'# @phpcs:ignore', + 'after' => '# @phpcs:enable', + ], + 'old style: slash comment, next line nested single line suppression' => [ + 'before' => '// @codingStandardsIgnoreStart'.PHP_EOL.'// @codingStandardsIgnoreLine', + 'after' => '// @codingStandardsIgnoreEnd', + ], + ]; - }//end testNestedSuppressLine() + }//end dataNestedSuppressLine() /** From 32ecc588e293a653617a96e673772419f5271466 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 14:17:32 +0200 Subject: [PATCH 347/733] ErrorSuppressionTest: fix bug in testSuppressScope() As the "no suppression" test would yield no errors, the test wasn't actually testing anything as there were no errors to suppress. Fixed now by using a different sniff to test against, which does yield an error on the line being suppressed by the rest of the tests. --- tests/Core/ErrorSuppressionTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 7d392ce55d..09616d3bc1 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -579,7 +579,7 @@ public function testSuppressScope() { $config = new Config(); $config->standards = ['PEAR']; - $config->sniffs = ['PEAR.NamingConventions.ValidVariableName']; + $config->sniffs = ['PEAR.Functions.FunctionDeclaration']; $ruleset = new Ruleset($config); @@ -590,8 +590,8 @@ public function testSuppressScope() $errors = $file->getErrors(); $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + $this->assertEquals(1, $numErrors); + $this->assertCount(1, $errors); // Process with suppression. $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; From 9165a9a8a061b9bf24a11e203095792a27dc77fa Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 14:32:44 +0200 Subject: [PATCH 348/733] ErrorSuppressionTest::testSuppressScope(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 150 ++++++++++++++-------------- 1 file changed, 77 insertions(+), 73 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 09616d3bc1..32201cfdfa 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -573,94 +573,98 @@ public function dataNestedSuppressLine() /** * Test suppressing a scope opener. * + * @param string $before Annotation to place before the scope opener. + * @param string $after Annotation to place after the scope opener. + * @param int $expectedErrors Optional. Number of errors expected. + * Defaults to 0. + * + * @dataProvider dataSuppressScope + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testSuppressScope() + public function testSuppressScope($before, $after, $expectedErrors=0) { - $config = new Config(); - $config->standards = ['PEAR']; - $config->sniffs = ['PEAR.Functions.FunctionDeclaration']; - - $ruleset = new Ruleset($config); - - // Process without suppression. - $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; - $file = new DummyFile($content, $ruleset, $config); - $file->process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - - // Process with suppression. - $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; - $file = new DummyFile($content, $ruleset, $config); - $file->process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with suppression (hash comment). - $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; - $file = new DummyFile($content, $ruleset, $config); - $file->process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + static $config, $ruleset; - // Process with suppression. - $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; - $file = new DummyFile($content, $ruleset, $config); - $file->process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['PEAR']; + $config->sniffs = ['PEAR.Functions.FunctionDeclaration']; - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + $ruleset = new Ruleset($config); + } - // Process with suppression (deprecated syntax). - $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; + $content = 'foo(); + } +} +EOD; $file = new DummyFile($content, $ruleset, $config); $file->process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - - // Process with a docblock suppression. - $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; - $file = new DummyFile($content, $ruleset, $config); + $this->assertSame($expectedErrors, $file->getErrorCount()); + $this->assertCount($expectedErrors, $file->getErrors()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + }//end testSuppressScope() - // Process with a docblock @ suppression. - $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; - $file = new DummyFile($content, $ruleset, $config); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + /** + * Data provider. + * + * @see testSuppressScope() + * + * @return array + */ + public function dataSuppressScope() + { + return [ + 'no suppression' => [ + 'before' => '', + 'after' => '', + 'expectedErrors' => 1, + ], - // Process with a docblock suppression (deprecated syntax). - $content = 'foo();'.PHP_EOL.'}'.PHP_EOL.'}'; - $file = new DummyFile($content, $ruleset, $config); + // Process with suppression. + 'disable/enable: slash comment' => [ + 'before' => '//phpcs:disable', + 'after' => '//phpcs:enable', + ], + 'disable/enable: slash comment, with @' => [ + 'before' => '//@phpcs:disable', + 'after' => '//@phpcs:enable', + ], + 'disable/enable: hash comment' => [ + 'before' => '#phpcs:disable', + 'after' => '#phpcs:enable', + ], + 'disable/enable: single line docblock comment' => [ + 'before' => '/** phpcs:disable */', + 'after' => '/** phpcs:enable */', + ], + 'disable/enable: single line docblock comment, with @' => [ + 'before' => '/** @phpcs:disable */', + 'after' => '/** @phpcs:enable */', + ], - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); + // Deprecated syntax. + 'old style: start/end, slash comment' => [ + 'before' => '//@codingStandardsIgnoreStart', + 'after' => '//@codingStandardsIgnoreEnd', + ], + 'old style: start/end, single line docblock comment' => [ + 'before' => '/** @codingStandardsIgnoreStart */', + 'after' => '/** @codingStandardsIgnoreEnd */', + ], + ]; - }//end testSuppressScope() + }//end dataSuppressScope() /** From e67a5df595630febb1409b59e312d5eb9cefda0f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 16:45:03 +0200 Subject: [PATCH 349/733] ErrorSuppressionTest::testSuppressFile(): fix bugs in test This fixes two bugs in two test cases for the `testSuppressFile()` method: 1. The code snippet as was, contained a parse error (missing semi-colon after class instantiation). 2. The code snippet did not contain anything which would trigger the warning the test is looking for in the first place, so these two tests would always pass. --- tests/Core/ErrorSuppressionTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 32201cfdfa..7f9c2d5290 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -751,7 +751,7 @@ public function testSuppressFile() $this->assertCount(0, $warnings); // Process late comment. - $content = 'process(); @@ -761,7 +761,7 @@ public function testSuppressFile() $this->assertCount(0, $warnings); // Process late comment (deprecated syntax). - $content = 'process(); From 99d83b1ad27e060c602d8ac1efccae03abbba742 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 17:15:05 +0200 Subject: [PATCH 350/733] ErrorSuppressionTest::testSuppressFile(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 217 ++++++++++------------------ 1 file changed, 73 insertions(+), 144 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 7f9c2d5290..3ecb4e1021 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -670,167 +670,96 @@ public function dataSuppressScope() /** * Test suppressing a whole file. * + * @param string $before Annotation to place before the code. + * @param string $after Optional. Annotation to place after the code. + * Defaults to an empty string. + * @param int $expectedWarnings Optional. Number of warnings expected. + * Defaults to 0. + * + * @dataProvider dataSuppressFile + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testSuppressFile() + public function testSuppressFile($before, $after='', $expectedWarnings=0) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = ['Generic.Commenting.Todo']; - - $ruleset = new Ruleset($config); - - // Process without suppression. - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Process with suppression. - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Process with @ suppression. - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Process with suppression (hash comment). - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Process with @ suppression (hash comment). - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Process with suppression (deprecated syntax). - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Process mixed case. - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Process late comment. - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Process late comment (deprecated syntax). - $content = 'process(); - - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + static $config, $ruleset; - // Process with a block comment suppression. - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = ['Generic.Commenting.Todo']; - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + $ruleset = new Ruleset($config); + } - // Process with a multi-line block comment suppression. - $content = 'process(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Process with a block comment suppression (deprecated syntax). - $content = 'process(); + $this->assertSame($expectedWarnings, $file->getWarningCount()); + $this->assertCount($expectedWarnings, $file->getWarnings()); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + }//end testSuppressFile() - // Process with a multi-line block comment suppression (deprecated syntax). - $content = 'process(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + /** + * Data provider. + * + * @see testSuppressFile() + * + * @return array + */ + public function dataSuppressFile() + { + return [ + 'no suppression' => [ + 'before' => '', + 'after' => '', + 'expectedErrors' => 1, + ], - // Process with docblock suppression. - $content = 'process(); + // Process with suppression. + 'ignoreFile: start of file, slash comment' => ['before' => '// phpcs:ignoreFile'], + 'ignoreFile: start of file, slash comment, with @' => ['before' => '// @phpcs:ignoreFile'], + 'ignoreFile: start of file, slash comment, mixed case' => ['before' => '// PHPCS:Ignorefile'], + 'ignoreFile: start of file, hash comment' => ['before' => '# phpcs:ignoreFile'], + 'ignoreFile: start of file, hash comment, with @' => ['before' => '# @phpcs:ignoreFile'], + 'ignoreFile: start of file, single-line star comment' => ['before' => '/* phpcs:ignoreFile */'], + 'ignoreFile: start of file, multi-line star comment' => [ + 'before' => '/*'.PHP_EOL.' phpcs:ignoreFile'.PHP_EOL.' */', + ], + 'ignoreFile: start of file, single-line docblock comment' => ['before' => '/** phpcs:ignoreFile */'], - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + // Process late comment. + 'ignoreFile: late comment, slash comment' => [ + 'before' => '', + 'after' => '// phpcs:ignoreFile', + ], - // Process with docblock suppression (deprecated syntax). - $content = 'process(); + // Deprecated syntax. + 'old style: start of file, slash comment' => ['before' => '// @codingStandardsIgnoreFile'], + 'old style: start of file, single-line star comment' => ['before' => '/* @codingStandardsIgnoreFile */'], + 'old style: start of file, multi-line star comment' => [ + 'before' => '/*'.PHP_EOL.' @codingStandardsIgnoreFile'.PHP_EOL.' */', + ], + 'old style: start of file, single-line docblock comment' => ['before' => '/** @codingStandardsIgnoreFile */'], - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + // Deprecated syntax, late comment. + 'old style: late comment, slash comment' => [ + 'before' => '', + 'after' => '// @codingStandardsIgnoreFile', + ], + ]; - }//end testSuppressFile() + }//end dataSuppressFile() /** From b1e74cc102ed330581efd236d8cb0429853ee96d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 18:38:25 +0200 Subject: [PATCH 351/733] ErrorSuppressionTest::testDisableSelected(): refactor to data provider **Note**: the "docblock" tests have changed to use the same basic code sample as the other tests. In practice this means that instead of having 0 errors and 0/1 warnings, they will now yield 1 error and 0/1 warnings. Functionally these tests still test the same principle. * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 221 +++++++++++----------------- 1 file changed, 82 insertions(+), 139 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 3ecb4e1021..e4151a5a7a 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -765,162 +765,105 @@ public function dataSuppressFile() /** * Test disabling specific sniffs. * + * @param string $before Annotation to place before the code. + * @param int $expectedErrors Optional. Number of errors expected. + * Defaults to 0. + * @param int $expectedWarnings Optional. Number of warnings expected. + * Defaults to 0. + * + * @dataProvider dataDisableSelected + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testDisableSelected() + public function testDisableSelected($before, $expectedErrors=0, $expectedWarnings=0) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = [ - 'Generic.PHP.LowerCaseConstant', - 'Generic.Commenting.Todo', - ]; - - $ruleset = new Ruleset($config); - - // Suppress a single sniff. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Suppress a single sniff with reason (hash comment). - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Suppress multiple sniffs. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Suppress adding sniffs. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + static $config, $ruleset; - // Suppress a category of sniffs. - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = [ + 'Generic.PHP.LowerCaseConstant', + 'Generic.Commenting.Todo', + ]; - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + $ruleset = new Ruleset($config); + } - // Suppress a whole standard. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Suppress using docblocks. - $content = 'process(); + $this->assertSame($expectedErrors, $file->getErrorCount()); + $this->assertCount($expectedErrors, $file->getErrors()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + $this->assertSame($expectedWarnings, $file->getWarningCount()); + $this->assertCount($expectedWarnings, $file->getWarnings()); - $content = 'process(); + }//end testDisableSelected() - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - // Suppress wrong category using docblocks. - $content = 'process(); + /** + * Data provider. + * + * @see testDisableSelected() + * + * @return array + */ + public function dataDisableSelected() + { + return [ + // Single sniff. + 'disable: single sniff' => [ + 'before' => '// phpcs:disable Generic.Commenting.Todo', + 'expectedErrors' => 1, + ], + 'disable: single sniff with reason' => [ + 'before' => '# phpcs:disable Generic.Commenting.Todo -- for reasons', + 'expectedErrors' => 1, + ], + 'disable: single sniff, docblock' => [ + 'before' => '/**'.PHP_EOL.' * phpcs:disable Generic.Commenting.Todo'.PHP_EOL.' */ ', + 'expectedErrors' => 1, + ], + 'disable: single sniff, docblock, with @' => [ + 'before' => '/**'.PHP_EOL.' * @phpcs:disable Generic.Commenting.Todo'.PHP_EOL.' */ ', + 'expectedErrors' => 1, + ], - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); + // Multiple sniffs. + 'disable: multiple sniffs in one comment' => ['before' => '// phpcs:disable Generic.Commenting.Todo,Generic.PHP.LowerCaseConstant'], + 'disable: multiple sniff in multiple comments' => [ + 'before' => '// phpcs:disable Generic.Commenting.Todo'.PHP_EOL.'// phpcs:disable Generic.PHP.LowerCaseConstant', + ], - $content = 'process(); + // Selectiveness variations. + 'disable: complete category' => [ + 'before' => '// phpcs:disable Generic.Commenting', + 'expectedErrors' => 1, + ], + 'disable: whole standard' => ['before' => '// phpcs:disable Generic'], - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); + // Wrong category using docblocks. + 'disable: wrong category, docblock' => [ + 'before' => '/**'.PHP_EOL.' * phpcs:disable Generic.Files'.PHP_EOL.' */ ', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + 'disable: wrong category, docblock, with @' => [ + 'before' => '/**'.PHP_EOL.' * @phpcs:disable Generic.Files'.PHP_EOL.' */ ', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + ]; - }//end testDisableSelected() + }//end dataDisableSelected() /** From 888479a405feb6c8259d4b39820b7590a9f6b63d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 May 2021 19:49:13 +0200 Subject: [PATCH 352/733] ErrorSuppressionTest::testDisableSelected(): add some more tests By the looks of it, combining disabling at different levels and disabling error codes wasn't covered in the tests. --- tests/Core/ErrorSuppressionTest.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index e4151a5a7a..ec2c6e20e6 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -849,8 +849,18 @@ public function dataDisableSelected() 'expectedErrors' => 1, ], 'disable: whole standard' => ['before' => '// phpcs:disable Generic'], + 'disable: single errorcode' => [ + 'before' => '# @phpcs:disable Generic.Commenting.Todo.TaskFound', + 'expectedErrors' => 1, + ], + 'disable: single errorcode and a category' => ['before' => '// phpcs:disable Generic.PHP.LowerCaseConstant.Found,Generic.Commenting'], - // Wrong category using docblocks. + // Wrong category/sniff/code. + 'disable: wrong error code and category' => [ + 'before' => '/**'.PHP_EOL.' * phpcs:disable Generic.PHP.LowerCaseConstant.Upper,Generic.Comments'.PHP_EOL.' */ ', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], 'disable: wrong category, docblock' => [ 'before' => '/**'.PHP_EOL.' * phpcs:disable Generic.Files'.PHP_EOL.' */ ', 'expectedErrors' => 1, From 68c1867a5e21614aed261afabeeff404bec807c4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 8 May 2021 17:22:46 +0200 Subject: [PATCH 353/733] ErrorSuppressionTest::testEnableSelected(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 310 ++++++++++++++-------------- 1 file changed, 156 insertions(+), 154 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index ec2c6e20e6..320f93ad7a 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -879,174 +879,176 @@ public function dataDisableSelected() /** * Test re-enabling specific sniffs that have been disabled. * + * @param string $code Code pattern to check. + * @param int $expectedErrors Number of errors expected. + * @param int $expectedWarnings Number of warnings expected. + * + * @dataProvider dataEnableSelected + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testEnableSelected() + public function testEnableSelected($code, $expectedErrors, $expectedWarnings) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = [ - 'Generic.PHP.LowerCaseConstant', - 'Generic.Commenting.Todo', - ]; - - $ruleset = new Ruleset($config); - - // Suppress a single sniff and re-enable. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Suppress multiple sniffs and re-enable. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Suppress multiple sniffs and re-enable one. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Suppress a category of sniffs and re-enable. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Suppress a whole standard and re-enable. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Suppress a whole standard and re-enable a category. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Suppress a category and re-enable a whole standard. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); + static $config, $ruleset; - // Suppress a sniff and re-enable a category. - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = [ + 'Generic.PHP.LowerCaseConstant', + 'Generic.Commenting.Todo', + ]; - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); + $ruleset = new Ruleset($config); + } - // Suppress a whole standard and re-enable a sniff. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); + $this->assertSame($expectedErrors, $file->getErrorCount()); + $this->assertCount($expectedErrors, $file->getErrors()); - // Suppress a whole standard and re-enable and re-disable a sniff. - $content = 'process(); + $this->assertSame($expectedWarnings, $file->getWarningCount()); + $this->assertCount($expectedWarnings, $file->getWarnings()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(2, $numWarnings); - $this->assertCount(2, $warnings); + }//end testEnableSelected() - // Suppress a whole standard and re-enable 2 specific sniffs independently. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(2, $numWarnings); - $this->assertCount(2, $warnings); + /** + * Data provider. + * + * @see testEnableSelected() + * + * @return array + */ + public function dataEnableSelected() + { + return [ + 'disable/enable: a single sniff' => [ + 'code' => ' + // phpcs:disable Generic.Commenting.Todo + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting.Todo + //TODO: write some code', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + 'disable/enable: multiple sniffs' => [ + 'code' => ' + // phpcs:disable Generic.Commenting.Todo,Generic.PHP.LowerCaseConstant + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting.Todo,Generic.PHP.LowerCaseConstant + //TODO: write some code + $var = FALSE;', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + 'disable: multiple sniffs; enable: one' => [ + 'code' => ' + # phpcs:disable Generic.Commenting.Todo,Generic.PHP.LowerCaseConstant + $var = FALSE; + //TODO: write some code + # phpcs:enable Generic.Commenting.Todo + //TODO: write some code + $var = FALSE;', + 'expectedErrors' => 0, + 'expectedWarnings' => 1, + ], + 'disable/enable: complete category' => [ + 'code' => ' + // phpcs:disable Generic.Commenting + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting + //TODO: write some code', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + 'disable/enable: whole standard' => [ + 'code' => ' + // phpcs:disable Generic + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic + //TODO: write some code', + 'expectedErrors' => 0, + 'expectedWarnings' => 1, + ], + 'disable: whole standard; enable: category from the standard' => [ + 'code' => ' + // phpcs:disable Generic + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting + //TODO: write some code', + 'expectedErrors' => 0, + 'expectedWarnings' => 1, + ], + 'disable: a category; enable: the whole standard containing the category' => [ + 'code' => ' + # phpcs:disable Generic.Commenting + $var = FALSE; + //TODO: write some code + # phpcs:enable Generic + //TODO: write some code', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + 'disable: single sniff; enable: the category containing the sniff' => [ + 'code' => ' + // phpcs:disable Generic.Commenting.Todo + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting + //TODO: write some code', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + 'disable: whole standard; enable: single sniff from the standard' => [ + 'code' => ' + // phpcs:disable Generic + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting.Todo + //TODO: write some code', + 'expectedErrors' => 0, + 'expectedWarnings' => 1, + ], + 'disable: whole standard; enable: single sniff from the standard; disable: that same sniff; enable: everything' => [ + 'code' => ' + // phpcs:disable Generic + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting.Todo + //TODO: write some code + // phpcs:disable Generic.Commenting.Todo + //TODO: write some code + // phpcs:enable + //TODO: write some code', + 'expectedErrors' => 0, + 'expectedWarnings' => 2, + ], + 'disable: whole standard; enable: single sniff from the standard; enable: other sniff from the standard' => [ + 'code' => ' + // phpcs:disable Generic + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting.Todo + //TODO: write some code + $var = FALSE; + // phpcs:enable Generic.PHP.LowerCaseConstant + //TODO: write some code + $var = FALSE;', + 'expectedErrors' => 1, + 'expectedWarnings' => 2, + ], + ]; - }//end testEnableSelected() + }//end dataEnableSelected() /** From 46f010011115d177a9b6197ba5d424111d5a02b6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 8 May 2021 17:41:07 +0200 Subject: [PATCH 354/733] ErrorSuppressionTest::testIgnoreSelected(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 155 +++++++++++++--------------- 1 file changed, 72 insertions(+), 83 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 320f93ad7a..b45f173a6f 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -1054,104 +1054,93 @@ public function dataEnableSelected() /** * Test ignoring specific sniffs. * + * @param string $before Annotation to place before the code. + * @param int $expectedErrors Number of errors expected. + * @param int $expectedWarnings Number of warnings expected. + * + * @dataProvider dataIgnoreSelected + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testIgnoreSelected() + public function testIgnoreSelected($before, $expectedErrors, $expectedWarnings) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = [ - 'Generic.PHP.LowerCaseConstant', - 'Generic.Commenting.Todo', - ]; - - $ruleset = new Ruleset($config); - - // No suppression. - $content = 'process(); - - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(2, $numErrors); - $this->assertCount(2, $errors); - $this->assertEquals(2, $numWarnings); - $this->assertCount(2, $warnings); + static $config, $ruleset; - // Suppress a single sniff. - $content = 'process(); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = [ + 'Generic.PHP.LowerCaseConstant', + 'Generic.Commenting.Todo', + ]; - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(2, $numErrors); - $this->assertCount(2, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); + $ruleset = new Ruleset($config); + } - // Suppress multiple sniffs. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Add to suppression. - $content = 'process(); + $this->assertSame($expectedErrors, $file->getErrorCount()); + $this->assertCount($expectedErrors, $file->getErrors()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); + $this->assertSame($expectedWarnings, $file->getWarningCount()); + $this->assertCount($expectedWarnings, $file->getWarnings()); - // Suppress a category of sniffs. - $content = 'process(); + }//end testIgnoreSelected() - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(2, $numErrors); - $this->assertCount(2, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - // Suppress a whole standard. - $content = 'process(); + /** + * Data provider. + * + * @see testIgnoreSelected() + * + * @return array + */ + public function dataIgnoreSelected() + { + return [ + 'no suppression' => [ + 'before' => '', + 'expectedErrors' => 2, + 'expectedWarnings' => 2, + ], - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); + // With suppression. + 'ignore: single sniff' => [ + 'before' => '// phpcs:ignore Generic.Commenting.Todo', + 'expectedErrors' => 2, + 'expectedWarnings' => 1, + ], + 'ignore: multiple sniffs' => [ + 'before' => '// phpcs:ignore Generic.Commenting.Todo,Generic.PHP.LowerCaseConstant', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + 'disable: single sniff; ignore: single sniff' => [ + 'before' => '// phpcs:disable Generic.Commenting.Todo'.PHP_EOL.'// phpcs:ignore Generic.PHP.LowerCaseConstant', + 'expectedErrors' => 1, + 'expectedWarnings' => 0, + ], + 'ignore: category of sniffs' => [ + 'before' => '# phpcs:ignore Generic.Commenting', + 'expectedErrors' => 2, + 'expectedWarnings' => 1, + ], + 'ignore: whole standard' => [ + 'before' => '// phpcs:ignore Generic', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + ]; - }//end testIgnoreSelected() + }//end dataIgnoreSelected() /** From 41c16010707cb9fcc76ab2ae49e502a5a6bb438b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 8 May 2021 21:59:49 +0200 Subject: [PATCH 355/733] ErrorSuppressionTest::testCommenting(): refactor to data provider * Maintains the exact same existing tests, now using a data provider. The data provider uses keys which allows for much more descriptive output when using the PHPUnit `--testdox` option, as well as for easier debugging if/when a test would fail. * Orders the tests in logical groups in the data provider. * Switches out `assertEquals()` (loose type comparison) for `assertSame()` (strict type comparison). * Caches the `$config` and `$ruleset` for the test via static local variables to prevent the test run from becoming slower due to the change to the data provider. --- tests/Core/ErrorSuppressionTest.php | 154 +++++++++++++++------------- 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index b45f173a6f..7181613c62 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -1146,90 +1146,102 @@ public function dataIgnoreSelected() /** * Test ignoring specific sniffs. * + * @param string $code Code pattern to check. + * @param int $expectedErrors Number of errors expected. + * @param int $expectedWarnings Number of warnings expected. + * + * @dataProvider dataCommenting + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + * * @return void */ - public function testCommenting() + public function testCommenting($code, $expectedErrors, $expectedWarnings) { - $config = new Config(); - $config->standards = ['Generic']; - $config->sniffs = [ - 'Generic.PHP.LowerCaseConstant', - 'Generic.Commenting.Todo', - ]; + static $config, $ruleset; - $ruleset = new Ruleset($config); + if (isset($config, $ruleset) === false) { + $config = new Config(); + $config->standards = ['Generic']; + $config->sniffs = [ + 'Generic.PHP.LowerCaseConstant', + 'Generic.Commenting.Todo', + ]; - // Suppress a single sniff. - $content = 'process(); + $ruleset = new Ruleset($config); + } - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(2, $numErrors); - $this->assertCount(2, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Suppress a single sniff and re-enable. - $content = 'process(); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Suppress a single sniff using block comments. - $content = 'process(); + $this->assertSame($expectedErrors, $file->getErrorCount()); + $this->assertCount($expectedErrors, $file->getErrors()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(1, $numErrors); - $this->assertCount(1, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - - // Suppress a single sniff with a multi-line comment. - $content = 'process(); + $this->assertSame($expectedWarnings, $file->getWarningCount()); + $this->assertCount($expectedWarnings, $file->getWarnings()); - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(2, $numErrors); - $this->assertCount(2, $errors); - $this->assertEquals(1, $numWarnings); - $this->assertCount(1, $warnings); - - // Ignore an enable before a disable. - $content = 'process(); + }//end testCommenting() - $errors = $file->getErrors(); - $numErrors = $file->getErrorCount(); - $warnings = $file->getWarnings(); - $numWarnings = $file->getWarningCount(); - $this->assertEquals(0, $numErrors); - $this->assertCount(0, $errors); - $this->assertEquals(0, $numWarnings); - $this->assertCount(0, $warnings); - }//end testCommenting() + /** + * Data provider. + * + * @see testCommenting() + * + * @return array + */ + public function dataCommenting() + { + return [ + 'ignore: single sniff' => [ + 'code' => ' + // phpcs:ignore Generic.Commenting.Todo -- Because reasons + $var = FALSE; //TODO: write some code + $var = FALSE; //TODO: write some code', + 'expectedErrors' => 2, + 'expectedWarnings' => 1, + ], + 'disable: single sniff; enable: same sniff - test whitespace handling around reason delimiter' => [ + 'code' => ' + // phpcs:disable Generic.Commenting.Todo --Because reasons + $var = FALSE; + //TODO: write some code + // phpcs:enable Generic.Commenting.Todo -- Because reasons + //TODO: write some code', + 'expectedErrors' => 1, + 'expectedWarnings' => 1, + ], + 'disable: single sniff, multi-line comment' => [ + 'code' => ' + /* + Disable some checks + phpcs:disable Generic.Commenting.Todo + */ + $var = FALSE; + //TODO: write some code', + 'expectedErrors' => 1, + 'expectedWarnings' => 0, + ], + 'ignore: single sniff, multi-line slash comment' => [ + 'code' => ' + // Turn off a check for the next line of code. + // phpcs:ignore Generic.Commenting.Todo + $var = FALSE; //TODO: write some code + $var = FALSE; //TODO: write some code', + 'expectedErrors' => 2, + 'expectedWarnings' => 1, + ], + 'enable before disable, sniff not in standard' => [ + 'code' => ' + // phpcs:enable Generic.PHP.NoSilencedErrors -- Because reasons + $var = @delete( $filename ); + ', + 'expectedErrors' => 0, + 'expectedWarnings' => 0, + ], + ]; + + }//end dataCommenting() }//end class From 848f45afe2673664296e0e87343373ee7dbb4858 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 10 May 2021 01:20:57 +0200 Subject: [PATCH 356/733] NamedFunctionCallArgumentsTest: minor fixes The error message would display the token code, which is not helfpul for a human, so changing it to the token `type`. --- tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php index 13be10e5f6..52845ac72c 100644 --- a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php +++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php @@ -38,7 +38,7 @@ public function testNamedFunctionCallArguments($testMarker, $parameters) $this->assertSame( T_PARAM_NAME, $tokens[$label]['code'], - 'Token tokenized as '.$tokens[$label]['code'].', not T_PARAM_NAME (code)' + 'Token tokenized as '.$tokens[$label]['type'].', not T_PARAM_NAME (code)' ); $this->assertSame( 'T_PARAM_NAME', @@ -278,7 +278,7 @@ public function testOtherTstringInFunctionCall($testMarker, $content) $this->assertSame( T_STRING, $tokens[$label]['code'], - 'Token tokenized as '.$tokens[$label]['code'].', not T_STRING (code)' + 'Token tokenized as '.$tokens[$label]['type'].', not T_STRING (code)' ); $this->assertSame( 'T_STRING', @@ -709,7 +709,7 @@ public function testReservedKeywordsAsName($testMarker, $tokenTypes, $tokenConte $this->assertSame( T_PARAM_NAME, $tokens[$label]['code'], - 'Token tokenized as '.$tokens[$label]['code'].', not T_PARAM_NAME (code)' + 'Token tokenized as '.$tokens[$label]['type'].', not T_PARAM_NAME (code)' ); $this->assertSame( 'T_PARAM_NAME', From 39c44103b4e02855462bb93e0f43940670677e03 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 10 May 2021 02:12:45 +0200 Subject: [PATCH 357/733] Tokenizer/PHP: bug fix Interface and trait names should always be `T_STRING`. --- src/Tokenizers/PHP.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 60c89c4507..978e5e6e00 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1899,6 +1899,8 @@ function return types. We want to keep the parenthesis map clean, T_NULLSAFE_OBJECT_OPERATOR => true, T_FUNCTION => true, T_CLASS => true, + T_INTERFACE => true, + T_TRAIT => true, T_EXTENDS => true, T_IMPLEMENTS => true, T_ATTRIBUTE => true, From 596bdf93aafd2437b79fadb7554a8e78a0bc80c1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 10 May 2021 11:10:38 +0200 Subject: [PATCH 358/733] PHP 8.0 | PEAR/FunctionDeclaration: fix handling of constructor property promotion Adjusts the `PEAR.Functions.FunctionDeclaration` sniff and by extension, the `Squiz.Functions.MultiLineFunctionDeclaration` and the `PSR12.Classes.AnonClassDeclaration` sniffs to handle constructor properties preceded by docblocks correctly. There is one functional change: The sniffs will no longer throw a `FirstParamSpacing` error when there are blank lines between the function open parenthesis and the first parameter. As the sniffs still throw a `EmptyLine` error in that case - and will auto-fix that error - I've deemed that acceptable as the "fixed" file will still be the same. Fixes 3342 --- .../Functions/FunctionDeclarationSniff.php | 12 ++-- .../Functions/FunctionDeclarationUnitTest.inc | 58 +++++++++++++++++++ .../FunctionDeclarationUnitTest.inc.fixed | 58 +++++++++++++++++++ .../Functions/FunctionDeclarationUnitTest.php | 15 +++++ .../Classes/AnonClassDeclarationUnitTest.php | 2 +- .../MultiLineFunctionDeclarationSniff.php | 2 +- .../MultiLineFunctionDeclarationUnitTest.inc | 58 +++++++++++++++++++ ...iLineFunctionDeclarationUnitTest.inc.fixed | 58 +++++++++++++++++++ .../MultiLineFunctionDeclarationUnitTest.php | 16 ++++- 9 files changed, 272 insertions(+), 7 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php index 9843629454..1924f9c4df 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php @@ -456,9 +456,10 @@ public function processArgumentList($phpcsFile, $stackPtr, $indent, $type='funct } // We changed lines, so this should be a whitespace indent token. - if ($tokens[$i]['code'] !== T_WHITESPACE) { - $foundIndent = 0; - } else if ($tokens[$i]['line'] !== $tokens[($i + 1)]['line']) { + $foundIndent = 0; + if ($tokens[$i]['code'] === T_WHITESPACE + && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] + ) { // This is an empty line, so don't check the indent. $foundIndent = $expectedIndent; @@ -467,8 +468,11 @@ public function processArgumentList($phpcsFile, $stackPtr, $indent, $type='funct if ($fix === true) { $phpcsFile->fixer->replaceToken($i, ''); } - } else { + } else if ($tokens[$i]['code'] === T_WHITESPACE) { + $foundIndent = $tokens[$i]['length']; + } else if ($tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE) { $foundIndent = $tokens[$i]['length']; + ++$expectedIndent; } if ($expectedIndent !== $foundIndent) { diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc index 7af7200017..0003ca0bda 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc @@ -314,3 +314,61 @@ if(true) { ; } } + +class ConstructorPropertyPromotionSingleLineDocblockIndentOK +{ + public function __construct( + /** @var string */ + public string $public, + /** @var string */ + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIndentOK +{ + public function __construct( + /** + * @var string + * @Assert\NotBlank() + */ + public string $public, + /** + * @var string + * @Assert\NotBlank() + */ + #[NotBlank] + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionSingleLineDocblockIncorrectIndent +{ + public function __construct( + /** @var string */ + public string $public, + /** @var string */ + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIncorrectIndent +{ + public function __construct( + /** + * @var string + * @Assert\NotBlank() + */ + public string $public, +/** + * @var string + * @Assert\NotBlank() + */ +#[NotBlank] +private string $private, + ) { + } +} diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed index b993e3f079..0f8d39db06 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed @@ -312,3 +312,61 @@ if(true) { abstract function baz(); } } + +class ConstructorPropertyPromotionSingleLineDocblockIndentOK +{ + public function __construct( + /** @var string */ + public string $public, + /** @var string */ + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIndentOK +{ + public function __construct( + /** + * @var string + * @Assert\NotBlank() + */ + public string $public, + /** + * @var string + * @Assert\NotBlank() + */ + #[NotBlank] + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionSingleLineDocblockIncorrectIndent +{ + public function __construct( + /** @var string */ + public string $public, + /** @var string */ + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIncorrectIndent +{ + public function __construct( + /** + * @var string + * @Assert\NotBlank() + */ + public string $public, + /** + * @var string + * @Assert\NotBlank() + */ + #[NotBlank] + private string $private, + ) { + } +} diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php index 7a8a53a1c3..161d2b34f5 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php @@ -82,6 +82,21 @@ public function getErrorList($testFile='FunctionDeclarationUnitTest.inc') 309 => 1, 313 => 1, 314 => 1, + 350 => 1, + 351 => 1, + 352 => 1, + 353 => 1, + 361 => 1, + 362 => 1, + 363 => 1, + 364 => 1, + 365 => 1, + 366 => 1, + 367 => 1, + 368 => 1, + 369 => 1, + 370 => 1, + 371 => 1, ]; } else { $errors = [ diff --git a/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.php b/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.php index 3f65602e6e..cc162b290b 100644 --- a/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.php +++ b/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.php @@ -32,7 +32,7 @@ public function getErrorList() 32 => 1, 33 => 1, 34 => 1, - 35 => 2, + 35 => 1, 36 => 1, 37 => 3, 39 => 1, diff --git a/src/Standards/Squiz/Sniffs/Functions/MultiLineFunctionDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Functions/MultiLineFunctionDeclarationSniff.php index a4ea79cfbd..c12bd0c47c 100644 --- a/src/Standards/Squiz/Sniffs/Functions/MultiLineFunctionDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Functions/MultiLineFunctionDeclarationSniff.php @@ -200,7 +200,7 @@ public function processBracket($phpcsFile, $openBracket, $tokens, $type='functio // The open bracket should be the last thing on the line. if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) { $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($openBracket + 1), null, true); - if ($tokens[$next]['line'] !== ($tokens[$openBracket]['line'] + 1)) { + if ($tokens[$next]['line'] === $tokens[$openBracket]['line']) { $error = 'The first parameter of a multi-line '.$type.' declaration must be on the line after the opening bracket'; $fix = $phpcsFile->addFixableError($error, $next, $errorPrefix.'FirstParamSpacing'); if ($fix === true) { diff --git a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc index 3076104a2b..fce0b237ba 100644 --- a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc +++ b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc @@ -197,3 +197,61 @@ function foo( $bar ) { } + +class ConstructorPropertyPromotionSingleLineDocblockIndentOK +{ + public function __construct( + /** @var string */ + public string $public, + /** @var string */ + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIndentOK +{ + public function __construct( + /** + * @var string + * @Assert\NotBlank() + */ + public string $public, + /** + * @var string + * @Assert\NotBlank() + */ + #[NotBlank] + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionSingleLineDocblockIncorrectIndent +{ + public function __construct( + /** @var string */ + public string $public, + /** @var string */ + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIncorrectIndent +{ + public function __construct( + /** + * @var string + * @Assert\NotBlank() + */ + public string $public, +/** + * @var string + * @Assert\NotBlank() + */ +#[NotBlank] +private string $private, + ) { + } +} diff --git a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc.fixed index 4965f237c0..b927a001b3 100644 --- a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc.fixed @@ -209,3 +209,61 @@ function foo( $bar ) { } + +class ConstructorPropertyPromotionSingleLineDocblockIndentOK +{ + public function __construct( + /** @var string */ + public string $public, + /** @var string */ + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIndentOK +{ + public function __construct( + /** + * @var string + * @Assert\NotBlank() + */ + public string $public, + /** + * @var string + * @Assert\NotBlank() + */ + #[NotBlank] + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionSingleLineDocblockIncorrectIndent +{ + public function __construct( + /** @var string */ + public string $public, + /** @var string */ + private string $private, + ) { + } +} + +class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIncorrectIndent +{ + public function __construct( + /** + * @var string + * @Assert\NotBlank() + */ + public string $public, + /** + * @var string + * @Assert\NotBlank() + */ + #[NotBlank] + private string $private, + ) { + } +} diff --git a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.php b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.php index caf14d1de1..5208ad0cdb 100644 --- a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.php +++ b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.php @@ -55,7 +55,21 @@ public function getErrorList($testFile='MultiLineFunctionDeclarationUnitTest.inc 190 => 2, 194 => 1, 195 => 1, - 196 => 1, + 233 => 1, + 234 => 1, + 235 => 1, + 236 => 1, + 244 => 1, + 245 => 1, + 246 => 1, + 247 => 1, + 248 => 1, + 249 => 1, + 250 => 1, + 251 => 1, + 252 => 1, + 253 => 1, + 254 => 1, ]; } else { $errors = [ From 59e161a750a723a9e977814d84ccc64747248d6e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 May 2021 21:42:27 +0200 Subject: [PATCH 359/733] Generic/InlineControlStructure: bug fix for try/catch/finally Multi-catch and `finally` statements were not taken into account when determining the end of the statement within an inline control structure. Fixed now. Includes unit tests. Fixes 3345 --- .../InlineControlStructureSniff.php | 20 ++++++++++++------ .../InlineControlStructureUnitTest.1.inc | 19 +++++++++++++++++ ...InlineControlStructureUnitTest.1.inc.fixed | 21 +++++++++++++++++++ .../InlineControlStructureUnitTest.php | 2 ++ 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php b/src/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php index 19a58833be..a67a6a798c 100644 --- a/src/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php +++ b/src/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php @@ -210,7 +210,10 @@ public function process(File $phpcsFile, $stackPtr) if (isset($tokens[$end]['scope_opener']) === true) { $type = $tokens[$end]['code']; $end = $tokens[$end]['scope_closer']; - if ($type === T_DO || $type === T_IF || $type === T_ELSEIF || $type === T_TRY) { + if ($type === T_DO + || $type === T_IF || $type === T_ELSEIF + || $type === T_TRY || $type === T_CATCH || $type === T_FINALLY + ) { $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true); if ($next === false) { break; @@ -227,15 +230,20 @@ public function process(File $phpcsFile, $stackPtr) continue; } + // Account for TRY... CATCH/FINALLY statements. + if (($type === T_TRY + || $type === T_CATCH + || $type === T_FINALLY) + && ($nextType === T_CATCH + || $nextType === T_FINALLY) + ) { + continue; + } + // Account for DO... WHILE conditions. if ($type === T_DO && $nextType === T_WHILE) { $end = $phpcsFile->findNext(T_SEMICOLON, ($next + 1)); } - - // Account for TRY... CATCH statements. - if ($type === T_TRY && $nextType === T_CATCH) { - $end = $tokens[$next]['scope_closer']; - } } else if ($type === T_CLOSURE) { // There should be a semicolon after the closing brace. $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true); diff --git a/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc b/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc index 1a6ab6900a..fb4a7380fb 100644 --- a/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc +++ b/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc @@ -253,3 +253,22 @@ for ($i = 1, $j = 0; $i <= 10; $j += $i, print $i, $i++); if ($this->valid(fn(): bool => 2 > 1)) { } + +// Issue 3345. +function testMultiCatch() +{ + if (true) + try { + } catch (\LogicException $e) { + } catch (\Exception $e) { + } +} + +function testFinally() +{ + if (true) + try { + } catch (\LogicException $e) { + } finally { + } +} diff --git a/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc.fixed index 4cd8ee4d6c..d80a32652a 100644 --- a/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc.fixed @@ -286,3 +286,24 @@ for ($i = 1, $j = 0; $i <= 10; $j += $i, print $i, $i++); if ($this->valid(fn(): bool => 2 > 1)) { } + +// Issue 3345. +function testMultiCatch() +{ + if (true) { + try { + } catch (\LogicException $e) { + } catch (\Exception $e) { + } + } +} + +function testFinally() +{ + if (true) { + try { + } catch (\LogicException $e) { + } finally { + } + } +} diff --git a/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.php b/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.php index 92888b8581..a9c4f4f8ed 100644 --- a/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.php +++ b/src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.php @@ -71,6 +71,8 @@ public function getErrorList($testFile='InlineControlStructureUnitTest.1.inc') 236 => 1, 238 => 1, 242 => 1, + 260 => 1, + 269 => 1, ]; case 'InlineControlStructureUnitTest.js': From 790cf16a3f0359516138f0bd5d5223ad86488c8e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 May 2021 23:19:39 +0200 Subject: [PATCH 360/733] Squiz/ObjectInstantiation: bug fix - account for null coalesce The `Squiz.Objects.ObjectInstantiation` did not take null coalesce into account. It also was potentially a little too lenient for ternaries as an inline then or inline else token before the `new` keyword would be considered an assignment, while it was never checked that the result of the ternary was actually assigned to something. While still not 100%, this commit improves the sniffs to: 1. Allow for null coalesce assignments `??=`. 2. For ternary tokens + null coalesce `??`: verify if the result of either of these is actually "assigned" to something (or preceded by one of the other "allowed" tokens). This should reduce the number of both false positives as well as false negatives, though there is still the potential for some false negatives - see the test on line 32 of the test case file. This could be further reduced in a future iteration on this sniff, but for now, this fix can be considered a significant step forward. Includes unit tests. Fixes 3333 --- .../Objects/ObjectInstantiationSniff.php | 38 +++++++++++++------ .../Objects/ObjectInstantiationUnitTest.inc | 18 +++++++++ .../Objects/ObjectInstantiationUnitTest.php | 6 ++- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php b/src/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php index ea4970dbf8..84facf0540 100644 --- a/src/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php +++ b/src/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php @@ -48,21 +48,37 @@ public function process(File $phpcsFile, $stackPtr) $prev = $phpcsFile->findPrevious($allowedTokens, ($stackPtr - 1), null, true); $allowedTokens = [ - T_EQUAL => true, - T_DOUBLE_ARROW => true, - T_FN_ARROW => true, - T_MATCH_ARROW => true, - T_THROW => true, - T_RETURN => true, - T_INLINE_THEN => true, - T_INLINE_ELSE => true, + T_EQUAL => T_EQUAL, + T_COALESCE_EQUAL => T_COALESCE_EQUAL, + T_DOUBLE_ARROW => T_DOUBLE_ARROW, + T_FN_ARROW => T_FN_ARROW, + T_MATCH_ARROW => T_MATCH_ARROW, + T_THROW => T_THROW, + T_RETURN => T_RETURN, ]; - if (isset($allowedTokens[$tokens[$prev]['code']]) === false) { - $error = 'New objects must be assigned to a variable'; - $phpcsFile->addError($error, $stackPtr, 'NotAssigned'); + if (isset($allowedTokens[$tokens[$prev]['code']]) === true) { + return; } + $ternaryLikeTokens = [ + T_COALESCE => true, + T_INLINE_THEN => true, + T_INLINE_ELSE => true, + ]; + + // For ternary like tokens, walk a little further back to see if it is preceded by + // one of the allowed tokens (within the same statement). + if (isset($ternaryLikeTokens[$tokens[$prev]['code']]) === true) { + $hasAllowedBefore = $phpcsFile->findPrevious($allowedTokens, ($prev - 1), null, false, null, true); + if ($hasAllowedBefore !== false) { + return; + } + } + + $error = 'New objects must be assigned to a variable'; + $phpcsFile->addError($error, $stackPtr, 'NotAssigned'); + }//end process() diff --git a/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc b/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc index f58af275cd..41c881289d 100644 --- a/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc +++ b/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc @@ -23,6 +23,24 @@ function returnMatch() { } } +// Issue 3333. +$time2 ??= new \DateTime(); +$time3 = $time1 ?? new \DateTime(); +$time3 = $time1 ?? $time2 ?? new \DateTime(); + +function_call($time1 ?? new \DateTime()); +$return = function_call($time1 ?? new \DateTime()); // False negative depending on interpretation of the sniff. + +function returnViaTernary() { + return ($y == false ) ? ($x === true ? new Foo : new Bar) : new FooBar; +} + +function nonAssignmentTernary() { + if (($x ? new Foo() : new Bar) instanceof FooBar) { + // Do something. + } +} + // Intentional parse error. This must be the last test in the file. function new ?> diff --git a/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.php b/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.php index fa32521c85..f9979fa29f 100644 --- a/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.php +++ b/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.php @@ -26,8 +26,10 @@ class ObjectInstantiationUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 5 => 1, - 8 => 1, + 5 => 1, + 8 => 1, + 31 => 1, + 39 => 2, ]; }//end getErrorList() From 118891dac55f257949db32e7c7852694a0735d7a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 10 May 2021 03:47:41 +0200 Subject: [PATCH 361/733] Tokenizer/PHP: stabilize T_FINALLY backfill Make the backfill for T_FINALLY a little more stable by preventing re-tokenizing non-keyword `finally` strings to `T_FINALLY`. Includes unit tests. --- package.xml | 6 ++ src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/FinallyTest.inc | 40 ++++++++++++ tests/Core/Tokenizer/FinallyTest.php | 96 ++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 tests/Core/Tokenizer/FinallyTest.inc create mode 100644 tests/Core/Tokenizer/FinallyTest.php diff --git a/package.xml b/package.xml index e38a9bd114..2cadbf44c9 100644 --- a/package.xml +++ b/package.xml @@ -137,6 +137,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2054,6 +2056,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2140,6 +2144,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 60c89c4507..d0a35727cf 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2084,6 +2084,7 @@ function return types. We want to keep the parenthesis map clean, // where "finally" should be T_FINALLY instead of T_STRING. if ($newToken['code'] === T_STRING && strtolower($newToken['content']) === 'finally' + && $finalTokens[$lastNotEmptyToken]['code'] === T_CLOSE_CURLY_BRACKET ) { $newToken['code'] = T_FINALLY; $newToken['type'] = 'T_FINALLY'; diff --git a/tests/Core/Tokenizer/FinallyTest.inc b/tests/Core/Tokenizer/FinallyTest.inc new file mode 100644 index 0000000000..e65600b6b4 --- /dev/null +++ b/tests/Core/Tokenizer/FinallyTest.inc @@ -0,0 +1,40 @@ +finally = 'foo'; + } +} diff --git a/tests/Core/Tokenizer/FinallyTest.php b/tests/Core/Tokenizer/FinallyTest.php new file mode 100644 index 0000000000..2b28bc5d30 --- /dev/null +++ b/tests/Core/Tokenizer/FinallyTest.php @@ -0,0 +1,96 @@ + + * @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 FinallyTest extends AbstractMethodUnitTest +{ + + + /** + * Test that the finally keyword is tokenized as such. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataFinallyKeyword + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testFinallyKeyword($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $target = $this->getTargetToken($testMarker, [T_FINALLY, T_STRING]); + $this->assertSame(T_FINALLY, $tokens[$target]['code']); + $this->assertSame('T_FINALLY', $tokens[$target]['type']); + + }//end testFinallyKeyword() + + + /** + * Data provider. + * + * @see testFinallyKeyword() + * + * @return array + */ + public function dataFinallyKeyword() + { + return [ + ['/* testTryCatchFinally */'], + ['/* testTryFinallyCatch */'], + ['/* testTryFinally */'], + ]; + + }//end dataFinallyKeyword() + + + /** + * Test that 'finally' when not used as the reserved keyword is tokenized as `T_STRING`. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataFinallyNonKeyword + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testFinallyNonKeyword($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $target = $this->getTargetToken($testMarker, [T_FINALLY, T_STRING]); + $this->assertSame(T_STRING, $tokens[$target]['code']); + $this->assertSame('T_STRING', $tokens[$target]['type']); + + }//end testFinallyNonKeyword() + + + /** + * Data provider. + * + * @see testFinallyNonKeyword() + * + * @return array + */ + public function dataFinallyNonKeyword() + { + return [ + ['/* testFinallyUsedAsClassConstantName */'], + ['/* testFinallyUsedAsMethodName */'], + ['/* testFinallyUsedAsPropertyName */'], + ]; + + }//end dataFinallyNonKeyword() + + +}//end class From 59e949be4db1a88328c9a25c3ab87ff92ecf7fea Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 May 2021 21:19:13 +0200 Subject: [PATCH 362/733] Fixer/Cbf: add missing new line in level 2 debug output ... to make the output slightly more readable. --- src/Fixer.php | 3 +++ src/Reports/Cbf.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/Fixer.php b/src/Fixer.php index ed6adb15dc..1bea0555d4 100644 --- a/src/Fixer.php +++ b/src/Fixer.php @@ -184,6 +184,9 @@ public function fixFile() } echo ']... '; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo PHP_EOL; + } } if ($this->numFixes === 0 && $this->inConflict === false) { diff --git a/src/Reports/Cbf.php b/src/Reports/Cbf.php index 25249e858d..0ecde76e67 100644 --- a/src/Reports/Cbf.php +++ b/src/Reports/Cbf.php @@ -44,6 +44,9 @@ public function generateFileReport($report, File $phpcsFile, $showSources=false, ob_end_clean(); $startTime = microtime(true); echo "\t=> Fixing file: $errors/$errors violations remaining"; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + echo PHP_EOL; + } } $fixed = $phpcsFile->fixer->fixFile(); From 2b5d04959eadff62997750bffa6e8c9e1f06987d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 12 May 2021 05:09:21 +0200 Subject: [PATCH 363/733] DoubleArrowTest: minor bug fix The test case file contained one duplicate test marker and the actual test file contained a few duplicate array keys, which meant that a few cases weren't actually being tested. Fixed now. Note: the diff looks bigger than it really is due to the array arrow alignment. Best viewed while ignoring whitespace changes. --- tests/Core/Tokenizer/DoubleArrowTest.inc | 2 +- tests/Core/Tokenizer/DoubleArrowTest.php | 94 ++++++++++++------------ 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/tests/Core/Tokenizer/DoubleArrowTest.inc b/tests/Core/Tokenizer/DoubleArrowTest.inc index 3ca1025730..b67b066015 100644 --- a/tests/Core/Tokenizer/DoubleArrowTest.inc +++ b/tests/Core/Tokenizer/DoubleArrowTest.inc @@ -185,7 +185,7 @@ function matchShortArrayMismash() { } => match ($test) { /* testMatchArrowInComplexShortArrayValue1 */ 1 => [ /* testShortArrayArrowInComplexMatchValueinShortArrayValue */ 1 => 'a'], - /* testMatchArrowInComplexShortArrayValue1 */ + /* testMatchArrowInComplexShortArrayValue2 */ 2 => /* testFnArrowInComplexMatchValueInShortArrayValue */ fn($y) => callMe($y) }, ]; diff --git a/tests/Core/Tokenizer/DoubleArrowTest.php b/tests/Core/Tokenizer/DoubleArrowTest.php index 9a0b174c3c..ad1a411ff3 100644 --- a/tests/Core/Tokenizer/DoubleArrowTest.php +++ b/tests/Core/Tokenizer/DoubleArrowTest.php @@ -50,52 +50,52 @@ public function testDoubleArrow($testMarker) public function dataDoubleArrow() { return [ - 'simple_long_array' => ['/* testLongArrayArrowSimple */'], - 'simple_short_array' => ['/* testShortArrayArrowSimple */'], - 'simple_long_list' => ['/* testLongListArrowSimple */'], - 'simple_short_list' => ['/* testShortListArrowSimple */'], - 'simple_yield' => ['/* testYieldArrowSimple */'], - 'simple_foreach' => ['/* testForeachArrowSimple */'], - - 'long_array_with_match_value_1' => ['/* testLongArrayArrowWithNestedMatchValue1 */'], - 'long_array_with_match_value_2' => ['/* testLongArrayArrowWithNestedMatchValue2 */'], - 'short_array_with_match_value_1' => ['/* testShortArrayArrowWithNestedMatchValue1 */'], - 'short_array_with_match_value_2' => ['/* testShortArrayArrowWithNestedMatchValue2 */'], - - 'long_array_with_match_key' => ['/* testLongArrayArrowWithMatchKey */'], - 'short_array_with_match_key' => ['/* testShortArrayArrowWithMatchKey */'], - - 'long_array_in_match_body_1' => ['/* testLongArrayArrowInMatchBody1 */'], - 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody2 */'], - 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody3 */'], - 'short_array_in_match_body_1' => ['/* testShortArrayArrowInMatchBody1 */'], - 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody2 */'], - 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody3 */'], - - 'short_array_in_match_case_1' => ['/* testShortArrayArrowinMatchCase1 */'], - 'short_array_in_match_case_2' => ['/* testShortArrayArrowinMatchCase2 */'], - 'short_array_in_match_case_3' => ['/* testShortArrayArrowinMatchCase3 */'], - 'long_array_in_match_case_4' => ['/* testLongArrayArrowinMatchCase4 */'], - - 'in_complex_short_array_key_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayKey */'], - 'in_complex_short_array_toplevel' => ['/* testShortArrayArrowInComplexMatchArrayMismash */'], - 'in_complex_short_array_value_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayValue */'], - - 'long_list_in_match_body' => ['/* testLongListArrowInMatchBody */'], - 'long_list_in_match_case' => ['/* testLongListArrowInMatchCase */'], - 'short_list_in_match_body' => ['/* testShortListArrowInMatchBody */'], - 'short_list_in_match_case' => ['/* testShortListArrowInMatchCase */'], - 'long_list_with_match_in_key' => ['/* testLongListArrowWithMatchInKey */'], - 'short_list_with_match_in_key' => ['/* testShortListArrowWithMatchInKey */'], - - 'long_array_with_constant_default_in_key' => ['/* testLongArrayArrowWithClassConstantKey */'], - 'short_array_with_constant_default_in_key' => ['/* testShortArrayArrowWithClassConstantKey */'], - 'yield_with_constant_default_in_key' => ['/* testYieldArrowWithClassConstantKey */'], - - 'long_array_with_default_in_key_in_match' => ['/* testLongArrayArrowWithClassConstantKeyNestedInMatch */'], - 'short_array_with_default_in_key_in_match' => ['/* testShortArrayArrowWithClassConstantKeyNestedInMatch */'], - 'long_array_with_default_in_key_with_match' => ['/* testLongArrayArrowWithClassConstantKeyWithNestedMatch */'], - 'long_array_with_default_in_key_with_match' => ['/* testShortArrayArrowWithClassConstantKeyWithNestedMatch */'], + 'simple_long_array' => ['/* testLongArrayArrowSimple */'], + 'simple_short_array' => ['/* testShortArrayArrowSimple */'], + 'simple_long_list' => ['/* testLongListArrowSimple */'], + 'simple_short_list' => ['/* testShortListArrowSimple */'], + 'simple_yield' => ['/* testYieldArrowSimple */'], + 'simple_foreach' => ['/* testForeachArrowSimple */'], + + 'long_array_with_match_value_1' => ['/* testLongArrayArrowWithNestedMatchValue1 */'], + 'long_array_with_match_value_2' => ['/* testLongArrayArrowWithNestedMatchValue2 */'], + 'short_array_with_match_value_1' => ['/* testShortArrayArrowWithNestedMatchValue1 */'], + 'short_array_with_match_value_2' => ['/* testShortArrayArrowWithNestedMatchValue2 */'], + + 'long_array_with_match_key' => ['/* testLongArrayArrowWithMatchKey */'], + 'short_array_with_match_key' => ['/* testShortArrayArrowWithMatchKey */'], + + 'long_array_in_match_body_1' => ['/* testLongArrayArrowInMatchBody1 */'], + 'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody2 */'], + 'long_array_in_match_body_3' => ['/* testLongArrayArrowInMatchBody3 */'], + 'short_array_in_match_body_1' => ['/* testShortArrayArrowInMatchBody1 */'], + 'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody2 */'], + 'short_array_in_match_body_3' => ['/* testShortArrayArrowInMatchBody3 */'], + + 'short_array_in_match_case_1' => ['/* testShortArrayArrowinMatchCase1 */'], + 'short_array_in_match_case_2' => ['/* testShortArrayArrowinMatchCase2 */'], + 'short_array_in_match_case_3' => ['/* testShortArrayArrowinMatchCase3 */'], + 'long_array_in_match_case_4' => ['/* testLongArrayArrowinMatchCase4 */'], + + 'in_complex_short_array_key_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayKey */'], + 'in_complex_short_array_toplevel' => ['/* testShortArrayArrowInComplexMatchArrayMismash */'], + 'in_complex_short_array_value_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayValue */'], + + 'long_list_in_match_body' => ['/* testLongListArrowInMatchBody */'], + 'long_list_in_match_case' => ['/* testLongListArrowInMatchCase */'], + 'short_list_in_match_body' => ['/* testShortListArrowInMatchBody */'], + 'short_list_in_match_case' => ['/* testShortListArrowInMatchCase */'], + 'long_list_with_match_in_key' => ['/* testLongListArrowWithMatchInKey */'], + 'short_list_with_match_in_key' => ['/* testShortListArrowWithMatchInKey */'], + + 'long_array_with_constant_default_in_key' => ['/* testLongArrayArrowWithClassConstantKey */'], + 'short_array_with_constant_default_in_key' => ['/* testShortArrayArrowWithClassConstantKey */'], + 'yield_with_constant_default_in_key' => ['/* testYieldArrowWithClassConstantKey */'], + + 'long_array_with_default_in_key_in_match' => ['/* testLongArrayArrowWithClassConstantKeyNestedInMatch */'], + 'short_array_with_default_in_key_in_match' => ['/* testShortArrayArrowWithClassConstantKeyNestedInMatch */'], + 'long_array_with_default_in_key_with_match' => ['/* testLongArrayArrowWithClassConstantKeyWithNestedMatch */'], + 'short_array_with_default_in_key_with_match' => ['/* testShortArrayArrowWithClassConstantKeyWithNestedMatch */'], ]; }//end dataDoubleArrow() @@ -170,7 +170,7 @@ public function dataMatchArrow() 'in_complex_short_array_key_1' => ['/* testMatchArrowInComplexShortArrayKey1 */'], 'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayKey2 */'], 'in_complex_short_array_value_1' => ['/* testMatchArrowInComplexShortArrayValue1 */'], - 'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayValue1 */'], + 'in_complex_short_array_value_2' => ['/* testMatchArrowInComplexShortArrayValue2 */'], 'with_long_list_in_body' => ['/* testMatchArrowWithLongListBody */'], 'with_long_list_in_case' => ['/* testMatchArrowWithLongListInCase */'], From 478d4e52eb4b395670000488d3eee0af09f511c4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 13 May 2021 03:32:42 +0200 Subject: [PATCH 364/733] PSR2/SwitchDeclaration: bug fix - don't remove trailing comments As reported in 3352, the `PSR2.ControlStructures.SwitchDeclaration.BodyOnNextLineCASE` fixer could inadvertently remove trailing comments. This commit improves on the fix which was put in place in response to 683, by making the detection of what should be considered the start of the body more precise: * Any amount of trailing comments on the same line as the `case` will be ignored. * Any type of comment will be taken into consideration, not just PHPCS annotations and `T_COMMENT` tokens. * Comments _not_ on the same line as the `case` and non-empty tokens on the same line as the `case` will both be considered as the "start of the body". As for the fixer. The "body starts on same line" fixer was already okay. The "empty lines between case and body" fixer, however, will now ignore anything on the same line as the `case`, which prevents us having to account for comments which already include a new line. Includes unit tests. Fixes 3352 --- .../SwitchDeclarationSniff.php | 22 ++++++----- .../SwitchDeclarationUnitTest.inc | 38 +++++++++++++++++++ .../SwitchDeclarationUnitTest.inc.fixed | 30 +++++++++++++++ .../SwitchDeclarationUnitTest.php | 4 ++ 4 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php index 11f6a3f49e..2e8ec5a464 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php @@ -107,13 +107,13 @@ public function process(File $phpcsFile, $stackPtr) } } - $next = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), null, true); - if ($tokens[$next]['line'] === $tokens[$opener]['line'] - && ($tokens[$next]['code'] === T_COMMENT - || isset(Tokens::$phpcsCommentTokens[$tokens[$next]['code']]) === true) - ) { - // Skip comments on the same line. - $next = $phpcsFile->findNext(T_WHITESPACE, ($next + 1), null, true); + for ($next = ($opener + 1); $next < $nextCloser; $next++) { + if (isset(Tokens::$emptyTokens[$tokens[$next]['code']]) === false + || (isset(Tokens::$commentTokens[$tokens[$next]['code']]) === true + && $tokens[$next]['line'] !== $tokens[$opener]['line']) + ) { + break; + } } if ($tokens[$next]['line'] !== ($tokens[$opener]['line'] + 1)) { @@ -126,6 +126,11 @@ public function process(File $phpcsFile, $stackPtr) } else { $phpcsFile->fixer->beginChangeset(); for ($i = ($opener + 1); $i < $next; $i++) { + if ($tokens[$i]['line'] === $tokens[$opener]['line']) { + // Ignore trailing comments. + continue; + } + if ($tokens[$i]['line'] === $tokens[$next]['line']) { break; } @@ -133,10 +138,9 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->fixer->replaceToken($i, ''); } - $phpcsFile->fixer->addNewLineBefore($i); $phpcsFile->fixer->endChangeset(); } - } + }//end if }//end if if ($tokens[$nextCloser]['scope_condition'] === $nextCase) { diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc index 9bf0f6055c..fd66529c94 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc @@ -340,3 +340,41 @@ switch ($foo) { case 3: return 2; } + +// Issue 3352. +switch ( $test ) { + case 2: // comment followed by empty line + + break; + + case 3: /* phpcs:ignore Stnd.Cat.SniffName -- Verify correct handling of ignore comments. */ + + + + break; + + case 4: /** inline docblock */ + + + + break; + + case 5: /* checking how it handles */ /* two trailing comments */ + + break; + + case 6: + // Comment as first content of the body. + + break; + + case 7: + /* phpcs:ignore Stnd.Cat.SniffName -- Verify correct handling of ignore comments at start of body. */ + + break; + + case 8: + /** inline docblock */ + + break; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed index 85617836db..b9ceb1527d 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed @@ -343,3 +343,33 @@ switch ($foo) { case 3: return 2; } + +// Issue 3352. +switch ( $test ) { + case 2: // comment followed by empty line + break; + + case 3: /* phpcs:ignore Stnd.Cat.SniffName -- Verify correct handling of ignore comments. */ + break; + + case 4: /** inline docblock */ + break; + + case 5: /* checking how it handles */ /* two trailing comments */ + break; + + case 6: + // Comment as first content of the body. + + break; + + case 7: + /* phpcs:ignore Stnd.Cat.SniffName -- Verify correct handling of ignore comments at start of body. */ + + break; + + case 8: + /** inline docblock */ + + break; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php index 97a68704f5..1f3a349cab 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php @@ -49,6 +49,10 @@ public function getErrorList() 260 => 1, 300 => 1, 311 => 1, + 346 => 1, + 350 => 1, + 356 => 1, + 362 => 1, ]; }//end getErrorList() From ec015a8908a7f8afaf36ae1b47b036a37eb67433 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 13 May 2021 09:48:49 +0200 Subject: [PATCH 365/733] PSR2/SwitchDeclaration: bug fix - improve handling of comments Comments in certain places in the code flow would throw the `findNestedTerminator()` method used for determining the `TerminatingComment` error off. Fixed now. Includes unit tests. Without this fix, the first test would not throw an error (false negative), while the second would (false positive). --- .../SwitchDeclarationSniff.php | 6 ++--- .../SwitchDeclarationUnitTest.inc | 26 +++++++++++++++++++ .../SwitchDeclarationUnitTest.inc.fixed | 26 +++++++++++++++++++ .../SwitchDeclarationUnitTest.php | 1 + 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php index 2e8ec5a464..3c58e6203b 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php @@ -264,7 +264,7 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) $scopeOpener = $tokens[$currentCloser]['scope_opener']; $scopeCloser = $tokens[$currentCloser]['scope_closer']; - $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($scopeOpener - 1), $stackPtr, true); + $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($scopeOpener - 1), $stackPtr, true); if ($prevToken === false) { return false; } @@ -292,7 +292,7 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) return false; } - $currentCloser = $phpcsFile->findPrevious(T_WHITESPACE, ($prevToken - 1), $stackPtr, true); + $currentCloser = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevToken - 1), $stackPtr, true); if ($tokens[$prevToken]['code'] === T_ELSE) { $hasElseBlock = true; } @@ -309,7 +309,7 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) $opener = $tokens[$nextCase]['scope_opener']; - $nextCode = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), $endOfSwitch, true); + $nextCode = $phpcsFile->findNext(Tokens::$emptyTokens, ($opener + 1), $endOfSwitch, true); if ($tokens[$nextCode]['code'] === T_CASE || $tokens[$nextCode]['code'] === T_DEFAULT) { // This case statement has no content, so skip it. continue; diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc index fd66529c94..4dfa8fec9b 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc @@ -378,3 +378,29 @@ switch ( $test ) { break; } + +// Handle comments correctly. +switch ($foo) { + case 1: + if ($bar > 0) { + doSomething(); + } + // Comment + else { + return 1; + } + case 2: + return 2; +} + +switch ($foo) { + case 1: + if ($bar > 0) /*comment*/ { + return doSomething(); + } + else { + return 1; + } + case 2: + return 2; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed index b9ceb1527d..404880e67b 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed @@ -373,3 +373,29 @@ switch ( $test ) { break; } + +// Handle comments correctly. +switch ($foo) { + case 1: + if ($bar > 0) { + doSomething(); + } + // Comment + else { + return 1; + } + case 2: + return 2; +} + +switch ($foo) { + case 1: + if ($bar > 0) /*comment*/ { + return doSomething(); + } + else { + return 1; + } + case 2: + return 2; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php index 1f3a349cab..e099dd324a 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php @@ -53,6 +53,7 @@ public function getErrorList() 350 => 1, 356 => 1, 362 => 1, + 384 => 1, ]; }//end getErrorList() From b31b9b24aa6eac3202f294f5aa5b995b91a10c50 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 13 May 2021 10:16:04 +0200 Subject: [PATCH 366/733] PSR2/SwitchDeclaration: minor efficiency tweak --- .../ControlStructures/SwitchDeclarationSniff.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php index 3c58e6203b..60785bdc97 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php @@ -343,15 +343,15 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) // We found the last statement of the CASE. Now we want to // check whether it is a terminating one. $terminators = [ - T_RETURN, - T_BREAK, - T_CONTINUE, - T_THROW, - T_EXIT, + T_RETURN => T_RETURN, + T_BREAK => T_BREAK, + T_CONTINUE => T_CONTINUE, + T_THROW => T_THROW, + T_EXIT => T_EXIT, ]; $terminator = $phpcsFile->findStartOfStatement(($lastToken - 1)); - if (in_array($tokens[$terminator]['code'], $terminators, true) === true) { + if (isset($terminators[$tokens[$terminator]['code']]) === true) { return $terminator; } }//end if From 53911399cc92c6058efd79b17dee71b0747fb9d9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 13 May 2021 10:19:10 +0200 Subject: [PATCH 367/733] PSR2/SwitchDeclaration: bug fix - handle try/catch/finally correctly Add code to handle `try-catch-finally` statements correctly when determining whether a `case` needs a terminating comment. As per https://github.com/squizlabs/PHP_CodeSniffer/issues/3297#issuecomment-840260888: > * If there is a `finally` statement and the body of that contains a "terminating statement" (`return`, `break` etc), we don't need to check the body of the `try` or any of the `catch` statements as, no matter what, all execution branches are then covered by that one terminating statement. > * If there is **_no_** `finally` or if there is, but it does not contain a terminating statement, then all `try` and `catch` structures should each contain a terminating statement in the body. Includes plenty of unit tests. Fixes 3297 --- .../SwitchDeclarationSniff.php | 38 +++- .../SwitchDeclarationUnitTest.inc | 180 ++++++++++++++++++ .../SwitchDeclarationUnitTest.inc.fixed | 180 ++++++++++++++++++ .../SwitchDeclarationUnitTest.php | 4 + 4 files changed, 399 insertions(+), 3 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php index 60785bdc97..e36b134247 100644 --- a/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php @@ -254,12 +254,15 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) if ($tokens[$lastToken]['code'] === T_CLOSE_CURLY_BRACKET) { // We found a closing curly bracket and want to check if its block - // belongs to a SWITCH, IF, ELSEIF or ELSE clause. If yes, we - // continue searching for a terminating statement within that + // belongs to a SWITCH, IF, ELSEIF or ELSE, TRY, CATCH OR FINALLY clause. + // If yes, we continue searching for a terminating statement within that // block. Note that we have to make sure that every block of // the entire if/else/switch statement has a terminating statement. + // For a try/catch/finally statement, either the finally block has + // to have a terminating statement or every try/catch block has to have one. $currentCloser = $lastToken; $hasElseBlock = false; + $hasCatchWithoutTerminator = false; do { $scopeOpener = $tokens[$currentCloser]['scope_opener']; $scopeCloser = $tokens[$currentCloser]['scope_closer']; @@ -269,7 +272,7 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) return false; } - // SWITCH, IF and ELSEIF clauses possess a condition we have to account for. + // SWITCH, IF, ELSEIF, CATCH clauses possess a condition we have to account for. if ($tokens[$prevToken]['code'] === T_CLOSE_PARENTHESIS) { $prevToken = $tokens[$prevToken]['parenthesis_owner']; } @@ -296,6 +299,35 @@ private function findNestedTerminator($phpcsFile, $stackPtr, $end) if ($tokens[$prevToken]['code'] === T_ELSE) { $hasElseBlock = true; } + } else if ($tokens[$prevToken]['code'] === T_FINALLY) { + // If we find a terminating statement within this block, + // the whole try/catch/finally statement is covered. + $hasTerminator = $this->findNestedTerminator($phpcsFile, ($scopeOpener + 1), $scopeCloser); + if ($hasTerminator !== false) { + return $hasTerminator; + } + + // Otherwise, we continue with the previous TRY or CATCH clause. + $currentCloser = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevToken - 1), $stackPtr, true); + } else if ($tokens[$prevToken]['code'] === T_TRY) { + // If we've seen CATCH blocks without terminator statement and + // have not seen a FINALLY *with* a terminator statement, we + // don't even need to bother checking the TRY. + if ($hasCatchWithoutTerminator === true) { + return false; + } + + return $this->findNestedTerminator($phpcsFile, ($scopeOpener + 1), $scopeCloser); + } else if ($tokens[$prevToken]['code'] === T_CATCH) { + // Keep track of seen catch statements without terminating statement, + // but don't bow out yet as there may still be a FINALLY clause + // with a terminating statement before the CATCH. + $hasTerminator = $this->findNestedTerminator($phpcsFile, ($scopeOpener + 1), $scopeCloser); + if ($hasTerminator === false) { + $hasCatchWithoutTerminator = true; + } + + $currentCloser = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevToken - 1), $stackPtr, true); } else if ($tokens[$prevToken]['code'] === T_SWITCH) { $hasDefaultBlock = false; $endOfSwitch = $tokens[$prevToken]['scope_closer']; diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc index 4dfa8fec9b..c425a1f20d 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc @@ -404,3 +404,183 @@ switch ($foo) { case 2: return 2; } + +// Issue #3297. +// Okay - finally will always be executed, so all branches are covered by the `return` in finally. +switch ( $a ) { + case 1: + try { + doSomething(); + } catch (Exception $e) { + doSomething(); + } catch (AnotherException $e) { + doSomething(); + } finally { + return true; + } + default: + $other = $code; + break; +} + +// Okay - all - non-finally - branches have a terminating statement. +switch ( $a ) { + case 1: + try { + return false; + } catch (Exception $e) /*comment*/ { + return true; + } + // Comment + catch (AnotherException $e) { + return true; + } finally { + doSomething(); + } + default: + $other = $code; + break; +} + +// Okay - finally will always be executed, so all branches are covered by the `return` in finally. +// Non-standard structure order. +switch ( $a ) { + case 1: + try { + doSomething(); + } catch (Exception $e) { + doSomething(); + } finally { + return true; + } catch (AnotherException $e) { + doSomething(); + } + default: + $other = $code; + break; +} + +// Okay - all - non-finally - branches have a terminating statement. +// Non-standard structure order. +switch ( $a ) { + case 1: + try { + return false; + } finally { + doSomething(); + } catch (MyException $e) { + return true; + } catch (AnotherException $e) { + return true; + } + default: + $other = $code; + break; +} + +// All okay, no finally. Any exception still uncaught will terminate the case anyhow, so we're good. +switch ( $a ) { + case 1: + try { + return false; + } catch (MyException $e) { + return true; + } catch (AnotherException $e) { + return true; + } + default: + $other = $code; + break; +} + +// All okay, no catch +switch ( $a ) { + case 1: + try { + return true; + } finally { + doSomething(); + } + case 2: + $other = $code; + break; +} + +// All okay, try-catch nested in if. +switch ( $a ) { + case 1: + if ($a) { + try { + return true; + } catch (MyException $e) { + throw new Exception($e->getMessage()); + } + } else { + return true; + } + case 2: + $other = $code; + break; +} + +// Missing fall-through comment. +switch ( $a ) { + case 1: + try { + doSomething(); + } finally { + doSomething(); + } + case 2: + $other = $code; + break; +} + +// Missing fall-through comment. One of the catches does not have a terminating statement. +switch ( $a ) { + case 1: + try { + return false; + } catch (Exception $e) { + doSomething(); + } catch (AnotherException $e) { + return true; + } finally { + doSomething(); + } + default: + $other = $code; + break; +} + +// Missing fall-through comment. Try does not have a terminating statement. +switch ( $a ) { + case 1: + try { + doSomething(); + } finally { + doSomething(); + } catch (Exception $e) { + return true; + } catch (AnotherException $e) { + return true; + } + default: + $other = $code; + break; +} + +// Missing fall-through comment. One of the catches does not have a terminating statement. +switch ( $a ) { + case 1: + try { + return false; + } catch (Exception $e) { + doSomething(); + } catch (AnotherException $e) { + return true; + } + default: + $other = $code; + break; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed index 404880e67b..47ae8d3de3 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.inc.fixed @@ -399,3 +399,183 @@ switch ($foo) { case 2: return 2; } + +// Issue #3297. +// Okay - finally will always be executed, so all branches are covered by the `return` in finally. +switch ( $a ) { + case 1: + try { + doSomething(); + } catch (Exception $e) { + doSomething(); + } catch (AnotherException $e) { + doSomething(); + } finally { + return true; + } + default: + $other = $code; + break; +} + +// Okay - all - non-finally - branches have a terminating statement. +switch ( $a ) { + case 1: + try { + return false; + } catch (Exception $e) /*comment*/ { + return true; + } + // Comment + catch (AnotherException $e) { + return true; + } finally { + doSomething(); + } + default: + $other = $code; + break; +} + +// Okay - finally will always be executed, so all branches are covered by the `return` in finally. +// Non-standard structure order. +switch ( $a ) { + case 1: + try { + doSomething(); + } catch (Exception $e) { + doSomething(); + } finally { + return true; + } catch (AnotherException $e) { + doSomething(); + } + default: + $other = $code; + break; +} + +// Okay - all - non-finally - branches have a terminating statement. +// Non-standard structure order. +switch ( $a ) { + case 1: + try { + return false; + } finally { + doSomething(); + } catch (MyException $e) { + return true; + } catch (AnotherException $e) { + return true; + } + default: + $other = $code; + break; +} + +// All okay, no finally. Any exception still uncaught will terminate the case anyhow, so we're good. +switch ( $a ) { + case 1: + try { + return false; + } catch (MyException $e) { + return true; + } catch (AnotherException $e) { + return true; + } + default: + $other = $code; + break; +} + +// All okay, no catch +switch ( $a ) { + case 1: + try { + return true; + } finally { + doSomething(); + } + case 2: + $other = $code; + break; +} + +// All okay, try-catch nested in if. +switch ( $a ) { + case 1: + if ($a) { + try { + return true; + } catch (MyException $e) { + throw new Exception($e->getMessage()); + } + } else { + return true; + } + case 2: + $other = $code; + break; +} + +// Missing fall-through comment. +switch ( $a ) { + case 1: + try { + doSomething(); + } finally { + doSomething(); + } + case 2: + $other = $code; + break; +} + +// Missing fall-through comment. One of the catches does not have a terminating statement. +switch ( $a ) { + case 1: + try { + return false; + } catch (Exception $e) { + doSomething(); + } catch (AnotherException $e) { + return true; + } finally { + doSomething(); + } + default: + $other = $code; + break; +} + +// Missing fall-through comment. Try does not have a terminating statement. +switch ( $a ) { + case 1: + try { + doSomething(); + } finally { + doSomething(); + } catch (Exception $e) { + return true; + } catch (AnotherException $e) { + return true; + } + default: + $other = $code; + break; +} + +// Missing fall-through comment. One of the catches does not have a terminating statement. +switch ( $a ) { + case 1: + try { + return false; + } catch (Exception $e) { + doSomething(); + } catch (AnotherException $e) { + return true; + } + default: + $other = $code; + break; +} diff --git a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php index e099dd324a..0cd946d888 100644 --- a/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/ControlStructures/SwitchDeclarationUnitTest.php @@ -54,6 +54,10 @@ public function getErrorList() 356 => 1, 362 => 1, 384 => 1, + 528 => 1, + 541 => 1, + 558 => 1, + 575 => 1, ]; }//end getErrorList() From 59e7581e5667a06502d5634625d99c8bcfd06e2c Mon Sep 17 00:00:00 2001 From: Scott Dutton Date: Fri, 14 May 2021 20:47:14 +0100 Subject: [PATCH 368/733] Normalise License file As the BSD-3 licence has been adjusted github doesn't notice this is the BSD-3 licence, after these changes github will pick up the correct licence --- licence.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licence.txt b/licence.txt index f95432c87b..e3d644f721 100644 --- a/licence.txt +++ b/licence.txt @@ -8,7 +8,7 @@ modification, are permitted provided that the following conditions are met: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Squiz Pty Ltd nor the + * Neither the name of the license holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. From af1d3d71343c166c497c38e5fa9dd0acdb331944 Mon Sep 17 00:00:00 2001 From: Scott Dutton Date: Sat, 15 May 2021 07:35:26 +0100 Subject: [PATCH 369/733] Update licence.txt --- licence.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licence.txt b/licence.txt index e3d644f721..9f95b67713 100644 --- a/licence.txt +++ b/licence.txt @@ -8,7 +8,7 @@ modification, are permitted provided that the following conditions are met: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of the license holder nor the + * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. From 8defc6ee320626dedc36c76bb928bbc7a9d2bc41 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 15 May 2021 11:07:13 +0200 Subject: [PATCH 370/733] Generic/OpeningFunctionBraceBsdAllman: bug fix - prevent removing return types As reported in 3357, the `Generic.Functions.OpeningFunctionBraceBsdAllman` sniff would remove return types (and comments) when fixing code where blank lines existed between the end of the function declaration and the open brace. This commit fixes that bug. In the case of comments, the `BraceSpacing` error will no longer auto-fix as a dev should decide where the comment should go and/or whether it should be removed. Includes unit tests. Fixes 3357 --- .../OpeningFunctionBraceBsdAllmanSniff.php | 30 ++++++++++++++----- .../OpeningFunctionBraceBsdAllmanUnitTest.inc | 25 ++++++++++++++++ ...ngFunctionBraceBsdAllmanUnitTest.inc.fixed | 22 ++++++++++++++ .../OpeningFunctionBraceBsdAllmanUnitTest.php | 3 ++ 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php b/src/Standards/Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php index be1464d7ce..ff19526a9b 100644 --- a/src/Standards/Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php +++ b/src/Standards/Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php @@ -139,17 +139,31 @@ public function process(File $phpcsFile, $stackPtr) } else if ($lineDifference > 1) { $error = 'Opening brace should be on the line after the declaration; found %s blank line(s)'; $data = [($lineDifference - 1)]; - $fix = $phpcsFile->addFixableError($error, $openingBrace, 'BraceSpacing', $data); - if ($fix === true) { - for ($i = ($tokens[$stackPtr]['parenthesis_closer'] + 1); $i < $openingBrace; $i++) { - if ($tokens[$i]['line'] === $braceLine) { - $phpcsFile->fixer->addNewLineBefore($i); - break; + + $prevNonWs = $phpcsFile->findPrevious(T_WHITESPACE, ($openingBrace - 1), $closeBracket, true); + if ($prevNonWs !== $prev) { + // There must be a comment between the end of the function declaration and the open brace. + // Report, but don't fix. + $phpcsFile->addError($error, $openingBrace, 'BraceSpacing', $data); + } else { + $fix = $phpcsFile->addFixableError($error, $openingBrace, 'BraceSpacing', $data); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + for ($i = $openingBrace; $i > $prev; $i--) { + if ($tokens[$i]['line'] === $tokens[$openingBrace]['line']) { + if ($tokens[$i]['column'] === 1) { + $phpcsFile->fixer->addNewLineBefore($i); + } + + continue; + } + + $phpcsFile->fixer->replaceToken($i, ''); } - $phpcsFile->fixer->replaceToken($i, ''); + $phpcsFile->fixer->endChangeset(); } - } + }//end if }//end if $ignore = Tokens::$phpcsCommentTokens; diff --git a/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.inc b/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.inc index 854c38886e..3ae3b1ed0b 100644 --- a/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.inc +++ b/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.inc @@ -236,3 +236,28 @@ function myFunction($a, $lot, $of, $params) : array /* comment */ { return null; } + +class Issue3357 +{ + public function extraLine(string: $a): void + + { + // code here. + } +} + +function issue3357WithoutIndent(string: $a): void + + +{ + // code here. +} + +class Issue3357WithComment +{ + public function extraLine(string: $a): void + // Comment. + { + // code here. + } +} diff --git a/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.inc.fixed b/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.inc.fixed index c1f904b218..f164c4934e 100644 --- a/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.inc.fixed @@ -256,3 +256,25 @@ function myFunction($a, $lot, $of, $params) { return null; } + +class Issue3357 +{ + public function extraLine(string: $a): void + { + // code here. + } +} + +function issue3357WithoutIndent(string: $a): void +{ + // code here. +} + +class Issue3357WithComment +{ + public function extraLine(string: $a): void + // Comment. + { + // code here. + } +} diff --git a/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.php b/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.php index 27eaa4fd77..10af4fd55a 100644 --- a/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.php +++ b/src/Standards/Generic/Tests/Functions/OpeningFunctionBraceBsdAllmanUnitTest.php @@ -58,6 +58,9 @@ public function getErrorList() 220 => 1, 231 => 1, 236 => 1, + 244 => 1, + 252 => 1, + 260 => 1, ]; }//end getErrorList() From 3d6ae7ff7e2f80da33e39f6b4a3e0a8986fb6dd8 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 17 May 2021 14:15:11 +1000 Subject: [PATCH 371/733] Changelog for various short open tag changes --- package.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/package.xml b/package.xml index d49d78e65e..7832827e3d 100644 --- a/package.xml +++ b/package.xml @@ -29,6 +29,19 @@ http://pear.php.net/dtd/package-2.0.xsd"> - PHPCS annotations can now be specified using hash-style comments -- Previously, only slash-style and block-style comments could be used to do things like disable errors -- Thanks to Juliette Reinders Folmer for the patch + - Fixed an issue where some sniffs would not run on PHP files that only used the short open tag with echo tag + -- The following sniffs were affected: + --- Generic.Files.ExecutableFile + --- Generic.Files.LowercasedFilename + --- Generic.Files.LineEndings + --- Generic.Files.EndFileNewline + --- Generic.Files.EndFileNoNewline + --- Generic.PHP.ClosingPHPTag + --- Generic.PHP.Syntax + --- Generic.VersionControl.GitMergeConflict + --- Generic.WhiteSpace.DisallowSpaceIndent + --- Generic.WhiteSpace.DisallowTabIndent + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line -- Thanks to Alessandro Chitolina for the patch -- Thanks to Juliette Reinders Folmer for the tests From 783d9461e1876ed6d57960259e57c3a56640b039 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 21 May 2021 18:03:55 +0200 Subject: [PATCH 372/733] Generic/ScopeIndent: minor bug fix (undefined array index) While looking at issue 3362 in an attempt to debug it, binarykitten and me came across a `An error occurred during processing; checking has been aborted. The error message was: Undefined array key "" in /src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php on line 646 (Internal.Exception)` error notice. While this will probably only occur in exceptional circumstances, it was caused by an operator precedence issue in the scope closer check condition. Adding the extra parentheses fixes the operator precedence snafu and prevents the above error notice. Note: this does not fix 3362, but still, is one less bug to worry about ;-) --- src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index a01449197a..526a9e3c88 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -616,11 +616,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) ) { From 52f440f90c2e157b574854934e62bf1a03822665 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 27 May 2021 15:13:05 +1000 Subject: [PATCH 373/733] Changelog for #3340 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 7832827e3d..e1412b7f97 100644 --- a/package.xml +++ b/package.xml @@ -53,6 +53,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3317 : Problem with how phpcs handles ignored files when running in parallel -- Thanks to Emil Andersson for the patch - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call + - Fixed bug #3340 : Ensure interface and trait names are always tokenized as T_STRING + -- Thanks to Juliette Reinders Folmer for the patch From ea105e443eb7ac3fb370af5e8e1a12bafce304cc Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 27 May 2021 15:23:49 +1000 Subject: [PATCH 374/733] Changelog for 3315 --- package.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index e1412b7f97..e5bba1a171 100644 --- a/package.xml +++ b/package.xml @@ -29,7 +29,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - PHPCS annotations can now be specified using hash-style comments -- Previously, only slash-style and block-style comments could be used to do things like disable errors -- Thanks to Juliette Reinders Folmer for the patch - - Fixed an issue where some sniffs would not run on PHP files that only used the short open tag with echo tag + - Fixed an issue where some sniffs would not run on PHP files that only used the short echo tag -- The following sniffs were affected: --- Generic.Files.ExecutableFile --- Generic.Files.LowercasedFilename @@ -42,6 +42,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> --- Generic.WhiteSpace.DisallowSpaceIndent --- Generic.WhiteSpace.DisallowTabIndent -- Thanks to Juliette Reinders Folmer for the patch + - Squiz.Commenting.BlockComment now correctly applies rules for block comments after a short echo tag + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line -- Thanks to Alessandro Chitolina for the patch -- Thanks to Juliette Reinders Folmer for the tests From 10da08b93ccc9fd6aad113b3f6d7783a3c77535a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 27 May 2021 15:58:44 +1000 Subject: [PATCH 375/733] Changelog for #3357 (ref #3358) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 156cffc2c8..80db24430c 100644 --- a/package.xml +++ b/package.xml @@ -57,6 +57,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call - Fixed bug #3340 : Ensure interface and trait names are always tokenized as T_STRING -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3357 : Generic.Functions.OpeningFunctionBraceBsdAllman removes return type when additional lines are present + -- Thanks to Juliette Reinders Folmer for the patch From 801ae46f461d93ab04b1b33b4ffa7eed046ae19e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 27 May 2021 16:03:09 +1000 Subject: [PATCH 376/733] Changelogs for #3297 and #3352 (ref #3354) --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index 80db24430c..4fc39b241c 100644 --- a/package.xml +++ b/package.xml @@ -48,6 +48,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Alessandro Chitolina for the patch -- Thanks to Juliette Reinders Folmer for the tests - Fixed bug #3296 : PSR2.ControlStructures.SwitchDeclaration takes phpcs:ignore as content of case body + - Fixed bug #3297 : PSR2.ControlStructures.SwitchDeclaration.TerminatingComment does not handle try/finally blocks + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3302 : PHP 8.0 | Tokenizer/PHP: bugfix for union types using namespace operator -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3303 : findStartOfStatement() doesn't work with T_OPEN_TAG_WITH_ECHO @@ -57,6 +59,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call - Fixed bug #3340 : Ensure interface and trait names are always tokenized as T_STRING -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3352 : PSR2.ControlStructures.SwitchDeclaration can remove comments on the same line as the case statement while fixing + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3357 : Generic.Functions.OpeningFunctionBraceBsdAllman removes return type when additional lines are present -- Thanks to Juliette Reinders Folmer for the patch From 798ae549672b123e857fb7d7303b110bd41d04f4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 27 May 2021 16:14:39 +1000 Subject: [PATCH 377/733] Changelog for #3342 (ref #3343) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 4fc39b241c..32a563cdbe 100644 --- a/package.xml +++ b/package.xml @@ -59,6 +59,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call - Fixed bug #3340 : Ensure interface and trait names are always tokenized as T_STRING -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3342 : PSR12/Squiz/PEAR standards all error on promoted properties with docblocks + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3352 : PSR2.ControlStructures.SwitchDeclaration can remove comments on the same line as the case statement while fixing -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3357 : Generic.Functions.OpeningFunctionBraceBsdAllman removes return type when additional lines are present From d2574b9185cdde7185132647d0c9121da3e59f90 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 27 May 2021 16:21:27 +1000 Subject: [PATCH 378/733] Changelog for #3345 (ref #3347) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 32a563cdbe..ecd920dfa5 100644 --- a/package.xml +++ b/package.xml @@ -61,6 +61,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3342 : PSR12/Squiz/PEAR standards all error on promoted properties with docblocks -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3345 : IF statement with no braces and double catch turned into syntax error by auto-fixer + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3352 : PSR2.ControlStructures.SwitchDeclaration can remove comments on the same line as the case statement while fixing -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3357 : Generic.Functions.OpeningFunctionBraceBsdAllman removes return type when additional lines are present From 16b6dda81d720ac4e61f526c71ee4015ed126c69 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 12 May 2021 04:43:10 +0200 Subject: [PATCH 379/733] Generic/MultipleStatementAlignment: add extra tests .. safeguarding the tokenizer fix which should prevent the issue as reported in 3326. --- .../Formatting/MultipleStatementAlignmentUnitTest.inc | 10 ++++++++++ .../MultipleStatementAlignmentUnitTest.inc.fixed | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc index 286fdf1e75..e42225e89a 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc @@ -468,3 +468,13 @@ $resource = new class() { $one <<= 8; $onetwothree = 3; + +// Issue 3326. +class Test +{ + public const DEFAULT = 'default'; + public const SOS = 'sos'; + public const HELP = 'help'; + + protected static $thisIsAReallyLongVariableName = []; +} diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed index f08312ddf9..5d5516d1fa 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed @@ -468,3 +468,13 @@ $resource = new class() { $one <<= 8; $onetwothree = 3; + +// Issue 3326. +class Test +{ + public const DEFAULT = 'default'; + public const SOS = 'sos'; + public const HELP = 'help'; + + protected static $thisIsAReallyLongVariableName = []; +} From 65ab395d3d563c4396aaa5056198dd13fade7491 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 12 May 2021 06:54:06 +0200 Subject: [PATCH 380/733] Tests: add extra tests for the default keyword tokenization --- tests/Core/Tokenizer/DefaultKeywordTest.inc | 8 ++++++ tests/Core/Tokenizer/DefaultKeywordTest.php | 27 +++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/tests/Core/Tokenizer/DefaultKeywordTest.inc b/tests/Core/Tokenizer/DefaultKeywordTest.inc index 6de4485730..648149d2ff 100644 --- a/tests/Core/Tokenizer/DefaultKeywordTest.inc +++ b/tests/Core/Tokenizer/DefaultKeywordTest.inc @@ -193,3 +193,11 @@ function switchWithConstantNonDefault($i) { return 2; } } + +class Foo { + /* testClassConstant */ + const DEFAULT = 'foo'; + + /* testMethodDeclaration */ + public function default() {} +} diff --git a/tests/Core/Tokenizer/DefaultKeywordTest.php b/tests/Core/Tokenizer/DefaultKeywordTest.php index 2d1e68cb6c..9a5b061a05 100644 --- a/tests/Core/Tokenizer/DefaultKeywordTest.php +++ b/tests/Core/Tokenizer/DefaultKeywordTest.php @@ -267,9 +267,36 @@ public function dataNotDefaultKeyword() 'class-property-in-switch-case' => ['/* testClassPropertyInSwitchCase */'], 'namespaced-constant-in-switch-case' => ['/* testNamespacedConstantInSwitchCase */'], 'namespace-relative-constant-in-switch-case' => ['/* testNamespaceRelativeConstantInSwitchCase */'], + + 'class-constant-declaration' => ['/* testClassConstant */'], + 'class-method-declaration' => [ + '/* testMethodDeclaration */', + 'default', + ], ]; }//end dataNotDefaultKeyword() + /** + * Test a specific edge case where a scope opener would be incorrectly set. + * + * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/3326 + * + * @return void + */ + public function testIssue3326() + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken('/* testClassConstant */', [T_SEMICOLON]); + $tokenArray = $tokens[$token]; + + $this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set'); + $this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set'); + $this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set'); + + }//end testIssue3326() + + }//end class From 0d5663c7d235e13c1085bc877d63a3b0aa027ccc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 12 May 2021 07:01:31 +0200 Subject: [PATCH 381/733] Tokenizer/PHP: fix tokenization of the `default` keyword As per: https://github.com/squizlabs/PHP_CodeSniffer/issues/3326#issuecomment-839379113 > After `PHP::tokenize()`, the `DEFAULT` is still tokenized as `T_DEFAULT`. This causes the `Tokenizer::recurseScopeMap()` to assign it as the `scope_opener` to the `;` semi-colon at the end of the constant declaration, with the class close curly brace being set as the `scope_closer`. > In the `PHP::processAdditional()` method, the `DEFAULT` is subsequently retokenized to `T_STRING` as it is preceded by a `const` keyword, but that is too late. > > The `scope_opener` being set on the semi-colon is what is causing the errors to be displayed for the above code sample. The commit fixes this by: 1. Abstracting the list of `T_STRING` contexts out to a class property. 2. Using the list from the property in all places in the `Tokenizer\PHP` class where keyword tokens are (potentially) being re-tokenized to `T_STRING`, including in the `T_DEFAULT` tokenization code which was added to address the PHP 8.0 `match` expressions. Note: the issue was not introduced by `match` related code, however, that code being there does make it relatively easy now to fix this particular case. While this doesn't address 3336 yes, it is a step towards addressing it and will sort out one of the most common causes for bugs. --- src/Tokenizers/PHP.php | 71 ++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index fec0e9b124..b3a56fa09b 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -454,6 +454,29 @@ class PHP extends Tokenizer T_TYPE_UNION => 1, ]; + /** + * Contexts in which keywords should always be tokenized as T_STRING. + * + * @var array + */ + protected $tstringContexts = [ + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_FUNCTION => true, + T_CLASS => true, + T_INTERFACE => true, + T_TRAIT => true, + T_EXTENDS => true, + T_IMPLEMENTS => true, + T_ATTRIBUTE => true, + T_NEW => true, + T_CONST => true, + T_NS_SEPARATOR => true, + T_USE => true, + T_NAMESPACE => true, + T_PAAMAYIM_NEKUDOTAYIM => true, + ]; + /** * A cache of different token types, resolved into arrays. * @@ -1332,16 +1355,7 @@ protected function tokenize($string) break; } - $notMatchContext = [ - T_PAAMAYIM_NEKUDOTAYIM => true, - T_OBJECT_OPERATOR => true, - T_NULLSAFE_OBJECT_OPERATOR => true, - T_NS_SEPARATOR => true, - T_NEW => true, - T_FUNCTION => true, - ]; - - if (isset($notMatchContext[$finalTokens[$lastNotEmptyToken]['code']]) === true) { + if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) { // Also not a match expression. break; } @@ -1389,14 +1403,7 @@ protected function tokenize($string) if ($tokenIsArray === true && $token[0] === T_DEFAULT ) { - $ignoreContext = [ - T_OBJECT_OPERATOR => true, - T_NULLSAFE_OBJECT_OPERATOR => true, - T_NS_SEPARATOR => true, - T_PAAMAYIM_NEKUDOTAYIM => true, - ]; - - if (isset($ignoreContext[$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). @@ -1894,25 +1901,7 @@ function return types. We want to keep the parenthesis map clean, if ($tokenIsArray === true && $token[0] === T_STRING) { // Some T_STRING tokens should remain that way // due to their context. - $context = [ - T_OBJECT_OPERATOR => true, - T_NULLSAFE_OBJECT_OPERATOR => true, - T_FUNCTION => true, - T_CLASS => true, - T_INTERFACE => true, - T_TRAIT => true, - T_EXTENDS => true, - T_IMPLEMENTS => true, - T_ATTRIBUTE => true, - T_NEW => true, - T_CONST => true, - T_NS_SEPARATOR => true, - T_USE => true, - T_NAMESPACE => true, - T_PAAMAYIM_NEKUDOTAYIM => true, - ]; - - if (isset($context[$finalTokens[$lastNotEmptyToken]['code']]) === true) { + 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 @@ -2784,13 +2773,7 @@ protected function processAdditional() } } - $context = [ - T_OBJECT_OPERATOR => true, - T_NULLSAFE_OBJECT_OPERATOR => true, - T_NS_SEPARATOR => true, - T_PAAMAYIM_NEKUDOTAYIM => true, - ]; - if (isset($context[$this->tokens[$x]['code']]) === true) { + if (isset($this->tstringContexts[$this->tokens[$x]['code']]) === true) { if (PHP_CODESNIFFER_VERBOSITY > 1) { $line = $this->tokens[$i]['line']; $type = $this->tokens[$i]['type']; From 964a38c7cd4322daa085764979726b8d4a55f66e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 12 May 2021 04:57:39 +0200 Subject: [PATCH 382/733] Tokenizer/PHP: bug fix - fix performance issue Happened to come across this while investigating something else. As it was, as long as no open parenthesis or variable was encountered, this snippet would loop to the end of the file for each `T_ARRAY` token encountered as it would only `break` in the `if/elseif` and there was no `else` clause. Basically, we only want the `array` keyword to be tokenized as `T_ARRAY` if it is an actual array declaration. In all other cases, it should be tokenized as `T_STRING`. This fixes the performance leak by only looping to the first non-empty token after the keyword, checking if it's an open parenthesis and retokenizing the `T_ARRAY` to `T_STRING` in all other cases. It also removes the need for the separate _return type_ retokenization of the array keyword. Includes adding unit tests specifically for the array keyword. --- package.xml | 6 + src/Tokenizers/PHP.php | 40 ++--- tests/Core/Tokenizer/ArrayKeywordTest.inc | 35 +++++ tests/Core/Tokenizer/ArrayKeywordTest.php | 170 ++++++++++++++++++++++ 4 files changed, 225 insertions(+), 26 deletions(-) create mode 100644 tests/Core/Tokenizer/ArrayKeywordTest.inc create mode 100644 tests/Core/Tokenizer/ArrayKeywordTest.php diff --git a/package.xml b/package.xml index ecd920dfa5..ce682c585c 100644 --- a/package.xml +++ b/package.xml @@ -150,6 +150,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2098,6 +2100,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2186,6 +2190,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index fec0e9b124..49b232cbc5 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1769,23 +1769,6 @@ function return types. We want to keep the parenthesis map clean, break; }//end for - - // Any T_ARRAY tokens we find between here and the next - // token that can't be part of the return type, need to be - // converted to T_STRING tokens. - for ($x; $x < $numTokens; $x++) { - if ((is_array($tokens[$x]) === false && $tokens[$x] !== '|') - || (is_array($tokens[$x]) === true && isset($allowed[$tokens[$x][0]]) === false) - ) { - break; - } else if (is_array($tokens[$x]) === true && $tokens[$x][0] === T_ARRAY) { - $tokens[$x][0] = T_STRING; - - if (PHP_CODESNIFFER_VERBOSITY > 1) { - echo "\t\t* token $x changed from T_ARRAY to T_STRING".PHP_EOL; - } - } - } }//end if }//end if }//end if @@ -2066,20 +2049,25 @@ function return types. We want to keep the parenthesis map clean, } }//end if - // This is a special condition for T_ARRAY tokens used for - // type hinting function arguments as being arrays. We want to keep - // the parenthesis map clean, so let's tag these tokens as + // This is a special condition for T_ARRAY tokens used for anything else + // but array declarations, like type hinting function arguments as + // being arrays. + // We want to keep the parenthesis map clean, so let's tag these tokens as // T_STRING. if ($newToken['code'] === T_ARRAY) { - for ($i = $stackPtr; $i < $numTokens; $i++) { - if ($tokens[$i] === '(') { - break; - } else if ($tokens[$i][0] === T_VARIABLE) { - $newToken['code'] = T_STRING; - $newToken['type'] = 'T_STRING'; + for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { + if (is_array($tokens[$i]) === false + || isset(Util\Tokens::$emptyTokens[$tokens[$i][0]]) === false + ) { + // Non-empty content. break; } } + + if ($tokens[$i] !== '(' && $i !== $numTokens) { + $newToken['code'] = T_STRING; + $newToken['type'] = 'T_STRING'; + } } // This is a special case when checking PHP 5.5+ code in PHP < 5.5 diff --git a/tests/Core/Tokenizer/ArrayKeywordTest.inc b/tests/Core/Tokenizer/ArrayKeywordTest.inc new file mode 100644 index 0000000000..ce5c553cf6 --- /dev/null +++ b/tests/Core/Tokenizer/ArrayKeywordTest.inc @@ -0,0 +1,35 @@ + 10); + +/* testArrayWithComment */ +$var = Array /*comment*/ (1 => 10); + +/* testNestingArray */ +$var = array( + /* testNestedArray */ + array( + 'key' => 'value', + + /* testClosureReturnType */ + 'closure' => function($a) use($global) : Array {}, + ), +); + +/* testFunctionDeclarationParamType */ +function foo(array $a) {} + +/* testFunctionDeclarationReturnType */ +function foo($a) : int|array|null {} + +class Bar { + /* testClassConst */ + const ARRAY = []; + + /* testClassMethod */ + public function array() {} +} diff --git a/tests/Core/Tokenizer/ArrayKeywordTest.php b/tests/Core/Tokenizer/ArrayKeywordTest.php new file mode 100644 index 0000000000..237258a62a --- /dev/null +++ b/tests/Core/Tokenizer/ArrayKeywordTest.php @@ -0,0 +1,170 @@ + + * @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 ArrayKeywordTest extends AbstractMethodUnitTest +{ + + + /** + * Test that the array keyword is correctly tokenized as `T_ARRAY`. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent Optional. The token content to look for. + * + * @dataProvider dataArrayKeyword + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createTokenMap + * + * @return void + */ + public function testArrayKeyword($testMarker, $testContent='array') + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_ARRAY, T_STRING], $testContent); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_ARRAY, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_ARRAY (code)'); + $this->assertSame('T_ARRAY', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_ARRAY (type)'); + + $this->assertArrayHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is not set'); + $this->assertArrayHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is not set'); + $this->assertArrayHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is not set'); + + }//end testArrayKeyword() + + + /** + * Data provider. + * + * @see testArrayKeyword() + * + * @return array + */ + public function dataArrayKeyword() + { + return [ + 'empty array' => ['/* testEmptyArray */'], + 'array with space before parenthesis' => ['/* testArrayWithSpace */'], + 'array with comment before parenthesis' => [ + '/* testArrayWithComment */', + 'Array', + ], + 'nested: outer array' => ['/* testNestingArray */'], + 'nested: inner array' => ['/* testNestedArray */'], + ]; + + }//end dataArrayKeyword() + + + /** + * Test that the array keyword when used in a type declaration is correctly tokenized as `T_STRING`. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent Optional. The token content to look for. + * + * @dataProvider dataArrayType + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createTokenMap + * + * @return void + */ + public function testArrayType($testMarker, $testContent='array') + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_ARRAY, T_STRING], $testContent); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)'); + $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)'); + + $this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set'); + $this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set'); + $this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set'); + + }//end testArrayType() + + + /** + * Data provider. + * + * @see testArrayType() + * + * @return array + */ + public function dataArrayType() + { + return [ + 'closure return type' => [ + '/* testClosureReturnType */', + 'Array', + ], + 'function param type' => ['/* testFunctionDeclarationParamType */'], + 'function union return type' => ['/* testFunctionDeclarationReturnType */'], + ]; + + }//end dataArrayType() + + + /** + * Verify that the retokenization of `T_ARRAY` tokens to `T_STRING` is handled correctly + * for tokens with the contents 'array' which aren't in actual fact the array keyword. + * + * @param string $testMarker The comment prefacing the target token. + * @param string $testContent The token content to look for. + * + * @dataProvider dataNotArrayKeyword + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createTokenMap + * + * @return void + */ + public function testNotArrayKeyword($testMarker, $testContent='array') + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken($testMarker, [T_ARRAY, T_STRING], $testContent); + $tokenArray = $tokens[$token]; + + $this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)'); + $this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)'); + + $this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set'); + $this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set'); + $this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set'); + + }//end testNotArrayKeyword() + + + /** + * Data provider. + * + * @see testNotArrayKeyword() + * + * @return array + */ + public function dataNotArrayKeyword() + { + return [ + 'class-constant-name' => [ + '/* testClassConst */', + 'ARRAY', + ], + 'class-method-name' => ['/* testClassMethod */'], + ]; + + }//end dataNotArrayKeyword() + + +}//end class From a6daa05b1160873a661f28eee8d3937299564569 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 12 May 2021 06:04:19 +0200 Subject: [PATCH 383/733] Sniff test: adjust two test files to match This removes two expected errors due to the tokenizer change made in the previous commit. ### `Generic.Arrays.DisallowLongArraySyntax` The code on line 13 is a parse error, so the sniff no longer throwing an error for it should not be our concern. ```php $var = array; ``` ### `Squiz.PHP.CommentedOutCode` The code which triggered the warning on line 35 was a docblock written as a block comment and not really commented out code anyway, so this could be considered a fix for a false positive. ```php /* * The listeners array. * * @var array(PHP_CodeSniffer_Sniff) */ ``` --- .../Generic/Tests/Arrays/DisallowLongArraySyntaxUnitTest.php | 1 - src/Standards/Squiz/Tests/PHP/CommentedOutCodeUnitTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Standards/Generic/Tests/Arrays/DisallowLongArraySyntaxUnitTest.php b/src/Standards/Generic/Tests/Arrays/DisallowLongArraySyntaxUnitTest.php index 0297681061..af1d9c9a86 100644 --- a/src/Standards/Generic/Tests/Arrays/DisallowLongArraySyntaxUnitTest.php +++ b/src/Standards/Generic/Tests/Arrays/DisallowLongArraySyntaxUnitTest.php @@ -35,7 +35,6 @@ public function getErrorList($testFile='') 6 => 1, 7 => 1, 12 => 1, - 13 => 1, ]; case 'DisallowLongArraySyntaxUnitTest.2.inc': return [ diff --git a/src/Standards/Squiz/Tests/PHP/CommentedOutCodeUnitTest.php b/src/Standards/Squiz/Tests/PHP/CommentedOutCodeUnitTest.php index d51f23ca3b..36c556d8c2 100644 --- a/src/Standards/Squiz/Tests/PHP/CommentedOutCodeUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/CommentedOutCodeUnitTest.php @@ -49,7 +49,6 @@ public function getWarningList($testFile='CommentedOutCodeUnitTest.inc') 8 => 1, 15 => 1, 19 => 1, - 35 => 1, 87 => 1, 91 => 1, 97 => 1, From ac26fbb6599a3c8abb4b489c82c88d312f5bf7ed Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Thu, 3 Jun 2021 13:04:33 +0200 Subject: [PATCH 384/733] Replace hot Tokenizer loop with a mathematical expression This piece of code is very hot, executed thousands, possibly millions of times. I found this when I benchmarked our custom sniffs. The replaceTabsInToken() method is consistently reported as one of the absolute hottest ones. I looked at the method and was wondering why this particular piece is a loop when it can be a mathematical expression. According to my micro-benchmark (only measuring the changed piece) it's about twice as fast now. This will probably not have much of an effect in a real-world scenario. Still I find it worth it. --- src/Tokenizers/Tokenizer.php | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 4c5d391340..c50ab78100 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -642,21 +642,9 @@ public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth $tabNum++; // Move the pointer to the next tab stop. - if (($currColumn % $tabWidth) === 0) { - // This is the first tab, and we are already at a - // tab stop, so this tab counts as a single space. - $currColumn++; - } else { - $currColumn++; - while (($currColumn % $tabWidth) !== 0) { - $currColumn++; - } - - $currColumn++; - } - - $length += ($currColumn - $lastCurrColumn); - $newContent .= $prefix.str_repeat($padding, ($currColumn - $lastCurrColumn - 1)); + $length = $tabWidth - ($currColumn + $tabWidth - 1) % $tabWidth; + $currColumn += $length; + $newContent .= $prefix.str_repeat($padding, ($length - 1)); }//end foreach }//end if From 83f5723773f2985c1fee7d3a4cf4e940f5b0e322 Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Thu, 3 Jun 2021 13:06:18 +0200 Subject: [PATCH 385/733] Fix indention --- src/Tokenizers/Tokenizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index c50ab78100..2caff44197 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -643,7 +643,7 @@ public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth // Move the pointer to the next tab stop. $length = $tabWidth - ($currColumn + $tabWidth - 1) % $tabWidth; - $currColumn += $length; + $currColumn += $length; $newContent .= $prefix.str_repeat($padding, ($length - 1)); }//end foreach }//end if From 3dd9d32974fcf60bc8a26ba9e24615939e24a562 Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Thu, 3 Jun 2021 13:43:12 +0200 Subject: [PATCH 386/733] Fix minor mistake related to $length --- src/Tokenizers/Tokenizer.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 2caff44197..55f37a8351 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -642,9 +642,10 @@ public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth $tabNum++; // Move the pointer to the next tab stop. - $length = $tabWidth - ($currColumn + $tabWidth - 1) % $tabWidth; - $currColumn += $length; - $newContent .= $prefix.str_repeat($padding, ($length - 1)); + $pad = $tabWidth - ($currColumn + $tabWidth - 1) % $tabWidth; + $currColumn += $pad; + $length += $pad; + $newContent .= $prefix.str_repeat($padding, ($pad - 1)); }//end foreach }//end if From 07e182b875de4e1876a019a972caf2bc881e5501 Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Thu, 3 Jun 2021 14:40:32 +0200 Subject: [PATCH 387/733] Add pointless brackets --- src/Tokenizers/Tokenizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 55f37a8351..bf8bfce154 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -642,7 +642,7 @@ public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth $tabNum++; // Move the pointer to the next tab stop. - $pad = $tabWidth - ($currColumn + $tabWidth - 1) % $tabWidth; + $pad = ($tabWidth - ($currColumn + $tabWidth - 1) % $tabWidth); $currColumn += $pad; $length += $pad; $newContent .= $prefix.str_repeat($padding, ($pad - 1)); From 39649310a0f463b23a44bd36eb42dae8ccdb9ab7 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 18 Jun 2021 09:24:54 +1000 Subject: [PATCH 388/733] Fixed bug #3362 : Generic.WhiteSpace.ScopeIndent false positive for arrow functions inside arrays --- package.xml | 1 + .../Tests/WhiteSpace/ScopeIndentUnitTest.1.inc | 9 +++++++++ .../WhiteSpace/ScopeIndentUnitTest.1.inc.fixed | 9 +++++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.2.inc | 9 +++++++++ .../WhiteSpace/ScopeIndentUnitTest.2.inc.fixed | 9 +++++++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 ++++---- src/Tokenizers/PHP.php | 6 ++++-- tests/Core/Tokenizer/BackfillFnTokenTest.inc | 5 +++++ tests/Core/Tokenizer/BackfillFnTokenTest.php | 16 ++++++++++++++++ 9 files changed, 66 insertions(+), 6 deletions(-) diff --git a/package.xml b/package.xml index ecd920dfa5..6fe2ffa355 100644 --- a/package.xml +++ b/package.xml @@ -67,6 +67,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3357 : Generic.Functions.OpeningFunctionBraceBsdAllman removes return type when additional lines are present -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3362 : Generic.WhiteSpace.ScopeIndent false positive for arrow functions inside arrays diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index 6d63a64a34..6aadcc2095 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1547,6 +1547,15 @@ $foo = match ($type) { ], }; +$a = [ + 'a' => [ + 'a' => fn () => foo() + ], + 'a' => [ + 'a' => 'a', + ] +]; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 51000a01f7..7b0765002a 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1547,6 +1547,15 @@ $foo = match ($type) { ], }; +$a = [ + 'a' => [ + 'a' => fn () => foo() + ], + 'a' => [ + 'a' => 'a', + ] +]; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index 9e40384b2f..407fc8d125 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1547,6 +1547,15 @@ $foo = match ($type) { ], }; +$a = [ + 'a' => [ + 'a' => fn () => foo() + ], + 'a' => [ + 'a' => 'a', + ] +]; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index 3aed0fcf13..5821ab70e3 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1547,6 +1547,15 @@ $foo = match ($type) { ], }; +$a = [ + 'a' => [ + 'a' => fn () => foo() + ], + 'a' => [ + 'a' => 'a', + ] +]; + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index 03ade71b44..3a8a8c41c3 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -187,10 +187,10 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1527 => 1, 1529 => 1, 1530 => 1, - 1558 => 1, - 1559 => 1, - 1560 => 1, - 1561 => 1, + 1567 => 1, + 1568 => 1, + 1569 => 1, + 1570 => 1, ]; }//end getErrorList() diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index fec0e9b124..5f6352237e 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2344,8 +2344,10 @@ protected function processAdditional() if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) { if ($lastEndToken !== null - && $this->tokens[$scopeCloser]['code'] === T_CLOSE_PARENTHESIS - && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow + && ((isset($this->tokens[$scopeCloser]['parenthesis_opener']) === true + && $this->tokens[$scopeCloser]['parenthesis_opener'] < $arrow) + || (isset($this->tokens[$scopeCloser]['bracket_opener']) === true + && $this->tokens[$scopeCloser]['bracket_opener'] < $arrow)) ) { for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) { if (isset(Util\Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) { diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index ddd502bb61..13f165b77f 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -63,6 +63,11 @@ $a = [ 'a' => fn() => return 1, ]; +/* testArrayValueNoTrailingComma */ +$a = [ + 'a' => fn() => foo() +]; + /* testYield */ $a = fn($x) => yield 'k' => $x; diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 5529d2e96a..4d0f4c0649 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -307,6 +307,22 @@ public function testArrayValue() }//end testArrayValue() + /** + * Test arrow functions that are used as array values with no trailing comma. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testArrayValueNoTrailingComma() + { + $token = $this->getTargetToken('/* testArrayValueNoTrailingComma */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 4, 8, 'closing parenthesis'); + + }//end testArrayValueNoTrailingComma() + + /** * Test arrow functions that use the yield keyword. * From 357ed0110d64fc3394965f1ff5f51c165f9b3701 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 21 Jun 2021 22:04:34 +0200 Subject: [PATCH 389/733] GH Actions: set error reporting to E_ALL Turns out the default setting for `error_reporting` used by the SetupPHP action is `error_reporting=E_ALL & ~E_DEPRECATED & ~E_STRICT` and `display_errors` is set to `Off`. For the purposes of CI, I'd recommend running with `E_ALL` and `display_errors=On` to ensure **all** PHP notices are shown. Includes minor fix to the documentation of that part of the script. --- .github/workflows/test.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 66a1b80761..5002127b6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,15 +51,14 @@ jobs: - name: Setup ini config id: set_ini run: | - # On stable PHPCS versions, allow for PHP deprecation notices. - # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. - # Also set the "short_open_tag" ini to make sure specific conditions are tested. + # 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, date.timezone=Australia/Sydney, short_open_tag=On, asp_tags=On' + echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=E_ALL, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On, asp_tags=On' elif [[ ${{ matrix.custom_ini }} == true && "${{ matrix.php }}" == '7.0' ]]; then - echo '::set-output name=PHP_INI::phar.readonly=Off, date.timezone=Australia/Sydney, short_open_tag=On' + echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=E_ALL, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On' else - echo '::set-output name=PHP_INI::phar.readonly=Off' + echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=E_ALL, display_errors=On' fi - name: Install PHP From f76ee808e63c5e5fe363fcc6ace4c5beb1a1578e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 21 Jun 2021 22:24:11 +0200 Subject: [PATCH 390/733] PHP 8.1: Generic/ConstructorName: fix deprecation notices On PHP 8.1, passing `null` to `strtolower()` generates a `strtolower(): Passing null to parameter #1 ($string) of type string is deprecated` notice. The `File::getDeclarationName()` will return `null` for anonymous classes, so we need an extra safeguard here. This fix is covered by the existing unit tests and was exposed when running the tests on PHP 8.1 with `error_reporting` set to `E_ALL`. --- .../Sniffs/NamingConventions/ConstructorNameSniff.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php index b7f5427a22..a41960637c 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php @@ -66,7 +66,12 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop return; } - $className = strtolower($phpcsFile->getDeclarationName($currScope)); + $className = $phpcsFile->getDeclarationName($currScope); + if (empty($className) === false) { + // Not an anonymous class. + $className = strtolower($className); + } + if ($className !== $this->currentClass) { $this->loadFunctionNamesInScope($phpcsFile, $currScope); $this->currentClass = $className; From bf08940c11f5c1c08040c3235ab45207f7088970 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 25 Jun 2021 08:42:21 +1000 Subject: [PATCH 391/733] Changelog for 3381 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 6fe2ffa355..9e433da69a 100644 --- a/package.xml +++ b/package.xml @@ -42,6 +42,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> --- Generic.WhiteSpace.DisallowSpaceIndent --- Generic.WhiteSpace.DisallowTabIndent -- Thanks to Juliette Reinders Folmer for the patch + - Generic.NamingConventions.ConstructorName no longer throws deprecation notices on PHP 8.1 + -- Thanks to Juliette Reinders Folmer for the patch - Squiz.Commenting.BlockComment now correctly applies rules for block comments after a short echo tag -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line From 7e287c6830a66c0cceed29afda5217600b20892c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 1 Jul 2021 08:34:30 +1000 Subject: [PATCH 392/733] Changelog for #3326 (ref #3351) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 9e433da69a..f4244c4442 100644 --- a/package.xml +++ b/package.xml @@ -59,6 +59,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3317 : Problem with how phpcs handles ignored files when running in parallel -- Thanks to Emil Andersson for the patch - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call + - Fixed bug #3326 : Generic.Formatting.MultipleStatementAlignment error with const DEFAULT + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3340 : Ensure interface and trait names are always tokenized as T_STRING -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3342 : PSR12/Squiz/PEAR standards all error on promoted properties with docblocks From 5fff209600ef72fc74de851de321353e12184c03 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 19 Jul 2021 08:53:06 +1000 Subject: [PATCH 393/733] Fixed bug #3384 : Squiz.Commenting.FileComment.SpacingAfterComment false positive on empty file --- package.xml | 2 ++ .../Squiz/Sniffs/Commenting/FileCommentSniff.php | 2 +- .../Squiz/Tests/Commenting/FileCommentUnitTest.8.inc | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.8.inc diff --git a/package.xml b/package.xml index f4244c4442..17d2782b40 100644 --- a/package.xml +++ b/package.xml @@ -72,6 +72,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3357 : Generic.Functions.OpeningFunctionBraceBsdAllman removes return type when additional lines are present -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3362 : Generic.WhiteSpace.ScopeIndent false positive for arrow functions inside arrays + - Fixed bug #3384 : Squiz.Commenting.FileComment.SpacingAfterComment false positive on empty file @@ -1640,6 +1641,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php index 73eb31b71f..2685854769 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php @@ -127,7 +127,7 @@ public function process(File $phpcsFile, $stackPtr) // Exactly one blank line after the file comment. $next = $phpcsFile->findNext(T_WHITESPACE, ($commentEnd + 1), null, true); - if ($tokens[$next]['line'] !== ($tokens[$commentEnd]['line'] + 2)) { + if ($next !== false && $tokens[$next]['line'] !== ($tokens[$commentEnd]['line'] + 2)) { $error = 'There must be exactly one blank line after the file comment'; $phpcsFile->addError($error, $commentEnd, 'SpacingAfterComment'); } diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.8.inc b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.8.inc new file mode 100644 index 0000000000..5ef90f2ad1 --- /dev/null +++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.8.inc @@ -0,0 +1,9 @@ + + * @copyright 2010-2014 Squiz Pty Ltd (ABN 77 084 670 600) + */ From 75e7ec03ab6ee08a372123ab186b434f9b702c84 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 19 Jul 2021 09:12:03 +1000 Subject: [PATCH 394/733] Fixed some return value issues --- src/Config.php | 2 +- src/Fixer.php | 1 + src/Runner.php | 2 +- src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Config.php b/src/Config.php index 106b1c50b9..5e87e5dda0 100644 --- a/src/Config.php +++ b/src/Config.php @@ -459,7 +459,7 @@ public function setCommandLineValues($args) /** * Restore default values for all possible command line arguments. * - * @return array + * @return void */ public function restoreDefaults() { diff --git a/src/Fixer.php b/src/Fixer.php index 1bea0555d4..b8dc05b16e 100644 --- a/src/Fixer.php +++ b/src/Fixer.php @@ -421,6 +421,7 @@ public function endChangeset() } $this->changeset = []; + return true; }//end endChangeset() diff --git a/src/Runner.php b/src/Runner.php index c6f2c92edf..f6ee5bc391 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -232,7 +232,7 @@ public function runPHPCBF() /** * Exits if the minimum requirements of PHP_CodeSniffer are not met. * - * @return array + * @return void * @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the requirements are not met. */ public function checkRequirements() diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index eeed382952..a23032e280 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -758,6 +758,8 @@ protected function checkInheritdoc(File $phpcsFile, $stackPtr, $commentStart) } } + return false; + }//end checkInheritdoc() From b0ab30b2da06989e3f6576705000be782ae19738 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 21 Jul 2021 08:14:09 +1000 Subject: [PATCH 395/733] Renamed index from attributes to has_attributes --- src/Files/File.php | 10 +-- tests/Core/File/GetMethodParametersTest.php | 98 ++++++++++----------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 2e9c5b4bbb..e551dcbbf1 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1283,7 +1283,7 @@ public function getDeclarationName($stackPtr) * 'name' => '$var', // The variable name. * 'token' => integer, // The stack pointer to the variable name. * 'content' => string, // The full content of the variable definition. - * 'attributes' => boolean, // Does the parameter have one or more attributes attached ? + * 'has_attributes' => boolean, // Does the parameter have one or more attributes attached ? * 'pass_by_reference' => boolean, // Is the variable passed by reference? * 'reference_token' => integer, // The stack pointer to the reference operator * // or FALSE if the param is not passed by reference. @@ -1356,7 +1356,7 @@ public function getMethodParameters($stackPtr) $defaultStart = null; $equalToken = null; $paramCount = 0; - $attributes = false; + $hasAttributes = false; $passByReference = false; $referenceToken = false; $variableLength = false; @@ -1389,7 +1389,7 @@ public function getMethodParameters($stackPtr) switch ($this->tokens[$i]['code']) { case T_ATTRIBUTE: - $attributes = true; + $hasAttributes = true; // Skip to the end of the attribute. $i = $this->tokens[$i]['attribute_closer']; @@ -1510,7 +1510,7 @@ public function getMethodParameters($stackPtr) $vars[$paramCount]['default_equal_token'] = $equalToken; } - $vars[$paramCount]['attributes'] = $attributes; + $vars[$paramCount]['has_attributes'] = $hasAttributes; $vars[$paramCount]['pass_by_reference'] = $passByReference; $vars[$paramCount]['reference_token'] = $referenceToken; $vars[$paramCount]['variable_length'] = $variableLength; @@ -1536,7 +1536,7 @@ public function getMethodParameters($stackPtr) $paramStart = ($i + 1); $defaultStart = null; $equalToken = null; - $attributes = false; + $hasAttributes = false; $passByReference = false; $referenceToken = false; $variableLength = false; diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index 6de4622aed..e7692a7153 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -26,7 +26,7 @@ public function testPassByReference() $expected[0] = [ 'name' => '$var', 'content' => '&$var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => true, 'variable_length' => false, 'type_hint' => '', @@ -49,7 +49,7 @@ public function testArrayHint() $expected[0] = [ 'name' => '$var', 'content' => 'array $var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'array', @@ -72,7 +72,7 @@ public function testTypeHint() $expected[0] = [ 'name' => '$var1', 'content' => 'foo $var1', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'foo', @@ -82,7 +82,7 @@ public function testTypeHint() $expected[1] = [ 'name' => '$var2', 'content' => 'bar $var2', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'bar', @@ -105,7 +105,7 @@ public function testSelfTypeHint() $expected[0] = [ 'name' => '$var', 'content' => 'self $var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'self', @@ -128,7 +128,7 @@ public function testNullableTypeHint() $expected[0] = [ 'name' => '$var1', 'content' => '?int $var1', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?int', @@ -138,7 +138,7 @@ public function testNullableTypeHint() $expected[1] = [ 'name' => '$var2', 'content' => '?\bar $var2', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?\bar', @@ -161,7 +161,7 @@ public function testVariable() $expected[0] = [ 'name' => '$var', 'content' => '$var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -184,7 +184,7 @@ public function testSingleDefaultValue() $expected[0] = [ 'name' => '$var1', 'content' => '$var1=self::CONSTANT', - 'attributes' => false, + 'has_attributes' => false, 'default' => 'self::CONSTANT', 'pass_by_reference' => false, 'variable_length' => false, @@ -208,7 +208,7 @@ public function testDefaultValues() $expected[0] = [ 'name' => '$var1', 'content' => '$var1=1', - 'attributes' => false, + 'has_attributes' => false, 'default' => '1', 'pass_by_reference' => false, 'variable_length' => false, @@ -218,7 +218,7 @@ public function testDefaultValues() $expected[1] = [ 'name' => '$var2', 'content' => "\$var2='value'", - 'attributes' => false, + 'has_attributes' => false, 'default' => "'value'", 'pass_by_reference' => false, 'variable_length' => false, @@ -243,7 +243,7 @@ public function testBitwiseAndConstantExpressionDefaultValue() 'name' => '$a', 'content' => '$a = 10 & 20', 'default' => '10 & 20', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -266,7 +266,7 @@ public function testArrowFunction() $expected[0] = [ 'name' => '$a', 'content' => 'int $a', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int', @@ -276,7 +276,7 @@ public function testArrowFunction() $expected[1] = [ 'name' => '$b', 'content' => '...$b', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => true, 'type_hint' => '', @@ -299,7 +299,7 @@ public function testPHP8MixedTypeHint() $expected[0] = [ 'name' => '$var1', 'content' => 'mixed &...$var1', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => true, 'variable_length' => true, 'type_hint' => 'mixed', @@ -322,7 +322,7 @@ public function testPHP8MixedTypeHintNullable() $expected[0] = [ 'name' => '$var1', 'content' => '?Mixed $var1', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?Mixed', @@ -345,7 +345,7 @@ public function testNamespaceOperatorTypeHint() $expected[0] = [ 'name' => '$var1', 'content' => '?namespace\Name $var1', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?namespace\Name', @@ -368,7 +368,7 @@ public function testPHP8UnionTypesSimple() $expected[0] = [ 'name' => '$number', 'content' => 'int|float $number', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int|float', @@ -377,7 +377,7 @@ public function testPHP8UnionTypesSimple() $expected[1] = [ 'name' => '$obj', 'content' => 'self|parent &...$obj', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => true, 'variable_length' => true, 'type_hint' => 'self|parent', @@ -400,7 +400,7 @@ public function testPHP8UnionTypesWithSpreadOperatorAndReference() $expected[0] = [ 'name' => '$paramA', 'content' => 'float|null &$paramA', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => true, 'variable_length' => false, 'type_hint' => 'float|null', @@ -409,7 +409,7 @@ public function testPHP8UnionTypesWithSpreadOperatorAndReference() $expected[1] = [ 'name' => '$paramB', 'content' => 'string|int ...$paramB', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => true, 'type_hint' => 'string|int', @@ -433,7 +433,7 @@ public function testPHP8UnionTypesSimpleWithBitwiseOrInDefault() 'name' => '$var', 'content' => 'int|float $var = CONSTANT_A | CONSTANT_B', 'default' => 'CONSTANT_A | CONSTANT_B', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int|float', @@ -456,7 +456,7 @@ public function testPHP8UnionTypesTwoClasses() $expected[0] = [ 'name' => '$var', 'content' => 'MyClassA|\Package\MyClassB $var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'MyClassA|\Package\MyClassB', @@ -479,7 +479,7 @@ public function testPHP8UnionTypesAllBaseTypes() $expected[0] = [ 'name' => '$var', 'content' => 'array|bool|callable|int|float|null|object|string $var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'array|bool|callable|int|float|null|object|string', @@ -502,7 +502,7 @@ public function testPHP8UnionTypesAllPseudoTypes() $expected[0] = [ 'name' => '$var', 'content' => 'false|mixed|self|parent|iterable|Resource $var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'false|mixed|self|parent|iterable|Resource', @@ -525,7 +525,7 @@ public function testPHP8UnionTypesNullable() $expected[0] = [ 'name' => '$number', 'content' => '?int|float $number', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?int|float', @@ -549,7 +549,7 @@ public function testPHP8PseudoTypeNull() 'name' => '$var', 'content' => 'null $var = null', 'default' => 'null', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'null', @@ -573,7 +573,7 @@ public function testPHP8PseudoTypeFalse() 'name' => '$var', 'content' => 'false $var = false', 'default' => 'false', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'false', @@ -597,7 +597,7 @@ public function testPHP8PseudoTypeFalseAndBool() 'name' => '$var', 'content' => 'bool|false $var = false', 'default' => 'false', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'bool|false', @@ -620,7 +620,7 @@ public function testPHP8ObjectAndClass() $expected[0] = [ 'name' => '$var', 'content' => 'object|ClassName $var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'object|ClassName', @@ -643,7 +643,7 @@ public function testPHP8PseudoTypeIterableAndArray() $expected[0] = [ 'name' => '$var', 'content' => 'iterable|array|Traversable $var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'iterable|array|Traversable', @@ -666,7 +666,7 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment() $expected[0] = [ 'name' => '$var', 'content' => 'int | string /*comment*/ | INT $var', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int|string|INT', @@ -690,7 +690,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes() 'name' => '$x', 'content' => 'public $x = 0.0', 'default' => '0.0', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -701,7 +701,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes() 'name' => '$y', 'content' => 'protected $y = \'\'', 'default' => "''", - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -712,7 +712,7 @@ public function testPHP8ConstructorPropertyPromotionNoTypes() 'name' => '$z', 'content' => 'private $z = null', 'default' => 'null', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -736,7 +736,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes() $expected[0] = [ 'name' => '$x', 'content' => 'protected float|int $x', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'float|int', @@ -747,7 +747,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes() 'name' => '$y', 'content' => 'public ?string &$y = \'test\'', 'default' => "'test'", - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => true, 'variable_length' => false, 'type_hint' => '?string', @@ -757,7 +757,7 @@ public function testPHP8ConstructorPropertyPromotionWithTypes() $expected[2] = [ 'name' => '$z', 'content' => 'private mixed $z', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'mixed', @@ -781,7 +781,7 @@ public function testPHP8ConstructorPropertyPromotionAndNormalParam() $expected[0] = [ 'name' => '$promotedProp', 'content' => 'public int $promotedProp', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'int', @@ -791,7 +791,7 @@ public function testPHP8ConstructorPropertyPromotionAndNormalParam() $expected[1] = [ 'name' => '$normalArg', 'content' => '?int $normalArg', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?int', @@ -814,7 +814,7 @@ public function testPHP8ConstructorPropertyPromotionGlobalFunction() $expected[0] = [ 'name' => '$x', 'content' => 'private $x', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -838,7 +838,7 @@ public function testPHP8ConstructorPropertyPromotionAbstractMethod() $expected[0] = [ 'name' => '$y', 'content' => 'public callable $y', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'callable', @@ -848,7 +848,7 @@ public function testPHP8ConstructorPropertyPromotionAbstractMethod() $expected[1] = [ 'name' => '$x', 'content' => 'private ...$x', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => false, 'variable_length' => true, 'type_hint' => '', @@ -873,7 +873,7 @@ public function testCommentsInParameter() 'name' => '$param', 'content' => '// Leading comment. ?MyClass /*-*/ & /*-*/.../*-*/ $param /*-*/ = /*-*/ \'default value\' . /*-*/ \'second part\' // Trailing comment.', - 'attributes' => false, + 'has_attributes' => false, 'pass_by_reference' => true, 'variable_length' => true, 'type_hint' => '?MyClass', @@ -896,7 +896,7 @@ public function testParameterAttributesInFunctionDeclaration() $expected[0] = [ 'name' => '$constructorPropPromTypedParamSingleAttribute', 'content' => '#[\MyExample\MyAttribute] private string $constructorPropPromTypedParamSingleAttribute', - 'attributes' => true, + 'has_attributes' => true, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'string', @@ -908,7 +908,7 @@ public function testParameterAttributesInFunctionDeclaration() 'content' => '#[MyAttr([1, 2])] Type|false $typedParamSingleAttribute', - 'attributes' => true, + 'has_attributes' => true, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => 'Type|false', @@ -917,7 +917,7 @@ public function testParameterAttributesInFunctionDeclaration() $expected[2] = [ 'name' => '$nullableTypedParamMultiAttribute', 'content' => '#[MyAttribute(1234), MyAttribute(5678)] ?int $nullableTypedParamMultiAttribute', - 'attributes' => true, + 'has_attributes' => true, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '?int', @@ -926,7 +926,7 @@ public function testParameterAttributesInFunctionDeclaration() $expected[3] = [ 'name' => '$nonTypedParamTwoAttributes', 'content' => '#[WithoutArgument] #[SingleArgument(0)] $nonTypedParamTwoAttributes', - 'attributes' => true, + 'has_attributes' => true, 'pass_by_reference' => false, 'variable_length' => false, 'type_hint' => '', @@ -936,7 +936,7 @@ public function testParameterAttributesInFunctionDeclaration() 'name' => '$otherParam', 'content' => '#[MyAttribute(array("key" => "value"))] &...$otherParam', - 'attributes' => true, + 'has_attributes' => true, 'pass_by_reference' => true, 'variable_length' => true, 'type_hint' => '', From d5c1cf755070447305e6e847db0299bc4e4c0190 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 21 Jul 2021 08:16:30 +1000 Subject: [PATCH 396/733] Changelog for #3320 (ref #3298) --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index 17d2782b40..100a4b098f 100644 --- a/package.xml +++ b/package.xml @@ -42,6 +42,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> --- Generic.WhiteSpace.DisallowSpaceIndent --- Generic.WhiteSpace.DisallowTabIndent -- Thanks to Juliette Reinders Folmer for the patch + - File::getMethodParameters() no longer incorrectly returns argument attributes in the type hint array index + -- A new has_attributes array index is available and set to TRUE if the argument has attributes defined + -- Thanks to Juliette Reinders Folmer for the patch - Generic.NamingConventions.ConstructorName no longer throws deprecation notices on PHP 8.1 -- Thanks to Juliette Reinders Folmer for the patch - Squiz.Commenting.BlockComment now correctly applies rules for block comments after a short echo tag From 39dd4b701f5f429c3d7b3130c445b5dcfc4a81b4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Jul 2021 15:38:24 +0200 Subject: [PATCH 397/733] Squiz/BlockComment: prevent false positives with attributes PHP 8.0+ attributes can be placed between a docblock and the function/class declaration it applies to. The `Squiz.Commenting.BlockComment` sniff did not yet take this into account when determining whether a comment was a docblock or an block comment incorrectly using the docblock syntax. This would result in false positive `Block comments must be started with /*` errors. Fixed now. --- .../Sniffs/Commenting/BlockCommentSniff.php | 14 ++++++++++++-- .../Tests/Commenting/BlockCommentUnitTest.inc | 16 ++++++++++++++++ .../Commenting/BlockCommentUnitTest.inc.fixed | 16 ++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php index 93b60adaad..6a12bb7368 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php @@ -69,8 +69,18 @@ public function process(File $phpcsFile, $stackPtr) // If this is a function/class/interface doc block comment, skip it. // We are only interested in inline doc block comments. if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) { - $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); - $ignore = [ + $nextToken = $stackPtr; + do { + $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextToken + 1), null, true); + if ($tokens[$nextToken]['code'] === T_ATTRIBUTE) { + $nextToken = $tokens[$nextToken]['attribute_closer']; + continue; + } + + break; + } while (true); + + $ignore = [ T_CLASS => true, T_INTERFACE => true, T_TRAIT => true, diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc index daf50fa382..eed554b3f3 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc @@ -272,3 +272,19 @@ $contentToEcho * No blank line allowed above the comment if it's the first non-empty token after a PHP open tag. */ $contentToEcho + +/** + * Comment should be ignored, even though there is an attribute between the docblock and the class declaration. + */ + +#[AttributeA] + +final class MyClass +{ + /** + * Comment should be ignored, even though there is an attribute between the docblock and the function declaration + */ + #[AttributeA] + #[AttributeB] + final public function test() {} +} diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed index e402160f02..7471b582a9 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed @@ -274,3 +274,19 @@ $contentToEcho * No blank line allowed above the comment if it's the first non-empty token after a PHP open tag. */ $contentToEcho + +/** + * Comment should be ignored, even though there is an attribute between the docblock and the class declaration. + */ + +#[AttributeA] + +final class MyClass +{ + /** + * Comment should be ignored, even though there is an attribute between the docblock and the function declaration + */ + #[AttributeA] + #[AttributeB] + final public function test() {} +} From 82c9d12ee3fd8b324d97b3995ee76c178accae70 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Jul 2021 15:43:40 +0200 Subject: [PATCH 398/733] Squiz/InlineComment: prevent false positives with attributes PHP 8.0+ attributes can be placed between a docblock and the function/class declaration it applies to. The `Squiz.Commenting.InlineComment` sniff did not yet take this into account when determining whether a comment was a docblock or an single line comment incorrectly using the docblock syntax. This would result in false positive `Inline doc block comments are not allowed; use "/* Comment */" or "// Comment" instead` errors. Fixed now. --- .../Sniffs/Commenting/InlineCommentSniff.php | 16 ++++++++++------ .../Tests/Commenting/InlineCommentUnitTest.inc | 16 ++++++++++++++++ .../Commenting/InlineCommentUnitTest.inc.fixed | 16 ++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php index 09b4ee2528..7d7ee40e96 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php @@ -59,12 +59,16 @@ public function process(File $phpcsFile, $stackPtr) // We are only interested in inline doc block comments, which are // not allowed. if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) { - $nextToken = $phpcsFile->findNext( - Tokens::$emptyTokens, - ($stackPtr + 1), - null, - true - ); + $nextToken = $stackPtr; + do { + $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextToken + 1), null, true); + if ($tokens[$nextToken]['code'] === T_ATTRIBUTE) { + $nextToken = $tokens[$nextToken]['attribute_closer']; + continue; + } + + break; + } while (true); $ignore = [ T_CLASS, diff --git a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc index 377db023d9..1b97af0b92 100644 --- a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc @@ -149,6 +149,22 @@ if ($foo) { // another comment here. $foo++; +/** + * Comment should be ignored, even though there is an attribute between the docblock and the class declaration. + */ + +#[AttributeA] + +final class MyClass +{ + /** + * Comment should be ignored, even though there is an attribute between the docblock and the function declaration + */ + #[AttributeA] + #[AttributeB] + final public function test() {} +} + /* * 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 975143f2c5..6b66624176 100644 --- a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed @@ -142,6 +142,22 @@ if ($foo) { // another comment here. $foo++; +/** + * Comment should be ignored, even though there is an attribute between the docblock and the class declaration. + */ + +#[AttributeA] + +final class MyClass +{ + /** + * Comment should be ignored, even though there is an attribute between the docblock and the function declaration + */ + #[AttributeA] + #[AttributeB] + final public function test() {} +} + /* * 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 From 5b710b8d69df761792e4d9f6e5bc81cb3c9ca482 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Jul 2021 15:28:36 +0200 Subject: [PATCH 399/733] PEAR/FunctionComment: prevent false positives with attributes PHP 8.0+ attributes can be placed between a docblock and the function declaration it applies to. The `PEAR.Commenting.FunctionComment` sniff - and by extension the `Squiz.Commenting.FunctionComment` sniff - did not yet take this into account. This would result in a false positive `Missing doc comment for function ... ` error. Resolving that error though, would result in a new `There must be no blank lines after the function comment` error, so the blank line check also needed to be fixed. With the current fix, blank lines between the docblock and the function declaration are still not allowed, but non-blank lines between the two (i.e. lines containing attributes) will be ignored. Only one "no blank lines between" error will be thrown per function declaration. --- .../Commenting/FunctionCommentSniff.php | 37 ++++++++++++--- .../Commenting/FunctionCommentUnitTest.inc | 47 +++++++++++++++++++ .../FunctionCommentUnitTest.inc.fixed | 47 +++++++++++++++++++ .../Commenting/FunctionCommentUnitTest.php | 3 ++ 4 files changed, 128 insertions(+), 6 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php index 3c3dbfc408..408856fc5a 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php @@ -68,11 +68,25 @@ public function process(File $phpcsFile, $stackPtr) return; } - $tokens = $phpcsFile->getTokens(); - $ignore = Tokens::$methodPrefixes; - $ignore[] = T_WHITESPACE; + $tokens = $phpcsFile->getTokens(); + $ignore = Tokens::$methodPrefixes; + $ignore[T_WHITESPACE] = T_WHITESPACE; + + for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) { + if (isset($ignore[$tokens[$commentEnd]['code']]) === true) { + continue; + } + + if ($tokens[$commentEnd]['code'] === T_ATTRIBUTE_END + && isset($tokens[$commentEnd]['attribute_opener']) === true + ) { + $commentEnd = $tokens[$commentEnd]['attribute_opener']; + continue; + } + + break; + } - $commentEnd = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); if ($tokens[$commentEnd]['code'] === T_COMMENT) { // Inline comments might just be closing comments for // control structures or functions instead of function comments @@ -106,8 +120,19 @@ public function process(File $phpcsFile, $stackPtr) } if ($tokens[$commentEnd]['line'] !== ($tokens[$stackPtr]['line'] - 1)) { - $error = 'There must be no blank lines after the function comment'; - $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); + for ($i = ($commentEnd + 1); $i < $stackPtr; $i++) { + if ($tokens[$i]['column'] !== 1) { + continue; + } + + if ($tokens[$i]['code'] === T_WHITESPACE + && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] + ) { + $error = 'There must be no blank lines after the function comment'; + $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); + break; + } + } } $commentStart = $tokens[$commentEnd]['comment_opener']; diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc index 0e935eadf6..5c3295fd60 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc @@ -429,3 +429,50 @@ public function ignored() { } // phpcs:set PEAR.Commenting.FunctionComment specialMethods[] __construct,__destruct + +class Something implements JsonSerializable { + /** + * Single attribute. + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function jsonSerialize() {} + + /** + * Multiple attributes. + * + * @return Something + */ + #[AttributeA] + #[AttributeB] + public function methodName() {} + + /** + * Blank line between docblock and attribute. + * + * @return mixed + */ + + #[ReturnTypeWillChange] + public function blankLineDetectionA() {} + + /** + * Blank line between attribute and function declaration. + * + * @return mixed + */ + #[ReturnTypeWillChange] + + public function blankLineDetectionB() {} + + /** + * Blank line between both docblock and attribute and attribute and function declaration. + * + * @return mixed + */ + + #[ReturnTypeWillChange] + + public function blankLineDetectionC() {} +} diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 29588134d6..751b09c665 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -429,3 +429,50 @@ public function ignored() { } // phpcs:set PEAR.Commenting.FunctionComment specialMethods[] __construct,__destruct + +class Something implements JsonSerializable { + /** + * Single attribute. + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function jsonSerialize() {} + + /** + * Multiple attributes. + * + * @return Something + */ + #[AttributeA] + #[AttributeB] + public function methodName() {} + + /** + * Blank line between docblock and attribute. + * + * @return mixed + */ + + #[ReturnTypeWillChange] + public function blankLineDetectionA() {} + + /** + * Blank line between attribute and function declaration. + * + * @return mixed + */ + #[ReturnTypeWillChange] + + public function blankLineDetectionB() {} + + /** + * Blank line between both docblock and attribute and attribute and function declaration. + * + * @return mixed + */ + + #[ReturnTypeWillChange] + + public function blankLineDetectionC() {} +} diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php index a7b35e60a1..734ff73e50 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php @@ -70,6 +70,9 @@ public function getErrorList() 364 => 1, 406 => 1, 417 => 1, + 455 => 1, + 464 => 1, + 473 => 1, ]; }//end getErrorList() From 72637adf0620fd35b474f058f149fd8245e415e1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Jul 2021 17:59:39 +0200 Subject: [PATCH 400/733] PEAR/VariableComment: prevent false positives with attributes PHP 8.0+ attributes can be placed between a docblock and the property declaration it applies to. The `Squiz.Commenting.VariableComment` sniff did not yet take this into account. This would result in a false positive `Missing member variable doc comment` error. Fixed now. --- .../Commenting/VariableCommentSniff.php | 34 +++++++++++++------ .../Commenting/VariableCommentUnitTest.inc | 20 +++++++++++ .../VariableCommentUnitTest.inc.fixed | 20 +++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php index 7b9fc933ad..a7731bb661 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php @@ -30,18 +30,32 @@ public function processMemberVar(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $ignore = [ - T_PUBLIC, - T_PRIVATE, - T_PROTECTED, - T_VAR, - T_STATIC, - T_WHITESPACE, - T_STRING, - T_NS_SEPARATOR, - T_NULLABLE, + T_PUBLIC => T_PUBLIC, + T_PRIVATE => T_PRIVATE, + T_PROTECTED => T_PROTECTED, + T_VAR => T_VAR, + T_STATIC => T_STATIC, + T_WHITESPACE => T_WHITESPACE, + T_STRING => T_STRING, + T_NS_SEPARATOR => T_NS_SEPARATOR, + T_NULLABLE => T_NULLABLE, ]; - $commentEnd = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true); + for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) { + if (isset($ignore[$tokens[$commentEnd]['code']]) === true) { + continue; + } + + if ($tokens[$commentEnd]['code'] === T_ATTRIBUTE_END + && isset($tokens[$commentEnd]['attribute_opener']) === true + ) { + $commentEnd = $tokens[$commentEnd]['attribute_opener']; + continue; + } + + break; + } + if ($commentEnd === false || ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG && $tokens[$commentEnd]['code'] !== T_COMMENT) diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc index 65f4389bdc..c2046b179f 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc @@ -363,3 +363,23 @@ class Foo var int $noComment = 1; } + +class HasAttributes +{ + /** + * Short description of the member variable. + * + * @var array + */ + #[ORM\Id]#[ORM\Column("integer")] + private $id; + + /** + * Short description of the member variable. + * + * @var array + */ + #[ORM\GeneratedValue] + #[ORM\Column(ORM\Column::T_INTEGER)] + protected $height; +} diff --git a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed index ca0b052e35..37ca1cebbf 100644 --- a/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/VariableCommentUnitTest.inc.fixed @@ -363,3 +363,23 @@ class Foo var int $noComment = 1; } + +class HasAttributes +{ + /** + * Short description of the member variable. + * + * @var array + */ + #[ORM\Id]#[ORM\Column("integer")] + private $id; + + /** + * Short description of the member variable. + * + * @var array + */ + #[ORM\GeneratedValue] + #[ORM\Column(ORM\Column::T_INTEGER)] + protected $height; +} From 6b259f66cdb3a030dab9800e07b9765e68e0861e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Bugy=C3=ADk?= Date: Tue, 27 Jul 2021 19:14:40 +0200 Subject: [PATCH 401/733] Fix autoload issue --- .../Tests/NamingConventions/InterfaceNameSuffixUnitTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.php index b4d9696f91..be23f7a656 100644 --- a/src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/InterfaceNameSuffixUnitTest.php @@ -10,7 +10,7 @@ use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; -class InterfaceSuffixNameUnitTest extends AbstractSniffUnitTest +class InterfaceNameSuffixUnitTest extends AbstractSniffUnitTest { From 64f2a81e24517da68c4cdf6312983fc6be31037e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 29 Jul 2021 13:22:00 +1000 Subject: [PATCH 402/733] Fix false positives when attributes are used for member vars --- .../WhiteSpace/MemberVarSpacingSniff.php | 80 +++++++++++++------ .../WhiteSpace/MemberVarSpacingUnitTest.inc | 24 ++++++ .../MemberVarSpacingUnitTest.inc.fixed | 21 +++++ .../WhiteSpace/MemberVarSpacingUnitTest.php | 4 + 4 files changed, 106 insertions(+), 23 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php index f0c84fb8e2..ccd48995e7 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php @@ -55,11 +55,25 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) $endOfStatement = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1), null, false, null, true); - $ignore = $validPrefixes; - $ignore[] = T_WHITESPACE; + $ignore = $validPrefixes; + $ignore[T_WHITESPACE] = T_WHITESPACE; $start = $startOfStatement; - $prev = $phpcsFile->findPrevious($ignore, ($startOfStatement - 1), null, true); + for ($prev = ($startOfStatement - 1); $prev >= 0; $prev--) { + if (isset($ignore[$tokens[$prev]['code']]) === true) { + continue; + } + + if ($tokens[$prev]['code'] === T_ATTRIBUTE_END + && isset($tokens[$prev]['attribute_opener']) === true + ) { + $prev = $tokens[$prev]['attribute_opener']; + continue; + } + + break; + } + if (isset(Tokens::$commentTokens[$tokens[$prev]['code']]) === true) { // Assume the comment belongs to the member var if it is on a line by itself. $prevContent = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true); @@ -67,28 +81,48 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) // Check the spacing, but then skip it. $foundLines = ($tokens[$startOfStatement]['line'] - $tokens[$prev]['line'] - 1); if ($foundLines > 0) { - $error = 'Expected 0 blank lines after member var comment; %s found'; - $data = [$foundLines]; - $fix = $phpcsFile->addFixableError($error, $prev, 'AfterComment', $data); - if ($fix === true) { - $phpcsFile->fixer->beginChangeset(); - // Inline comments have the newline included in the content but - // docblock do not. - if ($tokens[$prev]['code'] === T_COMMENT) { - $phpcsFile->fixer->replaceToken($prev, rtrim($tokens[$prev]['content'])); + for ($i = ($prev + 1); $i < $startOfStatement; $i++) { + if ($tokens[$i]['column'] !== 1) { + continue; } - for ($i = ($prev + 1); $i <= $startOfStatement; $i++) { - if ($tokens[$i]['line'] === $tokens[$startOfStatement]['line']) { - break; - } - - $phpcsFile->fixer->replaceToken($i, ''); - } - - $phpcsFile->fixer->addNewline($prev); - $phpcsFile->fixer->endChangeset(); - } + if ($tokens[$i]['code'] === T_WHITESPACE + && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] + ) { + $error = 'Expected 0 blank lines after member var comment; %s found'; + $data = [$foundLines]; + $fix = $phpcsFile->addFixableError($error, $prev, 'AfterComment', $data); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + // Inline comments have the newline included in the content but + // docblocks do not. + if ($tokens[$prev]['code'] === T_COMMENT) { + $phpcsFile->fixer->replaceToken($prev, rtrim($tokens[$prev]['content'])); + } + + for ($i = ($prev + 1); $i <= $startOfStatement; $i++) { + if ($tokens[$i]['line'] === $tokens[$startOfStatement]['line']) { + break; + } + + // Remove the newline after the docblock, and any entirely + // empty lines before the member var. + if ($tokens[$i]['code'] === T_WHITESPACE + && $tokens[$i]['line'] === $tokens[$prev]['line'] + || ($tokens[$i]['column'] === 1 + && $tokens[$i]['line'] !== $tokens[($i + 1)]['line']) + ) { + $phpcsFile->fixer->replaceToken($i, ''); + } + } + + $phpcsFile->fixer->addNewline($prev); + $phpcsFile->fixer->endChangeset(); + }//end if + + break; + }//end if + }//end for }//end if $start = $prev; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc index fd7c6e34fc..ff467bb2df 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc @@ -332,3 +332,27 @@ class CommentedOutCodeAtStartOfClassNoBlankLine { */ public $property = true; } + +class HasAttributes +{ + /** + * Short description of the member variable. + * + * @var array + */ + + #[ORM\Id]#[ORM\Column("integer")] + + private $id; + + + /** + * Short description of the member variable. + * + * @var array + */ + #[ORM\GeneratedValue] + + #[ORM\Column(ORM\Column::T_INTEGER)] + protected $height; +} diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed index b6ebcc9ab1..5c42b1eda1 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed @@ -319,3 +319,24 @@ class CommentedOutCodeAtStartOfClassNoBlankLine { */ public $property = true; } + +class HasAttributes +{ + + /** + * Short description of the member variable. + * + * @var array + */ + #[ORM\Id]#[ORM\Column("integer")] + private $id; + + /** + * Short description of the member variable. + * + * @var array + */ + #[ORM\GeneratedValue] + #[ORM\Column(ORM\Column::T_INTEGER)] + protected $height; +} diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.php index 08a11bca41..b43a972b76 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.php @@ -57,6 +57,10 @@ public function getErrorList() 288 => 1, 292 => 1, 333 => 1, + 342 => 1, + 346 => 1, + 353 => 1, + 357 => 1, ]; }//end getErrorList() From d35d320ffb5e26047891bdf874a67c48d1e57f75 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 29 Jul 2021 15:01:10 +1000 Subject: [PATCH 403/733] Changelog for attribute false positives (ref #3396, #3397, #3398, #3399) --- package.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package.xml b/package.xml index 100a4b098f..d5fa57dc2f 100644 --- a/package.xml +++ b/package.xml @@ -49,6 +49,13 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Squiz.Commenting.BlockComment now correctly applies rules for block comments after a short echo tag -- Thanks to Juliette Reinders Folmer for the patch + - Fixed false positives when using attributes in the following sniffs: + -- PEAR.Commenting.FunctionComment + -- Squiz.Commenting.InlineComment + -- Squiz.Commenting.BlockComment + -- Squiz.Commenting.VariableComment + -- Squiz.WhiteSpace.MemberVarSpacing + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line -- Thanks to Alessandro Chitolina for the patch -- Thanks to Juliette Reinders Folmer for the tests From be8fe7250adbdb7023822f7d39f3cd5721064301 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 23 Jul 2021 14:00:24 +0200 Subject: [PATCH 404/733] PHP 8.1: prevent deprecation notices about missing return types A plain run against PHP 8.1.0-beta1 currently results in the following deprecation notices: ``` Deprecated: Return type of PHP_CodeSniffer\Files\FileList::current() should either be compatible with Iterator::current(): mixed, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in path/to/PHP_CodeSniffer/src/Files/FileList.php on line 186 Deprecated: Return type of PHP_CodeSniffer\Files\FileList::next() should either be compatible with Iterator::next(): void, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in in path/to/PHP_CodeSniffer/src/Files/FileList.php on line 217 Deprecated: Return type of PHP_CodeSniffer\Files\FileList::key() should either be compatible with Iterator::key(): mixed, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in in path/to/PHP_CodeSniffer/src/Files/FileList.php on line 204 Deprecated: Return type of PHP_CodeSniffer\Files\FileList::valid() should either be compatible with Iterator::valid(): bool, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in in path/to/PHP_CodeSniffer/src/Files/FileList.php on line 230 Deprecated: Return type of PHP_CodeSniffer\Files\FileList::rewind() should either be compatible with Iterator::rewind(): void, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in in path/to/PHP_CodeSniffer/src/Files/FileList.php on line 173 Deprecated: Return type of PHP_CodeSniffer\Files\FileList::count() should either be compatible with Countable::count(): int, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in in path/to/PHP_CodeSniffer/src/Files/FileList.php on line 247 Deprecated: Return type of PHP_CodeSniffer\Filters\Filter::getChildren() should either be compatible with RecursiveFilterIterator::getChildren(): ?RecursiveFilterIterator, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in in path/to/PHP_CodeSniffer/src/Filters/Filter.php on line 135 Deprecated: Return type of PHP_CodeSniffer\Filters\Filter::accept() should either be compatible with FilterIterator::accept(): bool, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in path/to/PHP_CodeSniffer/src/Filters/Filter.php on line 93 ``` The run will still succeed. These deprecation notices relate to the [Return types for internal methods RFC](https://wiki.php.net/rfc/internal_method_return_types) in PHP 8.1 and in particular, the change made in PHP PR #7239, which adds return types to the various SPL Iterator interface methods. Basically, as of PHP 8.1, these methods in classes which implement the Iterator interface are expected to have a return type declared. The return type can be the same as used in PHP itself or a more specific type. This complies with the Liskov principle of covariance, which allows the return type of a child overloaded method to be more specific than that of the parent. The problem with this is that return types were only introduced in PHP 7.0 and therefore cannot be used as PHP_CodeSniffer 3.x has a minimum PHP version of 5.4. Luckily an attribute has been added to silence the deprecation warning. While attributes are a PHP 8.0+ feature, due to the choice of the `#[]` syntax, in PHP < 8.0, attributes will just be ignored and treated as comments, so there is no drawback to using the attribute. --- src/Files/FileList.php | 7 +++++++ src/Filters/Filter.php | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/Files/FileList.php b/src/Files/FileList.php index e889fc3d7e..66833a3ee4 100644 --- a/src/Files/FileList.php +++ b/src/Files/FileList.php @@ -16,6 +16,7 @@ use PHP_CodeSniffer\Ruleset; use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Exceptions\DeepExitException; +use ReturnTypeWillChange; class FileList implements \Iterator, \Countable { @@ -169,6 +170,7 @@ private function getFilterClass() * * @return void */ + #[ReturnTypeWillChange] public function rewind() { reset($this->files); @@ -181,6 +183,7 @@ public function rewind() * * @return \PHP_CodeSniffer\Files\File */ + #[ReturnTypeWillChange] public function current() { $path = key($this->files); @@ -198,6 +201,7 @@ public function current() * * @return void */ + #[ReturnTypeWillChange] public function key() { return key($this->files); @@ -210,6 +214,7 @@ public function key() * * @return void */ + #[ReturnTypeWillChange] public function next() { next($this->files); @@ -222,6 +227,7 @@ public function next() * * @return boolean */ + #[ReturnTypeWillChange] public function valid() { if (current($this->files) === false) { @@ -238,6 +244,7 @@ public function valid() * * @return integer */ + #[ReturnTypeWillChange] public function count() { return $this->numFiles; diff --git a/src/Filters/Filter.php b/src/Filters/Filter.php index 5bed499b25..a1246a2c52 100644 --- a/src/Filters/Filter.php +++ b/src/Filters/Filter.php @@ -12,6 +12,7 @@ use PHP_CodeSniffer\Util; use PHP_CodeSniffer\Ruleset; use PHP_CodeSniffer\Config; +use ReturnTypeWillChange; class Filter extends \RecursiveFilterIterator { @@ -89,6 +90,7 @@ public function __construct($iterator, $basedir, Config $config, Ruleset $rulese * * @return bool */ + #[ReturnTypeWillChange] public function accept() { $filePath = $this->current(); @@ -130,6 +132,7 @@ public function accept() * * @return \RecursiveIterator */ + #[ReturnTypeWillChange] public function getChildren() { $filterClass = get_called_class(); From 5be0b006204f0c33829ffc2f9582970d1302db21 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 13 Aug 2021 08:17:49 +1000 Subject: [PATCH 405/733] Changelog for #3333 (ref #3348) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index d5fa57dc2f..7bb369c19f 100644 --- a/package.xml +++ b/package.xml @@ -71,6 +71,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call - Fixed bug #3326 : Generic.Formatting.MultipleStatementAlignment error with const DEFAULT -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3333 : Squiz.Objects.ObjectInstantiation: null coalesce operators are not recognized as assignment + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3340 : Ensure interface and trait names are always tokenized as T_STRING -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3342 : PSR12/Squiz/PEAR standards all error on promoted properties with docblocks From 7c5b4e68088916368f046fa209f1f20c006ad93b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 16 Aug 2021 23:56:42 +0200 Subject: [PATCH 406/733] PHP 8.1: fix retokenization of "&" character In PHP < 8.1, the ampersand was tokenized as a simple token, basically just a plain string "&". As of PHP 8.1, due to the introduction of intersection types, PHP is introducing two new tokens for the ampersand. This PR proposes to "undo" the new PHP 8.1 tokenization of the ampersand in favour of the pre-existing tokenization of the character as `T_BITWISE_AND` as it has been in PHPCS since forever. Includes taking the new tokens into account for the "next token after a function keyword token should be a `T_STRING`" logic. This change is already covered extensively by the tests for the `File::isReference()` method, though that method will need updating for PHP 8.1 intersection types, just like the `File::getMethodParameters()` method will need adjusting too. This PR, in combination with PR 3400, fixes all current test failures on PHP 8.1. We may want to consider adding an extra `'is_reference'` array key index to the token array for these tokens, which would allow the `File::isReference()` method to resolve tokens on PHP 8.1 much more quickly and more easily. We also may want to have a think about whether we want to move to the PHP 8.1 tokenization in PHPCS 4.x. All the same, this PR should not be held back by a decision like that as, for now, it just needs to be fixed for PHPCS 3.x. --- src/Tokenizers/PHP.php | 22 +++++++++++++++++++++- src/Util/Tokens.php | 9 +++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 2730b03150..4e94cad2c5 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -646,6 +646,25 @@ protected function tokenize($string) }//end if }//end if + /* + PHP 8.1 introduced two dedicated tokens for the & character. + Retokenizing both of these to T_BITWISE_AND, which is the + token PHPCS already tokenized them as. + */ + + if ($tokenIsArray === true + && ($token[0] === T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + || $token[0] === T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG) + ) { + $finalTokens[$newStackPtr] = [ + 'code' => T_BITWISE_AND, + 'type' => 'T_BITWISE_AND', + 'content' => $token[1], + ]; + $newStackPtr++; + continue; + } + /* If this is a double quoted string, PHP will tokenize the whole thing which causes problems with the scope map when braces are @@ -1667,7 +1686,8 @@ protected function tokenize($string) 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 + || (isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false + && $tokens[$x][1] !== '&') ) { // Non-empty content. break; diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 56afd27919..f501c7f0a4 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -154,6 +154,15 @@ define('T_ATTRIBUTE', 'PHPCS_T_ATTRIBUTE'); } +// Some PHP 8.1 tokens, replicated for lower versions. +if (defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') === false) { + define('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', 'PHPCS_T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG'); +} + +if (defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') === false) { + define('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', 'PHPCS_T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG'); +} + // 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'); From 0c089a0c58d1a93564b54da2292ec24511d8812e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 27 Aug 2021 09:10:43 +1000 Subject: [PATCH 407/733] Fixed spelling in changelog becasue I can't stand seeing it --- package.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 7bb369c19f..987599a4f2 100644 --- a/package.xml +++ b/package.xml @@ -2436,8 +2436,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - The PHP 8.0 T_NULLSAFE_OBJECT_OPERATOR token has been made available for older versions -- Existing sniffs that check for T_OBJECT_OPERATOR have been modified to apply the same rules for the nullsafe object operator -- Thanks to Juliette Reinders Folmer for the patch - - The new method of PHP 8.0 tokenizing for namespaced names has been revert to thr pre 8.0 method - -- This maintains backwards compatible for existing sniffs on PHP 8.0 + - The new method of PHP 8.0 tokenizing for namespaced names has been reverted to the pre 8.0 method + -- This maintains backwards compatibility for existing sniffs on PHP 8.0 -- This change will be removed in PHPCS 4.0 as the PHP 8.0 tokenizing method will be backported for pre 8.0 versions -- Thanks to Juliette Reinders Folmer for the patch - Added support for changes to the way PHP 8.0 tokenizes hash comments From bca0861ad13ab25b545c98ae1313d2b3da9909c1 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 27 Aug 2021 09:14:32 +1000 Subject: [PATCH 408/733] Changelog for #3411 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index 987599a4f2..fe13feceb8 100644 --- a/package.xml +++ b/package.xml @@ -42,6 +42,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> --- Generic.WhiteSpace.DisallowSpaceIndent --- Generic.WhiteSpace.DisallowTabIndent -- Thanks to Juliette Reinders Folmer for the patch + - The new PHP 8.1 tokenisation for ampersands has been reverted to use the existing PHP_CodeSniffer method + -- The PHP 8.1 tokens T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG and T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG are unsued + -- Ampersands continue to be tokenized as T_BITWISE_AND for all PHP versions + -- Thanks to Juliette Reinders Folmer and Anna Filina for the patch - File::getMethodParameters() no longer incorrectly returns argument attributes in the type hint array index -- A new has_attributes array index is available and set to TRUE if the argument has attributes defined -- Thanks to Juliette Reinders Folmer for the patch From 046ff7f15cacf7e47d7854fcd185f2b38b613fcd Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 27 Aug 2021 09:15:17 +1000 Subject: [PATCH 409/733] Changelog for #3400 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index fe13feceb8..065c8406aa 100644 --- a/package.xml +++ b/package.xml @@ -89,6 +89,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3362 : Generic.WhiteSpace.ScopeIndent false positive for arrow functions inside arrays - Fixed bug #3384 : Squiz.Commenting.FileComment.SpacingAfterComment false positive on empty file + - Fixed bug #3400 : PHP 8.1: prevent deprecation notices about missing return types + -- Thanks to Juliette Reinders Folmer for the patch From 21cd2e48a7503fe74e71fdc4684618cdf68f1f03 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 27 Aug 2021 09:19:55 +1000 Subject: [PATCH 410/733] Fixed bug #3394 : Fix PHP 8.1 auto_detect_line_endings deprecation notice --- package.xml | 1 + src/Runner.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 065c8406aa..2b106a2d5f 100644 --- a/package.xml +++ b/package.xml @@ -89,6 +89,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3362 : Generic.WhiteSpace.ScopeIndent false positive for arrow functions inside arrays - Fixed bug #3384 : Squiz.Commenting.FileComment.SpacingAfterComment false positive on empty file + - Fixed bug #3394 : Fix PHP 8.1 auto_detect_line_endings deprecation notice - Fixed bug #3400 : PHP 8.1: prevent deprecation notices about missing return types -- Thanks to Juliette Reinders Folmer for the patch diff --git a/src/Runner.php b/src/Runner.php index f6ee5bc391..03238cd59d 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -291,7 +291,7 @@ public function init() // Ensure this option is enabled or else line endings will not always // be detected properly for files created on a Mac with the /r line ending. - ini_set('auto_detect_line_endings', true); + @ini_set('auto_detect_line_endings', true); // Disable the PCRE JIT as this caused issues with parallel running. ini_set('pcre.jit', false); From 3476f28b86d73ed361c87c6f6748356dd276c204 Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Fri, 3 Sep 2021 10:28:28 +0200 Subject: [PATCH 411/733] Remove unused variable --- src/Tokenizers/Tokenizer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index bf8bfce154..c79323ccd3 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -638,7 +638,6 @@ public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth } // Process the tab that comes after the content. - $lastCurrColumn = $currColumn; $tabNum++; // Move the pointer to the next tab stop. From 94abd09e25f270e1b78ca3710d7d761edfffea1b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Sep 2021 03:09:50 +0200 Subject: [PATCH 412/733] GH Actions: minor matrix simplification We can remove the `experimental` key in favour of checking against the PHP version in `continue-on-error`. --- .github/workflows/test.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5002127b6e..b136bcfd82 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,29 +20,20 @@ jobs: # Keys: # - custom_ini: Whether to run with specific custom ini settings to hit very specific # code conditions. - # - experimental: Whether the build is "allowed to fail". matrix: - php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] + php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] custom_ini: [false] - experimental: [false] include: # Builds running the basic tests with different PHP ini settings. - php: '5.5' custom_ini: true - experimental: false - php: '7.0' custom_ini: true - experimental: false - - # Nightly. - - php: '8.1' - custom_ini: false - experimental: true name: "PHP: ${{ matrix.php }} ${{ matrix.custom_ini && ' with custom ini settings' || '' }}" - continue-on-error: ${{ matrix.experimental }} + continue-on-error: ${{ matrix.php == '8.1' }} steps: - name: Checkout code From d5934ef400c5b1e70b4d912630163073a355c076 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Sep 2021 03:11:46 +0200 Subject: [PATCH 413/733] GH Actions: stablelize the `error_reporting` What is included in `E_ALL` can vary across PHP versions. Setting `error_reporting` to `-1` will ensure ALL errors are always shown, no matter what. --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b136bcfd82..d6a6162e8b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,11 +45,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=E_ALL, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On, asp_tags=On' + 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' elif [[ ${{ matrix.custom_ini }} == true && "${{ matrix.php }}" == '7.0' ]]; then - echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=E_ALL, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On' + echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=-1, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On' else - echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=E_ALL, display_errors=On' + echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=-1, display_errors=On' fi - name: Install PHP From 1a2d19fb0caccde07c2474d6e6c6540deb991612 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Sep 2021 03:12:35 +0200 Subject: [PATCH 414/733] GH Actions: minor tweaks to the conditions The `matrix.php` setting is a text string, so let's compare against it as a text string. --- .github/workflows/test.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6a6162e8b..ecec176de5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,12 +63,12 @@ jobs: # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies - name: Install Composer dependencies - normal - if: ${{ matrix.php < 8.0 }} + if: ${{ matrix.php < '8.0' }} uses: "ramsey/composer-install@v1" # 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 }} + if: ${{ matrix.php >= '8.0' }} uses: "ramsey/composer-install@v1" with: composer-options: --ignore-platform-reqs @@ -79,27 +79,27 @@ jobs: run: php bin/phpcs --config-set php_path php - name: 'PHPUnit: run the tests' - if: ${{ matrix.php != 8.1 }} + if: ${{ matrix.php != '8.1' }} run: vendor/bin/phpunit tests/AllTests.php # We need to ignore the config file so that PHPUnit doesn't try to read it. # The config file causes an error on PHP 8.1+ with PHPunit 7, but it's not needed here anyway # as we can pass all required settings in the phpunit command. - name: 'PHPUnit: run the tests on PHP nightly' - if: ${{ matrix.php == 8.1 }} + if: ${{ matrix.php == '8.1' }} run: vendor/bin/phpunit tests/AllTests.php --no-configuration --bootstrap=tests/bootstrap.php --dont-report-useless-tests - name: 'PHPCS: check code style without cache, no parallel' - if: ${{ matrix.custom_ini == false && matrix.php != 7.4 }} + if: ${{ matrix.custom_ini == false && matrix.php != '7.4' }} run: php bin/phpcs --no-cache --parallel=1 - name: 'PHPCS: check code style to show results in PR' - if: ${{ matrix.custom_ini == false && matrix.php == 7.4 }} + if: ${{ matrix.custom_ini == false && matrix.php == '7.4' }} continue-on-error: true run: php bin/phpcs --no-cache --parallel=1 --report-full --report-checkstyle=./phpcs-report.xml - name: Show PHPCS results in PR - if: ${{ matrix.custom_ini == false && matrix.php == 7.4 }} + if: ${{ matrix.custom_ini == false && matrix.php == '7.4' }} run: cs2pr ./phpcs-report.xml - name: 'Composer: validate config' From 7eef328f4703620a6e7ebecb66b5fab9ca5015e3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Sep 2021 04:18:18 +0200 Subject: [PATCH 415/733] PHP 8.1 | Runner::processChildProcs(): fix passing null to non-nullable bug The `DummyFile::__construct()` method expects a `string` as the first parameter, subsequently passes it to the `File::setContent()` method, which also expects a `string`, which then passes it to the `Common::detectLineEndings()` method, which again expects a `string`. Within the `Common::detectLineEndings()` method, the string is then passed to the PHP native `preg_match()` function, which (again) expects as `string` for the `$subject` parameter. When using PHPCS with parallel processing turned on, on a system which allows for parallel processing, the `DummyFile` class was, however, being instantiated in the `Runner::processChildProcs()` method with `null` as the content of the first parameter, which on PHP 8.1 leads to `preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated` deprecation notices. This deprecation notice was then caught as a `RuntimeException` in the `File::setContent()` method and passed on to the `File::addWarningOnLine()`, which called the `File::addMessage()` method. The `File::addMessage()` parameter then ran into trouble as the `$this->config` property has not been set up yet, as `DummyFile::__construct()` calls `File::setContent()` before calling the `parent::__construct()` method, leading to the `Undefined array key "cache"` notices which were making the build fail. Fixed now by passing an empty string instead of `null` as the `$content` for the `DummyFile` in the `Runner::processChildProcs()` method. This then leaves one more issue: the `DummyFile::__construct()` method contains a conditional code block which was only run when `$content !== null`. As this conditional code block is also not necessary to be run when an empty string would be passed to the constructor, changing this condition to `$content !== ''` makes that the condition can still match and maintains the efficiency tweak the condition was safeguarding. --- src/Files/DummyFile.php | 2 +- src/Runner.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Files/DummyFile.php b/src/Files/DummyFile.php index 3275bf0920..601430301d 100644 --- a/src/Files/DummyFile.php +++ b/src/Files/DummyFile.php @@ -38,7 +38,7 @@ public function __construct($content, Ruleset $ruleset, Config $config) // This is done by including: phpcs_input_file: [file path] // as the first line of content. $path = 'STDIN'; - if ($content !== null) { + if ($content !== '') { if (substr($content, 0, 17) === 'phpcs_input_file:') { $eolPos = strpos($content, $this->eolChar); $filename = trim(substr($content, 17, ($eolPos - 17))); diff --git a/src/Runner.php b/src/Runner.php index 03238cd59d..253ec91f9f 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -732,7 +732,7 @@ private function processChildProcs($childProcs) if (isset($childOutput) === false) { // The child process died, so the run has failed. - $file = new DummyFile(null, $this->ruleset, $this->config); + $file = new DummyFile('', $this->ruleset, $this->config); $file->setErrorCounts(1, 0, 0, 0); $this->printProgress($file, $totalBatches, $numProcessed); $success = false; @@ -756,7 +756,7 @@ private function processChildProcs($childProcs) } // Fake a processed file so we can print progress output for the batch. - $file = new DummyFile(null, $this->ruleset, $this->config); + $file = new DummyFile('', $this->ruleset, $this->config); $file->setErrorCounts( $childOutput['totalErrors'], $childOutput['totalWarnings'], From 074622bc355fa1b096a546dcb38abc622ea4491d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Sep 2021 04:59:16 +0200 Subject: [PATCH 416/733] PEAR/FunctionDeclaration: ignore multi-line promoted properties Similar to (multi-line) arrays in a multi-line function declaration, ignore (multi-line) attributes for the purposes of the indentation checks in this sniff. This does mean that inconsistent indentation within multi-line attributes/ for an attribute closer will not be fixed, but that should be handled by a dedicated attribute formatting sniff in my opinion. Includes unit tests. Fixes 3424 --- .../Functions/FunctionDeclarationSniff.php | 7 +++ .../Functions/FunctionDeclarationUnitTest.inc | 44 +++++++++++++++++++ .../FunctionDeclarationUnitTest.inc.fixed | 44 +++++++++++++++++++ .../Functions/FunctionDeclarationUnitTest.php | 2 + 4 files changed, 97 insertions(+) diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php index 1924f9c4df..bfb3e5ea72 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php @@ -507,6 +507,13 @@ public function processArgumentList($phpcsFile, $stackPtr, $indent, $type='funct $lastLine = $tokens[$i]['line']; continue; } + + if ($tokens[$i]['code'] === T_ATTRIBUTE) { + // Skip attributes as they have their own indentation rules. + $i = $tokens[$i]['attribute_closer']; + $lastLine = $tokens[$i]['line']; + continue; + } }//end for }//end processArgumentList() diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc index 0003ca0bda..d675d3f812 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc @@ -372,3 +372,47 @@ private string $private, ) { } } + +class ConstructorPropertyPromotionMultiLineAttributesOK + public function __construct( + #[ORM\ManyToOne( + Something: true, + SomethingElse: 'text', + )] + #[Groups([ + 'ArrayEntry', + 'Another.ArrayEntry', + ])] + #[MoreGroups( + [ + 'ArrayEntry', + 'Another.ArrayEntry', + ] + )] + private Type $property + ) { + // Do something. + } +} + +class ConstructorPropertyPromotionMultiLineAttributesIncorrectIndent + public function __construct( + #[ORM\ManyToOne( + Something: true, + SomethingElse: 'text', + )] + #[Groups([ + 'ArrayEntry', + 'Another.ArrayEntry', + ])] + #[MoreGroups( + [ + 'ArrayEntry', + 'Another.ArrayEntry', + ] + )] + private Type $property + ) { + // Do something. + } +} diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed index 0f8d39db06..dce5d6535d 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed @@ -370,3 +370,47 @@ class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIncorrectIndent ) { } } + +class ConstructorPropertyPromotionMultiLineAttributesOK + public function __construct( + #[ORM\ManyToOne( + Something: true, + SomethingElse: 'text', + )] + #[Groups([ + 'ArrayEntry', + 'Another.ArrayEntry', + ])] + #[MoreGroups( + [ + 'ArrayEntry', + 'Another.ArrayEntry', + ] + )] + private Type $property + ) { + // Do something. + } +} + +class ConstructorPropertyPromotionMultiLineAttributesIncorrectIndent + public function __construct( + #[ORM\ManyToOne( + Something: true, + SomethingElse: 'text', + )] + #[Groups([ + 'ArrayEntry', + 'Another.ArrayEntry', + ])] + #[MoreGroups( + [ + 'ArrayEntry', + 'Another.ArrayEntry', + ] + )] + private Type $property + ) { + // Do something. + } +} diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php index 161d2b34f5..a97ae55c30 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php @@ -97,6 +97,8 @@ public function getErrorList($testFile='FunctionDeclarationUnitTest.inc') 369 => 1, 370 => 1, 371 => 1, + 400 => 1, + 404 => 1, ]; } else { $errors = [ From 18c5d6f922bb5818594b0fa018bba19db2ab5ee9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Sep 2021 04:59:51 +0200 Subject: [PATCH 417/733] PEAR/FunctionDeclaration: minor efficiency tweak --- .../PEAR/Sniffs/Functions/FunctionDeclarationSniff.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php index bfb3e5ea72..bd59a7cd90 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php @@ -460,14 +460,14 @@ public function processArgumentList($phpcsFile, $stackPtr, $indent, $type='funct if ($tokens[$i]['code'] === T_WHITESPACE && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] ) { - // This is an empty line, so don't check the indent. - $foundIndent = $expectedIndent; - $error = 'Blank lines are not allowed in a multi-line '.$type.' declaration'; $fix = $phpcsFile->addFixableError($error, $i, 'EmptyLine'); if ($fix === true) { $phpcsFile->fixer->replaceToken($i, ''); } + + // This is an empty line, so don't check the indent. + continue; } else if ($tokens[$i]['code'] === T_WHITESPACE) { $foundIndent = $tokens[$i]['length']; } else if ($tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE) { From f0c1acd9df43c33bb3b88097f647baabb62db1ed Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 6 Sep 2021 08:04:22 +0200 Subject: [PATCH 418/733] WSL support: replace remaining calls to `is_readable()` ... with calls to `Common::isReadable()` as introduced in d56e167a5dea3550996f197b5d92b1418b1e3fab . Includes adding additional error silencing to the `Common::isReadable()` function as each of these PHP native functions will throw a warning upon failure. Co-authored-by: Graham Wharton Fixes 3388 May also fix 3412, but needs testing. --- src/Config.php | 5 +++-- src/Util/Common.php | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Config.php b/src/Config.php index 5e87e5dda0..0f5190987d 100644 --- a/src/Config.php +++ b/src/Config.php @@ -14,6 +14,7 @@ use PHP_CodeSniffer\Exceptions\DeepExitException; use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Util\Common; /** * Stores the configuration used to run PHPCS and PHPCBF. @@ -363,7 +364,7 @@ public function __construct(array $cliArgs=[], $dieOnUnknownArg=true) $lastDir = $currentDir; $currentDir = dirname($currentDir); - } while ($currentDir !== '.' && $currentDir !== $lastDir && @is_readable($currentDir) === true); + } while ($currentDir !== '.' && $currentDir !== $lastDir && Common::isReadable($currentDir) === true); }//end if if (defined('STDIN') === false @@ -1656,7 +1657,7 @@ public static function getAllConfigData() return []; } - if (is_readable($configFile) === false) { + if (Common::isReadable($configFile) === false) { $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL; throw new DeepExitException($error, 3); } diff --git a/src/Util/Common.php b/src/Util/Common.php index e60eec1df2..204f44de14 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -60,11 +60,11 @@ public static function isPharFile($path) */ public static function isReadable($path) { - if (is_readable($path) === true) { + if (@is_readable($path) === true) { return true; } - if (file_exists($path) === true && is_file($path) === true) { + if (@file_exists($path) === true && @is_file($path) === true) { $f = @fopen($path, 'rb'); if (fclose($f) === true) { return true; From 42147848794b1b251c7c158c356308bfe98fee40 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 27 Sep 2021 14:18:35 +1000 Subject: [PATCH 419/733] Changelog for #3425 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 89a7e686c8..1b0566706e 100644 --- a/package.xml +++ b/package.xml @@ -92,6 +92,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3394 : Fix PHP 8.1 auto_detect_line_endings deprecation notice - Fixed bug #3400 : PHP 8.1: prevent deprecation notices about missing return types -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3425 : PHP 8.1 | Runner::processChildProcs(): fix passing null to non-nullable bug + -- Thanks to Juliette Reinders Folmer for the patch From 824a41c4c1c6eb6f4dc181e9204f6d1ec1ab291b Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 28 Sep 2021 08:27:25 +1000 Subject: [PATCH 420/733] Corrected parse errors in tests (ref #3427) --- .../PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc | 2 ++ .../Tests/Functions/FunctionDeclarationUnitTest.inc.fixed | 2 ++ .../PEAR/Tests/Functions/FunctionDeclarationUnitTest.php | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc index d675d3f812..02e0a20dbf 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc @@ -374,6 +374,7 @@ private string $private, } class ConstructorPropertyPromotionMultiLineAttributesOK +{ public function __construct( #[ORM\ManyToOne( Something: true, @@ -396,6 +397,7 @@ class ConstructorPropertyPromotionMultiLineAttributesOK } class ConstructorPropertyPromotionMultiLineAttributesIncorrectIndent +{ public function __construct( #[ORM\ManyToOne( Something: true, diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed index dce5d6535d..0d67e9f758 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed @@ -372,6 +372,7 @@ class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIncorrectIndent } class ConstructorPropertyPromotionMultiLineAttributesOK +{ public function __construct( #[ORM\ManyToOne( Something: true, @@ -394,6 +395,7 @@ class ConstructorPropertyPromotionMultiLineAttributesOK } class ConstructorPropertyPromotionMultiLineAttributesIncorrectIndent +{ public function __construct( #[ORM\ManyToOne( Something: true, diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php index a97ae55c30..01ab3e8482 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php @@ -97,8 +97,8 @@ public function getErrorList($testFile='FunctionDeclarationUnitTest.inc') 369 => 1, 370 => 1, 371 => 1, - 400 => 1, - 404 => 1, + 402 => 1, + 406 => 1, ]; } else { $errors = [ From ac912deb6362e1a9d8926f85f6d9fdb31973bb0a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 28 Sep 2021 08:28:07 +1000 Subject: [PATCH 421/733] Changelog for #3424 (ref #3427) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 1b0566706e..8020c228b9 100644 --- a/package.xml +++ b/package.xml @@ -92,6 +92,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3394 : Fix PHP 8.1 auto_detect_line_endings deprecation notice - Fixed bug #3400 : PHP 8.1: prevent deprecation notices about missing return types -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3424 : PHPCS fails when using PHP 8 Constructor property promotion with attributes + -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3425 : PHP 8.1 | Runner::processChildProcs(): fix passing null to non-nullable bug -- Thanks to Juliette Reinders Folmer for the patch From 9362d14ba3dbd366936652ae35d1530c08635c34 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 6 Oct 2021 10:51:40 +0200 Subject: [PATCH 422/733] Tokenizer/PHP: fix ? tokenization after attribute In a select set of circumstance, when a `?` would follow an attribute with a nested array in it, the `?` would be tokenized as `T_INLINE_THEN`, not `T_NULLABLE`. Fixed now. Includes unit test via the `PSR12.Operators.OperatorSpacing` sniff. Note: Ternary `?` vs nullable (and ternary `:` vs colon) should really get a full set of unit tests, but I don't currently have the time to set that up. Fixes 3445 --- .../Tests/Operators/OperatorSpacingUnitTest.inc | 12 ++++++++++++ .../Operators/OperatorSpacingUnitTest.inc.fixed | 12 ++++++++++++ src/Tokenizers/PHP.php | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc index 9ae9432118..c067e6a2a8 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc @@ -63,3 +63,15 @@ $fn = fn(array & $one) => 1; $fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); function issue3267(string|int ...$values) {} + +function setDefault(#[ImportValue( + constraints: [ + [ + Assert\Type::class, + ['type' => 'bool'], + ], + ] + )] ?bool $value = null): void + { + // Do something + } diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed index 504ae43e5f..76764291fa 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.inc.fixed @@ -63,3 +63,15 @@ $fn = fn(array & $one) => 1; $fn = static fn(DateTime $a, DateTime $b): int => -($a->getTimestamp() <=> $b->getTimestamp()); function issue3267(string|int ...$values) {} + +function setDefault(#[ImportValue( + constraints: [ + [ + Assert\Type::class, + ['type' => 'bool'], + ], + ] + )] ?bool $value = null): void + { + // Do something + } diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 4f9e016167..1ba26363af 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1576,7 +1576,7 @@ protected function tokenize($string) && isset(Util\Tokens::$emptyTokens[$tokenType]) === false ) { // Found the previous non-empty token. - if ($tokenType === ':' || $tokenType === ',') { + if ($tokenType === ':' || $tokenType === ',' || $tokenType === T_ATTRIBUTE_END) { $newToken['code'] = T_NULLABLE; $newToken['type'] = 'T_NULLABLE'; From 769aa73235e95845474a2c9f017cbb76be4d2fbb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 6 Oct 2021 12:20:49 +0200 Subject: [PATCH 423/733] Squiz/ScopeClosingBrace: bug fix - prevent removing inline HTML when fixing When non-empty inline HTML is on the same line, before the scope closing, the sniff would incorrectly throw the `Closing brace indented incorrectly; expected %s spaces, found %s` error, instead of the `Closing brace must be on a line by itself` error. This would ultimately lead to the fixer removing the non-empty inline HTML, potentially breaking HTML display/structure. Fixed now. Includes unit test. Fixes 3422 --- .../Sniffs/WhiteSpace/ScopeClosingBraceSniff.php | 13 +++++++++++-- .../Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc | 6 ++++++ .../WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed | 7 +++++++ .../Tests/WhiteSpace/ScopeClosingBraceUnitTest.php | 1 + 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php index f68613932d..fd03875347 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php @@ -65,11 +65,20 @@ public function process(File $phpcsFile, $stackPtr) // Check that the closing brace is on it's own line. $lastContent = $phpcsFile->findPrevious([T_INLINE_HTML, T_WHITESPACE, T_OPEN_TAG], ($scopeEnd - 1), $scopeStart, true); - if ($tokens[$lastContent]['line'] === $tokens[$scopeEnd]['line']) { + for ($lineStart = $scopeEnd; $tokens[$lineStart]['column'] > 1; $lineStart--); + + if ($tokens[$lastContent]['line'] === $tokens[$scopeEnd]['line'] + || ($tokens[$lineStart]['code'] === T_INLINE_HTML + && trim($tokens[$lineStart]['content']) !== '') + ) { $error = 'Closing brace must be on a line by itself'; $fix = $phpcsFile->addFixableError($error, $scopeEnd, 'ContentBefore'); if ($fix === true) { - $phpcsFile->fixer->addNewlineBefore($scopeEnd); + if ($tokens[$lastContent]['line'] === $tokens[$scopeEnd]['line']) { + $phpcsFile->fixer->addNewlineBefore($scopeEnd); + } else { + $phpcsFile->fixer->addNewlineBefore(($lineStart + 1)); + } } return; diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc index b71c0be729..ecd80b5504 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc @@ -114,3 +114,9 @@ $match = match ($test) { 1 => 'a', 2 => 'b' }; + +?> + +
+ +
diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed index b5d87d3d56..a30dfd0e6f 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed @@ -116,3 +116,10 @@ $match = match ($test) { 1 => 'a', 2 => 'b' }; + +?> + +
+ +
+ diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php index 668332562e..0df8603f86 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/ScopeClosingBraceUnitTest.php @@ -33,6 +33,7 @@ public function getErrorList() 102 => 1, 111 => 1, 116 => 1, + 122 => 1, ]; }//end getErrorList() From ca4d10066278a6021c588668a311d4a99916381f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 11 Oct 2021 14:55:01 +1100 Subject: [PATCH 424/733] Changelog for #3455 (ref #3446) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 8020c228b9..50b44b2cf4 100644 --- a/package.xml +++ b/package.xml @@ -96,6 +96,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patch - Fixed bug #3425 : PHP 8.1 | Runner::processChildProcs(): fix passing null to non-nullable bug -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3445 : Nullable parameter after attribute incorrectly tokenized as ternary operator + -- Thanks to Juliette Reinders Folmer for the patch From f268ca40d54617c6e06757f83f699775c9b3ff2e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 11 Oct 2021 15:00:11 +1100 Subject: [PATCH 425/733] Prepare for 3.6.1 release --- package.xml | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 50b44b2cf4..b84d69730c 100644 --- a/package.xml +++ b/package.xml @@ -14,8 +14,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> gsherwood@squiz.net yes - 2021-04-09 - + 2021-10-11 + 3.6.1 3.6.1 @@ -2263,6 +2263,92 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 3.6.1 + 3.6.1 + + + stable + stable + + 2021-10-11 + BSD License + + - PHPCS annotations can now be specified using hash-style comments + -- Previously, only slash-style and block-style comments could be used to do things like disable errors + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed an issue where some sniffs would not run on PHP files that only used the short echo tag + -- The following sniffs were affected: + --- Generic.Files.ExecutableFile + --- Generic.Files.LowercasedFilename + --- Generic.Files.LineEndings + --- Generic.Files.EndFileNewline + --- Generic.Files.EndFileNoNewline + --- Generic.PHP.ClosingPHPTag + --- Generic.PHP.Syntax + --- Generic.VersionControl.GitMergeConflict + --- Generic.WhiteSpace.DisallowSpaceIndent + --- Generic.WhiteSpace.DisallowTabIndent + -- Thanks to Juliette Reinders Folmer for the patch + - The new PHP 8.1 tokenisation for ampersands has been reverted to use the existing PHP_CodeSniffer method + -- The PHP 8.1 tokens T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG and T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG are unsued + -- Ampersands continue to be tokenized as T_BITWISE_AND for all PHP versions + -- Thanks to Juliette Reinders Folmer and Anna Filina for the patch + - File::getMethodParameters() no longer incorrectly returns argument attributes in the type hint array index + -- A new has_attributes array index is available and set to TRUE if the argument has attributes defined + -- Thanks to Juliette Reinders Folmer for the patch + - Generic.NamingConventions.ConstructorName no longer throws deprecation notices on PHP 8.1 + -- Thanks to Juliette Reinders Folmer for the patch + - Squiz.Commenting.BlockComment now correctly applies rules for block comments after a short echo tag + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed false positives when using attributes in the following sniffs: + -- PEAR.Commenting.FunctionComment + -- Squiz.Commenting.InlineComment + -- Squiz.Commenting.BlockComment + -- Squiz.Commenting.VariableComment + -- Squiz.WhiteSpace.MemberVarSpacing + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line + -- Thanks to Alessandro Chitolina for the patch + -- Thanks to Juliette Reinders Folmer for the tests + - Fixed bug #3296 : PSR2.ControlStructures.SwitchDeclaration takes phpcs:ignore as content of case body + - Fixed bug #3297 : PSR2.ControlStructures.SwitchDeclaration.TerminatingComment does not handle try/finally blocks + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3302 : PHP 8.0 | Tokenizer/PHP: bugfix for union types using namespace operator + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3303 : findStartOfStatement() doesn't work with T_OPEN_TAG_WITH_ECHO + - Fixed bug #3316 : Arrow function not tokenized correctly when using null in union type + - Fixed bug #3317 : Problem with how phpcs handles ignored files when running in parallel + -- Thanks to Emil Andersson for the patch + - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call + - Fixed bug #3326 : Generic.Formatting.MultipleStatementAlignment error with const DEFAULT + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3333 : Squiz.Objects.ObjectInstantiation: null coalesce operators are not recognized as assignment + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3340 : Ensure interface and trait names are always tokenized as T_STRING + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3342 : PSR12/Squiz/PEAR standards all error on promoted properties with docblocks + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3345 : IF statement with no braces and double catch turned into syntax error by auto-fixer + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3352 : PSR2.ControlStructures.SwitchDeclaration can remove comments on the same line as the case statement while fixing + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3357 : Generic.Functions.OpeningFunctionBraceBsdAllman removes return type when additional lines are present + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3362 : Generic.WhiteSpace.ScopeIndent false positive for arrow functions inside arrays + - Fixed bug #3384 : Squiz.Commenting.FileComment.SpacingAfterComment false positive on empty file + - Fixed bug #3394 : Fix PHP 8.1 auto_detect_line_endings deprecation notice + - Fixed bug #3400 : PHP 8.1: prevent deprecation notices about missing return types + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3424 : PHPCS fails when using PHP 8 Constructor property promotion with attributes + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3425 : PHP 8.1 | Runner::processChildProcs(): fix passing null to non-nullable bug + -- Thanks to Juliette Reinders Folmer for the patch + - Fixed bug #3445 : Nullable parameter after attribute incorrectly tokenized as ternary operator + -- Thanks to Juliette Reinders Folmer for the patch + + 3.6.0 From 76faf86949bb60531f78fcff53aaeb2e61653a50 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 21 Oct 2021 15:47:56 +0200 Subject: [PATCH 426/733] PSR12/ClassInstantiation: bug fix for attributes with anonymous classes The `class` keyword was correctly tokenized as `T_ANON_CLASS`, even when there is an attribute between the `new` and the `class` keyword, however, the `PSR12.Classes.ClassInstantiation` sniff did not take that possibility into account, leading to false positives. This commit: * Adds code to skip over attributes. Note: I've elected to skip over attributes, but as these attributes AFAICS can only be added in combination with an anonymous class and anonymous classes are ignored by this sniff, it would also be a valid option to bow out of the sniff when an attribute is encountered. As this would be inconsistent with how the `T_ANON_CLASS` token was handled so far, I've not gone with that option. * Adds some additional extra guard code to prevent future false positives for when the class name could not be determined. Includes unit test. Fixes 3456 --- .../Sniffs/Classes/ClassInstantiationSniff.php | 15 ++++++++++++++- .../Tests/Classes/ClassInstantiationUnitTest.inc | 6 ++++++ .../Classes/ClassInstantiationUnitTest.inc.fixed | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php index 804ecfe1c7..e4ebd576a3 100644 --- a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php +++ b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php @@ -63,6 +63,14 @@ public function process(File $phpcsFile, $stackPtr) continue; } + // Skip over potential attributes for anonymous classes. + if ($tokens[$i]['code'] === T_ATTRIBUTE + && isset($tokens[$i]['attribute_closer']) === true + ) { + $i = $tokens[$i]['attribute_closer']; + continue; + } + if ($tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET || $tokens[$i]['code'] === T_OPEN_CURLY_BRACKET ) { @@ -72,7 +80,7 @@ public function process(File $phpcsFile, $stackPtr) $classNameEnd = $i; break; - } + }//end for if ($classNameEnd === null) { return; @@ -88,6 +96,11 @@ public function process(File $phpcsFile, $stackPtr) return; } + if ($classNameEnd === $stackPtr) { + // Failed to find the class name. + return; + } + $error = 'Parentheses must be used when instantiating a new class'; $fix = $phpcsFile->addFixableError($error, $stackPtr, 'MissingParentheses'); if ($fix === true) { diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc index ca0dc3614a..d933ee273e 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc @@ -36,3 +36,9 @@ $a = new ${$varHoldingClassName}; $class = new $obj?->classname(); $class = new $obj?->classname; $class = new ${$obj?->classname}; + +// Issue 3456. +// Anon classes should be skipped, even when there is an attribute between the new and the class keywords. +$anonWithAttribute = new #[SomeAttribute('summary')] class { + public const SOME_STUFF = 'foo'; +}; diff --git a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed index a4d209cc16..02e3544fa6 100644 --- a/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed @@ -36,3 +36,9 @@ $a = new ${$varHoldingClassName}(); $class = new $obj?->classname(); $class = new $obj?->classname(); $class = new ${$obj?->classname}(); + +// Issue 3456. +// Anon classes should be skipped, even when there is an attribute between the new and the class keywords. +$anonWithAttribute = new #[SomeAttribute('summary')] class { + public const SOME_STUFF = 'foo'; +}; From 1f392460dbe265edc09e633dfcb14a1b534e358e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 30 Oct 2021 15:25:36 +0200 Subject: [PATCH 427/733] Generic/MultipleStatementAlignment: bug fix for closure params with defaults For use within the loop in the `checkAlignment()` method, closures are explicitly removed for the `$scopeOpeners` array as they can be (and often are) assigned to a variable. However, a closure which contained a parameter with a default value would take the equal sign for the default value as the start of a new block. The `process()` method already contained a safeguard against this, but it's the `checkAlignment()` method which is being called recursively, so this check was not executed for those recursive calls. Calling the `process()` method recursively would be troublesome as the returned stackPtr is incremented by one in the `process()` method, so moving the initial check from the `process()` method to the `checkAlignment()` method seemed a reasonable solution. There will probably be other ways in which this could be solved and I'm not 100% sure this is the best solution, but it is a solution which works without breaking any of the existing unit tests. Includes a few additional unit tests to further safeguard against this issue and make sure nothing new is broken. --- .../MultipleStatementAlignmentSniff.php | 36 +++++++++---------- .../MultipleStatementAlignmentUnitTest.inc | 24 +++++++++++++ ...ltipleStatementAlignmentUnitTest.inc.fixed | 24 +++++++++++++ .../MultipleStatementAlignmentUnitTest.php | 3 ++ 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php index b1b3cc64e3..802e594485 100644 --- a/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php +++ b/src/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php @@ -80,25 +80,6 @@ public function register() */ public function process(File $phpcsFile, $stackPtr) { - $tokens = $phpcsFile->getTokens(); - - // Ignore assignments used in a condition, like an IF or FOR. - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { - // If the parenthesis is on the same line as the assignment, - // then it should be ignored as it is specifically being grouped. - $parens = $tokens[$stackPtr]['nested_parenthesis']; - $lastParen = array_pop($parens); - if ($tokens[$lastParen]['line'] === $tokens[$stackPtr]['line']) { - return; - } - - foreach ($tokens[$stackPtr]['nested_parenthesis'] as $start => $end) { - if (isset($tokens[$start]['parenthesis_owner']) === true) { - return; - } - } - } - $lastAssign = $this->checkAlignment($phpcsFile, $stackPtr); return ($lastAssign + 1); @@ -120,6 +101,23 @@ public function checkAlignment($phpcsFile, $stackPtr, $end=null) { $tokens = $phpcsFile->getTokens(); + // Ignore assignments used in a condition, like an IF or FOR or closure param defaults. + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { + // If the parenthesis is on the same line as the assignment, + // then it should be ignored as it is specifically being grouped. + $parens = $tokens[$stackPtr]['nested_parenthesis']; + $lastParen = array_pop($parens); + if ($tokens[$lastParen]['line'] === $tokens[$stackPtr]['line']) { + return $stackPtr; + } + + foreach ($tokens[$stackPtr]['nested_parenthesis'] as $start => $end) { + if (isset($tokens[$start]['parenthesis_owner']) === true) { + return $stackPtr; + } + } + } + $assignments = []; $prevAssign = null; $lastLine = $tokens[$stackPtr]['line']; diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc index e42225e89a..ec71d4b670 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc @@ -478,3 +478,27 @@ class Test protected static $thisIsAReallyLongVariableName = []; } + +// Issue #3460. +function issue3460_invalid() { + $a = static function ($variables = false) use ($foo) { + return $variables; + }; + $b = $a; +} + +function issue3460_valid() { + $a = static function ($variables = false) use ($foo) { + return $variables; + }; + $b = $a; +} + +function makeSureThatAssignmentWithinClosureAreStillHandled() { + $a = static function ($variables = []) use ($temp) { + $a = 'foo'; + $bar = 'bar'; + $longer = 'longer'; + return $variables; + }; +} diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed index 5d5516d1fa..137a8ef9af 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.inc.fixed @@ -478,3 +478,27 @@ class Test protected static $thisIsAReallyLongVariableName = []; } + +// Issue #3460. +function issue3460_invalid() { + $a = static function ($variables = false) use ($foo) { + return $variables; + }; + $b = $a; +} + +function issue3460_valid() { + $a = static function ($variables = false) use ($foo) { + return $variables; + }; + $b = $a; +} + +function makeSureThatAssignmentWithinClosureAreStillHandled() { + $a = static function ($variables = []) use ($temp) { + $a = 'foo'; + $bar = 'bar'; + $longer = 'longer'; + return $variables; + }; +} diff --git a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php index eef66a5d04..23f2f9a780 100644 --- a/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php +++ b/src/Standards/Generic/Tests/Formatting/MultipleStatementAlignmentUnitTest.php @@ -118,6 +118,9 @@ public function getWarningList($testFile='MultipleStatementAlignmentUnitTest.inc 442 => 1, 443 => 1, 454 => 1, + 487 => 1, + 499 => 1, + 500 => 1, ]; break; case 'MultipleStatementAlignmentUnitTest.js': From f1fc1bf5976a9cc55bddc173eba83c9925f22197 Mon Sep 17 00:00:00 2001 From: Andy Postnikov Date: Tue, 12 Oct 2021 06:52:05 +0300 Subject: [PATCH 428/733] Fix deprecation for PHP 8.1 Closes #3448 --- src/Util/Timing.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/Timing.php b/src/Util/Timing.php index cf27dcfe36..95ee85216d 100644 --- a/src/Util/Timing.php +++ b/src/Util/Timing.php @@ -64,7 +64,7 @@ public static function printRunTime($force=false) if ($time > 60000) { $mins = floor($time / 60000); - $secs = round((($time % 60000) / 1000), 2); + $secs = round((fmod($time, 60000) / 1000), 2); $time = $mins.' mins'; if ($secs !== 0) { $time .= ", $secs secs"; From 8dd16e8e17dae02fb1fd6983615f1eb13c0ac2bb Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 1 Nov 2021 11:54:26 +1100 Subject: [PATCH 429/733] Changelog for #3448 (ref #3449) --- package.xml | 78 +++----------------------------------------------- src/Config.php | 2 +- 2 files changed, 5 insertions(+), 75 deletions(-) diff --git a/package.xml b/package.xml index b84d69730c..dd46204920 100644 --- a/package.xml +++ b/package.xml @@ -17,8 +17,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2021-10-11 - 3.6.1 - 3.6.1 + 3.6.2 + 3.6.2 stable @@ -26,78 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - - PHPCS annotations can now be specified using hash-style comments - -- Previously, only slash-style and block-style comments could be used to do things like disable errors - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed an issue where some sniffs would not run on PHP files that only used the short echo tag - -- The following sniffs were affected: - --- Generic.Files.ExecutableFile - --- Generic.Files.LowercasedFilename - --- Generic.Files.LineEndings - --- Generic.Files.EndFileNewline - --- Generic.Files.EndFileNoNewline - --- Generic.PHP.ClosingPHPTag - --- Generic.PHP.Syntax - --- Generic.VersionControl.GitMergeConflict - --- Generic.WhiteSpace.DisallowSpaceIndent - --- Generic.WhiteSpace.DisallowTabIndent - -- Thanks to Juliette Reinders Folmer for the patch - - The new PHP 8.1 tokenisation for ampersands has been reverted to use the existing PHP_CodeSniffer method - -- The PHP 8.1 tokens T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG and T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG are unsued - -- Ampersands continue to be tokenized as T_BITWISE_AND for all PHP versions - -- Thanks to Juliette Reinders Folmer and Anna Filina for the patch - - File::getMethodParameters() no longer incorrectly returns argument attributes in the type hint array index - -- A new has_attributes array index is available and set to TRUE if the argument has attributes defined - -- Thanks to Juliette Reinders Folmer for the patch - - Generic.NamingConventions.ConstructorName no longer throws deprecation notices on PHP 8.1 - -- Thanks to Juliette Reinders Folmer for the patch - - Squiz.Commenting.BlockComment now correctly applies rules for block comments after a short echo tag - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed false positives when using attributes in the following sniffs: - -- PEAR.Commenting.FunctionComment - -- Squiz.Commenting.InlineComment - -- Squiz.Commenting.BlockComment - -- Squiz.Commenting.VariableComment - -- Squiz.WhiteSpace.MemberVarSpacing - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3294 : Bug in attribute tokenization when content contains PHP end token or attribute closer on new line - -- Thanks to Alessandro Chitolina for the patch - -- Thanks to Juliette Reinders Folmer for the tests - - Fixed bug #3296 : PSR2.ControlStructures.SwitchDeclaration takes phpcs:ignore as content of case body - - Fixed bug #3297 : PSR2.ControlStructures.SwitchDeclaration.TerminatingComment does not handle try/finally blocks - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3302 : PHP 8.0 | Tokenizer/PHP: bugfix for union types using namespace operator - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3303 : findStartOfStatement() doesn't work with T_OPEN_TAG_WITH_ECHO - - Fixed bug #3316 : Arrow function not tokenized correctly when using null in union type - - Fixed bug #3317 : Problem with how phpcs handles ignored files when running in parallel - -- Thanks to Emil Andersson for the patch - - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call - - Fixed bug #3326 : Generic.Formatting.MultipleStatementAlignment error with const DEFAULT - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3333 : Squiz.Objects.ObjectInstantiation: null coalesce operators are not recognized as assignment - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3340 : Ensure interface and trait names are always tokenized as T_STRING - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3342 : PSR12/Squiz/PEAR standards all error on promoted properties with docblocks - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3345 : IF statement with no braces and double catch turned into syntax error by auto-fixer - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3352 : PSR2.ControlStructures.SwitchDeclaration can remove comments on the same line as the case statement while fixing - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3357 : Generic.Functions.OpeningFunctionBraceBsdAllman removes return type when additional lines are present - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3362 : Generic.WhiteSpace.ScopeIndent false positive for arrow functions inside arrays - - Fixed bug #3384 : Squiz.Commenting.FileComment.SpacingAfterComment false positive on empty file - - Fixed bug #3394 : Fix PHP 8.1 auto_detect_line_endings deprecation notice - - Fixed bug #3400 : PHP 8.1: prevent deprecation notices about missing return types - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3424 : PHPCS fails when using PHP 8 Constructor property promotion with attributes - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3425 : PHP 8.1 | Runner::processChildProcs(): fix passing null to non-nullable bug - -- Thanks to Juliette Reinders Folmer for the patch - - Fixed bug #3445 : Nullable parameter after attribute incorrectly tokenized as ternary operator - -- Thanks to Juliette Reinders Folmer 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 diff --git a/src/Config.php b/src/Config.php index 5e87e5dda0..6e16d7fb14 100644 --- a/src/Config.php +++ b/src/Config.php @@ -79,7 +79,7 @@ class Config * * @var string */ - const VERSION = '3.6.1'; + const VERSION = '3.6.2'; /** * Package stability; either stable, beta or alpha. From 2f6db41f352dc4daf7920c3c7fa1aaf9b247b94c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 1 Nov 2021 12:09:54 +1100 Subject: [PATCH 430/733] Changelog for #3422 (ref #3447) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index dd46204920..b936299a63 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - 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 #3448 : PHP 8.1 deprecation notice while generating running time value -- Thanks to Juliette Reinders Folmer and Andy Postnikov for the patch From 57d8259a5efa701b0af50df54dd74ff45d0ac884 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 1 Nov 2021 13:05:46 +1100 Subject: [PATCH 431/733] Changelog for #3388 (ref #3428) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index b936299a63..5daac316e0 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - 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 #3448 : PHP 8.1 deprecation notice while generating running time value From 5fb9b643da9e0420f979b0f87fdac4667225d621 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 2 Nov 2021 09:30:24 +1100 Subject: [PATCH 432/733] Changelog for #3370 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 5daac316e0..d1d4464b77 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,8 @@ 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 From 2a3eef659421714947162ff3b03e80edde18bab1 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 20 Nov 2021 18:50:37 +0100 Subject: [PATCH 433/733] Unit tests for converting an explicit PHP 8.1 octal reference (e.g."0o777") from two separate T_NUMBER and T_STRING tokens into a single T_NUMBER token with the correct value --- .../Tokenizer/ExplicitOctalNotationTest.inc | 4 ++ .../Tokenizer/ExplicitOctalNotationTest.php | 63 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/Core/Tokenizer/ExplicitOctalNotationTest.inc create mode 100644 tests/Core/Tokenizer/ExplicitOctalNotationTest.php diff --git a/tests/Core/Tokenizer/ExplicitOctalNotationTest.inc b/tests/Core/Tokenizer/ExplicitOctalNotationTest.inc new file mode 100644 index 0000000000..4724229b1e --- /dev/null +++ b/tests/Core/Tokenizer/ExplicitOctalNotationTest.inc @@ -0,0 +1,4 @@ + + * @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 ExplicitOctalNotationTest extends AbstractMethodUnitTest +{ + + + /** + * Test that explicitly-defined octal values are tokenized as a single number and not as a number and a string. + * + * @param array $testData The data required for the specific test case. + * + * @dataProvider dataExplicitOctalNotation + * @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize + * + * @return void + */ + public function testExplicitOctalNotation($testData) + { + $tokens = self::$phpcsFile->getTokens(); + + $number = $this->getTargetToken($testData['marker'], [T_LNUMBER, T_DNUMBER, T_STRING]); + + $this->assertSame(constant($testData['type']), $tokens[$number]['code']); + $this->assertSame($testData['type'], $tokens[$number]['type']); + $this->assertSame($testData['value'], $tokens[$number]['content']); + + }//end testExplicitOctalNotation() + + + /** + * Data provider. + * + * @see testExplicitOctalNotation() + * + * @return array + */ + public function dataExplicitOctalNotation() + { + return [ + [ + [ + 'marker' => '/* testExplicitOctal declaration */', + 'type' => 'T_LNUMBER', + 'value' => '0o137041', + ], + ], + ]; + + }//end dataExplicitOctalNotation() + + +}//end class From 8ada8fc33d219cd6d414e0c96066b568d53d5aa0 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 20 Nov 2021 18:52:45 +0100 Subject: [PATCH 434/733] Tokenizer code changes for converting an explicit PHP 8.1 octal reference (e.g."0o777") from two separate T_NUMBER and T_STRING tokens into a single T_NUMBER token with the correct value --- src/Tokenizers/PHP.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 1ba26363af..5115425c9a 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -553,6 +553,23 @@ protected function tokenize($string) $lastNotEmptyToken = ($newStackPtr - 1); } + /* + For Explicit Octal Notation prior to PHP 8.1 we need to combine the + T_NUMBER 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 + && $tokens[($stackPtr + 1)][1][0] === 'o') + ) { + $token[1] .= $tokens[($stackPtr + 1)][1]; + $tokens[($stackPtr + 1)] = ''; + } + /* If we are using \r\n newline characters, the \r and \n are sometimes split over two tokens. This normally occurs after comments. We need @@ -1330,6 +1347,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 From 8a6979d51dfd8a09a09d47f26feddde431f01b7e Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 20 Nov 2021 18:54:51 +0100 Subject: [PATCH 435/733] Unit tests to verify that the PHP 7.4 underscore in numeric values still works correctly with Explicit octal values when they have to be backfilled --- tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc | 3 +++ tests/Core/Tokenizer/BackfillNumericSeparatorTest.php | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc index eef53f593b..fb7ab2fd6f 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc @@ -34,6 +34,9 @@ $foo = 0b0101_1111; /* testOctal */ $foo = 0137_041; +/* testExplicitOctal (Introduced in PHP 8.1) */ +$foo = 0o137_041; + /* testIntMoreThanMax */ $foo = 10_223_372_036_854_775_807; diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php index ee4275a214..3268245f56 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php @@ -132,6 +132,13 @@ public function dataTestBackfill() 'value' => '0137_041', ], ], + [ + [ + 'marker' => '/* testExplicitOctal (Introduced in PHP 8.1) */', + 'type' => 'T_LNUMBER', + 'value' => '0o137_041', + ], + ], [ [ 'marker' => '/* testIntMoreThanMax */', From 0033bb04e4c546380b1a86129c7379bf0ec75c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Fri, 19 Nov 2021 20:13:48 +0100 Subject: [PATCH 436/733] PHP 8.1: Added support for "readonly" keyword --- package.xml | 6 + src/Tokenizers/PHP.php | 67 ++++++++ src/Util/Tokens.php | 4 + tests/Core/Tokenizer/ReadonlyTest.inc | 92 +++++++++++ tests/Core/Tokenizer/ReadonlyTest.php | 224 ++++++++++++++++++++++++++ 5 files changed, 393 insertions(+) create mode 100644 tests/Core/Tokenizer/ReadonlyTest.inc create mode 100644 tests/Core/Tokenizer/ReadonlyTest.php diff --git a/package.xml b/package.xml index d1d4464b77..ace47935ad 100644 --- a/package.xml +++ b/package.xml @@ -141,6 +141,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2092,6 +2094,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2182,6 +2186,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 1ba26363af..77a95ec270 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -393,6 +393,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, @@ -2812,6 +2813,72 @@ protected function processAdditional() $this->tokens[$x]['code'] = T_STRING; $this->tokens[$x]['type'] = 'T_STRING'; } + } else if (($this->tokens[$i]['code'] === T_STRING && strtolower($this->tokens[$i]['content']) === 'readonly') + || $this->tokens[$i]['code'] === T_READONLY + ) { + /* + "readonly" keyword support + PHP < 8.1: Converts T_STRING to T_READONLY + PHP >= 8.1: Converts some T_READONLY to T_STRING because token_get_all() without the TOKEN_PARSE flag cannot distinguish between them in some situations + */ + + $allowedAfter = [ + T_STRING => T_STRING, + T_NS_SEPARATOR => T_NS_SEPARATOR, + T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED, + T_NAME_RELATIVE => T_NAME_RELATIVE, + T_NAME_QUALIFIED => T_NAME_QUALIFIED, + T_TYPE_UNION => T_TYPE_UNION, + T_BITWISE_OR => T_BITWISE_OR, + T_ARRAY => T_ARRAY, + T_CALLABLE => T_CALLABLE, + T_SELF => T_SELF, + T_PARENT => T_PARENT, + T_NULL => T_FALSE, + T_NULLABLE => T_NULLABLE, + T_STATIC => T_STATIC, + T_PUBLIC => T_PUBLIC, + T_PROTECTED => T_PROTECTED, + T_PRIVATE => T_PRIVATE, + T_VAR => T_VAR, + ]; + + $shouldBeReadonly = true; + + for ($x = ($i + 1); $x < $numTokens; $x++) { + if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) { + continue; + } + + if ($this->tokens[$x]['code'] === T_VARIABLE) { + break; + } + + if (isset($allowedAfter[$this->tokens[$x]['code']]) === false) { + $shouldBeReadonly = false; + break; + } + } + + if ($this->tokens[$i]['code'] === T_STRING && $shouldBeReadonly === true) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + echo "\t* token $i on line $line changed from T_STRING to T_READONLY".PHP_EOL; + } + + $this->tokens[$i]['code'] = T_READONLY; + $this->tokens[$i]['type'] = 'T_READONLY'; + } else if ($this->tokens[$i]['code'] === T_READONLY && $shouldBeReadonly === false) { + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$i]['line']; + echo "\t* token $i on line $line changed from T_READONLY to T_STRING".PHP_EOL; + } + + $this->tokens[$i]['code'] = T_STRING; + $this->tokens[$i]['type'] = 'T_STRING'; + } + + continue; }//end if if (($this->tokens[$i]['code'] !== T_CASE diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index f501c7f0a4..0bc1747275 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -163,6 +163,10 @@ 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'); +} + // 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'); diff --git a/tests/Core/Tokenizer/ReadonlyTest.inc b/tests/Core/Tokenizer/ReadonlyTest.inc new file mode 100644 index 0000000000..7b10ed681d --- /dev/null +++ b/tests/Core/Tokenizer/ReadonlyTest.inc @@ -0,0 +1,92 @@ +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; + +/* testParseErrorLiveCoding */ +// This must be the last test in the file. +readonly diff --git a/tests/Core/Tokenizer/ReadonlyTest.php b/tests/Core/Tokenizer/ReadonlyTest.php new file mode 100644 index 0000000000..4426f730b4 --- /dev/null +++ b/tests/Core/Tokenizer/ReadonlyTest.php @@ -0,0 +1,224 @@ + + * @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 ReadonlyTest 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', + ], + [ + '/* 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', + ], + [ + '/* testReadonlyPropertyInAnonymousClass */', + '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 From e908da764dbfa6eb6e31d908a2fa16b9c16c7944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 17:38:22 +0100 Subject: [PATCH 437/733] PHP 8.1: File::getMemberProperties() detects if property is readonly --- src/Files/File.php | 6 ++++++ tests/Core/File/GetMemberPropertiesTest.inc | 5 +++++ tests/Core/File/GetMemberPropertiesTest.php | 22 +++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/src/Files/File.php b/src/Files/File.php index e551dcbbf1..cfe6f52af3 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1811,6 +1811,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 +1819,7 @@ public function getMemberProperties($stackPtr) $scope = 'public'; $scopeSpecified = false; $isStatic = false; + $isReadonly = false; $startOfStatement = $this->findPrevious( [ @@ -1850,6 +1852,9 @@ public function getMemberProperties($stackPtr) case T_STATIC: $isStatic = true; break; + case T_READONLY: + $isReadonly = true; + break; } }//end for @@ -1901,6 +1906,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, diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index ea47e9fc57..e156099382 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -239,6 +239,11 @@ $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; + + /* testPHP81NotReadonly */ + private string $notReadonly; + /* testPHP81Readonly */ + public readonly int $readonly; }; $anon = class { diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index 0af0437932..82764b01e5 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -610,6 +610,28 @@ public function dataGetMemberProperties() 'nullable_type' => false, ], ], + [ + '/* testPHP81NotReadonly */', + [ + 'scope' => 'private', + 'scope_specified' => true, + 'is_static' => false, + 'is_readonly' => false, + 'type' => 'string', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP81Readonly */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'is_readonly' => true, + 'type' => 'int', + 'nullable_type' => false, + ], + ], [ '/* testPHP8PropertySingleAttribute */', [ From 0ed1d8c5ac325d6222396f7034d9c8e9d68a0cc9 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 23 Nov 2021 09:20:58 +1100 Subject: [PATCH 438/733] Fixed bug #3437 : PSR12 does not forbid blank lines at the start of the class body --- package.xml | 6 ++ .../Sniffs/Classes/OpeningBraceSpaceSniff.php | 80 +++++++++++++++++++ .../Classes/OpeningBraceSpaceUnitTest.inc | 49 ++++++++++++ .../OpeningBraceSpaceUnitTest.inc.fixed | 41 ++++++++++ .../Classes/OpeningBraceSpaceUnitTest.php | 54 +++++++++++++ src/Standards/PSR12/ruleset.xml | 1 + 6 files changed, 231 insertions(+) create mode 100644 src/Standards/PSR12/Sniffs/Classes/OpeningBraceSpaceSniff.php create mode 100644 src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc create mode 100644 src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc.fixed create mode 100644 src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.php diff --git a/package.xml b/package.xml index d1d4464b77..ec25161efb 100644 --- a/package.xml +++ b/package.xml @@ -32,6 +32,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 #3448 : PHP 8.1 deprecation notice while generating running time value -- Thanks to Juliette Reinders Folmer and Andy Postnikov for the patch @@ -1109,6 +1111,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + @@ -1150,6 +1153,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + diff --git a/src/Standards/PSR12/Sniffs/Classes/OpeningBraceSpaceSniff.php b/src/Standards/PSR12/Sniffs/Classes/OpeningBraceSpaceSniff.php new file mode 100644 index 0000000000..83ffda4df0 --- /dev/null +++ b/src/Standards/PSR12/Sniffs/Classes/OpeningBraceSpaceSniff.php @@ -0,0 +1,80 @@ + + * @copyright 2006-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\Standards\PSR12\Sniffs\Classes; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Tokens; + +class OpeningBraceSpaceSniff implements Sniff +{ + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return Tokens::$ooScopeTokens; + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + return; + } + + $opener = $tokens[$stackPtr]['scope_opener']; + $next = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), null, true); + if ($next === false + || $tokens[$next]['line'] <= ($tokens[$opener]['line'] + 1) + ) { + return; + } + + $error = 'Opening brace must not be followed by a blank line'; + $fix = $phpcsFile->addFixableError($error, $opener, 'Found'); + if ($fix === false) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + for ($i = ($opener + 1); $i < $next; $i++) { + if ($tokens[$i]['line'] === $tokens[$opener]['line']) { + continue; + } + + if ($tokens[$i]['line'] === $tokens[$next]['line']) { + break; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + + }//end process() + + +}//end class diff --git a/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc b/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc new file mode 100644 index 0000000000..509c48c316 --- /dev/null +++ b/src/Standards/PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc @@ -0,0 +1,49 @@ + + * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\PSR12\Tests\Classes; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class OpeningBraceSpaceUnitTest extends AbstractSniffUnitTest +{ + + + /** + * Returns the lines where errors should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of errors that should occur on that line. + * + * @return array + */ + public function getErrorList() + { + return [ + 10 => 1, + 18 => 1, + 24 => 1, + 34 => 1, + 41 => 1, + ]; + + }//end getErrorList() + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + + }//end getWarningList() + + +}//end class diff --git a/src/Standards/PSR12/ruleset.xml b/src/Standards/PSR12/ruleset.xml index 1467004353..ce8b71a756 100644 --- a/src/Standards/PSR12/ruleset.xml +++ b/src/Standards/PSR12/ruleset.xml @@ -123,6 +123,7 @@ + From 1fdb1e64112b33c07a3143a064972f49ac6aa761 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 27 Nov 2021 16:08:28 +0100 Subject: [PATCH 439/733] GH Actions: PHP 8.1 has been released PHP 8.1 has been released, so build against it should no longer be allowed to fail. Also adding PHP 8.2 as the new "nightly" build (which is allowed to fail). --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ecec176de5..4b6736d980 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: # - custom_ini: Whether to run with specific custom ini settings to hit very specific # code conditions. matrix: - php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] custom_ini: [false] include: @@ -33,7 +33,7 @@ jobs: name: "PHP: ${{ matrix.php }} ${{ matrix.custom_ini && ' with custom ini settings' || '' }}" - continue-on-error: ${{ matrix.php == '8.1' }} + continue-on-error: ${{ matrix.php == '8.2' }} steps: - name: Checkout code @@ -79,14 +79,14 @@ jobs: run: php bin/phpcs --config-set php_path php - name: 'PHPUnit: run the tests' - if: ${{ matrix.php != '8.1' }} + if: ${{ matrix.php != '8.1' && matrix.php != '8.2' }} run: vendor/bin/phpunit tests/AllTests.php # We need to ignore the config file so that PHPUnit doesn't try to read it. # The config file causes an error on PHP 8.1+ with PHPunit 7, but it's not needed here anyway # as we can pass all required settings in the phpunit command. - - name: 'PHPUnit: run the tests on PHP nightly' - if: ${{ matrix.php == '8.1' }} + - name: 'PHPUnit: run the tests on PHP > 8.0' + if: ${{ matrix.php == '8.1' || matrix.php == '8.2' }} run: vendor/bin/phpunit tests/AllTests.php --no-configuration --bootstrap=tests/bootstrap.php --dont-report-useless-tests - name: 'PHPCS: check code style without cache, no parallel' From 7f7a2cc3c1046bf5edc1120e19dbd580ae3c813b Mon Sep 17 00:00:00 2001 From: javer Date: Fri, 24 Sep 2021 21:16:59 +0300 Subject: [PATCH 440/733] PHP 8.0 | Squiz.WhiteSpace.MemberVarSpacing: fix support for attributes --- .../Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php | 5 +++-- .../Tests/WhiteSpace/MemberVarSpacingUnitTest.inc | 9 +++++++++ .../WhiteSpace/MemberVarSpacingUnitTest.inc.fixed | 10 ++++++++++ .../Tests/WhiteSpace/MemberVarSpacingUnitTest.php | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php b/src/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php index ccd48995e7..0ece1acad2 100644 --- a/src/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php +++ b/src/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php @@ -67,7 +67,8 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) if ($tokens[$prev]['code'] === T_ATTRIBUTE_END && isset($tokens[$prev]['attribute_opener']) === true ) { - $prev = $tokens[$prev]['attribute_opener']; + $prev = $tokens[$prev]['attribute_opener']; + $start = $prev; continue; } @@ -140,7 +141,7 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) $first = $tokens[$start]['comment_opener']; } else { $first = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($start - 1), null, true); - $first = $phpcsFile->findNext(Tokens::$commentTokens, ($first + 1)); + $first = $phpcsFile->findNext(array_merge(Tokens::$commentTokens, [T_ATTRIBUTE]), ($first + 1)); } // Determine if this is the first member var. diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc index ff467bb2df..038072dfe0 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc @@ -355,4 +355,13 @@ class HasAttributes #[ORM\Column(ORM\Column::T_INTEGER)] protected $height; + + #[SingleAttribute] + protected $propertySingle; + + #[FirstAttribute] + #[SecondAttribute] + protected $propertyDouble; + #[ThirdAttribute] + protected $propertyWithoutSpacing; } diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed index 5c42b1eda1..3cb2ca3a48 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed @@ -339,4 +339,14 @@ class HasAttributes #[ORM\GeneratedValue] #[ORM\Column(ORM\Column::T_INTEGER)] protected $height; + + #[SingleAttribute] + protected $propertySingle; + + #[FirstAttribute] + #[SecondAttribute] + protected $propertyDouble; + + #[ThirdAttribute] + protected $propertyWithoutSpacing; } diff --git a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.php index b43a972b76..9b4066811a 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.php @@ -61,6 +61,7 @@ public function getErrorList() 346 => 1, 353 => 1, 357 => 1, + 366 => 1, ]; }//end getErrorList() From 8884d1641c00caf6824d2b16dad2a872e83fe659 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 3 Dec 2021 14:46:47 +1100 Subject: [PATCH 441/733] Changelog for #3440 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index ec25161efb..23e1bc2def 100644 --- a/package.xml +++ b/package.xml @@ -34,6 +34,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 From 24af403a0cad616fbf311e6170c235c8b8d64117 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 3 Dec 2021 14:46:13 +0100 Subject: [PATCH 442/733] Resolve double-increment of the CyclomaticComplexity count for do/while loop This is a bugfix for Issue #3468, which identified that the count was being incremented twice when calculating the score because the sniff was checking for both the T_DO and the terminating T_WHILE tokens, incrementing CYC by two (once for each token) in the do/while loop, rather than just by one (as it should). Code in the unit test .inc file has ben modified to reflect this change, and the test php file has also been modified to reflect the change in error line numbers. --- .../Sniffs/Metrics/CyclomaticComplexitySniff.php | 3 ++- .../Metrics/CyclomaticComplexityUnitTest.inc | 16 ++++++++++------ .../Metrics/CyclomaticComplexityUnitTest.php | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php index df70df221f..7a9cd7949c 100644 --- a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php +++ b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php @@ -78,7 +78,8 @@ public function process(File $phpcsFile, $stackPtr) T_FOR => true, T_FOREACH => true, T_WHILE => true, - T_DO => true, + // T_DO is not required for incrementing CYC, as the terminating while in a do/while loop triggers the branch. + // T_DO => true. T_ELSEIF => true, ]; diff --git a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc index 151ffed6a1..a4f6654c46 100644 --- a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc +++ b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc @@ -79,9 +79,11 @@ function complexityTwenty() switch ($condition) { case '1': - if ($condition) { - } else if ($cond) { - } + do { + if ($condition) { + } else if ($cond) { + } + } while ($cond); break; case '2': while ($cond) { @@ -116,9 +118,11 @@ function complexityTwenty() function complexityTwentyOne() { while ($condition === true) { - if ($condition) { - } else if ($cond) { - } + do { + if ($condition) { + } else if ($cond) { + } + } while ($cond); } switch ($condition) { diff --git a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php index e8099317cd..ff2feb2de3 100644 --- a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php +++ b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php @@ -25,7 +25,7 @@ class CyclomaticComplexityUnitTest extends AbstractSniffUnitTest */ public function getErrorList() { - return [116 => 1]; + return [118 => 1]; }//end getErrorList() From 9553ab6b69427e951277f35eb526a6d8028b81f5 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 3 Dec 2021 15:01:28 +0100 Subject: [PATCH 443/733] Factor ternary operator, null coalescence (and assignment) operators into CyclomaticComplexity calculation This is for Issue #3469, which identified that these operators were not being included in the count, when they should have been (treated as an if/else condition). Unit tests provided. --- .../Metrics/CyclomaticComplexitySniff.php | 19 +- .../Metrics/CyclomaticComplexityUnitTest.inc | 241 ++++++++++++++++++ .../Metrics/CyclomaticComplexityUnitTest.php | 9 +- 3 files changed, 259 insertions(+), 10 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php index 7a9cd7949c..fefd4bc617 100644 --- a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php +++ b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php @@ -71,16 +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_CASE => true, + T_DEFAULT => true, + T_CATCH => true, + T_IF => true, + T_FOR => true, + T_FOREACH => true, + T_WHILE => true, // T_DO is not required for incrementing CYC, as the terminating while in a do/while loop triggers the branch. // T_DO => true. - T_ELSEIF => true, + T_ELSEIF => true, + T_INLINE_THEN => true, + T_COALESCE => true, + T_COALESCE_EQUAL => true, ]; $complexity = 1; diff --git a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc index a4f6654c46..fa17d2d15f 100644 --- a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc +++ b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc @@ -161,4 +161,245 @@ function complexityTwentyOne() } } + +function complexityTenWithTernaries() +{ + $value1 = (empty($condition1)) ? $value1A : $value1B; + $value2 = (empty($condition2)) ? $value2A : $value2B; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityElevenWithTernaries() +{ + $value1 = (empty($condition1)) ? $value1A : $value1B; + $value2 = (empty($condition2)) ? $value2A : $value2B; + $value3 = (empty($condition3)) ? $value3A : $value3B; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityTenWithNestedTernaries() +{ + $value1 = true ? $value1A : false ? $value1B : $value1C; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityElevenWithNestedTernaries() +{ + $value1 = (empty($condition1)) ? $value1A : $value1B; + $value2 = true ? $value2A : false ? $value2B : $value2C; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityTenWithNullCoalescence() +{ + $value1 = $value1A ?? $value1B; + $value2 = $value2A ?? $value2B; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityElevenWithNullCoalescence() +{ + $value1 = $value1A ?? $value1B; + $value2 = $value2A ?? $value2B; + $value3 = $value3A ?? $value3B; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityTenWithNestedNullCoalescence() +{ + $value1 = $value1A ?? $value1B ?? $value1C; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityElevenWithNestedNullCoalescence() +{ + $value1 = $value1A ?? $value1B; + $value2 = $value2A ?? $value2B ?? $value2C; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityTenWithNullCoalescenceAssignment() +{ + $value1 ??= $default1; + $value2 ??= $default2; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + + +function complexityElevenWithNullCoalescenceAssignment() +{ + $value1 ??= $default1; + $value2 ??= $default2; + $value3 ??= $default3; + + switch ($condition) { + case '1': + if ($condition) { + } else if ($cond) { + } + break; + case '2': + while ($cond) { + echo 'hi'; + } + break; + case '3': + break; + default: + break; + } +} + ?> diff --git a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php index ff2feb2de3..ad1cc0155e 100644 --- a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php +++ b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php @@ -41,8 +41,13 @@ public function getErrorList() public function getWarningList() { return [ - 45 => 1, - 72 => 1, + 45 => 1, + 72 => 1, + 189 => 1, + 237 => 1, + 285 => 1, + 333 => 1, + 381 => 1, ]; }//end getWarningList() From a29192b796191ca1af7b468793cb7caf5d26ee70 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 3 Dec 2021 15:48:26 +0100 Subject: [PATCH 444/733] Factor match expression into the CyclomaticComplexity calculation This is for Issue #3472, which identified that match() was not being included in the count, when it should have been (similar to a switch statement). There is no need to add one for the T_MATCH_DEFAULT token, because this is always followed by a T_MATCH_ARROW token, and to do so would be to double-count that entry. Unit tests provided. --- .../Metrics/CyclomaticComplexitySniff.php | 1 + .../Metrics/CyclomaticComplexityUnitTest.inc | 31 +++++++++++++++++++ .../Metrics/CyclomaticComplexityUnitTest.php | 1 + 3 files changed, 33 insertions(+) diff --git a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php index fefd4bc617..5b9850c33a 100644 --- a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php +++ b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php @@ -84,6 +84,7 @@ public function process(File $phpcsFile, $stackPtr) T_INLINE_THEN => true, T_COALESCE => true, T_COALESCE_EQUAL => true, + T_MATCH_ARROW => true, ]; $complexity = 1; diff --git a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc index fa17d2d15f..f6c6bb757b 100644 --- a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc +++ b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.inc @@ -402,4 +402,35 @@ function complexityElevenWithNullCoalescenceAssignment() } } + +function complexityFiveWithMatch() +{ + return match(strtolower(substr($monthName, 0, 3))){ + 'apr', 'jun', 'sep', 'nov' => 30, + 'jan', 'mar', 'may', 'jul', 'aug', 'oct', 'dec' => 31, + 'feb' => is_leap_year($year) ? 29 : 28, + default => throw new InvalidArgumentException("Invalid month"), + } +} + + +function complexityFourteenWithMatch() +{ + return match(strtolower(substr($monthName, 0, 3))) { + 'jan' => 31, + 'feb' => is_leap_year($year) ? 29 : 28, + 'mar' => 31, + 'apr' => 30, + 'may' => 31, + 'jun' => 30, + 'jul' => 31, + 'aug' => 31, + 'sep' => 30, + 'oct' => 31, + 'nov' => 30, + 'dec' => 31, + default => throw new InvalidArgumentException("Invalid month"), + }; +} + ?> diff --git a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php index ad1cc0155e..92635fc44d 100644 --- a/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php +++ b/src/Standards/Generic/Tests/Metrics/CyclomaticComplexityUnitTest.php @@ -48,6 +48,7 @@ public function getWarningList() 285 => 1, 333 => 1, 381 => 1, + 417 => 1, ]; }//end getWarningList() From 171bd2d864aa1c20332ddab1b702252cb9de5ef0 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 7 Dec 2021 08:27:28 +1100 Subject: [PATCH 445/733] Removed commented out code (ref #3495) --- .../Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php index 5b9850c33a..18100c2d67 100644 --- a/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php +++ b/src/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php @@ -78,8 +78,6 @@ public function process(File $phpcsFile, $stackPtr) T_FOR => true, T_FOREACH => true, T_WHILE => true, - // T_DO is not required for incrementing CYC, as the terminating while in a do/while loop triggers the branch. - // T_DO => true. T_ELSEIF => true, T_INLINE_THEN => true, T_COALESCE => true, From 25d87b77890b756cd322b6218a3b4a73d26dec8c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 7 Dec 2021 08:30:01 +1100 Subject: [PATCH 446/733] Changelog for #3495 --- package.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.xml b/package.xml index 23e1bc2def..63cafe3d3d 100644 --- a/package.xml +++ b/package.xml @@ -38,6 +38,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 #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 From 586a51f839039dd295aeab1921d2aa49c85b7b2b Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 7 Dec 2021 08:39:54 +1100 Subject: [PATCH 447/733] Changelog for #3460 (ref #3464) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 63cafe3d3d..9364b8cde4 100644 --- a/package.xml +++ b/package.xml @@ -38,6 +38,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 #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 From e40edd843b523988107bcc6d6f721f1cef1cbff9 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 13 Dec 2021 08:28:54 +1100 Subject: [PATCH 448/733] Changelog for #3456 (ref #3457) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 9364b8cde4..30e4af92d7 100644 --- a/package.xml +++ b/package.xml @@ -38,6 +38,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 From 5e4e71592f69da17871dba6e80dd51bce74a351a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 13 Dec 2021 08:44:58 +1100 Subject: [PATCH 449/733] Prepare for 3.6.2 release --- package.xml | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 30e4af92d7..4cfe75f2da 100644 --- a/package.xml +++ b/package.xml @@ -14,8 +14,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> gsherwood@squiz.net yes - 2021-10-11 - + 2021-12-13 + 3.6.2 3.6.2 @@ -2217,6 +2217,42 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 3.6.2 + 3.6.2 + + + stable + stable + + 2021-12-13 + BSD 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 + + 3.6.1 From 2c4f3cc1f9e950bcb4695ca9c5775b4e1bb6c75b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 6 Sep 2021 10:20:51 +0200 Subject: [PATCH 450/733] Generic/[Upper|Lower]CaseConstant: ignore type declarations This commit adjusts the `Generic.PHP.UpperCaseConstant` and the `Generic.PHP.LowerCaseConstant` sniffs to ignore property, parameter and return type declarations. Type declarations have their own rules and should not be treated the same as other `false`, `true` and `null` uses. Includes unit tests. Fixes 3332 --- .../Sniffs/PHP/LowerCaseConstantSniff.php | 106 +++++++++++++++++- .../Sniffs/PHP/UpperCaseConstantSniff.php | 106 +++++++++++++++++- .../Tests/PHP/LowerCaseConstantUnitTest.inc | 17 +++ .../PHP/LowerCaseConstantUnitTest.inc.fixed | 17 +++ .../Tests/PHP/LowerCaseConstantUnitTest.php | 35 +++--- .../Tests/PHP/UpperCaseConstantUnitTest.inc | 19 +++- .../PHP/UpperCaseConstantUnitTest.inc.fixed | 19 +++- .../Tests/PHP/UpperCaseConstantUnitTest.php | 7 ++ 8 files changed, 298 insertions(+), 28 deletions(-) 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/UpperCaseConstantSniff.php b/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php index 54aa07f22a..5df77ff631 100644 --- a/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php @@ -11,10 +11,22 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Tokens; class UpperCaseConstantSniff implements Sniff { + /** + * 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. @@ -23,11 +35,14 @@ class UpperCaseConstantSniff 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() @@ -42,10 +57,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 = strtoupper($keyword); + if ($keyword !== $expected) { if ($keyword === strtolower($keyword)) { $phpcsFile->recordMetric($stackPtr, 'PHP constant case', 'lower'); @@ -67,7 +161,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/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/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() From e2bbbceda08adc2aed7ba7a3339e1f77b65b86c5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 25 Jul 2021 21:28:55 +0200 Subject: [PATCH 451/733] Generic/One*PerFile sniffs: efficiency tweak Each of these sniffs would start walking the rest of the file from the token next to the OO keyword to the end of the file in search of the next (class/interface/trait) keyword. As nested OO structure declarations (with the exception of anonymous classes, but those are outside the realm of these sniffs) are not supported in PHP, we can make these sniffs a lot faster by starting the search _after_ the scope closer for the current OO structure. --- .../Generic/Sniffs/Files/OneClassPerFileSniff.php | 8 +++++++- .../Generic/Sniffs/Files/OneInterfacePerFileSniff.php | 8 +++++++- .../Sniffs/Files/OneObjectStructurePerFileSniff.php | 8 +++++++- .../Generic/Sniffs/Files/OneTraitPerFileSniff.php | 8 +++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Files/OneClassPerFileSniff.php b/src/Standards/Generic/Sniffs/Files/OneClassPerFileSniff.php index 9bcc00c1c2..52d5d84c74 100644 --- a/src/Standards/Generic/Sniffs/Files/OneClassPerFileSniff.php +++ b/src/Standards/Generic/Sniffs/Files/OneClassPerFileSniff.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 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..b2565c2bdc 100644 --- a/src/Standards/Generic/Sniffs/Files/OneObjectStructurePerFileSniff.php +++ b/src/Standards/Generic/Sniffs/Files/OneObjectStructurePerFileSniff.php @@ -43,7 +43,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'); From 8adefd444293363cf442e5fc6b9c8e1b7bea1c55 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Wed, 15 Dec 2021 12:03:22 +0100 Subject: [PATCH 452/733] Adjust explicit octal tag in NumericSeparator test --- tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc | 2 +- tests/Core/Tokenizer/BackfillNumericSeparatorTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc index fb7ab2fd6f..ba07f958f7 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc @@ -34,7 +34,7 @@ $foo = 0b0101_1111; /* testOctal */ $foo = 0137_041; -/* testExplicitOctal (Introduced in PHP 8.1) */ +/* testExplicitOctal */ $foo = 0o137_041; /* testIntMoreThanMax */ diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php index 3268245f56..a177b8e01f 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php @@ -134,7 +134,7 @@ public function dataTestBackfill() ], [ [ - 'marker' => '/* testExplicitOctal (Introduced in PHP 8.1) */', + 'marker' => '/* testExplicitOctal */', 'type' => 'T_LNUMBER', 'value' => '0o137_041', ], From 9a78378766f6476ea0e0d8517e4e76d4de8b7812 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Wed, 15 Dec 2021 12:08:18 +0100 Subject: [PATCH 453/733] Rename test files to identify that this is backfill functionality --- ...alNotationTest.inc => BackfillExplicitOctalNotationTest.inc} | 0 ...alNotationTest.php => BackfillExplicitOctalNotationTest.php} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/Core/Tokenizer/{ExplicitOctalNotationTest.inc => BackfillExplicitOctalNotationTest.inc} (100%) rename tests/Core/Tokenizer/{ExplicitOctalNotationTest.php => BackfillExplicitOctalNotationTest.php} (96%) diff --git a/tests/Core/Tokenizer/ExplicitOctalNotationTest.inc b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc similarity index 100% rename from tests/Core/Tokenizer/ExplicitOctalNotationTest.inc rename to tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc diff --git a/tests/Core/Tokenizer/ExplicitOctalNotationTest.php b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php similarity index 96% rename from tests/Core/Tokenizer/ExplicitOctalNotationTest.php rename to tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php index af591c351f..7271c1b3d9 100644 --- a/tests/Core/Tokenizer/ExplicitOctalNotationTest.php +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; -class ExplicitOctalNotationTest extends AbstractMethodUnitTest +class BackfillExplicitOctalNotationTest extends AbstractMethodUnitTest { From b11f021df404c657322f3f3b2e57df299aa4acb6 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Wed, 15 Dec 2021 13:27:18 +0100 Subject: [PATCH 454/733] Additional test case for capital O when expressing explicit octal values --- .../Core/Tokenizer/BackfillExplicitOctalNotationTest.inc | 5 ++++- .../Core/Tokenizer/BackfillExplicitOctalNotationTest.php | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc index 4724229b1e..d08553db69 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc @@ -1,4 +1,7 @@ '/* testExplicitOctal declaration */', + 'marker' => '/* testExplicitOctal */', 'type' => 'T_LNUMBER', 'value' => '0o137041', ], ], + [ + [ + 'marker' => '/* testExplicitOctal capitalised */', + 'type' => 'T_LNUMBER', + 'value' => '0O137041', + ], + ], ]; }//end dataExplicitOctalNotation() From a0bc9d612659ffac0b95953f90680472e7ab4577 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Wed, 15 Dec 2021 17:18:12 +0100 Subject: [PATCH 455/733] We should only be testing for T_LNUMBER --- tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php index e65d16e5be..e3c62594c3 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php @@ -29,7 +29,7 @@ public function testExplicitOctalNotation($testData) { $tokens = self::$phpcsFile->getTokens(); - $number = $this->getTargetToken($testData['marker'], [T_LNUMBER, T_DNUMBER, T_STRING]); + $number = $this->getTargetToken($testData['marker'], [T_LNUMBER]); $this->assertSame(constant($testData['type']), $tokens[$number]['code']); $this->assertSame($testData['type'], $tokens[$number]['type']); From b363a7c65923e81b469adc7e694dc4da6516b21a Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Wed, 15 Dec 2021 17:42:28 +0100 Subject: [PATCH 456/733] Move code block down after the initial comment processing logic. Check for lower- or upper-case 'o'. Don't adjust $token and $tokens variables; but set the token in $finalTokens, and adjust pointers. --- src/Tokenizers/PHP.php | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5115425c9a..4e335a1513 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -553,23 +553,6 @@ protected function tokenize($string) $lastNotEmptyToken = ($newStackPtr - 1); } - /* - For Explicit Octal Notation prior to PHP 8.1 we need to combine the - T_NUMBER 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 - && $tokens[($stackPtr + 1)][1][0] === 'o') - ) { - $token[1] .= $tokens[($stackPtr + 1)][1]; - $tokens[($stackPtr + 1)] = ''; - } - /* If we are using \r\n newline characters, the \r and \n are sometimes split over two tokens. This normally occurs after comments. We need @@ -663,6 +646,29 @@ 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') + ) { + $finalTokens[$newStackPtr] = [ + 'code' => T_LNUMBER, + 'type' => 'T_LNUMBER', + 'content' => $token[1] .= $tokens[($stackPtr + 1)][1], + ]; + $stackPtr++; + $newStackPtr++; + continue; + } + /* PHP 8.1 introduced two dedicated tokens for the & character. Retokenizing both of these to T_BITWISE_AND, which is the From 878472a846256d245bec5f6056fb571fbcbeb819 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 17 Dec 2021 14:38:40 +1100 Subject: [PATCH 457/733] Build the phar without stripping whitespace and comments to allow for PHP 8.1 support This was previously stripping annotations that caused 8.1 notices. Building on 8.0+ resolved this, but then made the phar incompatible with PHP < 8.0. --- scripts/build-phar.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/build-phar.php b/scripts/build-phar.php index 6b2800c883..67116e90b8 100644 --- a/scripts/build-phar.php +++ b/scripts/build-phar.php @@ -65,15 +65,14 @@ } $path = 'src'.substr($fullpath, $srcDirLen); - - $phar->addFromString($path, php_strip_whitespace($fullpath)); + $phar->addFile($fullpath, $path); } // Add autoloader. - $phar->addFromString('autoload.php', php_strip_whitespace(realpath(__DIR__.'/../autoload.php'))); + $phar->addFile(realpath(__DIR__.'/../autoload.php'), 'autoload.php'); // 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; From a697187682527e7726efbf8955981bf2f7d6cadc Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 17 Dec 2021 14:41:14 +1100 Subject: [PATCH 458/733] Use standard naming format (ref #3481) --- tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc | 2 +- tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc index d08553db69..7c5e8c089a 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc @@ -3,5 +3,5 @@ /* testExplicitOctal */ $foo = 0o137041; -/* testExplicitOctal capitalised */ +/* testExplicitOctalCapitalised */ $bar = 0O137041; diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php index e3c62594c3..22b1daa61d 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php @@ -57,7 +57,7 @@ public function dataExplicitOctalNotation() ], [ [ - 'marker' => '/* testExplicitOctal capitalised */', + 'marker' => '/* testExplicitOctalCapitalised */', 'type' => 'T_LNUMBER', 'value' => '0O137041', ], From e3b91d5c32cbbb379990b910cf20366904171699 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 17 Dec 2021 14:43:22 +1100 Subject: [PATCH 459/733] Added test for capitalised O (ref #3481) --- tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc | 3 +++ tests/Core/Tokenizer/BackfillNumericSeparatorTest.php | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc index ba07f958f7..66f1a9a02a 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc @@ -37,6 +37,9 @@ $foo = 0137_041; /* testExplicitOctal */ $foo = 0o137_041; +/* testExplicitOctalCapitalised */ +$foo = 0O137_041; + /* testIntMoreThanMax */ $foo = 10_223_372_036_854_775_807; diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php index a177b8e01f..27efdec5af 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php @@ -139,6 +139,13 @@ public function dataTestBackfill() 'value' => '0o137_041', ], ], + [ + [ + 'marker' => '/* testExplicitOctalCapitalised */', + 'type' => 'T_LNUMBER', + 'value' => '0O137_041', + ], + ], [ [ 'marker' => '/* testIntMoreThanMax */', From d6a58bdd8707bab1032344f33d01c0627e0f3f97 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 17 Dec 2021 14:47:47 +1100 Subject: [PATCH 460/733] Changelog + PEAR entries for #3481 --- package.xml | 33 ++++++++++----------------------- src/Config.php | 2 +- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/package.xml b/package.xml index 4cfe75f2da..253f40a051 100644 --- a/package.xml +++ b/package.xml @@ -17,8 +17,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2021-12-13 - 3.6.2 - 3.6.2 + 3.7.0 + 3.7.0 stable @@ -26,27 +26,8 @@ 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 + - 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 @@ -135,6 +116,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2090,6 +2073,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2180,6 +2165,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Config.php b/src/Config.php index 6bb5b3298f..4e7b2b6204 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.0'; /** * Package stability; either stable, beta or alpha. From 6296b27cddd86a4d0d905881e628e94ea44ec565 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 17 Dec 2021 15:50:07 +1100 Subject: [PATCH 461/733] Minor cleanup of readonly keyword support Renamed test files and added support for readonly const syntax, which doesn't make sense but we need to match PHP 8.1 tokenizing. --- package.xml | 15 +++++++++------ src/Tokenizers/PHP.php | 16 ++++++++++------ ...ReadonlyTest.inc => BackfillReadonlyTest.inc} | 2 ++ ...ReadonlyTest.php => BackfillReadonlyTest.php} | 6 +++++- 4 files changed, 26 insertions(+), 13 deletions(-) rename tests/Core/Tokenizer/{ReadonlyTest.inc => BackfillReadonlyTest.inc} (97%) rename tests/Core/Tokenizer/{ReadonlyTest.php => BackfillReadonlyTest.php} (97%) diff --git a/package.xml b/package.xml index 8862df8676..a70ea3b5f8 100644 --- a/package.xml +++ b/package.xml @@ -29,6 +29,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 + - 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 @@ -138,8 +141,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - + + @@ -2097,8 +2100,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - + + @@ -2191,8 +2194,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5227855d49..27f5544e35 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2837,13 +2837,15 @@ protected function processAdditional() $this->tokens[$x]['code'] = T_STRING; $this->tokens[$x]['type'] = 'T_STRING'; } - } else if (($this->tokens[$i]['code'] === T_STRING && strtolower($this->tokens[$i]['content']) === 'readonly') - || $this->tokens[$i]['code'] === T_READONLY + } else if ($this->tokens[$i]['code'] === T_READONLY + || ($this->tokens[$i]['code'] === T_STRING + && strtolower($this->tokens[$i]['content']) === 'readonly') ) { /* - "readonly" keyword support - PHP < 8.1: Converts T_STRING to T_READONLY - PHP >= 8.1: Converts some T_READONLY to T_STRING because token_get_all() without the TOKEN_PARSE flag cannot distinguish between them in some situations + Adds "readonly" keyword support: + PHP < 8.1: Converts T_STRING to T_READONLY + PHP >= 8.1: Converts some T_READONLY to T_STRING because token_get_all() + without the TOKEN_PARSE flag cannot distinguish between them in some situations. */ $allowedAfter = [ @@ -2874,7 +2876,9 @@ protected function processAdditional() continue; } - if ($this->tokens[$x]['code'] === T_VARIABLE) { + if ($this->tokens[$x]['code'] === T_VARIABLE + || $this->tokens[$x]['code'] === T_CONST + ) { break; } diff --git a/tests/Core/Tokenizer/ReadonlyTest.inc b/tests/Core/Tokenizer/BackfillReadonlyTest.inc similarity index 97% rename from tests/Core/Tokenizer/ReadonlyTest.inc rename to tests/Core/Tokenizer/BackfillReadonlyTest.inc index 7b10ed681d..f585134ca3 100644 --- a/tests/Core/Tokenizer/ReadonlyTest.inc +++ b/tests/Core/Tokenizer/BackfillReadonlyTest.inc @@ -12,6 +12,8 @@ class Foo static readonly int $staticReadonlyProperty; /* testReadonlyStaticProperty */ readonly static int $readonlyStaticProperty; + /* testConstReadonlyProperty */ + public readonly const MYCONSTANT = 'foo'; /* testReadonlyPropertyWithoutType */ readonly $propertyWithoutType; /* testPublicReadonlyProperty */ diff --git a/tests/Core/Tokenizer/ReadonlyTest.php b/tests/Core/Tokenizer/BackfillReadonlyTest.php similarity index 97% rename from tests/Core/Tokenizer/ReadonlyTest.php rename to tests/Core/Tokenizer/BackfillReadonlyTest.php index 4426f730b4..0f4e922862 100644 --- a/tests/Core/Tokenizer/ReadonlyTest.php +++ b/tests/Core/Tokenizer/BackfillReadonlyTest.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; -class ReadonlyTest extends AbstractMethodUnitTest +class BackfillReadonlyTest extends AbstractMethodUnitTest { @@ -67,6 +67,10 @@ public function dataReadonly() '/* testReadonlyStaticProperty */', 'readonly', ], + [ + '/* testConstReadonlyProperty */', + 'readonly', + ], [ '/* testReadonlyPropertyWithoutType */', 'readonly', From 7d4514d350629bc7b96524809401bd13bd0624d6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 5 Sep 2021 03:28:29 +0200 Subject: [PATCH 462/733] GH Actions: split PHAR build test from other tests As things were in the CI script, the `phpcs` and `phpcbf` Phars would be build against each supported PHP version and the tests run with the `phpcs` Phar would use the Phar as build on the same PHP version as the test was being run on. This test was not representative of the reality as with each release, only one `phpcs` and one `phpcbf` phar is released. The changes in this commit: * Split the building of the Phar off from the main test script. * This new `build` job: - Tests building the Phar against all supported PHP versions as an early detection system for problems in the Phar extension/Phar building script. - Uploads both phars as build against PHP 7.4. The uploaded Phars match the Phars as would be included on a release as they are build against the same PHP version as used for releases. These Phars will now also be available for PRs and merges to `master` to allow for easier testing of change proposals by issue reporters who may not have a git clone of the repo. The uploaded Phars will be available for 28 days (default 90). - Does a cursory test with both the `phpcs.phar` as well as the `phpcbf.phar` to test that the build phar files are functional. * In the `test` job, which now depends on the `build` job, the Phar will now no longer be build, but the Phar as uploaded in the `build` job - i.e. the Phar build against the same PHP version as used when releasing Phars - is downloaded and used to run the Phar test. The uploaded Phar files can be found on the "Summary" page of the `test` workflow after the build has finished for each build run. --- .github/workflows/test.yml | 66 ++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b6736d980..30b3da6215 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@v2 + + - 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@v2 + if: ${{ success() && matrix.php == '7.4' }} + with: + name: phpcs-phar + path: ./phpcs.phar + if-no-files-found: error + retention-days: 28 + + - name: Upload the PHPCBF phar + uses: actions/upload-artifact@v2 + if: ${{ success() && matrix.php == '7.4' }} + 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: @@ -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 '::set-output name=PHP_INI::error_reporting=-1, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On, asp_tags=On' 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 '::set-output name=PHP_INI::error_reporting=-1, display_errors=On, date.timezone=Australia/Sydney, short_open_tag=On' else - echo '::set-output name=PHP_INI::phar.readonly=Off, error_reporting=-1, display_errors=On' + echo '::set-output name=PHP_INI::error_reporting=-1, display_errors=On' fi - name: Install PHP @@ -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@v2 + 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 From 46d8d1641728e587ea326d014d26fb6def7310b7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 16 Dec 2021 18:59:07 +0100 Subject: [PATCH 463/733] GH Actions: change the building of the (release) PHARs to PHP 8.0 The PHP native `php_strip_whitespace()` function used in the PHAR build script is PHP version sensitive. The function strips comments and whitespace out of the code, but in PHP 7.4, attributes are seen as comments due to the `#[]` syntax, which means that if the PHAR files were being generated on PHP 7.4, the PHP 8.1 #[ReturnTypeWillChange]` attributes as put in via PR 3400 would be stripped out. In other words: for the PHAR files to be cross-version compatible, they *MUST* be generated on PHP 8.0 or higher. This fixes this for the CI part of things. Ref: https://www.php.net/manual/en/function.php-strip-whitespace.php --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30b3da6215..dad38714f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: - name: Upload the PHPCS phar uses: actions/upload-artifact@v2 - if: ${{ success() && matrix.php == '7.4' }} + if: ${{ success() && matrix.php == '8.0' }} with: name: phpcs-phar path: ./phpcs.phar @@ -49,7 +49,7 @@ jobs: - name: Upload the PHPCBF phar uses: actions/upload-artifact@v2 - if: ${{ success() && matrix.php == '7.4' }} + if: ${{ success() && matrix.php == '8.0' }} with: name: phpcbf-phar path: ./phpcbf.phar From f4bec0a730ccccdf06f5a7174bce23904c7aeed8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 17 Dec 2021 05:19:34 +0100 Subject: [PATCH 464/733] Build Phar: minor tweak to allow testing the script on Windows --- scripts/build-phar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-phar.php b/scripts/build-phar.php index 67116e90b8..2fc38e24e1 100644 --- a/scripts/build-phar.php +++ b/scripts/build-phar.php @@ -60,7 +60,7 @@ } $fullpath = $file->getPathname(); - if (strpos($fullpath, '/Tests/') !== false) { + if (strpos($fullpath, DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR) !== false) { continue; } From a4b52173750b2dcd7b28ceef87ddaaadf2a087b2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 17 Dec 2021 05:21:03 +0100 Subject: [PATCH 465/733] Build Phar: show more debug information * Number of files added for each PHAR. * Total time the script took to run. --- scripts/build-phar.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/build-phar.php b/scripts/build-phar.php index 2fc38e24e1..9661225303 100644 --- a/scripts/build-phar.php +++ b/scripts/build-phar.php @@ -21,6 +21,8 @@ exit(1); } +$startTime = microtime(true); + $scripts = [ 'phpcs', 'phpcbf', @@ -51,6 +53,7 @@ $rdi = new \RecursiveDirectoryIterator($srcDir, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); $di = new \RecursiveIteratorIterator($rdi, 0, \RecursiveIteratorIterator::CATCH_GET_CHILD); + $fileCount = 0; foreach ($di as $file) { $filename = $file->getFilename(); @@ -66,7 +69,9 @@ $path = 'src'.substr($fullpath, $srcDirLen); $phar->addFile($fullpath, $path); - } + + ++$fileCount; + }//end foreach // Add autoloader. $phar->addFile(realpath(__DIR__.'/../autoload.php'), 'autoload.php'); @@ -75,6 +80,7 @@ $phar->addFile(realpath(__DIR__.'/../licence.txt'), 'licence.txt'); echo 'done'.PHP_EOL; + echo "\t Added ".$fileCount.' files'.PHP_EOL; /* Add the stub. @@ -93,3 +99,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; From d5359c780da829e86b712e6565faf60b84c708ee Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 17 Dec 2021 12:09:29 +0100 Subject: [PATCH 466/733] Include NullSafe operator in calculation for Cyclomatic Complexity Sniff, with appropriate unit tests --- .../Metrics/CyclomaticComplexitySniff.php | 25 ++++++++++--------- .../Metrics/CyclomaticComplexityUnitTest.inc | 18 +++++++++++++ .../Metrics/CyclomaticComplexityUnitTest.php | 1 + 3 files changed, 32 insertions(+), 12 deletions(-) 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/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() From 0735c1c5af759b5871a61e394b332fac494c0730 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 06:24:50 +0100 Subject: [PATCH 467/733] Build Phar: add custom "strip whitespace and comments" function This implements option 3 as discussion in the thread for PR 3442. A custom `stripWhitespaceAndComments()` function has been created and added to the script used to build the PHAR files and replaces the use of the PHP native `php_strip_whitespace()` function, which does not allow for creating PHP cross-version compatible PHAR files if attributes are used anywhere in the code. The problem with the PHP native `php_strip_whitespace()` function is this: * When run on PHP <= 7.4, attributes would be stripped from the code as they are seen as `#` comments. This undoes the deprecation silencing for methods for which no return type can be added yet (as needed for full PHP 8.1 compatibility). * When run on PHP >= 8.0, attributes are _not_ stripped, but recognized correctly, however, the function strips **all** new lines, turning the file effectively into one long line of code. This is problematic when the PHAR would subsequently be run on PHP < 8.0, as any code after the first attribute would then be seen as "commented out", leading to the PHAR not running with a parse error. To solve this, the new `stripWhitespaceAndComments()` function emulates the PHP native function, with two important differences: * New lines are very selectively left in the regenerated content of the files. By ensuring that there is always a new line after an attribute closer, we can prevent code from being seen as commented out in PHP < 8.0. * As the PHPCS native PHP tokenizer is used to interpret the file content, the token stream will be the same PHP cross-version, meaning that attributes will be recognized on all supported PHP versions and the script can now be run again on any supported PHP version and the generated PHAR files will be the same. As an additional performance tweak, `xml` files will no longer be passed to the whitespace/comment stripping. This had now effect previously and as XML files would tokenize as 100% `T_INLINE_HTML`, passing these to the new function would have no effect either (other than slowing down the script). Same goes for the `license.txt` file. --- scripts/build-phar.php | 70 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/scripts/build-phar.php b/scripts/build-phar.php index 9661225303..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,58 @@ 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 = [ @@ -53,7 +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(); @@ -68,13 +128,19 @@ } $path = 'src'.substr($fullpath, $srcDirLen); - $phar->addFile($fullpath, $path); + + if (substr($filename, -4) === '.xml') { + $phar->addFile($fullpath, $path); + } else { + // PHP file. + $phar->addFromString($path, stripWhitespaceAndComments($fullpath, $config)); + } ++$fileCount; }//end foreach // Add autoloader. - $phar->addFile(realpath(__DIR__.'/../autoload.php'), 'autoload.php'); + $phar->addFromString('autoload.php', stripWhitespaceAndComments(realpath(__DIR__.'/../autoload.php'), $config)); // Add licence file. $phar->addFile(realpath(__DIR__.'/../licence.txt'), 'licence.txt'); From 23e4e6110adf0e780bbd9e21507420a6e262d86e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 08:28:26 +0100 Subject: [PATCH 468/733] Tokenizer/PHP: readonly bug fix While going through some sniffs to add support, I realized that parameters with a reference were not accounted for correctly. Fixed with test. --- src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/BackfillReadonlyTest.inc | 3 +++ tests/Core/Tokenizer/BackfillReadonlyTest.php | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 27f5544e35..68da7f6393 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2856,6 +2856,7 @@ protected function processAdditional() T_NAME_QUALIFIED => T_NAME_QUALIFIED, T_TYPE_UNION => T_TYPE_UNION, T_BITWISE_OR => T_BITWISE_OR, + T_BITWISE_AND => T_BITWISE_AND, T_ARRAY => T_ARRAY, T_CALLABLE => T_CALLABLE, T_SELF => T_SELF, diff --git a/tests/Core/Tokenizer/BackfillReadonlyTest.inc b/tests/Core/Tokenizer/BackfillReadonlyTest.inc index f585134ca3..ab7c16c321 100644 --- a/tests/Core/Tokenizer/BackfillReadonlyTest.inc +++ b/tests/Core/Tokenizer/BackfillReadonlyTest.inc @@ -52,6 +52,9 @@ class Foo public function __construct(private readonly bool $constructorPropertyPromotion) { } + + /* testReadonlyConstructorPropertyPromotionWithReference */ + public function __construct(private ReadOnly bool &$constructorPropertyPromotion) {} } $anonymousClass = new class () { diff --git a/tests/Core/Tokenizer/BackfillReadonlyTest.php b/tests/Core/Tokenizer/BackfillReadonlyTest.php index 0f4e922862..d347417143 100644 --- a/tests/Core/Tokenizer/BackfillReadonlyTest.php +++ b/tests/Core/Tokenizer/BackfillReadonlyTest.php @@ -139,6 +139,10 @@ public function dataReadonly() '/* testReadonlyConstructorPropertyPromotion */', 'readonly', ], + [ + '/* testReadonlyConstructorPropertyPromotionWithReference */', + 'ReadOnly', + ], [ '/* testReadonlyPropertyInAnonymousClass */', 'readonly', From 68b9c0cedce643f537c1f147cead7bdf58754fc9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 08:32:26 +0100 Subject: [PATCH 469/733] Generic/LowerCaseKeyword: allow for readonly keyword Includes tests. --- src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php | 1 + src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc | 4 ++++ .../Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed | 4 ++++ src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php | 1 + 4 files changed, 10 insertions(+) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php index e10047b64e..2ddf314a02 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php @@ -82,6 +82,7 @@ public function register() T_PRIVATE, T_PROTECTED, T_PUBLIC, + T_READONLY, T_REQUIRE, T_REQUIRE_ONCE, T_RETURN, diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc index 8d003c3bcc..6c3e3f9a21 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc @@ -35,5 +35,9 @@ $r = Match ($x) { DEFAULT, => 3, }; +class Reading { + Public READOnly int $var; +} + __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..1c8550387b 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed @@ -35,5 +35,9 @@ $r = match ($x) { default, => 3, }; +class Reading { + public readonly int $var; +} + __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..bf859e7fb9 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php @@ -40,6 +40,7 @@ public function getErrorList() 31 => 1, 32 => 1, 35 => 1, + 39 => 2, ]; }//end getErrorList() From e730d6a99f0662e9c42e95da7da2f30a3f058351 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 08:44:32 +0100 Subject: [PATCH 470/733] Squiz/BlockComment: prevent false positives for readonly keyword Includes tests. --- .../Squiz/Sniffs/Commenting/BlockCommentSniff.php | 1 + .../Squiz/Tests/Commenting/BlockCommentUnitTest.inc | 11 +++++++++++ .../Tests/Commenting/BlockCommentUnitTest.inc.fixed | 11 +++++++++++ 3 files changed, 23 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php index 6a12bb7368..d59fe1e113 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php @@ -93,6 +93,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/Tests/Commenting/BlockCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc index eed554b3f3..b25de27679 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc @@ -288,3 +288,14 @@ final class MyClass #[AttributeB] final public function test() {} } + +/** + * Comment should be ignored. + */ +abstract class MyClass +{ + /** + * Comment should be ignored. + */ + readonly public string $prop; +} diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed index 7471b582a9..e1e821bf56 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed @@ -290,3 +290,14 @@ final class MyClass #[AttributeB] final public function test() {} } + +/** + * Comment should be ignored. + */ +abstract class MyClass +{ + /** + * Comment should be ignored. + */ + readonly public string $prop; +} From 1043c5474ea0020ffcd96ba89be9c17876d5f755 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 08:49:37 +0100 Subject: [PATCH 471/733] Squiz/DocCommentAlignment: allow for readonly keyword Includes tests. --- .../Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php | 1 + .../Tests/Commenting/DocCommentAlignmentUnitTest.inc | 8 ++++++++ .../Commenting/DocCommentAlignmentUnitTest.inc.fixed | 8 ++++++++ .../Tests/Commenting/DocCommentAlignmentUnitTest.php | 2 ++ 4 files changed, 19 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php index 2624bc2246..b557ae24cd 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php @@ -74,6 +74,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/Tests/Commenting/DocCommentAlignmentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc index e7d880d682..5de613da22 100644 --- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc @@ -77,6 +77,14 @@ class MyClass2 var $x; } +abstract class MyClass +{ + /** +* Property comment + */ + readonly public string $prop; +} + /** ************************************************************************ * 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..7395d2fab4 100644 --- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed @@ -77,6 +77,14 @@ class MyClass2 var $x; } +abstract class MyClass +{ + /** + * Property comment + */ + readonly public string $prop; +} + /** ************************************************************************ * Example with no errors. **************************************************************************/ diff --git a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php index 974951ce42..a862d4a819 100644 --- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php @@ -45,6 +45,8 @@ public function getErrorList($testFile='DocCommentAlignmentUnitTest.inc') if ($testFile === 'DocCommentAlignmentUnitTest.inc') { $errors[75] = 1; + $errors[83] = 1; + $errors[84] = 1; } return $errors; From b043ae451265a43f0ec4912dda3a5d462f745a62 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 08:41:06 +0100 Subject: [PATCH 472/733] Squiz/VariableComment: allow for readonly keyword Includes tests. --- .../Commenting/VariableCommentSniff.php | 1 + .../Commenting/VariableCommentUnitTest.inc | 19 +++++++++++++++++++ .../VariableCommentUnitTest.inc.fixed | 19 +++++++++++++++++++ .../Commenting/VariableCommentUnitTest.php | 2 ++ 4 files changed, 41 insertions(+) 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/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() From 068635e0b718529faf2560e10e653e966dcb46ba Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 09:14:49 +0100 Subject: [PATCH 473/733] PSR2/PropertyDeclaration: allow for readonly keyword Includes tests. --- .../PSR2/Sniffs/Classes/PropertyDeclarationSniff.php | 1 + .../PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc | 10 ++++++++++ .../Classes/PropertyDeclarationUnitTest.inc.fixed | 10 ++++++++++ .../PSR2/Tests/Classes/PropertyDeclarationUnitTest.php | 3 +++ 4 files changed, 24 insertions(+) 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/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() From 1d660ccf35de557da12a315804a39818adb06ae9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 19:02:16 +0100 Subject: [PATCH 474/733] Tokenizer/PHP: readonly vs union types bug fix Done some more stress testing and found that the retokenization of `|` to `T_TYPE_UNION` was broken when combined with the `readonly` keyword. Fixed now, includes test. --- src/Tokenizers/PHP.php | 3 ++- tests/Core/Tokenizer/BitwiseOrTest.inc | 3 +++ tests/Core/Tokenizer/BitwiseOrTest.php | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 27f5544e35..a0f94112bf 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2710,7 +2710,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; diff --git a/tests/Core/Tokenizer/BitwiseOrTest.inc b/tests/Core/Tokenizer/BitwiseOrTest.inc index 2af0ecae98..0baca8c5c2 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.inc +++ b/tests/Core/Tokenizer/BitwiseOrTest.inc @@ -33,6 +33,9 @@ class TypeUnion /* testTypeUnionPropertyFullyQualified */ public \Fully\Qualified\NameA|\Fully\Qualified\NameB $fullyQual; + /* testTypeUnionPropertyWithReadOnlyKeyword */ + protected readonly string|null $array; + 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..64b5c32986 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.php +++ b/tests/Core/Tokenizer/BitwiseOrTest.php @@ -105,6 +105,7 @@ public function dataTypeUnion() ['/* testTypeUnionPropertyNamespaceRelative */'], ['/* testTypeUnionPropertyPartiallyQualified */'], ['/* testTypeUnionPropertyFullyQualified */'], + ['/* testTypeUnionPropertyWithReadOnlyKeyword */'], ['/* testTypeUnionParam1 */'], ['/* testTypeUnionParam2 */'], ['/* testTypeUnionParam3 */'], From c715f09d4a23a05d58856705e58583c13e704400 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 20 Dec 2021 16:15:05 +1100 Subject: [PATCH 475/733] Changelog for readonly sniff support --- package.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package.xml b/package.xml index a70ea3b5f8..c590d834fb 100644 --- a/package.xml +++ b/package.xml @@ -32,6 +32,13 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 + - Support for new PHP 8.1 readonly keyword has been added to the following sniffs: + -- Generic.PHP.LowerCaseKeyword + -- PSR2.Classes.PropertyDeclaration + -- Squiz.Commenting.BlockCommentS + -- Squiz.Commenting.DocCommentAlignment + -- Squiz.Commenting.VariableComment + -- Thanks to Juliette Reinders Folmer for the patches From f653fd244fd393d23b8232b7b8ad2be5ee1ce44b Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 20 Dec 2021 17:11:29 +1100 Subject: [PATCH 476/733] Fixed bug #3503 : Squiz.Commenting.FunctionComment.ThrowsNoFullStop false positive when one line @throw --- package.xml | 1 + .../Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 2 ++ .../Squiz/Tests/Commenting/FunctionCommentUnitTest.inc | 5 +++++ .../Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/package.xml b/package.xml index c590d834fb..2112edcae2 100644 --- a/package.xml +++ b/package.xml @@ -39,6 +39,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Squiz.Commenting.DocCommentAlignment -- Squiz.Commenting.VariableComment -- Thanks to Juliette Reinders Folmer for the patches + - Fixed bug #3503 : Squiz.Commenting.FunctionComment.ThrowsNoFullStop false positive when one line @throw 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/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() {} From 67c82d975fc15b67725c7e4a0312bf8f28354c0d Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 20 Dec 2021 17:20:45 +1100 Subject: [PATCH 477/733] Fixed bug #3502 : A match statement within an array produces Squiz.Arrays.ArrayDeclaration.NoKeySpecified --- package.xml | 1 + .../Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php | 1 + .../Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc | 7 +++++++ .../Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed | 7 +++++++ 4 files changed, 16 insertions(+) diff --git a/package.xml b/package.xml index 2112edcae2..3836361538 100644 --- a/package.xml +++ b/package.xml @@ -39,6 +39,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Squiz.Commenting.DocCommentAlignment -- Squiz.Commenting.VariableComment -- Thanks to Juliette Reinders Folmer for the patches + - 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 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/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' => From 187e5dc1df538cca81390411800a744c93a1e45a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 08:27:15 +0100 Subject: [PATCH 478/733] PHP 8.1 | Squiz/ScopeKeywordSpacing: allow for readonly keyword Includes tests. --- .../Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php | 1 + .../WhiteSpace/ScopeKeywordSpacingUnitTest.inc | 13 +++++++++++++ .../ScopeKeywordSpacingUnitTest.inc.fixed | 13 +++++++++++++ .../WhiteSpace/ScopeKeywordSpacingUnitTest.php | 3 +++ 4 files changed, 30 insertions(+) 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/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() From ca6801628a84c609396352fd20ec0e8e53370f96 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 18:13:17 +0100 Subject: [PATCH 479/733] PHP 8.1 | File::getMethodParameters(): allow for readonly keyword ... in constructor property promotion. Note: the `property_readonly` key will always be added when constructor property promotion has been detected. The `readonly_token` only when the `readonly` token has actually been found. Includes unit tests. --- src/Files/File.php | 15 +++++++ tests/Core/File/GetMethodParametersTest.inc | 5 +++ tests/Core/File/GetMethodParametersTest.php | 43 +++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/Files/File.php b/src/Files/File.php index cfe6f52af3..864c9424ba 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1310,6 +1310,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 +1368,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 @@ -1491,6 +1494,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 +1531,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 +1560,7 @@ public function getMethodParameters($stackPtr) $typeHintEndToken = false; $nullableType = false; $visibilityToken = null; + $readonlyToken = null; $paramCount++; break; diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index ed1762e7d2..e7565e4cad 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) {} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index e7692a7153..e07bd869d3 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -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. * From 2b7bdb344d1527bcdb157e6ee60c6ce9fcca07ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sun, 21 Nov 2021 11:51:25 +0100 Subject: [PATCH 480/733] Improved tokenizing of context sensitive keywords --- package.xml | 6 + src/Tokenizers/PHP.php | 265 +++++----- src/Util/Tokens.php | 75 +++ .../ContextSensitiveKeywordsTest.inc | 208 ++++++++ .../ContextSensitiveKeywordsTest.php | 469 ++++++++++++++++++ 5 files changed, 874 insertions(+), 149 deletions(-) create mode 100644 tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc create mode 100644 tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php diff --git a/package.xml b/package.xml index 3836361538..f66e4fda60 100644 --- a/package.xml +++ b/package.xml @@ -138,6 +138,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2097,6 +2099,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2191,6 +2195,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5b7abf1cb9..7ddfa35350 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -589,6 +589,64 @@ 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 + ) { + $preserveKeyword = false; + + // `new class` should be preserved + if ($token[0] === T_CLASS && $finalTokens[$lastNotEmptyToken]['code'] === T_NEW) { + $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; + } + } + + 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. */ @@ -1113,6 +1171,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 @@ -1446,57 +1505,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; @@ -1693,14 +1737,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 @@ -1708,37 +1747,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 = []; @@ -1926,31 +1934,31 @@ 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 + // 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', + ]; + } - $newStackPtr++; - continue; - }//end if + $newStackPtr++; + continue; }//end if $newToken = null; @@ -2114,16 +2122,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. @@ -2819,34 +2817,11 @@ 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'; - } - } else if ($this->tokens[$i]['code'] === T_READONLY - || ($this->tokens[$i]['code'] === T_STRING - && strtolower($this->tokens[$i]['content']) === 'readonly') + } else if ($this->tokens[$i]['code'] === T_STRING + && strtolower($this->tokens[$i]['content']) === 'readonly' ) { /* - Adds "readonly" keyword support: - PHP < 8.1: Converts T_STRING to T_READONLY - PHP >= 8.1: Converts some T_READONLY to T_STRING because token_get_all() - without the TOKEN_PARSE flag cannot distinguish between them in some situations. + Adds "readonly" keyword support for PHP < 8.1. */ $allowedAfter = [ @@ -2890,7 +2865,7 @@ protected function processAdditional() } } - if ($this->tokens[$i]['code'] === T_STRING && $shouldBeReadonly === true) { + if ($shouldBeReadonly === true) { if (PHP_CODESNIFFER_VERBOSITY > 1) { $line = $this->tokens[$i]['line']; echo "\t* token $i on line $line changed from T_STRING to T_READONLY".PHP_EOL; @@ -2898,14 +2873,6 @@ protected function processAdditional() $this->tokens[$i]['code'] = T_READONLY; $this->tokens[$i]['type'] = 'T_READONLY'; - } else if ($this->tokens[$i]['code'] === T_READONLY && $shouldBeReadonly === false) { - if (PHP_CODESNIFFER_VERBOSITY > 1) { - $line = $this->tokens[$i]['line']; - echo "\t* token $i on line $line changed from T_READONLY to T_STRING".PHP_EOL; - } - - $this->tokens[$i]['code'] = T_STRING; - $this->tokens[$i]['type'] = 'T_STRING'; } continue; diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 0bc1747275..b05eb6161a 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -653,6 +653,81 @@ 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_ENDDECLARE => T_ENDDECLARE, + T_ENDFOR => T_ENDFOR, + T_ENDFOREACH => T_ENDFOREACH, + T_ENDIF => T_ENDIF, + T_ENDSWITCH => T_ENDSWITCH, + T_ENDWHILE => T_ENDWHILE, + 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_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_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/Tokenizer/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc new file mode 100644 index 0000000000..9506a35c6e --- /dev/null +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc @@ -0,0 +1,208 @@ + '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 {}; + +class Foo extends /* testNamespaceInNameIsKeyword */ namespace\Exception +{} diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php new file mode 100644 index 0000000000..4c200fbc30 --- /dev/null +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php @@ -0,0 +1,469 @@ + + * @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 */'], + ['/* testEndDeclare */'], + ['/* testEndFor */'], + ['/* testEndForeach */'], + ['/* testEndIf */'], + ['/* testEndSwitch */'], + ['/* testEndWhile */'], + ['/* testExit */'], + ['/* testExtends */'], + ['/* testFinal */'], + ['/* testFinally */'], + ['/* testFn */'], + ['/* testFor */'], + ['/* testForeach */'], + ['/* testFunction */'], + ['/* testGlobal */'], + ['/* testGoto */'], + ['/* testIf */'], + ['/* testImplements */'], + ['/* testInclude */'], + ['/* testIncludeOnce */'], + ['/* testInstanceOf */'], + ['/* testInsteadOf */'], + ['/* testInterface */'], + ['/* testList */'], + ['/* testMatch */'], + ['/* testNamespace */'], + ['/* testNew */'], + ['/* testParent */'], + ['/* testPrint */'], + ['/* testPrivate */'], + ['/* testProtected */'], + ['/* testPublic */'], + ['/* testReadonly */'], + ['/* testRequire */'], + ['/* testRequireOnce */'], + ['/* testReturn */'], + ['/* testSelf */'], + ['/* testStatic */'], + ['/* testSwitch */'], + ['/* testThrows */'], + ['/* testTrait */'], + ['/* testTry */'], + ['/* testUse */'], + ['/* testVar */'], + ['/* testWhile */'], + ['/* testYield */'], + ['/* testYieldFrom */'], + ['/* testAnd */'], + ['/* testOr */'], + ['/* testXor */'], + + ['/* testKeywordAfterNamespaceShouldBeString */'], + ['/* testNamespaceNameIsString1 */'], + ['/* testNamespaceNameIsString2 */'], + ['/* testNamespaceNameIsString3 */'], + ]; + + }//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', + ], + + [ + '/* testNewIsKeyword */', + 'T_NEW', + ], + [ + '/* testInstanceOfIsKeyword */', + 'T_INSTANCEOF', + ], + [ + '/* testCloneIsKeyword */', + 'T_CLONE', + ], + + [ + '/* testIfIsKeyword */', + 'T_IF', + ], + [ + '/* 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', + ], + [ + '/* testExitIsKeyword */', + 'T_EXIT', + ], + + [ + '/* 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', + ], + [ + '/* testNamespaceInNameIsKeyword */', + 'T_NAMESPACE', + ], + ]; + + }//end dataKeywords() + + +}//end class From c73f456fb650e2d1ec3796dce41d08b09fa27c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Tue, 21 Dec 2021 10:24:31 +0100 Subject: [PATCH 481/733] Removed dead code --- src/Tokenizers/PHP.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 7ddfa35350..0d37929ff2 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1786,22 +1786,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 From 932ab66819c013a11c661a8b286c63523bc3c9dc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 23 Dec 2021 02:16:11 +0100 Subject: [PATCH 482/733] Tokenizer/PHP: fix incorrect condition order As things were, `$tokens[i]` may not exist and the current condition order would lead to a `PHP Warning: Undefined array key 517 in php_codesniffer/src/Tokenizers/PHP.php on line 2101` notice if the end of the file was reached. Fixed now. --- src/Tokenizers/PHP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 5b7abf1cb9..f18bc4017c 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2098,7 +2098,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'; } From afeaefa510ac78465e1847d7a0b9474097aa03bf Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 25 Dec 2021 18:59:36 +0100 Subject: [PATCH 483/733] GH Actions: version update for `ramsey/composer-install` The action used to install Composer packages and handle the caching has released a new major (and some follow-up patch releases), which means, the action reference needs to be updated to benefit from it. Refs: * https://github.com/ramsey/composer-install/releases/tag/2.0.0 * https://github.com/ramsey/composer-install/releases/tag/2.0.1 * https://github.com/ramsey/composer-install/releases/tag/2.0.2 --- .github/workflows/phpstan.yml | 2 +- .github/workflows/test.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 0d33a0d8ca..70d871556b 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -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 dad38714f6..4921efa97b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,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 From b46ba51ab54e5fec049adb197fcee8569539fb01 Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Fri, 31 Dec 2021 23:02:36 +0100 Subject: [PATCH 484/733] Close popen handle with pclose --- src/Reports/Gitblame.php | 2 +- src/Reports/Hgblame.php | 2 +- src/Reports/Svnblame.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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); From 4898fc64c5205e01af4f305c6cc2445d30bd90ae Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 4 Jan 2022 21:09:33 +0100 Subject: [PATCH 485/733] FileComment: update year in test file The regular annual update to make sure the build still passes ;-) --- .../Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed | 2 +- .../Squiz/Tests/Commenting/FileCommentUnitTest.1.js.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed index 5cc4764205..e42e037c70 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 2022 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..90046c7102 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 2022 Squiz Pty Ltd (ABN 77 084 670 600) * @license http://www.php.net/license/3_0.txt * @summary An unknown summary tag * From 1865fbaa9363f864403dc32d466ddd742abcdd24 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Tue, 21 Dec 2021 14:32:28 -0800 Subject: [PATCH 486/733] Get rid of busy-waiting in the parent process --- src/Runner.php | 90 ++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 47 deletions(-) diff --git a/src/Runner.php b/src/Runner.php index 253ec91f9f..020adbb7c8 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -460,10 +460,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 +533,7 @@ private function run() $output .= ";\n?".'>'; file_put_contents($childOutFilename, $output); - exit($pid); + exit(); }//end if }//end for @@ -719,54 +716,53 @@ 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; - } - - $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']; + $pid = pcntl_waitpid(0, $status); + if ($pid > 0) { + $out = $childProcs[$pid]; + unset($childProcs[$pid]); + if (file_exists($out) === true) { + include $out; - if (isset($debugOutput) === true) { - echo $debugOutput; - } + unlink($out); - if (isset($childCache) === true) { - foreach ($childCache as $path => $cache) { - Cache::set($path, $cache); - } - } + $numProcessed++; - // Fake a processed file so we can print progress output for the batch. + if (isset($childOutput) === false) { + // The child process died, so the run has failed. $file = new DummyFile('', $this->ruleset, $this->config); - $file->setErrorCounts( - $childOutput['totalErrors'], - $childOutput['totalWarnings'], - $childOutput['totalFixable'], - $childOutput['totalFixed'] - ); + $file->setErrorCounts(1, 0, 0, 0); $this->printProgress($file, $totalBatches, $numProcessed); - }//end if + $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 if - }//end foreach + }//end if }//end while return $success; From 1b1fc31f391d9c2f676e6a2fb39a31898afd0969 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 5 Jan 2022 23:30:56 +0100 Subject: [PATCH 487/733] PHP 8.1 | PSR12/ConstantVisibility: allow for class constants to be `final` PHP 8.1 introduces the ability to declare class constants as `final`. The tokenization of the `final` keyword in this context appears to be consistent PHPCS cross-version, so AFAICS no changes are needed to the PHP tokenizer. However, the `PSR12.Properties.ConstantVisibility` sniff does need to allow for them. Includes unit tests. Ref: https://wiki.php.net/rfc/final_class_const --- .../Sniffs/Properties/ConstantVisibilitySniff.php | 5 ++++- .../Tests/Properties/ConstantVisibilityUnitTest.inc | 10 ++++++++++ .../Tests/Properties/ConstantVisibilityUnitTest.php | 5 ++++- 3 files changed, 18 insertions(+), 2 deletions(-) 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/Properties/ConstantVisibilityUnitTest.inc b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc index c07b1b91eb..88d2a92c34 100644 --- a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc +++ b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc @@ -5,3 +5,13 @@ 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'; +} diff --git a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php index 3917eb6821..a4236b41ea 100644 --- a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php +++ b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php @@ -40,7 +40,10 @@ public function getErrorList() */ public function getWarningList() { - return [4 => 1]; + return [ + 4 => 1, + 12 => 1, + ]; }//end getWarningList() From d57ddc23f239db5cc2c58a51bf401ecb6d6de57f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 10 Jan 2022 16:03:28 +1100 Subject: [PATCH 488/733] Changelog for #3526 (ref #3527) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 3836361538..ff79a80ca1 100644 --- a/package.xml +++ b/package.xml @@ -41,6 +41,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patches - 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 #3526 : PSR12.Properties.ConstantVisibility false positive when using public final const syntax + -- Thanks to Juliette Reinders Folmer for the patch From 5672074bce33eb885980648d765bb29adca338a2 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 10 Jan 2022 16:27:37 +1100 Subject: [PATCH 489/733] Fixed bug #3530 : Line indented incorrectly false positive when using match-expression inside switch case --- package.xml | 1 + .../Sniffs/WhiteSpace/ScopeIndentSniff.php | 7 +++++-- .../Tests/WhiteSpace/ScopeIndentUnitTest.1.inc | 18 +++++++++++++++++- .../WhiteSpace/ScopeIndentUnitTest.1.inc.fixed | 18 +++++++++++++++++- .../Tests/WhiteSpace/ScopeIndentUnitTest.2.inc | 18 +++++++++++++++++- .../WhiteSpace/ScopeIndentUnitTest.2.inc.fixed | 18 +++++++++++++++++- .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 ++++---- 7 files changed, 78 insertions(+), 10 deletions(-) diff --git a/package.xml b/package.xml index ff79a80ca1..ee04a0e3e5 100644 --- a/package.xml +++ b/package.xml @@ -43,6 +43,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3503 : Squiz.Commenting.FunctionComment.ThrowsNoFullStop false positive when one line @throw - 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 diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index a01449197a..92d2ed8b84 100644 --- a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php @@ -1325,11 +1325,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/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index 6aadcc2095..caeb503aa8 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1556,6 +1556,22 @@ $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'; +} + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> @@ -1564,7 +1580,7 @@ $a = [ 'custom_1', + default => 'a' + }; + return $foo; + case 'b': + return match ($foo) { + 'bar' => 'custom_1', + default => 'b' + }; + default: + return 'default'; +} + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> @@ -1564,7 +1580,7 @@ $a = [ 'custom_1', + default => 'a' + }; + return $foo; + case 'b': + return match ($foo) { + 'bar' => 'custom_1', + default => 'b' + }; + default: + return 'default'; +} + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> @@ -1564,7 +1580,7 @@ $a = [ 'custom_1', + default => 'a' + }; + return $foo; + case 'b': + return match ($foo) { + 'bar' => 'custom_1', + default => 'b' + }; + default: + return 'default'; +} + /* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */ ?> @@ -1564,7 +1580,7 @@ $a = [ 1, 1529 => 1, 1530 => 1, - 1567 => 1, - 1568 => 1, - 1569 => 1, - 1570 => 1, + 1583 => 1, + 1584 => 1, + 1585 => 1, + 1586 => 1, ]; }//end getErrorList() From f236c6a39ee93e2741ccb60224519ee2f0c33be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Fri, 19 Nov 2021 20:13:48 +0100 Subject: [PATCH 490/733] PHP 8.1: Added support for "enum" keyword --- package.xml | 6 + src/Tokenizers/PHP.php | 45 ++++ src/Util/Tokens.php | 8 + tests/Core/Tokenizer/BackfillEnumTest.inc | 95 ++++++++ tests/Core/Tokenizer/BackfillEnumTest.php | 229 ++++++++++++++++++ .../ContextSensitiveKeywordsTest.inc | 2 + .../ContextSensitiveKeywordsTest.php | 5 + 7 files changed, 390 insertions(+) create mode 100644 tests/Core/Tokenizer/BackfillEnumTest.inc create mode 100644 tests/Core/Tokenizer/BackfillEnumTest.php diff --git a/package.xml b/package.xml index f3ceca5c0b..fa24f0c587 100644 --- a/package.xml +++ b/package.xml @@ -131,6 +131,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2092,6 +2094,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2188,6 +2192,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index c8efba537d..7f24a86af0 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,7 @@ class PHP extends Tokenizer T_ENDIF => 5, T_ENDSWITCH => 9, T_ENDWHILE => 8, + T_ENUM => 4, T_EVAL => 4, T_EXTENDS => 7, T_FILE => 8, @@ -467,6 +475,7 @@ class PHP extends Tokenizer T_CLASS => true, T_INTERFACE => true, T_TRAIT => true, + T_ENUM => true, T_EXTENDS => true, T_IMPLEMENTS => true, T_ATTRIBUTE => true, @@ -952,6 +961,42 @@ 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 + ) { + $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 + /* As of PHP 8.0 fully qualified, partially qualified and namespace relative identifier names are tokenized differently. diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index b05eb6161a..64014a17e0 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -167,6 +167,10 @@ 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'); @@ -194,6 +198,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, @@ -419,6 +424,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, @@ -633,6 +639,7 @@ final class Tokens T_ANON_CLASS => T_ANON_CLASS, T_INTERFACE => T_INTERFACE, T_TRAIT => T_TRAIT, + T_ENUM => T_ENUM, ]; /** @@ -684,6 +691,7 @@ final class Tokens T_ENDIF => T_ENDIF, T_ENDSWITCH => T_ENDSWITCH, T_ENDWHILE => T_ENDWHILE, + T_ENUM => T_ENUM, T_EXIT => T_EXIT, T_EXTENDS => T_EXTENDS, T_FINAL => T_FINAL, diff --git a/tests/Core/Tokenizer/BackfillEnumTest.inc b/tests/Core/Tokenizer/BackfillEnumTest.inc new file mode 100644 index 0000000000..28feb2f28c --- /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..8653e8c1b9 --- /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', + 6, + 28, + ], + [ + '/* testBackedStringEnum */', + 'enum', + 6, + 28, + ], + [ + '/* testComplexEnum */', + 'enum', + 10, + 71, + ], + [ + '/* 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/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc index 9506a35c6e..eb1ca72058 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc @@ -28,6 +28,7 @@ class ContextSensitiveKeywords const /* testEndIf */ ENDIF = 'ENDIF'; const /* testEndSwitch */ ENDSWITCH = 'ENDSWITCH'; const /* testEndWhile */ ENDWHILE = 'ENDWHILE'; + const /* testEnum */ ENUM = 'ENUM'; const /* testExit */ EXIT = 'EXIT'; const /* testExtends */ EXTENDS = 'EXTENDS'; const /* testFinal */ FINAL = 'FINAL'; @@ -113,6 +114,7 @@ namespace /* testNamespaceNameIsString1 */ my\ /* testNamespaceNameIsString2 */ /* testInterfaceIsKeyword */ interface SomeInterface {} /* testTraitIsKeyword */ trait SomeTrait {} +/* testEnumIsKeyword */ enum SomeEnum {} $object = /* testNewIsKeyword */ new SomeClass(); $object /* testInstanceOfIsKeyword */ instanceof SomeClass; diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php index 4c200fbc30..72aeac6859 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php @@ -71,6 +71,7 @@ public function dataStrings() ['/* testEndIf */'], ['/* testEndSwitch */'], ['/* testEndWhile */'], + ['/* testEnum */'], ['/* testExit */'], ['/* testExtends */'], ['/* testFinal */'], @@ -251,6 +252,10 @@ public function dataKeywords() '/* testTraitIsKeyword */', 'T_TRAIT', ], + [ + '/* testEnumIsKeyword */', + 'T_ENUM', + ], [ '/* testNewIsKeyword */', From b7a931ab8792081be3df406bf13a2a9960f44f0d Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 17 Jan 2022 09:07:05 +1100 Subject: [PATCH 491/733] Changelog for #3474 (ref #3478) --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index fa24f0c587..059cb352d1 100644 --- a/package.xml +++ b/package.xml @@ -29,6 +29,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 + - Added support for PHP 8.1 enums + -- This new syntax has been backfilled for PHP versions less than 8.1 + -- Thanks to Jaroslav Hanslík for the patch - 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 From 0720ba75481b133abdad48ceb6bb45a57f6d8e5e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 17 Jan 2022 10:48:29 +1100 Subject: [PATCH 492/733] Have the uppercase sniff extend the lowercase one to remove duplicate code --- .../Sniffs/PHP/UpperCaseConstantSniff.php | 110 +----------------- 1 file changed, 1 insertion(+), 109 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php b/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php index 5df77ff631..f5f72b5581 100644 --- a/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php @@ -13,117 +13,9 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; -class UpperCaseConstantSniff implements Sniff +class UpperCaseConstantSniff extends LowerCaseConstantSniff { - /** - * 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. - * - * @return array - */ - public function register() - { - $targets = $this->targets; - - // Register function keywords to filter out type declarations. - $targets[] = T_FUNCTION; - $targets[] = T_CLOSURE; - $targets[] = T_FN; - - return $targets; - - }//end register() - - - /** - * Processes this sniff, when one of its tokens is encountered. - * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in the - * stack passed in $tokens. - * - * @return void - */ - public function process(File $phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - - // 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. From da7fbdc48b52d83e72e369230b3f33e4cfc79ffc Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 17 Jan 2022 10:50:48 +1100 Subject: [PATCH 493/733] Changelog for #3332 (ref #3429) --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index 059cb352d1..06b3c9cbad 100644 --- a/package.xml +++ b/package.xml @@ -42,6 +42,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Squiz.Commenting.DocCommentAlignment -- Squiz.Commenting.VariableComment -- Thanks to Juliette Reinders Folmer for the patches + - 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 - 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 #3526 : PSR12.Properties.ConstantVisibility false positive when using public final const syntax From 0379bba5b2037af226d80265584b9af5e3705038 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 17 Jan 2022 11:42:55 +1100 Subject: [PATCH 494/733] Changelog for #3505 (ref #3506) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 06b3c9cbad..a92e252071 100644 --- a/package.xml +++ b/package.xml @@ -47,6 +47,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer 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 From 67eac60fad210df0978018fcc9b00a558d16f834 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 17 Jan 2022 12:03:29 +1100 Subject: [PATCH 495/733] Changelog for #3516 --- package.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.xml b/package.xml index a92e252071..68d7876a01 100644 --- a/package.xml +++ b/package.xml @@ -35,6 +35,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 + - File::getMethodParameters now supports readonly tokens + -- 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 From 498a939c8ff49f0b00769144bdd07933352d77e8 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 17 Jan 2022 12:04:39 +1100 Subject: [PATCH 496/733] Changelog for #3517 --- package.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 68d7876a01..6a07f9c3bc 100644 --- a/package.xml +++ b/package.xml @@ -35,7 +35,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 - - File::getMethodParameters now supports readonly tokens + - 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 @@ -47,6 +47,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Squiz.Commenting.BlockCommentS -- Squiz.Commenting.DocCommentAlignment -- Squiz.Commenting.VariableComment + -- Squiz.WhiteSpace.ScopeKeywordSpacing -- Thanks to Juliette Reinders Folmer for the patches - 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 From f4115beb9d62f721b42dc1d1dea9ec481558c997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 20:32:15 +0100 Subject: [PATCH 497/733] PHP 8.1: Added T_ENUM_CASE --- package.xml | 6 ++ src/Tokenizers/PHP.php | 63 ++++++++++++++ src/Util/Tokens.php | 1 + tests/Core/Tokenizer/EnumCaseTest.inc | 78 +++++++++++++++++ tests/Core/Tokenizer/EnumCaseTest.php | 116 ++++++++++++++++++++++++++ 5 files changed, 264 insertions(+) create mode 100644 tests/Core/Tokenizer/EnumCaseTest.inc create mode 100644 tests/Core/Tokenizer/EnumCaseTest.php diff --git a/package.xml b/package.xml index 6a07f9c3bc..4c2cbad383 100644 --- a/package.xml +++ b/package.xml @@ -164,6 +164,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2127,6 +2129,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2225,6 +2229,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 7f24a86af0..313c7f87b3 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -347,6 +347,7 @@ class PHP extends Tokenizer T_ENDSWITCH => 9, T_ENDWHILE => 8, T_ENUM => 4, + T_ENUM_CASE => 4, T_EVAL => 4, T_EXTENDS => 7, T_FILE => 8, @@ -982,6 +983,9 @@ protected function tokenize($string) && 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'; @@ -997,6 +1001,65 @@ protected function tokenize($string) } }//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. diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 64014a17e0..febd9c5732 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -80,6 +80,7 @@ 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'); // Some PHP 5.5 tokens, replicated for lower versions. if (defined('T_FINALLY') === false) { diff --git a/tests/Core/Tokenizer/EnumCaseTest.inc b/tests/Core/Tokenizer/EnumCaseTest.inc new file mode 100644 index 0000000000..7a69152ed7 --- /dev/null +++ b/tests/Core/Tokenizer/EnumCaseTest.inc @@ -0,0 +1,78 @@ + + * @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() + + +}//end class From 9fd9b01ef69dde0db3147f361f96bd12aabb107b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sun, 21 Nov 2021 10:24:55 +0100 Subject: [PATCH 498/733] PHP 8.1: Enum case name should be always tokenized as T_STRING --- src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/EnumCaseTest.inc | 17 +++++++++++ tests/Core/Tokenizer/EnumCaseTest.php | 41 +++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 313c7f87b3..659b1c9c91 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -477,6 +477,7 @@ class PHP extends Tokenizer T_INTERFACE => true, T_TRAIT => true, T_ENUM => true, + T_ENUM_CASE => true, T_EXTENDS => true, T_IMPLEMENTS => true, T_ATTRIBUTE => true, diff --git a/tests/Core/Tokenizer/EnumCaseTest.inc b/tests/Core/Tokenizer/EnumCaseTest.inc index 7a69152ed7..13b87242e1 100644 --- a/tests/Core/Tokenizer/EnumCaseTest.inc +++ b/tests/Core/Tokenizer/EnumCaseTest.inc @@ -76,3 +76,20 @@ switch ($x) { enum Bar {} break; } + +enum Foo: string { + /* testKeywordAsEnumCaseNameShouldBeString1 */ + case INTERFACE = 'interface'; + /* testKeywordAsEnumCaseNameShouldBeString2 */ + case TRAIT = 'trait'; + /* testKeywordAsEnumCaseNameShouldBeString3 */ + case ENUM = 'enum'; + /* testKeywordAsEnumCaseNameShouldBeString4 */ + case FUNCTION = 'function'; + /* testKeywordAsEnumCaseNameShouldBeString5 */ + case FALSE = 'false'; + /* testKeywordAsEnumCaseNameShouldBeString6 */ + case DEFAULT = 'default'; + /* testKeywordAsEnumCaseNameShouldBeString7 */ + case ARRAY = 'array'; +} diff --git a/tests/Core/Tokenizer/EnumCaseTest.php b/tests/Core/Tokenizer/EnumCaseTest.php index 6921b0c64e..61141da4fe 100644 --- a/tests/Core/Tokenizer/EnumCaseTest.php +++ b/tests/Core/Tokenizer/EnumCaseTest.php @@ -113,4 +113,45 @@ public function dataNotEnumCases() }//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 From bb0339014322af3d67bc40584dfd08bcad28b1d9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 16:44:05 +0100 Subject: [PATCH 499/733] PHP 8.1 | Tokenizer/PHP: bugfix - name of typed enum tokenized as T_GOTO_LABEL Follow up on 3478. When an `enum` would be backed by a type and the colon would directly follow the name of the `enum`, the name would be incorrectly tokenized (including the colon) as `T_GOTO_LABEL`. Discovered while reviewing 3482 Also related to 3161 --- src/Tokenizers/PHP.php | 2 ++ tests/Core/Tokenizer/GotoLabelTest.inc | 3 +++ tests/Core/Tokenizer/GotoLabelTest.php | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 7f24a86af0..c9d342ff39 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1905,6 +1905,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--) { @@ -1915,6 +1916,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].':', 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() From 4d0be7c9c21b1b1b88a67050f5a7af5d7f98c335 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 17:05:40 +0100 Subject: [PATCH 500/733] Tests/BackfillEnum: update token calculations ... after the bugfix for `T_GOTO_LABEL`. Also added a minor tweak to one test case to make sure that spacing around the type colon is handled correctly. --- tests/Core/Tokenizer/BackfillEnumTest.inc | 2 +- tests/Core/Tokenizer/BackfillEnumTest.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillEnumTest.inc b/tests/Core/Tokenizer/BackfillEnumTest.inc index 28feb2f28c..82bfe24fd5 100644 --- a/tests/Core/Tokenizer/BackfillEnumTest.inc +++ b/tests/Core/Tokenizer/BackfillEnumTest.inc @@ -13,7 +13,7 @@ enum Boo: int { } /* testBackedStringEnum */ -enum Hoo: string +enum Hoo : string { case ONE = 'one'; case TWO = 'two'; diff --git a/tests/Core/Tokenizer/BackfillEnumTest.php b/tests/Core/Tokenizer/BackfillEnumTest.php index 8653e8c1b9..33cff3a2c7 100644 --- a/tests/Core/Tokenizer/BackfillEnumTest.php +++ b/tests/Core/Tokenizer/BackfillEnumTest.php @@ -87,20 +87,20 @@ public function dataEnums() [ '/* testBackedIntEnum */', 'enum', - 6, - 28, + 7, + 29, ], [ '/* testBackedStringEnum */', 'enum', - 6, - 28, + 8, + 30, ], [ '/* testComplexEnum */', 'enum', - 10, - 71, + 11, + 72, ], [ '/* testEnumWithEnumAsClassName */', From 36fe0735c331868e8faf25b12f4172230c58e939 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 25 Jan 2022 15:37:48 +0100 Subject: [PATCH 501/733] CodeSniffer.conf: end code with semicolon While not problematic, it just feels very untidy for the `CodeSniffer.conf` file not to end the code block with a semicolon. --- CodeSniffer.conf.dist | 2 +- src/Config.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/src/Config.php b/src/Config.php index 4e7b2b6204..9b80a10ffe 100644 --- a/src/Config.php +++ b/src/Config.php @@ -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; From 0802c3c746762f335c3826e1fc63720d503d3c52 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 25 Jan 2022 23:04:06 +0100 Subject: [PATCH 502/733] PHPCS -i: display standards in same order cross-platform I noticed this while debugging something completely different: the display order of the registered standards can vary wildly between different computers/OSes. For example, on Windows, they always seem to be ordered alphabetically: > The installed coding standards are MySource, PEAR, PSR1, PSR12, PSR2, Squiz and Zend And in case there are external standards registered, they are sorted alphabetically per "set of standards", i.e. per registered "installed path": > The installed coding standards are MySource, PEAR, PSR1, PSR12, PSR2, Squiz, Zend, WordPress, WordPress-Core, WordPress-Docs, WordPress-Extra and PHPCompatibility However, on Linux, I was seeing a completely different (more random) order, like for example: > The installed coding standards are PEAR, PSR2, PSR1, Squiz, PSR12, MySource and Zend **I'd like to propose to always display the standards in the same order and to use natural ordering per "set of standards" for this.** The order of external standards would still be determined by the order in which those standards are registered to the `installed_paths` configuration setting, but for a group of standards within the same `installed_path`, the standards would now be ordered by natural order too. For the PHPCS native standards, this will result in the standards displaying the same cross-OS, like this: > The installed coding standards are MySource, PEAR, PSR1, PSR2, PSR12, Squiz and Zend Take note of the "saner" ordering of the PSR standards which also improves the experience on Windows. Implementation note: to avoid using the `array_merge()`, I've added the standards name as keys to the array as well, which allows for safely using array union instead. --- src/Util/Standards.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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; From da0c874e89260514c4eff8f6bfec29e0df501f35 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 1 Feb 2022 04:16:42 +0100 Subject: [PATCH 503/733] Ruleset::explain(): fix plural vs singular phrasing When running the `phpcs --standard=Name -e` command, the line at the top of the output would always presume that a standard contains more than one sniff. The sniff count for the standards within the standard - `StandardName (# sniff[s])` - already handled this correctly. Fixed the top line now. Output without this fix: ``` The DummySubDir standard contains 1 sniffs DummySubDir (1 sniff) ---------------------- DummySubDir.Demo.Demo ``` Output with this fix: ``` The DummySubDir standard contains 1 sniff DummySubDir (1 sniff) ---------------------- DummySubDir.Demo.Demo ``` --- src/Ruleset.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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(); From 6100f991a13a0b31cd7f042948b73fa870eb9f3b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 6 Feb 2022 23:33:03 +0100 Subject: [PATCH 504/733] QA: minor code simplification Replace a nested function call with a singular function call, which does the same. --- src/Config.php | 4 ++-- src/Reports/Code.php | 2 +- src/Standards/Generic/Tests/Files/ExecutableFileUnitTest.php | 2 +- src/Tokenizers/PHP.php | 2 +- src/Util/Common.php | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Config.php b/src/Config.php index 4e7b2b6204..dee97a9b3c 100644 --- a/src/Config.php +++ b/src/Config.php @@ -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'; 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/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/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 7f24a86af0..2c460889fd 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -511,7 +511,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; } } diff --git a/src/Util/Common.php b/src/Util/Common.php index 204f44de14..caf4db772b 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('`(? Date: Thu, 10 Feb 2022 11:26:18 +0100 Subject: [PATCH 505/733] Tokenizer/PHP: bug fix - parent/static keywords in class instantiations Follow up on 3484 Just like `new class`, `new parent`, `new self` and `new static` should also be preserved and with the `parent`, `self` and `static` keywords remaining as their dedicated token. * For `new static`, the tokenization changed due to the context sensitive keywords change. This has now been fixed. * `new self` was fine before and is still fine. * `new parent` apparently wasn't handled correctly, even before the change. The condition which was in place for handling the same situation for `self` has now been updated to also handle `parent`. Includes unit tests. --- src/Tokenizers/PHP.php | 25 +- .../ContextSensitiveKeywordsTest.inc | 424 +++++++++--------- .../ContextSensitiveKeywordsTest.php | 12 + 3 files changed, 244 insertions(+), 217 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 2c460889fd..89749c6ecb 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -608,8 +608,11 @@ protected function tokenize($string) ) { $preserveKeyword = false; - // `new class` should be preserved - if ($token[0] === T_CLASS && $finalTokens[$lastNotEmptyToken]['code'] === T_NEW) { + // `new class`, and `new static` should be preserved. + if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW + && ($token[0] === T_CLASS + || $token[0] === T_STATIC) + ) { $preserveKeyword = true; } @@ -1968,16 +1971,24 @@ function return types. We want to keep the parenthesis map clean, && $token[0] === T_STRING && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true ) { - // Special case for syntax like: return new self - // where self should not be a string. + // 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 - && strtolower($token[1]) === 'self' + && ($tokenContentLower === 'self' || $tokenContentLower === 'parent') ) { $finalTokens[$newStackPtr] = [ 'content' => $token[1], - 'code' => T_SELF, - 'type' => 'T_SELF', ]; + if ($tokenContentLower === 'self') { + $finalTokens[$newStackPtr]['code'] = T_SELF; + $finalTokens[$newStackPtr]['type'] = 'T_SELF'; + } + + if ($tokenContentLower === 'parent') { + $finalTokens[$newStackPtr]['code'] = T_PARENT; + $finalTokens[$newStackPtr]['type'] = 'T_PARENT'; + } } else { $finalTokens[$newStackPtr] = [ 'content' => $token[1], diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc index eb1ca72058..0b073fcadf 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc @@ -1,210 +1,214 @@ - '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 {}; - -class Foo extends /* testNamespaceInNameIsKeyword */ namespace\Exception -{} + '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 +{} diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php index 72aeac6859..7420fd38bf 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php @@ -462,6 +462,18 @@ public function dataKeywords() '/* testImplementsInAnonymousClassIsKeyword */', 'T_IMPLEMENTS', ], + [ + '/* testClassInstantiationParentIsKeyword */', + 'T_PARENT', + ], + [ + '/* testClassInstantiationSelfIsKeyword */', + 'T_SELF', + ], + [ + '/* testClassInstantiationStaticIsKeyword */', + 'T_STATIC', + ], [ '/* testNamespaceInNameIsKeyword */', 'T_NAMESPACE', From d8d735b906a8b9e96dc57e9982dc316e62ad1db8 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 15 Feb 2022 08:40:36 +1100 Subject: [PATCH 506/733] Changelog for #3534 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 6a07f9c3bc..c2ee3d1daa 100644 --- a/package.xml +++ b/package.xml @@ -59,6 +59,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 From dd8a7096b04427daf6ed91acbdde90cd7d4d2315 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 15 Feb 2022 09:05:31 +1100 Subject: [PATCH 507/733] Got rid of some IF nesting by continuing early (ref #3519) --- src/Runner.php | 87 ++++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/Runner.php b/src/Runner.php index 020adbb7c8..fa68ff51bf 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -717,52 +717,55 @@ private function processChildProcs($childProcs) while (count($childProcs) > 0) { $pid = pcntl_waitpid(0, $status); - if ($pid > 0) { - $out = $childProcs[$pid]; - unset($childProcs[$pid]); - if (file_exists($out) === true) { - include $out; - - unlink($out); - - $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; - } + 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']; + $out = $childProcs[$pid]; + unset($childProcs[$pid]); + if (file_exists($out) === false) { + continue; + } - if (isset($debugOutput) === true) { - echo $debugOutput; - } + include $out; + unlink($out); - if (isset($childCache) === true) { - foreach ($childCache as $path => $cache) { - Cache::set($path, $cache); - } - } + $numProcessed++; - // 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 + 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; From 812d33cdae56ae35c24d54be15a8410e3bd39ec3 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 23 Feb 2022 09:06:50 +1100 Subject: [PATCH 508/733] Changelog for #3519 (ref #3144) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index c2ee3d1daa..565e3eb384 100644 --- a/package.xml +++ b/package.xml @@ -49,6 +49,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Squiz.Commenting.VariableComment -- Squiz.WhiteSpace.ScopeKeywordSpacing -- Thanks to Juliette Reinders Folmer for the patches + - The parallel feature is now more efficent and runs faster in some situations due to improved process managment + -- Thanks to Sergei Morozov 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 From 154a68891806abc963404e26a01a816bbe43cf28 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Wed, 23 Feb 2022 09:18:48 +1100 Subject: [PATCH 509/733] Changelog for #3546 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 565e3eb384..bcc98a7129 100644 --- a/package.xml +++ b/package.xml @@ -63,6 +63,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 From 174a0a659d2ee68058c64c61a4c0c2c97df577ed Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 25 Feb 2022 14:31:28 +0100 Subject: [PATCH 510/733] PSR2/SwitchDeclaration: bug fix when determining terminating statement Trailing comments within control structures nested within a switch case would break the determination of whether or not there is a terminating statement within the nested control structure. Making the `findNestedTerminator()` method look for non-empty instead of non-whitespace tokens fixes that and shouldn't break the `TerminatingComment` check as that has it's own check whether the last token in the case statement is a comment. Includes unit tests. Fixes 3550 --- .../ControlStructures/SwitchDeclarationSniff.php | 2 +- .../SwitchDeclarationUnitTest.inc | 14 +++++++++++++- .../SwitchDeclarationUnitTest.inc.fixed | 14 +++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) 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/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; +} From 72a66aa003a188a794a314890cacd701aab9da17 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 28 Feb 2022 04:56:15 +0100 Subject: [PATCH 511/733] PHP 8.1 | Tokenizer/PHP: bug fix for overeager explicit octal notation backfill Follow up on 3481. Just like for all other type of integer notations, if a numeric literal separator is used, it is not allowed between the prefix and the actual number. ```php // This is fine. $b = 0b1_0; $o = 0o6_3; // This is an invalid use of the numeric literal separator. $b = 0b_10; $o = 0o_63; ``` This PR fixes the backfill for explicit octal notation to NOT backfill these type of invalid sequences as the inconsistent tokenization across PHP versions which that causes, can create havoc in sniffs. Includes adding additional unit tests. --- src/Tokenizers/PHP.php | 3 ++- .../BackfillExplicitOctalNotationTest.inc | 9 +++++++ .../BackfillExplicitOctalNotationTest.php | 21 +++++++++++++++ .../BackfillNumericSeparatorTest.inc | 6 +++++ .../BackfillNumericSeparatorTest.php | 26 +++++++++++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index c6fb6917ef..b5c5e2c0f7 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -728,7 +728,8 @@ protected function tokenize($string) && (isset($tokens[($stackPtr + 1)]) === true && is_array($tokens[($stackPtr + 1)]) === true && $tokens[($stackPtr + 1)][0] === T_STRING - && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o') + && strtolower($tokens[($stackPtr + 1)][1][0]) === 'o' + && $tokens[($stackPtr + 1)][1][1] !== '_') ) { $finalTokens[$newStackPtr] = [ 'code' => T_LNUMBER, diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc index 7c5e8c089a..0f2d0cf738 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc @@ -5,3 +5,12 @@ $foo = 0o137041; /* testExplicitOctalCapitalised */ $bar = 0O137041; + +/* testExplicitOctalWithNumericSeparator */ +$octal = 0o137_041; + +/* testInvalid1 */ +$foo = 0o_137; + +/* testInvalid2 */ +$foo = 0O_41; diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php index 22b1daa61d..836235a245 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php @@ -62,6 +62,27 @@ public function dataExplicitOctalNotation() 'value' => '0O137041', ], ], + [ + [ + 'marker' => '/* testExplicitOctalWithNumericSeparator */', + 'type' => 'T_LNUMBER', + 'value' => '0o137_041', + ], + ], + [ + [ + 'marker' => '/* testInvalid1 */', + 'type' => 'T_LNUMBER', + 'value' => '0', + ], + ], + [ + [ + 'marker' => '/* testInvalid2 */', + 'type' => 'T_LNUMBER', + 'value' => '0', + ], + ], ]; }//end dataExplicitOctalNotation() diff --git a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc index 66f1a9a02a..d8559705c3 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc @@ -77,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 27efdec5af..645088fd6c 100644 --- a/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php +++ b/tests/Core/Tokenizer/BackfillNumericSeparatorTest.php @@ -336,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 */', [ From 37a1a98cf08be9f7b02491cbda36b47618343c95 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 5 Mar 2022 18:44:00 +0100 Subject: [PATCH 512/733] GH Actions: version update for various predefined actions A number of predefined actions have had major release, which warrant an update the workflow(s). These updates don't actually contain any changed functionality, they are mostly just a change of the Node version used by the action itself (from Node 14 to Node 16). Refs: * https://github.com/actions/checkout/releases * https://github.com/actions/download-artifact/releases * https://github.com/actions/upload-artifact/releases --- .github/workflows/phpstan.yml | 2 +- .github/workflows/test.yml | 10 +++++----- .github/workflows/validate.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 0d33a0d8ca..43b847acb4 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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dad38714f6..9c26e05248 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -39,7 +39,7 @@ jobs: run: php scripts/build-phar.php - name: Upload the PHPCS phar - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: ${{ success() && matrix.php == '8.0' }} with: name: phpcs-phar @@ -48,7 +48,7 @@ jobs: retention-days: 28 - name: Upload the PHPCBF phar - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: ${{ success() && matrix.php == '8.0' }} with: name: phpcbf-phar @@ -89,7 +89,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup ini config id: set_ini @@ -159,7 +159,7 @@ jobs: run: composer validate --no-check-all --strict - name: Download the PHPCS phar - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: phpcs-phar diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9fb5685d32..840330c7d6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -22,7 +22,7 @@ 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 @@ -63,7 +63,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install PHP uses: shivammathur/setup-php@v2 From 51335eb46b2b940b6c429643fe96f514d4a4e4a1 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 8 Mar 2022 08:04:56 +1100 Subject: [PATCH 513/733] Updated changelog for T_ENUM_CASE (ref #3483) --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index 4d77ca2771..1f1511cf3e 100644 --- a/package.xml +++ b/package.xml @@ -31,6 +31,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Mark Baker for the patch - 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 - Added support for the PHP 8.1 readonly token -- Tokenzing of the readonly keyword has been backfilled for PHP versions less than 8.1 From b135bd51a20c34ca0e7719debd1cef6b493316df Mon Sep 17 00:00:00 2001 From: Remy Bos <27890746+sjokkateer@users.noreply.github.com> Date: Sun, 13 Mar 2022 11:42:44 +0100 Subject: [PATCH 514/733] [Fix] Docblock of Sniff::process() --- src/Sniffs/Sniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: * * From 93e703a01ab1b448a633d24036ad8eb963e2cd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 17:46:07 +0100 Subject: [PATCH 515/733] PHP 8.1: Squiz/ValidVariableName - Added tests for enums support --- .../NamingConventions/ValidVariableNameUnitTest.inc | 9 +++++++++ .../NamingConventions/ValidVariableNameUnitTest.php | 2 ++ 2 files changed, 11 insertions(+) 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; From be50d2768b38fbef8040f7c6c1f15ff490345a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 17:48:36 +0100 Subject: [PATCH 516/733] PHP 8.1: Zend/ValidVariableName - Added tests for enums support --- .../NamingConventions/ValidVariableNameUnitTest.inc | 9 +++++++++ .../NamingConventions/ValidVariableNameUnitTest.php | 2 ++ 2 files changed, 11 insertions(+) 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() From c47ca1d335fb1f0c611d2270061e5519b613f33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 17:52:43 +0100 Subject: [PATCH 517/733] PHP 8.1: Squiz/MemberVarSpacing - Added test for enums support --- .../Squiz/Tests/WhiteSpace/MemberVarSpacingUnitTest.inc | 7 +++++++ .../Tests/WhiteSpace/MemberVarSpacingUnitTest.inc.fixed | 7 +++++++ 2 files changed, 14 insertions(+) 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'; +} From 2d5939826c0b8a5273d4a56404570d224b2e8e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 17:55:57 +0100 Subject: [PATCH 518/733] PHP 8.1: Squiz/MethodScope - Added tests for enums support --- src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc | 8 ++++++++ src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.php | 1 + 2 files changed, 9 insertions(+) diff --git a/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc b/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc index cec0355c43..bc77d46d1d 100644 --- a/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc +++ b/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc @@ -40,3 +40,11 @@ class Nested { }; } } + +enum SomeEnum +{ + function func1() {} + public function func1() {} + private function func1() {} + protected function func1() {} +} 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() From 45d953ac8b509cb256dfdafb462a7c22995c0580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:00:10 +0100 Subject: [PATCH 519/733] PHP 8.1: Squiz/NonExecutableCode - Added test for enums support --- .../Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc | 6 ++++++ src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc index 407c4740b1..6435bd0663 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: From e3b93c5964de1ff76a17775a98863c700ae5755d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:03:35 +0100 Subject: [PATCH 520/733] PHP 8.1: Squiz/LowercaseClassKeywords - Added test for enums support --- .../Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc | 1 + .../Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed | 1 + .../Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc index 511bbe4710..758fcaf2a8 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 {} class MyClass { diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed index 859d0d2db2..fad6c7c5a2 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 {} class MyClass { diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php index f2fc20b438..47e4b3da4f 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 => 1, 10 => 1, - 13 => 1, + 11 => 1, + 14 => 1, ]; return $errors; From bbe14b0f777d2627ed430561e5516f5cfd4daeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:05:43 +0100 Subject: [PATCH 521/733] PHP 8.1: PSR2/MethodDeclaration - Added tests for enums support --- .../PSR2/Tests/Methods/MethodDeclarationUnitTest.inc | 9 +++++++++ .../Tests/Methods/MethodDeclarationUnitTest.inc.fixed | 9 +++++++++ .../PSR2/Tests/Methods/MethodDeclarationUnitTest.php | 2 ++ 3 files changed, 20 insertions(+) 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() From 1e8d2b565df28ebdfaa646dbe97f12c726a76025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:11:19 +0100 Subject: [PATCH 522/733] PHP 8.1: PSR12/UseDeclaration - Added tests for enums support --- .../PSR12/Tests/Traits/UseDeclarationUnitTest.inc | 11 +++++++++++ .../Tests/Traits/UseDeclarationUnitTest.inc.fixed | 10 ++++++++++ .../PSR12/Tests/Traits/UseDeclarationUnitTest.php | 1 + 3 files changed, 22 insertions(+) diff --git a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc index e62489fe03..c3cc6348a1 100644 --- a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc @@ -207,3 +207,14 @@ 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..196fbc49c7 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, + 218 => 1, ]; }//end getErrorList() From 2df2ae223d4b4bc3605fce0d4fe9214e35b465d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:13:08 +0100 Subject: [PATCH 523/733] PHP 8.1: PSR12/ConstantVisibility - Added tests for enums support --- .../PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc | 5 +++++ .../PSR12/Tests/Properties/ConstantVisibilityUnitTest.php | 1 + 2 files changed, 6 insertions(+) diff --git a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc index 88d2a92c34..84ea24b2e8 100644 --- a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc +++ b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.inc @@ -15,3 +15,8 @@ class SampleEnum 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 a4236b41ea..b738706da2 100644 --- a/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php +++ b/src/Standards/PSR12/Tests/Properties/ConstantVisibilityUnitTest.php @@ -43,6 +43,7 @@ public function getWarningList() return [ 4 => 1, 12 => 1, + 21 => 1, ]; }//end getWarningList() From b8b3a5b29b6b44e74fa93c63c44cf401ebb26a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:16:58 +0100 Subject: [PATCH 524/733] PHP 8.1: PSR12/ImportStatement - Added test for enums support --- .../PSR12/Tests/Files/ImportStatementUnitTest.inc | 7 +++++++ .../PSR12/Tests/Files/ImportStatementUnitTest.inc.fixed | 7 +++++++ 2 files changed, 14 insertions(+) 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; +} From 3fe3fc16fe9ec759d39027ed2c3c2ac30571f0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:22:34 +0100 Subject: [PATCH 525/733] PHP 8.1: File::getDeclarationName() supports enums --- src/Files/File.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 864c9424ba..e00ba7117b 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -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 From 8f4a6cf662799ebd0cdb428a4e971a72e7f2c908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:24:08 +0100 Subject: [PATCH 526/733] PHP 8.1: PEAR/ValidFunctionName - Added tests for enums support --- .../Tests/NamingConventions/ValidFunctionNameUnitTest.inc | 5 +++++ .../Tests/NamingConventions/ValidFunctionNameUnitTest.php | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc index 78280cdd90..e89aea6078 100644 --- a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc +++ b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc @@ -220,3 +220,8 @@ abstract class My_Class { public function my_class() {} public function _MY_CLASS() {} } + +enum My_Enum { + public function my_class() {} + public function _MY_CLASS() {} +} diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php index 9bb6de0d84..26f9775bcb 100644 --- a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php +++ b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php @@ -122,6 +122,8 @@ public function getErrorList() 212 => 1, 213 => 1, 214 => 1, + 225 => 1, + 226 => 2, ]; }//end getErrorList() From 7e0d1e99fc2eef05cbe9ddefb64c84a1b3679e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:25:48 +0100 Subject: [PATCH 527/733] PHP 8.1: Generic/CamelCapsFunctionName - Added tests for enums support --- .../NamingConventions/CamelCapsFunctionNameUnitTest.inc | 5 +++++ .../NamingConventions/CamelCapsFunctionNameUnitTest.php | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc index 9bda11472e..f9a9885b3a 100644 --- a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc +++ b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc @@ -165,3 +165,8 @@ abstract class My_Class { public function my_class() {} public function _MY_CLASS() {} } + +enum My_Enum { + public function my_class() {} + public function _MY_CLASS() {} +} diff --git a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php index 502498d8bc..839c30243e 100644 --- a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php @@ -63,6 +63,8 @@ public function getErrorList() 147 => 2, 158 => 1, 159 => 1, + 170 => 1, + 171 => 1, ]; return $errors; From 7bd83afc2b1f40a445784a6d1aecae605784dae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:29:38 +0100 Subject: [PATCH 528/733] PHP 8.1: Generic/DuplicateClassName - Added support for enums --- .../Generic/Sniffs/Classes/DuplicateClassNameSniff.php | 1 + .../Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc | 4 +++- .../Generic/Tests/Classes/DuplicateClassNameUnitTest.php | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php b/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php index 8374bfd535..3243f8b7dc 100644 --- a/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php +++ b/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php @@ -53,6 +53,7 @@ public function process(File $phpcsFile, $stackPtr) T_CLASS, T_INTERFACE, T_TRAIT, + T_ENUM, T_NAMESPACE, T_CLOSE_TAG, ]; diff --git a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc index d0c136cbfa..38ddef181e 100644 --- a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc +++ b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc @@ -4,8 +4,10 @@ class YourClass {} interface MyInterface {} interface YourInterface {} trait MyTrait {} +enum MyEnum {} trait YourTrait {} class MyClass {} interface MyInterface {} trait MyTrait {} -?> \ No newline at end of file +enum MyEnum {} +?> diff --git a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php index b3b3edb502..6caaf873c3 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, ]; break; case 'DuplicateClassNameUnitTest.2.inc': From 8a746106a4321d565e8ae537946b5bfc9b1de4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:32:56 +0100 Subject: [PATCH 529/733] PHP 8.1: Generic/OpeningBraceSameLine - Added support for enums --- .../Generic/Sniffs/Classes/OpeningBraceSameLineSniff.php | 1 + .../Generic/Tests/Classes/OpeningBraceSameLineUnitTest.inc | 4 ++++ .../Tests/Classes/OpeningBraceSameLineUnitTest.inc.fixed | 4 ++++ .../Generic/Tests/Classes/OpeningBraceSameLineUnitTest.php | 1 + 4 files changed, 10 insertions(+) diff --git a/src/Standards/Generic/Sniffs/Classes/OpeningBraceSameLineSniff.php b/src/Standards/Generic/Sniffs/Classes/OpeningBraceSameLineSniff.php index fb92d82c08..dfd925f918 100644 --- a/src/Standards/Generic/Sniffs/Classes/OpeningBraceSameLineSniff.php +++ b/src/Standards/Generic/Sniffs/Classes/OpeningBraceSameLineSniff.php @@ -27,6 +27,7 @@ public function register() T_CLASS, T_INTERFACE, T_TRAIT, + T_ENUM, ]; }//end register() 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() From ef2172be9fb78dca7bcdf73edafc3dd23116a908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:34:46 +0100 Subject: [PATCH 530/733] PHP 8.1: Generic/OneObjectStructurePerFile - Added support for enums --- .../Sniffs/Files/OneObjectStructurePerFileSniff.php | 1 + .../Tests/Files/OneObjectStructurePerFileUnitTest.inc | 9 +++++++-- .../Tests/Files/OneObjectStructurePerFileUnitTest.php | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Files/OneObjectStructurePerFileSniff.php b/src/Standards/Generic/Sniffs/Files/OneObjectStructurePerFileSniff.php index d9d71b6996..939086d3f0 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() 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() From c9e014476f75af966dc2eca9866c07bb8456c61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:36:42 +0100 Subject: [PATCH 531/733] PHP 8.1: Generic/LowerCaseKeyword - Added support for enums --- src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php | 1 + src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc | 3 +++ .../Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed | 3 +++ src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php | 1 + 4 files changed, 8 insertions(+) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php index 2ddf314a02..f26b6b53ec 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php @@ -50,6 +50,7 @@ public function register() T_ENDIF, T_ENDSWITCH, T_ENDWHILE, + T_ENUM, T_EVAL, T_EXIT, T_EXTENDS, diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc index 6c3e3f9a21..1566b6e017 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc @@ -39,5 +39,8 @@ class Reading { Public READOnly int $var; } +EnuM Enum { +} + __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 1c8550387b..690b7c5eed 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed @@ -39,5 +39,8 @@ class Reading { public readonly int $var; } +enum Enum { +} + __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 bf859e7fb9..e551149226 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php @@ -41,6 +41,7 @@ public function getErrorList() 32 => 1, 35 => 1, 39 => 2, + 42 => 1, ]; }//end getErrorList() From 6a323824a9e26b76d63e947a2d431f646b562e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:41:44 +0100 Subject: [PATCH 532/733] PHP 8.1: PEAR/ClassDeclaration - Added support for enums --- src/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php | 1 + .../PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc | 2 ++ .../PEAR/Tests/Classes/ClassDeclarationUnitTest.1.inc.fixed | 4 ++++ src/Standards/PEAR/Tests/Classes/ClassDeclarationUnitTest.php | 1 + 4 files changed, 8 insertions(+) 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/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: From 209d417cb6e0eae8a328fe9c9958a4937b9d4e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:45:44 +0100 Subject: [PATCH 533/733] PHP 8.1: PEAR/ClassComment - Added support for enums --- .../PEAR/Sniffs/Commenting/ClassCommentSniff.php | 1 + .../PEAR/Tests/Commenting/ClassCommentUnitTest.inc | 10 ++++++++++ .../PEAR/Tests/Commenting/ClassCommentUnitTest.php | 1 + 3 files changed, 12 insertions(+) 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/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() From ad369a10a1a609636c3c5a167008daa5f75b4fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:51:03 +0100 Subject: [PATCH 534/733] PHP 8.1: PEAR/FileComment - Added support for enums --- package.xml | 1 + src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php | 1 + .../PEAR/Tests/Commenting/FileCommentUnitTest.3.inc | 8 ++++++++ .../PEAR/Tests/Commenting/FileCommentUnitTest.php | 3 +++ 4 files changed, 13 insertions(+) create mode 100644 src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.3.inc diff --git a/package.xml b/package.xml index 1f1511cf3e..1f449f114e 100644 --- a/package.xml +++ b/package.xml @@ -1012,6 +1012,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + 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/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 From bc00f4c1d5ae0f838110c3c0b373247efc0f98c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:53:39 +0100 Subject: [PATCH 535/733] PHP 8.1: PEAR/ValidClassName - Added support for enums --- .../NamingConventions/ValidClassNameSniff.php | 1 + .../ValidClassNameUnitTest.inc | 22 +++++++++++++++++++ .../ValidClassNameUnitTest.php | 6 +++++ 3 files changed, 29 insertions(+) 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/NamingConventions/ValidClassNameUnitTest.inc b/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc index c6d15df7f0..bc0e92a1d5 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 {} + +enum invalid_Name {} + +enum invalid_name {} + +enum Invalid_name {} + +enum VALID_Name {} + +enum VALID_NAME {} + +enum VALID_Name {} + +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() From 6c96e557c0a3166a434841b8ecb4798dd79ec9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:55:19 +0100 Subject: [PATCH 536/733] PHP 8.1: PSR12/ClosingBrace - Added support for enums --- src/Standards/PSR12/Sniffs/Classes/ClosingBraceSniff.php | 1 + src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.inc | 5 +++++ src/Standards/PSR12/Tests/Classes/ClosingBraceUnitTest.php | 1 + 3 files changed, 7 insertions(+) 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/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() From 7beba375b5f807ce967b20e900fc08dff1ccf416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 18:58:32 +0100 Subject: [PATCH 537/733] PHP 8.1: PSR1/ClassDeclaration - Added support for enums --- package.xml | 1 + src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php | 5 +++-- .../PSR1/Tests/Classes/ClassDeclarationUnitTest.3.inc | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 src/Standards/PSR1/Tests/Classes/ClassDeclarationUnitTest.3.inc diff --git a/package.xml b/package.xml index 1f449f114e..69598e10a6 100644 --- a/package.xml +++ b/package.xml @@ -1101,6 +1101,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + 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/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 @@ + Date: Sat, 20 Nov 2021 19:04:00 +0100 Subject: [PATCH 538/733] PHP 8.1: PSR1/SideEffects - Added support for enums --- src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php | 1 + src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc | 6 ++++++ 2 files changed, 7 insertions(+) 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/Files/SideEffectsUnitTest.1.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc index 596d6cf9b0..ca538823c1 100644 --- a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc @@ -6,6 +6,7 @@ const CONSTANT2 = 2; use Something; use SomethingElse; +use function interface_exists; declare(ticks=1); @@ -63,6 +64,11 @@ if (!interface_exists('MyInterface')) { interface MyInterface {} } +if (!interface_exists('MyEnum')) { + // Define an enum. + enum MyEnum {} +} + #[\Attribute] namespace { class A {} From b2b38ed1f6af134d2efda2debfc6d4d8790a263b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:06:08 +0100 Subject: [PATCH 539/733] PHP 8.1: PSR2/UseDeclaration - Added support for enums --- .../PSR2/Sniffs/Namespaces/UseDeclarationSniff.php | 2 +- .../PSR2/Tests/Namespaces/UseDeclarationUnitTest.1.inc | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) 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/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 From 313d9dbae6893775fdd05f2e2710448bd860c032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:10:23 +0100 Subject: [PATCH 540/733] PHP 8.1: Squiz/ClassFileName - Added support for enums --- .../Squiz/Sniffs/Classes/ClassFileNameSniff.php | 1 + .../Squiz/Tests/Classes/ClassFileNameUnitTest.inc | 10 +++++++++- .../Squiz/Tests/Classes/ClassFileNameUnitTest.php | 15 +++++++++++---- 3 files changed, 21 insertions(+), 5 deletions(-) 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/Tests/Classes/ClassFileNameUnitTest.inc b/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc index a346a00f21..204374f396 100644 --- a/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc @@ -5,6 +5,7 @@ class ClassFileNameUnitTest {} interface ClassFileNameUnitTest {} trait ClassFileNameUnitTest {} +enum ClassFileNameUnitTest {} // Invalid filename matching class name (case sensitive). @@ -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() From ed1e5a87452b43e8245eb2d4e73ed85057268fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:12:53 +0100 Subject: [PATCH 541/733] PHP 8.1: Squiz/ValidClassName - Added support for enums --- .../Sniffs/Classes/ValidClassNameSniff.php | 1 + .../Tests/Classes/ValidClassNameUnitTest.inc | 44 +++++++++++++++++++ .../Tests/Classes/ValidClassNameUnitTest.php | 5 +++ 3 files changed, 50 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php b/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php index 10de719dca..8d45c86e0c 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() diff --git a/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc b/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc index aadbab5b8e..62a769a034 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 {} + + +// Incorrect usage of camel case. +enum invalidCamelCaseClass {} +enum Invalid_Camel_Case_Class_With_Underscores {} + + +// All lowercase. +enum invalidlowercaseclass {} +enum invalid_lowercase_class_with_underscores {} + + +// All uppercase. +enum VALIDUPPERCASECLASS {} +enum INVALID_UPPERCASE_CLASS_WITH_UNDERSCORES {} + + +// Mix camel case with uppercase. +enum ValidCamelCaseClassWithUPPERCASE {} + + +// Usage of numeric characters. +enum ValidCamelCaseClassWith1Number {} +enum ValidCamelCaseClassWith12345Numbers {} +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() From 25318a958be00064913230aafc88362a56fced66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:19:03 +0100 Subject: [PATCH 542/733] PHP 8.1: Squiz/BlockComment - Enums should be ignored --- .../Squiz/Sniffs/Commenting/BlockCommentSniff.php | 1 + .../Squiz/Tests/Commenting/BlockCommentUnitTest.inc | 8 ++++++++ .../Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php index d59fe1e113..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, diff --git a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc index b25de27679..7cd04a2114 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc @@ -299,3 +299,11 @@ abstract class MyClass */ 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 e1e821bf56..2e97614e2c 100644 --- a/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/BlockCommentUnitTest.inc.fixed @@ -301,3 +301,11 @@ abstract class MyClass */ readonly public string $prop; } + +/** + * Comment should be ignored + * + */ +enum MyEnum { + +} From 18d03c481b8cb8dcc000eca780ce2738f3327973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:22:17 +0100 Subject: [PATCH 543/733] PHP 8.1: Squiz/FileComment - Added support for enums --- package.xml | 1 + .../Squiz/Sniffs/Commenting/FileCommentSniff.php | 1 + .../Squiz/Tests/Commenting/FileCommentUnitTest.9.inc | 12 ++++++++++++ .../Squiz/Tests/Commenting/FileCommentUnitTest.php | 1 + 4 files changed, 15 insertions(+) create mode 100644 src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.9.inc diff --git a/package.xml b/package.xml index 69598e10a6..38160016a3 100644 --- a/package.xml +++ b/package.xml @@ -1653,6 +1653,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + 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/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': From 4010b603681295f7676d2d9052c74c34d109e086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:24:10 +0100 Subject: [PATCH 544/733] PHP 8.1: Squiz/InlineComment - Enums should be ignored --- .../Squiz/Sniffs/Commenting/InlineCommentSniff.php | 1 + .../Squiz/Tests/Commenting/InlineCommentUnitTest.inc | 8 ++++++++ .../Tests/Commenting/InlineCommentUnitTest.inc.fixed | 8 ++++++++ 3 files changed, 17 insertions(+) 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/Tests/Commenting/InlineCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc index 1b97af0b92..081bd7fa8f 100644 --- a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc @@ -173,3 +173,11 @@ final class MyClass */ // For this test line having an empty line below it, is fine. + +/** + * Comment should be ignored. + * + */ +enum MyEnum { + +} diff --git a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed index 6b66624176..07e6bf6240 100644 --- a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed @@ -166,3 +166,11 @@ final class MyClass */ // For this test line having an empty line below it, is fine. + +/** + * Comment should be ignored. + * + */ +enum MyEnum { + +} From a34b5000759b341c1d47a99e4299caa7c10a33ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:26:20 +0100 Subject: [PATCH 545/733] PHP 8.1: Squiz/FileExtension - Added support for enums --- package.xml | 1 + src/Standards/Squiz/Sniffs/Files/FileExtensionSniff.php | 2 +- src/Standards/Squiz/Tests/Files/FileExtensionUnitTest.5.inc | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 src/Standards/Squiz/Tests/Files/FileExtensionUnitTest.5.inc diff --git a/package.xml b/package.xml index 38160016a3..3bc6e28610 100644 --- a/package.xml +++ b/package.xml @@ -1771,6 +1771,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + 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/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 @@ + From 718f390b7466e0d11b18f0513e176882a5892b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:27:58 +0100 Subject: [PATCH 546/733] PHP 8.1: Squiz/ControlStructureSpacing - Enums should be ignored --- .../Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php | 1 + .../Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc | 4 ++++ .../WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed | 4 ++++ 3 files changed, 9 insertions(+) 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/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc index 0371ce49b4..dbab24be1d 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc @@ -261,3 +261,7 @@ $expr = match( $foo ){ }; echo $expr; + +enum SomeEnum +{ +} diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed index ad4505c3e1..4a655dc916 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed @@ -253,3 +253,7 @@ $expr = match($foo){ }; echo $expr; + +enum SomeEnum +{ +} From b520d4b6a432e63dcf07b08f982e5a2e0d4536b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 20 Nov 2021 19:31:38 +0100 Subject: [PATCH 547/733] PHP 8.1: Squiz/StaticThisUsage - Added support for enums --- .../Sniffs/Scope/StaticThisUsageSniff.php | 2 +- .../Tests/Scope/StaticThisUsageUnitTest.inc | 6 +++++ .../Tests/Scope/StaticThisUsageUnitTest.php | 25 ++++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php b/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php index 0bafbf4db4..79cb9f3f48 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() diff --git a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc index 38b443f2fd..13bb44f1fe 100644 --- a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc +++ b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc @@ -115,3 +115,9 @@ $b = new class() return $This; } } + +enum MyEnum { + 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..36d833903a 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, + 121 => 1, ]; }//end getErrorList() From fc2c525e676e1aaf692106815bf1c24ccd893a89 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 22:52:47 +0100 Subject: [PATCH 548/733] File::getDeclarationName(): tweak method description --- src/Files/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Files/File.php b/src/Files/File.php index e00ba7117b..5c86faa918 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. From 024c47da6a0b0dbad62490c743f421d040d5ec8e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 14:09:25 +0100 Subject: [PATCH 549/733] Generic/DuplicateClassName: improve enum tests * Test case file 1 should contain both a duplicate and a non-duplicate enum. * Test case file 2 should contain an enum which was already declared in file 1 to verify cross-file detection. --- .../Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc | 3 ++- .../Generic/Tests/Classes/DuplicateClassNameUnitTest.2.inc | 1 + .../Generic/Tests/Classes/DuplicateClassNameUnitTest.php | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc index 38ddef181e..91ab9d3975 100644 --- a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc +++ b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc @@ -4,8 +4,9 @@ class YourClass {} interface MyInterface {} interface YourInterface {} trait MyTrait {} -enum MyEnum {} trait YourTrait {} +enum MyEnum {} +enum YourEnum {} class MyClass {} interface MyInterface {} trait MyTrait {} 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 6caaf873c3..68982f8686 100644 --- a/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php +++ b/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php @@ -45,10 +45,10 @@ public function getWarningList($testFile='') switch ($testFile) { case 'DuplicateClassNameUnitTest.1.inc': return [ - 9 => 1, 10 => 1, 11 => 1, 12 => 1, + 13 => 1, ]; break; case 'DuplicateClassNameUnitTest.2.inc': @@ -56,6 +56,7 @@ public function getWarningList($testFile='') 2 => 1, 3 => 1, 4 => 1, + 5 => 1, ]; break; case 'DuplicateClassNameUnitTest.5.inc': From 16d8ee62c34b2dd1983a3deeb51754403158dbcb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 16:31:44 +0100 Subject: [PATCH 550/733] Generic/CamelCapsFunctionName: improve enum tests Ensure that all aspects of the sniff are tested for enums. While these tests pass, if you look at the actual error message output, you will see some weirdness (declaration name for the `enum` set as `string`). This should be fixed once PR 3534 has been merged. --- .../CamelCapsFunctionNameUnitTest.inc | 19 ++++++++++++++++--- .../CamelCapsFunctionNameUnitTest.php | 6 ++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc index f9a9885b3a..8441060d27 100644 --- a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc +++ b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.inc @@ -166,7 +166,20 @@ abstract class My_Class { public function _MY_CLASS() {} } -enum My_Enum { - 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 839c30243e..3d0320c3ae 100644 --- a/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/CamelCapsFunctionNameUnitTest.php @@ -63,8 +63,10 @@ public function getErrorList() 147 => 2, 158 => 1, 159 => 1, - 170 => 1, - 171 => 1, + 179 => 1, + 180 => 2, + 183 => 1, + 184 => 1, ]; return $errors; From d2c86abdd92796a19ee026e85e732dc96bf5eff6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 21:55:06 +0100 Subject: [PATCH 551/733] Generic/ValidFunctionName: improve enum tests Ensure that all aspects of the sniff are tested for enums. While these tests pass, if you look at the actual error message output, you will see some weirdness (declaration name for the `enum` set as `string`). This should be fixed once PR 3534 has been merged. --- .../ValidFunctionNameUnitTest.inc | 22 ++++++++++++++++--- .../ValidFunctionNameUnitTest.php | 6 +++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc index e89aea6078..18b1a48176 100644 --- a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc +++ b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.inc @@ -221,7 +221,23 @@ abstract class My_Class { public function _MY_CLASS() {} } -enum My_Enum { - 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 26f9775bcb..4639a1e2ab 100644 --- a/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php +++ b/src/Standards/PEAR/Tests/NamingConventions/ValidFunctionNameUnitTest.php @@ -122,8 +122,10 @@ public function getErrorList() 212 => 1, 213 => 1, 214 => 1, - 225 => 1, - 226 => 2, + 235 => 1, + 236 => 2, + 239 => 1, + 242 => 1, ]; }//end getErrorList() From 2632099909fd2803740f15926c087ca828d8382d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 22:16:24 +0100 Subject: [PATCH 552/733] PEAR/ValidClassName: improve enum tests Ensure that backed enum signatures are included in the tests. Note: the adjusted tests _without a space between the enum name and the colon_ will fail until PR 3534 has been merged. --- .../Tests/NamingConventions/ValidClassNameUnitTest.inc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc b/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc index bc0e92a1d5..053a4fee2f 100644 --- a/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc +++ b/src/Standards/PEAR/Tests/NamingConventions/ValidClassNameUnitTest.inc @@ -67,19 +67,19 @@ trait ___ {} trait Invalid__Name {} -enum Valid_Name {} +enum Valid_Name: string {} -enum invalid_Name {} +enum invalid_Name : String {} enum invalid_name {} -enum Invalid_name {} +enum Invalid_name: Int {} enum VALID_Name {} enum VALID_NAME {} -enum VALID_Name {} +enum VALID_Name : int {} enum ValidName {} From 37bc4e098f64a1317d66f3167513b0d4a3b51303 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 23:19:08 +0100 Subject: [PATCH 553/733] PSR12/UseDeclaration: improve enum tests Test more aspects of the sniff are applied correctly to enums. --- src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc | 3 ++- src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc index c3cc6348a1..c8ad746a73 100644 --- a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.inc @@ -215,6 +215,7 @@ enum SomeEnum1 enum SomeEnum2 { - use FirstTrait; + + use FirstTrait; } diff --git a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php index 196fbc49c7..797a2912e7 100644 --- a/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php +++ b/src/Standards/PSR12/Tests/Traits/UseDeclarationUnitTest.php @@ -47,7 +47,7 @@ public function getErrorList() 165 => 1, 170 => 1, 208 => 1, - 218 => 1, + 219 => 3, ]; }//end getErrorList() From 19b57751201dc6408f92557996ef6c2e7fdc0079 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 17:39:56 +0100 Subject: [PATCH 554/733] Squiz/InlineComment: fix enum test The enum test was placed _below_ a test which was marked as testing a specific situation at the end of a file, which invalidated the "end of file" test. Fixed by moving the new test up. --- .../Tests/Commenting/InlineCommentUnitTest.inc | 16 ++++++++-------- .../Commenting/InlineCommentUnitTest.inc.fixed | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc index 081bd7fa8f..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 @@ -173,11 +181,3 @@ final class MyClass */ // For this test line having an empty line below it, is fine. - -/** - * Comment should be ignored. - * - */ -enum MyEnum { - -} diff --git a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed index 07e6bf6240..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 @@ -166,11 +174,3 @@ final class MyClass */ // For this test line having an empty line below it, is fine. - -/** - * Comment should be ignored. - * - */ -enum MyEnum { - -} From b1ae480c25114f7f1755335736ef2682abe5cda5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 22:51:15 +0100 Subject: [PATCH 555/733] Squiz/ClassFileName: improve enum tests Add a _valid_ test case for a backed enum. Note: this new test will fail until PR 3534 has been merged. --- src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc b/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc index 204374f396..8b5a5aa708 100644 --- a/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/ClassFileNameUnitTest.inc @@ -6,7 +6,7 @@ class ClassFileNameUnitTest {} interface ClassFileNameUnitTest {} trait ClassFileNameUnitTest {} enum ClassFileNameUnitTest {} - +enum ClassFileNameUnitTest: int {} // Invalid filename matching class name (case sensitive). class classFileNameUnitTest {} From 7494c3d693df0fa4be70aa9bfb12ceead15b7dcf Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 23:06:06 +0100 Subject: [PATCH 556/733] Squiz/LowercaseClassKeywords: improve enum tests Let's also test the implements keyword when used with an enum. --- .../Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc | 2 +- .../Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed | 2 +- .../Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc index 758fcaf2a8..ea8cd89ec5 100644 --- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc @@ -3,7 +3,7 @@ Abstract Class MyClass Extends MyClass {} Final Class MyClass Implements MyInterface {} Interface MyInterface {} Trait MyTrait {} -Enum MyEnum {} +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 fad6c7c5a2..f573905217 100644 --- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed @@ -3,7 +3,7 @@ abstract class MyClass extends MyClass {} final class MyClass implements MyInterface {} interface MyInterface {} trait MyTrait {} -enum MyEnum {} +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 47e4b3da4f..8c4d10c79a 100644 --- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php +++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php @@ -30,7 +30,7 @@ public function getErrorList() 3 => 3, 4 => 1, 5 => 1, - 6 => 1, + 6 => 2, 10 => 1, 11 => 1, 14 => 1, From 32f38328b0c10446b8fb2964e79a23bfde5a0dff Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 22:29:54 +0100 Subject: [PATCH 557/733] Squiz/ValidClassName: improve enum tests + bugfix Ensure that backed enum signatures are included in the tests. As this sniff retrieves the class name itself instead of using the `File::getDeclarationName()` method, these tests _will_ find the name correctly (not mistake the type for the name), but _may_ include the colon in the name as the "name end" determination checks on scope opener and whitespace and doesn't take the colon for the type declaration into account. --- .../Squiz/Sniffs/Classes/ValidClassNameSniff.php | 2 +- .../Squiz/Tests/Classes/ValidClassNameUnitTest.inc | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php b/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php index 8d45c86e0c..ffddd2cde7 100644 --- a/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php +++ b/src/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php @@ -59,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/Tests/Classes/ValidClassNameUnitTest.inc b/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc index 62a769a034..3fe39435b4 100644 --- a/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/ValidClassNameUnitTest.inc @@ -138,7 +138,7 @@ trait Base } // Valid enum name. -enum ValidCamelCaseClass {} +enum ValidCamelCaseClass: string {} // Incorrect usage of camel case. @@ -147,22 +147,22 @@ enum Invalid_Camel_Case_Class_With_Underscores {} // All lowercase. -enum invalidlowercaseclass {} +enum invalidlowercaseclass: INT {} enum invalid_lowercase_class_with_underscores {} // All uppercase. -enum VALIDUPPERCASECLASS {} +enum VALIDUPPERCASECLASS: int {} enum INVALID_UPPERCASE_CLASS_WITH_UNDERSCORES {} // Mix camel case with uppercase. -enum ValidCamelCaseClassWithUPPERCASE {} +enum ValidCamelCaseClassWithUPPERCASE : string {} // Usage of numeric characters. enum ValidCamelCaseClassWith1Number {} -enum ValidCamelCaseClassWith12345Numbers {} +enum ValidCamelCaseClassWith12345Numbers : string {} enum ValidCamelCaseClassEndingWithNumber5 {} enum Testing{} From b0b4addfd10c0694e49e33ac5aaff29928214715 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 23:26:31 +0100 Subject: [PATCH 558/733] Squiz/NonExecutableCode: minor test tweak Only use tabs when in the test cases when they serve a specific purpose. --- .../Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc index 6435bd0663..c9bf052f34 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc @@ -46,9 +46,9 @@ trait Something { } enum Something { - function getReturnType() { - echo 'no error'; - } + function getReturnType() { + echo 'no error'; + } } $a = new class { From 5629985d0fbe21b44b0e1c01f4cee31096038959 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 23:09:55 +0100 Subject: [PATCH 559/733] Squiz/StaticThisUsage: improve enum tests Let's also test that `$this` in a non-static enum method doesn't get flagged. --- src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc | 4 ++++ src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc index 13bb44f1fe..dd6530e802 100644 --- a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc +++ b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.inc @@ -117,6 +117,10 @@ $b = new class() } 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 36d833903a..b1a5dd6afd 100644 --- a/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.php +++ b/src/Standards/Squiz/Tests/Scope/StaticThisUsageUnitTest.php @@ -38,7 +38,7 @@ public function getErrorList() 80 => 1, 84 => 1, 99 => 1, - 121 => 1, + 125 => 1, ]; }//end getErrorList() From 15a0b4ebfb942474322647dda062301f39ed029b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 23:33:35 +0100 Subject: [PATCH 560/733] Squiz/ControlStructureSpacing: fix the enum test The test as-it-was, wasn't actually testing anything. As the sniff is about control structures, the `enum` needs to be nested in one before the test would fail without the fix, --- .../Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc | 6 ++++-- .../WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc index dbab24be1d..70abae434d 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc +++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc @@ -262,6 +262,8 @@ $expr = match( $foo ){ }; echo $expr; -enum SomeEnum -{ +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 4a655dc916..c64de25e16 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/ControlStructureSpacingUnitTest.inc.fixed @@ -254,6 +254,8 @@ $expr = match($foo){ echo $expr; -enum SomeEnum -{ +if($true) { + + enum SomeEnum {} + } From 53bf08b5b60a1735ca03579989eec7dad0b8c356 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 18 Mar 2022 14:21:54 +1100 Subject: [PATCH 561/733] Changelog for #3550 (ref #3551) --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 1f1511cf3e..8d216e9e0c 100644 --- a/package.xml +++ b/package.xml @@ -66,6 +66,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 From 1900302971fb7ebd7a15ff4b69e825fa59ed0ae4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 18 Mar 2022 14:28:23 +1100 Subject: [PATCH 562/733] Changelog for #3552 --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index 8d216e9e0c..e6d5a1a233 100644 --- a/package.xml +++ b/package.xml @@ -29,6 +29,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 From 8925809a8097c0374512b3879f1cac8d41dae800 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 19 Mar 2022 08:02:08 +0100 Subject: [PATCH 563/733] GH Actions/CS: fix build failure Fix failing installation of the xmllint tooling. --- .github/workflows/validate.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9fb5685d32..8dbe80ce94 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -25,7 +25,9 @@ jobs: uses: actions/checkout@v2 - 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 From f62bb577a9a234a3042f1c67f0474d81259760ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jefferson=20Sim=C3=A3o=20Gon=C3=A7alves?= <411493+jeffersonsimaogoncalves@users.noreply.github.com> Date: Mon, 21 Mar 2022 19:07:50 -0300 Subject: [PATCH 564/733] Update README.md Removing $ to make copying the command easier --- README.md | 65 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) 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. From a6704c2e9e9029925b38b150ccd703364b9b434b Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 1 Apr 2022 09:18:39 +1100 Subject: [PATCH 565/733] Changelog for #3539 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 473ab51aee..5526129d25 100644 --- a/package.xml +++ b/package.xml @@ -53,6 +53,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer for the patches - The parallel feature is now more efficent and runs faster in some situations due to improved process managment -- 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 From 6bf214054a11fdd7105a07e7e4349efddcf3cd85 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 19 Jan 2022 01:59:31 +0100 Subject: [PATCH 566/733] PEAR/ScopeClosingBrace: add tests for enum support --- .../Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc | 10 ++++++++++ .../WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed | 11 +++++++++++ .../Tests/WhiteSpace/ScopeClosingBraceUnitTest.php | 2 ++ 3 files changed, 23 insertions(+) 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() From 6cbb7f78c54a36d806602a6e7bf7aa3f90ab0b99 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 19 Jan 2022 01:23:40 +0100 Subject: [PATCH 567/733] PSR12/OpeningBraceSpace: add tests for enum support --- .../PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.inc | 8 ++++++++ .../Tests/Classes/OpeningBraceSpaceUnitTest.inc.fixed | 7 +++++++ .../PSR12/Tests/Classes/OpeningBraceSpaceUnitTest.php | 1 + 3 files changed, 16 insertions(+) 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() From ec3945a1cc7bf4f542ed743023f98bd9cace8efa Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 19 Jan 2022 02:40:59 +0100 Subject: [PATCH 568/733] Squiz/ClosingDeclarationComment: add support for enums --- .../Sniffs/Commenting/ClosingDeclarationCommentSniff.php | 5 ++++- .../Tests/Commenting/ClosingDeclarationCommentUnitTest.inc | 6 +++++- .../Tests/Commenting/ClosingDeclarationCommentUnitTest.php | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) 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/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() From 380e17be84ec1f90dbcf3df0f207edb76581cfdb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 19 Jan 2022 02:50:54 +0100 Subject: [PATCH 569/733] Squiz/DocCommentAlignment: add support for enums --- .../Sniffs/Commenting/DocCommentAlignmentSniff.php | 2 ++ .../Tests/Commenting/DocCommentAlignmentUnitTest.inc | 12 ++++++++++++ .../Commenting/DocCommentAlignmentUnitTest.inc.fixed | 12 ++++++++++++ .../Tests/Commenting/DocCommentAlignmentUnitTest.php | 4 ++++ 4 files changed, 30 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php index b557ae24cd..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, diff --git a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc index 5de613da22..e42cf8ab27 100644 --- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc @@ -85,6 +85,18 @@ abstract class MyClass 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 7395d2fab4..6182b539cd 100644 --- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed @@ -85,6 +85,18 @@ abstract class MyClass 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 a862d4a819..acbf13e869 100644 --- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.php @@ -47,6 +47,10 @@ public function getErrorList($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; From 2bb6f394d195b45728a5daa18c590692d8c3abd8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 19 Jan 2022 02:06:05 +0100 Subject: [PATCH 570/733] Squiz/ScopeClosingBrace: add tests for enum support --- .../Tests/WhiteSpace/ScopeClosingBraceUnitTest.inc | 12 ++++++++++++ .../WhiteSpace/ScopeClosingBraceUnitTest.inc.fixed | 13 +++++++++++++ .../Tests/WhiteSpace/ScopeClosingBraceUnitTest.php | 2 ++ 3 files changed, 27 insertions(+) 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() From bf17a5b5b7ea8835ca0be5b1a8286f3f5e66f71e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 18 Jan 2022 16:17:39 +0100 Subject: [PATCH 571/733] PHP 8.1 | Generic/LowerCaseKeyword: simplify registered tokens + add enum support This is a PR to simplify maintenance of this sniff. Since PR 3484, the `Tokens` class contains a `$contextSensitiveKeywords` property which largely overlaps with the list of tokens registered for this sniff. All tokens in that list should always be included in the targets for this sniff anyway, so we may as well leverage the new token array. Includes adding the new custom `T_ENUM_CASE` token to the list of additional tokens to take into account. Includes unit test. --- .../Sniffs/PHP/LowerCaseKeywordSniff.php | 93 ++++--------------- .../Tests/PHP/LowerCaseKeywordUnitTest.inc | 4 +- .../PHP/LowerCaseKeywordUnitTest.inc.fixed | 4 +- .../Tests/PHP/LowerCaseKeywordUnitTest.php | 1 + 4 files changed, 23 insertions(+), 79 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php index f26b6b53ec..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,83 +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_ENUM, - 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_READONLY, - 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() @@ -124,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/Tests/PHP/LowerCaseKeywordUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc index 1566b6e017..37579d3217 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc @@ -39,7 +39,9 @@ class Reading { Public READOnly int $var; } -EnuM Enum { +EnuM ENUM: string +{ + Case HEARTS; } __HALT_COMPILER(); // An exception due to phar support. diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed index 690b7c5eed..7063327ae8 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.inc.fixed @@ -39,7 +39,9 @@ class Reading { public readonly int $var; } -enum Enum { +enum ENUM: string +{ + case HEARTS; } __HALT_COMPILER(); // An exception due to phar support. diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php index e551149226..6d08e12751 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseKeywordUnitTest.php @@ -42,6 +42,7 @@ public function getErrorList() 35 => 1, 39 => 2, 42 => 1, + 44 => 1, ]; }//end getErrorList() From 10c10acece4e22eeb9fcff531f624b46b3bd68da Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 19 Jan 2022 01:36:54 +0100 Subject: [PATCH 572/733] Squiz/MethodScope: bugfix for unconventional spacing The `Squiz.Scope.MethodScope` sniff is intended to check whether each method has visibility declared, but would in actual fact also enforce that the visibility should be on the same line as the `function` keyword as it stopped searching for the visibility keyword once it reached the start of the line. Fixed now by using the `File::getMethodProperties()` method for determining visibility instead of searching with sniff specific logic. Includes unit test. --- .../Squiz/Sniffs/Scope/MethodScopeSniff.php | 13 ++----------- .../Squiz/Tests/Scope/MethodScopeUnitTest.inc | 7 +++++++ 2 files changed, 9 insertions(+), 11 deletions(-) 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/Tests/Scope/MethodScopeUnitTest.inc b/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc index bc77d46d1d..3cc617d75b 100644 --- a/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc +++ b/src/Standards/Squiz/Tests/Scope/MethodScopeUnitTest.inc @@ -48,3 +48,10 @@ enum SomeEnum private function func1() {} protected function func1() {} } + +class UnconventionalSpacing { + public + static + function + myFunction() {} +} From daff2729ced9297b4545d93901d4bcf3684f4b53 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 1 Apr 2022 02:09:37 +0200 Subject: [PATCH 573/733] PHP 8.1 | File::findImplementedInterfaceNames(): add support for enums implementing interfaces An `enum` declaration can implement one or more interfaces. This commit updates the `File::findImplementedInterfaceNames()` method to allow it to find the interfaces implemented by an `enum` as well. Includes unit tests. --- src/Files/File.php | 5 +++-- .../File/FindImplementedInterfaceNamesTest.inc | 9 +++++++++ .../File/FindImplementedInterfaceNamesTest.php | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 5c86faa918..945a430a4c 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -2765,11 +2765,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 */ @@ -2782,6 +2782,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/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() From 152fa05c5519422f816dae17a9acd5fedfd1bf49 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 1 Apr 2022 01:57:45 +0200 Subject: [PATCH 574/733] File::getMemberProperties(): handle enum properties same as interface properties Neither enums nor interfaces support properties being declared on them. This adjusts the `File::getMemberProperties() method to handle properties declared on an `enum` in the same way as properties declared on an `interface` were already being handled. Includes unit test. --- src/Files/File.php | 13 ++++++++----- tests/Core/File/GetMemberPropertiesTest.inc | 15 +++++++++++++++ tests/Core/File/GetMemberPropertiesTest.php | 5 +++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 5c86faa918..09da72693c 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1791,23 +1791,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 + // T_VARIABLEs in interfaces/enums can actually be method arguments // but they wont 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) { diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index e156099382..3daf52c998 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -261,3 +261,18 @@ $anon = class { ] private mixed $baz; }; + +enum Suit +{ + /* testEnumProperty */ + protected $anonymous; +} + +enum Direction implements ArrayAccess +{ + case Up; + case Down; + + /* testEnumMethodParamNotProperty */ + public function offsetGet($val) { ... } +} diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index 82764b01e5..d05814239d 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -662,6 +662,10 @@ public function dataGetMemberProperties() 'nullable_type' => false, ], ], + [ + '/* testEnumProperty */', + [], + ], ]; }//end dataGetMemberProperties() @@ -703,6 +707,7 @@ public function dataNotClassProperty() ['/* testGlobalVariable */'], ['/* testNestedMethodParam 1 */'], ['/* testNestedMethodParam 2 */'], + ['/* testEnumMethodParamNotProperty */'], ]; }//end dataNotClassProperty() From 2d71c525d4477851729287ae5cc06bd0085d31c1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 10 Apr 2022 16:42:18 +0200 Subject: [PATCH 575/733] PHP 8.1 | Tokenizer/PHP: hotfix for overeager explicit octal notation backfill Follow up on 3481 and 3552. While working on PHPCompatibility/PHPCSUtils, I found another instance where the explicit octal notation backfill is overeager. PHP natively will tokenize invalid octals, like `0o91` and `T_LNUMBER` + `T_STRING` in all PHP versions, but with the backfill in place, this would no longer be the case and on PHP < 8.1, this would now be tokenized as `T_LNUMBER`, making tokenization across PHP versions unpredictable and inconsistent. Fixed now. Including tests. --- src/Tokenizers/PHP.php | 22 +++++++++++++-- .../BackfillExplicitOctalNotationTest.inc | 12 ++++++++ .../BackfillExplicitOctalNotationTest.php | 28 +++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index d61d72d1e7..955d1e3629 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -732,16 +732,32 @@ protected function tokenize($string) && $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] .= $tokens[($stackPtr + 1)][1], + 'content' => $token[1] .= $matches[1], ]; - $stackPtr++; $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. diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc index 0f2d0cf738..154d489506 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc @@ -14,3 +14,15 @@ $foo = 0o_137; /* testInvalid2 */ $foo = 0O_41; + +/* testInvalid3 */ +$foo = 0o91; + +/* testInvalid4 */ +$foo = 0O282; + +/* testInvalid5 */ +$foo = 0o28_2; + +/* testInvalid6 */ +$foo = 0o2_82; diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php index 836235a245..4ffa84ca09 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php @@ -83,6 +83,34 @@ public function dataExplicitOctalNotation() 'value' => '0', ], ], + [ + [ + 'marker' => '/* testInvalid3 */', + 'type' => 'T_LNUMBER', + 'value' => '0', + ], + ], + [ + [ + 'marker' => '/* testInvalid4 */', + 'type' => 'T_LNUMBER', + 'value' => '0O2', + ], + ], + [ + [ + 'marker' => '/* testInvalid5 */', + 'type' => 'T_LNUMBER', + 'value' => '0o2', + ], + ], + [ + [ + 'marker' => '/* testInvalid6 */', + 'type' => 'T_LNUMBER', + 'value' => '0o2', + ], + ], ]; }//end dataExplicitOctalNotation() From 915b12a1fdde541076c94ba3f7f4d8fd8403b255 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 10 Apr 2022 18:14:03 +0200 Subject: [PATCH 576/733] BackfillExplicitOctalNotationTest: refactor and improve the test As the tokenizer may now split a `T_STRING` token which follows a `T_LNUMBER` into two tokens, it is important to also test that the next token is set correctly. To that end, I've refactored the test to include this test. Includes: * Simplifying the data provider by getting rid of the unnecessary nested array. * Removing the assertions checking for `code` and `type` of the found token as those would always pass as the `getTargetToken()` method would otherwise fail the test already. * Adding assertions testing the token code + content for the next token. * Adding `$message` parameters to the assertions to be able to distinguish which one failed (if one fails). --- .../BackfillExplicitOctalNotationTest.php | 97 +++++++++---------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php index 4ffa84ca09..e1c55bd934 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php @@ -18,22 +18,26 @@ 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 array $testData The data required for the specific test case. + * @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($testData) + public function testExplicitOctalNotation($marker, $value, $nextToken, $nextContent) { $tokens = self::$phpcsFile->getTokens(); - $number = $this->getTargetToken($testData['marker'], [T_LNUMBER]); + $number = $this->getTargetToken($marker, [T_LNUMBER]); - $this->assertSame(constant($testData['type']), $tokens[$number]['code']); - $this->assertSame($testData['type'], $tokens[$number]['type']); - $this->assertSame($testData['value'], $tokens[$number]['content']); + $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() @@ -49,67 +53,58 @@ public function dataExplicitOctalNotation() { return [ [ - [ - 'marker' => '/* testExplicitOctal */', - 'type' => 'T_LNUMBER', - 'value' => '0o137041', - ], + 'marker' => '/* testExplicitOctal */', + 'value' => '0o137041', + 'nextToken' => T_SEMICOLON, + 'nextContent' => ';', ], [ - [ - 'marker' => '/* testExplicitOctalCapitalised */', - 'type' => 'T_LNUMBER', - 'value' => '0O137041', - ], + 'marker' => '/* testExplicitOctalCapitalised */', + 'value' => '0O137041', + 'nextToken' => T_SEMICOLON, + 'nextContent' => ';', ], [ - [ - 'marker' => '/* testExplicitOctalWithNumericSeparator */', - 'type' => 'T_LNUMBER', - 'value' => '0o137_041', - ], + 'marker' => '/* testExplicitOctalWithNumericSeparator */', + 'value' => '0o137_041', + 'nextToken' => T_SEMICOLON, + 'nextContent' => ';', ], [ - [ - 'marker' => '/* testInvalid1 */', - 'type' => 'T_LNUMBER', - 'value' => '0', - ], + 'marker' => '/* testInvalid1 */', + 'value' => '0', + 'nextToken' => T_STRING, + 'nextContent' => 'o_137', ], [ - [ - 'marker' => '/* testInvalid2 */', - 'type' => 'T_LNUMBER', - 'value' => '0', - ], + 'marker' => '/* testInvalid2 */', + 'value' => '0', + 'nextToken' => T_STRING, + 'nextContent' => 'O_41', ], [ - [ - 'marker' => '/* testInvalid3 */', - 'type' => 'T_LNUMBER', - 'value' => '0', - ], + 'marker' => '/* testInvalid3 */', + 'value' => '0', + 'nextToken' => T_STRING, + 'nextContent' => 'o91', ], [ - [ - 'marker' => '/* testInvalid4 */', - 'type' => 'T_LNUMBER', - 'value' => '0O2', - ], + 'marker' => '/* testInvalid4 */', + 'value' => '0O2', + 'nextToken' => T_LNUMBER, + 'nextContent' => '82', ], [ - [ - 'marker' => '/* testInvalid5 */', - 'type' => 'T_LNUMBER', - 'value' => '0o2', - ], + 'marker' => '/* testInvalid5 */', + 'value' => '0o2', + 'nextToken' => T_LNUMBER, + 'nextContent' => '8_2', ], [ - [ - 'marker' => '/* testInvalid6 */', - 'type' => 'T_LNUMBER', - 'value' => '0o2', - ], + 'marker' => '/* testInvalid6 */', + 'value' => '0o2', + 'nextToken' => T_STRING, + 'nextContent' => '_82', ], ]; From 040f5ab28b00b4bcb96a95e36ed9decc1f8027d8 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 18 Apr 2022 09:50:47 +1000 Subject: [PATCH 577/733] Changelog for #3573 --- package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/package.xml b/package.xml index 5526129d25..8dac4c15b0 100644 --- a/package.xml +++ b/package.xml @@ -34,6 +34,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 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 From b314140057170a81075556a71764f2f302e3b8be Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 18 Apr 2022 10:25:09 +1000 Subject: [PATCH 578/733] Updated changelog --- package.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 8dac4c15b0..15a4fd490c 100644 --- a/package.xml +++ b/package.xml @@ -34,7 +34,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 sniff support + -- 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 From 9d4c6d1ea298a72f49cba8249a83fd6a64512e1f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 18 Dec 2021 07:24:23 +0100 Subject: [PATCH 579/733] File::getMemberProperties(): minor tweaks after readonly merge Follow up on 3480 I realized after the merge that I had not published a few follow up comments. Addressing those here. * The new `is_readonly` key still needed to be added to the documentation for the `File::getMemberProperties()` method. * For consistency in how the method is tested, adding the `is_readonly` property to all other test expectations, which makes the `testPHP81NotReadonly` test case redundant. * Adding two additional test cases. In particular I wanted to verify (and safeguard) that the retokenization of the `?` to `T_NULLABLE` after a `readonly` keyword would not be broken (which it partially was, but that has now been fixed in 3513). --- src/Files/File.php | 1 + tests/Core/File/GetMemberPropertiesTest.inc | 8 +- tests/Core/File/GetMemberPropertiesTest.php | 82 +++++++++++++++++++-- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index 021949cb67..b0e67cec10 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1759,6 +1759,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. diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index 3daf52c998..4533c5ffaf 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -240,10 +240,14 @@ $anon = class() { // 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; - /* testPHP81NotReadonly */ - private string $notReadonly; /* testPHP81Readonly */ public readonly int $readonly; + + /* testPHP81ReadonlyWithNullableType */ + public readonly ?array $array; + + /* testPHP81ReadonlyWithUnionType */ + protected ReadOnly string|null $array; }; $anon = class { diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index d05814239d..cc709e69c7 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,7 @@ public function dataGetMemberProperties() 'scope' => 'private', 'scope_specified' => true, 'is_static' => true, + 'is_readonly' => false, 'type' => '', 'nullable_type' => false, ], @@ -201,6 +216,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => true, + 'is_readonly' => false, 'type' => '', 'nullable_type' => false, ], @@ -211,6 +227,7 @@ public function dataGetMemberProperties() 'scope' => 'protected', 'scope_specified' => true, 'is_static' => true, + 'is_readonly' => false, 'type' => '', 'nullable_type' => false, ], @@ -221,6 +238,7 @@ public function dataGetMemberProperties() 'scope' => 'private', 'scope_specified' => true, 'is_static' => true, + 'is_readonly' => false, 'type' => '', 'nullable_type' => false, ], @@ -231,6 +249,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => 'float', 'nullable_type' => false, ], @@ -241,6 +260,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => 'float', 'nullable_type' => false, ], @@ -251,6 +271,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => true, + 'is_readonly' => false, 'type' => '?string', 'nullable_type' => true, ], @@ -261,6 +282,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => true, + 'is_readonly' => false, 'type' => '?string', 'nullable_type' => true, ], @@ -271,6 +293,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => false, 'is_static' => false, + 'is_readonly' => false, 'type' => '', 'nullable_type' => false, ], @@ -281,6 +304,7 @@ public function dataGetMemberProperties() '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,29 +661,41 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => 'int|string|INT', 'nullable_type' => false, ], ], [ - '/* testPHP81NotReadonly */', + '/* testPHP81Readonly */', [ - 'scope' => 'private', + 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, - 'is_readonly' => false, - 'type' => 'string', + 'is_readonly' => true, + 'type' => 'int', 'nullable_type' => false, ], ], [ - '/* testPHP81Readonly */', + '/* testPHP81ReadonlyWithNullableType */', [ 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, 'is_readonly' => true, - 'type' => 'int', + 'type' => '?array', + 'nullable_type' => true, + ], + ], + [ + '/* testPHP81ReadonlyWithUnionType */', + [ + 'scope' => 'protected', + 'scope_specified' => true, + 'is_static' => false, + 'is_readonly' => true, + 'type' => 'string|null', 'nullable_type' => false, ], ], @@ -638,6 +705,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => 'string', 'nullable_type' => false, ], @@ -648,6 +716,7 @@ public function dataGetMemberProperties() 'scope' => 'protected', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => '?int|float', 'nullable_type' => true, ], @@ -658,6 +727,7 @@ public function dataGetMemberProperties() 'scope' => 'private', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => 'mixed', 'nullable_type' => false, ], From 01be758f5105b2fe85c60d7d325245fc4a656142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Fri, 22 Apr 2022 13:42:24 +0200 Subject: [PATCH 580/733] PHP 8.1: Support of "never" type --- .../Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 1 + .../Tests/PHP/LowerCaseTypeUnitTest.inc | 4 ++ .../Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 4 ++ .../Tests/PHP/LowerCaseTypeUnitTest.php | 1 + tests/Core/File/GetMethodPropertiesTest.inc | 9 +++- tests/Core/File/GetMethodPropertiesTest.php | 46 +++++++++++++++++++ .../NamedFunctionCallArgumentsTest.inc | 3 ++ .../NamedFunctionCallArgumentsTest.php | 1 + 8 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index a738c30d66..62894393f7 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, ]; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index 8e2dca17e0..efcfa7e867 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -81,3 +81,7 @@ class ConstructorPropertyPromotionWithTypes { class ConstructorPropertyPromotionAndNormalParams { public function __construct(public Int $promotedProp, ?Int $normalArg) {} } + +function (): NeVeR { + exit; +}; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed index 6e03f7c2d5..7d0a8d05ae 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -81,3 +81,7 @@ class ConstructorPropertyPromotionWithTypes { class ConstructorPropertyPromotionAndNormalParams { public function __construct(public int $promotedProp, ?int $normalArg) {} } + +function (): never { + exit; +}; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php index d447e8f47e..d95872a7dd 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php @@ -65,6 +65,7 @@ public function getErrorList() 74 => 3, 78 => 3, 82 => 2, + 85 => 1, ]; }//end getErrorList() diff --git a/tests/Core/File/GetMethodPropertiesTest.inc b/tests/Core/File/GetMethodPropertiesTest.inc index 3e9682df6e..d890aad92b 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,10 @@ 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 {} diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php index d912d6b1ce..9872cf3987 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -728,6 +728,52 @@ 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() + + /** * Test helper. * diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc index d05d27d951..19ef930069 100644 --- a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc +++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc @@ -396,3 +396,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..9683765fd6 100644 --- a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php +++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php @@ -831,6 +831,7 @@ public function dataReservedKeywordsAsName() 'resource', 'mixed', 'numeric', + 'never', // Not reserved keyword, but do have their own token in PHPCS. 'parent', From d009ba6f68000681f04eefb198c34c637e1e1979 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 17 May 2022 08:24:51 +1000 Subject: [PATCH 581/733] Changelog for #3582 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 15a4fd490c..4a1151a7df 100644 --- a/package.xml +++ b/package.xml @@ -59,6 +59,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 From 51ebe2de909a000757742a5e646c7d6163b4b5f1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 21 Feb 2021 17:10:05 +0100 Subject: [PATCH 582/733] Documentation: various minor fixes .. picked up along the way. --- src/Files/File.php | 2 +- src/Sniffs/AbstractArraySniff.php | 6 +++--- .../Generic/Sniffs/PHP/DisallowRequestSuperglobalSniff.php | 5 +++-- .../PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php | 4 ++-- src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php | 6 +++--- src/Tokenizers/PHP.php | 2 +- src/Util/Common.php | 2 +- tests/Core/Autoloader/DetermineLoadedClassTest.php | 2 +- tests/Core/File/FindStartOfStatementTest.php | 4 ++-- tests/Core/Tokenizer/AttributesTest.php | 2 +- 10 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index b0e67cec10..c2a01ee5f7 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1796,7 +1796,7 @@ public function getMemberProperties($stackPtr) || $this->tokens[$ptr]['code'] === T_ENUM) ) { // T_VARIABLEs in interfaces/enums can actually be method arguments - // but they wont be seen as being inside the method because there + // 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 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/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/PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php b/src/Standards/PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php index 7a33bd9471..db67fb9428 100644 --- a/src/Standards/PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php +++ b/src/Standards/PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php @@ -21,14 +21,14 @@ class AnonClassDeclarationSniff extends ClassDeclarationSniff /** * The PSR2 MultiLineFunctionDeclarations sniff. * - * @var MultiLineFunctionDeclarationSniff + * @var \PHP_CodeSniffer\Standards\Squiz\Sniffs\Functions\MultiLineFunctionDeclarationSniff */ private $multiLineSniff = null; /** * The Generic FunctionCallArgumentSpacing sniff. * - * @var FunctionCallArgumentSpacingSniff + * @var \PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\FunctionCallArgumentSpacingSniff */ private $functionCallSniff = null; diff --git a/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php b/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php index 79cb9f3f48..f3b5495d90 100644 --- a/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php +++ b/src/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php @@ -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/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index d61d72d1e7..f585327719 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1186,7 +1186,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 diff --git a/src/Util/Common.php b/src/Util/Common.php index caf4db772b..ce7967cc33 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -372,7 +372,7 @@ public static function isCamelCaps( for ($i = 1; $i < $length; $i++) { $ascii = ord($string[$i]); if ($ascii >= 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/tests/Core/Autoloader/DetermineLoadedClassTest.php b/tests/Core/Autoloader/DetermineLoadedClassTest.php index 65542b670b..0d12230771 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) 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/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php index b10a1efb95..1c93154a63 100644 --- a/tests/Core/Tokenizer/AttributesTest.php +++ b/tests/Core/Tokenizer/AttributesTest.php @@ -289,7 +289,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. From 1fff686becbcf67d2a0a1eac25b81f0db7e6816f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Fri, 22 Apr 2022 16:21:59 +0200 Subject: [PATCH 583/733] Resolve T_READONLY earlier --- src/Tokenizers/PHP.php | 91 +++++++------------ tests/Core/File/GetMemberPropertiesTest.inc | 8 +- tests/Core/File/GetMemberPropertiesTest.php | 22 +++++ tests/Core/Tokenizer/BackfillReadonlyTest.inc | 3 + tests/Core/Tokenizer/BackfillReadonlyTest.php | 4 + tests/Core/Tokenizer/BitwiseOrTest.inc | 12 +++ tests/Core/Tokenizer/BitwiseOrTest.php | 4 + .../NamedFunctionCallArgumentsTest.inc | 3 + .../NamedFunctionCallArgumentsTest.php | 1 + 9 files changed, 88 insertions(+), 60 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index d61d72d1e7..7528eca279 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1241,6 +1241,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 @@ -2924,65 +2956,6 @@ protected function processAdditional() $this->tokens[$i]['code'] = T_STRING; $this->tokens[$i]['type'] = 'T_STRING'; } - } else if ($this->tokens[$i]['code'] === T_STRING - && strtolower($this->tokens[$i]['content']) === 'readonly' - ) { - /* - Adds "readonly" keyword support for PHP < 8.1. - */ - - $allowedAfter = [ - T_STRING => T_STRING, - T_NS_SEPARATOR => T_NS_SEPARATOR, - T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED, - T_NAME_RELATIVE => T_NAME_RELATIVE, - T_NAME_QUALIFIED => T_NAME_QUALIFIED, - T_TYPE_UNION => T_TYPE_UNION, - T_BITWISE_OR => T_BITWISE_OR, - T_BITWISE_AND => T_BITWISE_AND, - T_ARRAY => T_ARRAY, - T_CALLABLE => T_CALLABLE, - T_SELF => T_SELF, - T_PARENT => T_PARENT, - T_NULL => T_FALSE, - T_NULLABLE => T_NULLABLE, - T_STATIC => T_STATIC, - T_PUBLIC => T_PUBLIC, - T_PROTECTED => T_PROTECTED, - T_PRIVATE => T_PRIVATE, - T_VAR => T_VAR, - ]; - - $shouldBeReadonly = true; - - for ($x = ($i + 1); $x < $numTokens; $x++) { - if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) { - continue; - } - - if ($this->tokens[$x]['code'] === T_VARIABLE - || $this->tokens[$x]['code'] === T_CONST - ) { - break; - } - - if (isset($allowedAfter[$this->tokens[$x]['code']]) === false) { - $shouldBeReadonly = false; - break; - } - } - - if ($shouldBeReadonly === true) { - if (PHP_CODESNIFFER_VERBOSITY > 1) { - $line = $this->tokens[$i]['line']; - echo "\t* token $i on line $line changed from T_STRING to T_READONLY".PHP_EOL; - } - - $this->tokens[$i]['code'] = T_READONLY; - $this->tokens[$i]['type'] = 'T_READONLY'; - } - - continue; }//end if if (($this->tokens[$i]['code'] !== T_CASE diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index 4533c5ffaf..62773212eb 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -247,7 +247,13 @@ $anon = class() { public readonly ?array $array; /* testPHP81ReadonlyWithUnionType */ - protected ReadOnly string|null $array; + public readonly string|int $readonlyWithUnionType; + + /* testPHP81ReadonlyWithUnionTypeWithNull */ + protected ReadOnly string|null $readonlyWithUnionTypeWithNull; + + /* testPHP81OnlyReadonlyWithUnionType */ + readonly string|int $onlyReadonly; }; $anon = class { diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index cc709e69c7..9d6f28aadc 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -690,6 +690,17 @@ public function dataGetMemberProperties() ], [ '/* testPHP81ReadonlyWithUnionType */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'is_readonly' => true, + 'type' => 'string|int', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP81ReadonlyWithUnionTypeWithNull */', [ 'scope' => 'protected', 'scope_specified' => true, @@ -699,6 +710,17 @@ public function dataGetMemberProperties() 'nullable_type' => false, ], ], + [ + '/* testPHP81OnlyReadonlyWithUnionType */', + [ + 'scope' => 'public', + 'scope_specified' => false, + 'is_static' => false, + 'is_readonly' => true, + 'type' => 'string|int', + 'nullable_type' => false, + ], + ], [ '/* testPHP8PropertySingleAttribute */', [ diff --git a/tests/Core/Tokenizer/BackfillReadonlyTest.inc b/tests/Core/Tokenizer/BackfillReadonlyTest.inc index ab7c16c321..eaf0b4b3cc 100644 --- a/tests/Core/Tokenizer/BackfillReadonlyTest.inc +++ b/tests/Core/Tokenizer/BackfillReadonlyTest.inc @@ -92,6 +92,9 @@ $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 index d347417143..dddc18ebc2 100644 --- a/tests/Core/Tokenizer/BackfillReadonlyTest.php +++ b/tests/Core/Tokenizer/BackfillReadonlyTest.php @@ -147,6 +147,10 @@ public function dataReadonly() '/* testReadonlyPropertyInAnonymousClass */', 'readonly', ], + [ + '/* testReadonlyUsedAsFunctionCallWithSpaceBetweenKeywordAndParens */', + 'readonly', + ], [ '/* testParseErrorLiveCoding */', 'readonly', diff --git a/tests/Core/Tokenizer/BitwiseOrTest.inc b/tests/Core/Tokenizer/BitwiseOrTest.inc index 0baca8c5c2..bfdbdc18c1 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.inc +++ b/tests/Core/Tokenizer/BitwiseOrTest.inc @@ -36,6 +36,18 @@ class TypeUnion /* 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 64b5c32986..d56e7340aa 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.php +++ b/tests/Core/Tokenizer/BitwiseOrTest.php @@ -106,6 +106,10 @@ public function dataTypeUnion() ['/* testTypeUnionPropertyPartiallyQualified */'], ['/* testTypeUnionPropertyFullyQualified */'], ['/* testTypeUnionPropertyWithReadOnlyKeyword */'], + ['/* testTypeUnionPropertyWithReadOnlyKeywordFirst */'], + ['/* testTypeUnionPropertyWithStaticAndReadOnlyKeywords */'], + ['/* testTypeUnionPropertyWithVarAndReadOnlyKeywords */'], + ['/* testTypeUnionPropertyWithOnlyReadOnlyKeyword */'], ['/* testTypeUnionParam1 */'], ['/* testTypeUnionParam2 */'], ['/* testTypeUnionParam3 */'], diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc index 19ef930069..4497f5c380 100644 --- a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc +++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc @@ -310,6 +310,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); diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php index 9683765fd6..0a878c1d3b 100644 --- a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php +++ b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php @@ -804,6 +804,7 @@ public function dataReservedKeywordsAsName() 'private', 'protected', 'public', + 'readonly', 'require', 'require_once', 'return', From c80367101d241b7e4236a718ec96b13b227ad45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Fri, 22 Apr 2022 11:13:49 +0200 Subject: [PATCH 584/733] PHP 8.1: Support of intersection types --- package.xml | 6 + src/Files/File.php | 41 +++--- .../Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 12 +- src/Tokenizers/PHP.php | 66 +++++---- src/Util/Tokens.php | 1 + tests/Core/File/GetMemberPropertiesTest.inc | 16 ++ tests/Core/File/GetMemberPropertiesTest.php | 40 +++++ tests/Core/File/GetMethodParametersTest.inc | 17 +++ tests/Core/File/GetMethodParametersTest.php | 133 +++++++++++++++++ tests/Core/File/GetMethodPropertiesTest.inc | 17 +++ tests/Core/File/GetMethodPropertiesTest.php | 116 +++++++++++++++ tests/Core/Tokenizer/TypeIntersectionTest.inc | 120 +++++++++++++++ tests/Core/Tokenizer/TypeIntersectionTest.php | 138 ++++++++++++++++++ 13 files changed, 676 insertions(+), 47 deletions(-) create mode 100644 tests/Core/Tokenizer/TypeIntersectionTest.inc create mode 100644 tests/Core/Tokenizer/TypeIntersectionTest.php diff --git a/package.xml b/package.xml index 4a1151a7df..41fe14e2b4 100644 --- a/package.xml +++ b/package.xml @@ -199,6 +199,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + +
@@ -2168,6 +2170,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2268,6 +2272,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Files/File.php b/src/Files/File.php index b0e67cec10..a15af762fc 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1469,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. @@ -1685,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++) { @@ -1886,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++) { diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index 62894393f7..96331f11db 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -103,7 +103,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'], @@ -132,7 +134,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'], @@ -162,7 +166,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/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index d61d72d1e7..5233a59202 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -462,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, ]; /** @@ -2406,18 +2407,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']; @@ -2713,9 +2715,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 = [ @@ -2780,12 +2785,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--) { @@ -2798,13 +2803,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; } @@ -2870,17 +2875,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; + } } } @@ -2938,6 +2953,7 @@ protected function processAdditional() T_NAME_RELATIVE => T_NAME_RELATIVE, T_NAME_QUALIFIED => T_NAME_QUALIFIED, T_TYPE_UNION => T_TYPE_UNION, + T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, T_BITWISE_OR => T_BITWISE_OR, T_BITWISE_AND => T_BITWISE_AND, T_ARRAY => T_ARRAY, diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index febd9c5732..53e5ef4b98 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -81,6 +81,7 @@ 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) { diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index 4533c5ffaf..044caec185 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -280,3 +280,19 @@ enum Direction implements ArrayAccess /* 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 cc709e69c7..ae16a778fe 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -736,6 +736,46 @@ public function dataGetMemberProperties() '/* 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() diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index e7565e4cad..dc46549140 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -145,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 e07bd869d3..f70d64f2d1 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -991,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 d890aad92b..0c592369a5 100644 --- a/tests/Core/File/GetMethodPropertiesTest.inc +++ b/tests/Core/File/GetMethodPropertiesTest.inc @@ -133,3 +133,20 @@ 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 9872cf3987..2f552bfecd 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -774,6 +774,122 @@ public function testPHP81NullableNeverType() }//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/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 From 96b0866e57b00a91efb608d1938d9505c06b3fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Fri, 22 Apr 2022 11:13:49 +0200 Subject: [PATCH 585/733] PHP 8.1: Added missing tests of intersection types for LowerCaseTypeSniff --- src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc | 4 ++++ .../Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index efcfa7e867..ac2a1f9e95 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -85,3 +85,7 @@ class ConstructorPropertyPromotionAndNormalParams { function (): NeVeR { exit; }; + +function intersectionParamTypes (\Package\ClassName&\Package\Other_Class $var) {} + +function intersectionReturnTypes ($var): \Package\ClassName&\Package\Other_Class {} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed index 7d0a8d05ae..e3cc993b33 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -85,3 +85,7 @@ class ConstructorPropertyPromotionAndNormalParams { function (): never { exit; }; + +function intersectionParamTypes (\Package\ClassName&\Package\Other_Class $var) {} + +function intersectionReturnTypes ($var): \Package\ClassName&\Package\Other_Class {} From 99b6dd8c6a8a2a6da49bdb78400a4ea13726dde2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 2 Jun 2022 15:39:03 +0200 Subject: [PATCH 586/733] Tokenizer/PHP: bug fix for double quoted strings using `${` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While creating a sniff for PHPCompatibility to detect the PHP 8.2 deprecation of two of the four syntaxes to embed variables/expressions within text strings, I realized that for select examples of the "type 4" syntax - "Variable variables (“${expr}”, equivalent to (string) ${expr})" -, PHPCS did not, and probably never did, tokenize those correctly. Double quoted strings in PHPCS are normally tokenized as one token (per line), including any and all embedded variables and expressions to prevent problems in the scope map caused by braces within the string/expression. However, for some double quoted strings using `${` syntax, this was not the case and these were tokenized as outlined below, which would still lead to the problems in the scope map. Luckily, these syntax variants aren't used that frequently. Though I suspect if they were, this bug would have been discovered a lot sooner. Either way, fixed now. Including adding a dedicated test file based on examples related to the PHP 8.2 RFC. Note: this test file tests that these embedding syntaxes are tokenized correctly, it doesn't test the other parts of the related `Tokenizers\PHP` code block (re-tokenizing to one token per line, handling of binary casts). Example of the code for which the tokenizer was buggy: ```php "${foo["${bar}"]}"; ``` Was originally tokenized like so: ``` Ptr | Ln | Col | Cond | ( #) | Token Type | [len]: Content ------------------------------------------------------------------------- 2 | L3 | C 1 | CC 0 | ( 0) | T_ECHO | [ 4]: echo 3 | L3 | C 5 | CC 0 | ( 0) | T_WHITESPACE | [ 1]: ⸱ 4 | L3 | C 6 | CC 0 | ( 0) | T_DOUBLE_QUOTED_STRING | [ 8]: "${foo[" 5 | L3 | C 14 | CC 0 | ( 0) | T_DOLLAR_OPEN_CURLY_BRACES | [ 2]: ${ 6 | L3 | C 16 | CC 0 | ( 0) | T_STRING_VARNAME | [ 3]: bar 7 | L3 | C 19 | CC 0 | ( 0) | T_CLOSE_CURLY_BRACKET | [ 1]: } 8 | L3 | C 20 | CC 0 | ( 0) | T_DOUBLE_QUOTED_STRING | [ 4]: "]}" 9 | L3 | C 24 | CC 0 | ( 0) | T_SEMICOLON | [ 1]: ; 10 | L3 | C 25 | CC 0 | ( 0) | T_WHITESPACE | [ 0]: ``` Will be tokenized (correctly) like so with the fix included in this PR: ``` Ptr | Ln | Col | Cond | ( #) | Token Type | [len]: Content ------------------------------------------------------------------------- 2 | L3 | C 1 | CC 0 | ( 0) | T_ECHO | [ 4]: echo 3 | L3 | C 5 | CC 0 | ( 0) | T_WHITESPACE | [ 1]: ⸱ 4 | L3 | C 6 | CC 0 | ( 0) | T_DOUBLE_QUOTED_STRING | [ 18]: "${foo["${bar}"]}" 5 | L3 | C 24 | CC 0 | ( 0) | T_SEMICOLON | [ 1]: ; 6 | L3 | C 25 | CC 0 | ( 0) | T_WHITESPACE | [ 0]: ``` Refs: * https://www.php.net/manual/en/language.types.string.php#language.types.string.parsing * https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation * https://gist.github.com/iluuu1994/72e2154fc4150f2258316b0255b698f2 --- package.xml | 6 + src/Tokenizers/PHP.php | 3 +- .../Core/Tokenizer/DoubleQuotedStringTest.inc | 49 +++++++ .../Core/Tokenizer/DoubleQuotedStringTest.php | 131 ++++++++++++++++++ 4 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 tests/Core/Tokenizer/DoubleQuotedStringTest.inc create mode 100644 tests/Core/Tokenizer/DoubleQuotedStringTest.php diff --git a/package.xml b/package.xml index 0370ecb045..983391b5a9 100644 --- a/package.xml +++ b/package.xml @@ -182,6 +182,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2153,6 +2155,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2255,6 +2259,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 4b0bf76550..5f2dc2c8c5 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -789,7 +789,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; diff --git a/tests/Core/Tokenizer/DoubleQuotedStringTest.inc b/tests/Core/Tokenizer/DoubleQuotedStringTest.inc new file mode 100644 index 0000000000..52253619ed --- /dev/null +++ b/tests/Core/Tokenizer/DoubleQuotedStringTest.inc @@ -0,0 +1,49 @@ +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'}"}}"; diff --git a/tests/Core/Tokenizer/DoubleQuotedStringTest.php b/tests/Core/Tokenizer/DoubleQuotedStringTest.php new file mode 100644 index 0000000000..f455fe7172 --- /dev/null +++ b/tests/Core/Tokenizer/DoubleQuotedStringTest.php @@ -0,0 +1,131 @@ + + * @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\'}"}}"', + ], + ]; + + }//end dataDoubleQuotedString() + + +}//end class From 150c8c408f1e741b823bd9988a1c7bf8e350dec4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 2 Jun 2022 21:04:22 +0200 Subject: [PATCH 587/733] Tokenizer/PHP/DoubleQuotedStringTest: document how parse errors are handled --- tests/Core/Tokenizer/DoubleQuotedStringTest.inc | 3 +++ tests/Core/Tokenizer/DoubleQuotedStringTest.php | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/tests/Core/Tokenizer/DoubleQuotedStringTest.inc b/tests/Core/Tokenizer/DoubleQuotedStringTest.inc index 52253619ed..62535b1e41 100644 --- a/tests/Core/Tokenizer/DoubleQuotedStringTest.inc +++ b/tests/Core/Tokenizer/DoubleQuotedStringTest.inc @@ -47,3 +47,6 @@ "${foo->{${'a'}}}"; /* testNested5 */ "${foo->{"${'a'}"}}"; + +/* testParseError */ +"${foo["${bar diff --git a/tests/Core/Tokenizer/DoubleQuotedStringTest.php b/tests/Core/Tokenizer/DoubleQuotedStringTest.php index f455fe7172..cc9fe49ec4 100644 --- a/tests/Core/Tokenizer/DoubleQuotedStringTest.php +++ b/tests/Core/Tokenizer/DoubleQuotedStringTest.php @@ -123,6 +123,11 @@ public function dataDoubleQuotedString() 'testMarker' => '/* testNested5 */', 'expectedContent' => '"${foo->{"${\'a\'}"}}"', ], + [ + 'testMarker' => '/* testParseError */', + 'expectedContent' => '"${foo["${bar +', + ], ]; }//end dataDoubleQuotedString() From 854aba7e8656830a555c73ec2ea971d11092cf7f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 12 Jun 2022 20:28:41 +0200 Subject: [PATCH 588/733] Tokenizer/PHP: bug fix in improved context sensitive keyword support [1] As reported in 3607, the `eval` keyword was not included in the list of context sensitive keyword. This is a regression compared to PHPCS 3.6.2. Fixed now, including unit tests. Fixes 3607 --- src/Util/Tokens.php | 1 + tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc | 5 +++++ tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php | 7 +++++++ 3 files changed, 13 insertions(+) diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 53e5ef4b98..2368a90d3c 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -694,6 +694,7 @@ final class Tokens 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, diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc index 0b073fcadf..584ed407d8 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc @@ -29,6 +29,7 @@ class ContextSensitiveKeywords const /* testEndSwitch */ ENDSWITCH = 'ENDSWITCH'; const /* testEndWhile */ ENDWHILE = 'ENDWHILE'; const /* testEnum */ ENUM = 'ENUM'; + const /* testEval */ EVAL = 'EVAL'; const /* testExit */ EXIT = 'EXIT'; const /* testExtends */ EXTENDS = 'EXTENDS'; const /* testFinal */ FINAL = 'FINAL'; @@ -165,6 +166,8 @@ echo $foo; print $foo; /* testDieIsKeyword */ die($foo); +/* testEvalIsKeyword */ +eval(' Date: Sun, 12 Jun 2022 20:48:30 +0200 Subject: [PATCH 589/733] Tokenizer/PHP: bug fix in improved context sensitive keyword support [2] While investigating 3607, I figured adding a test with a function declared to return by reference would also not be amiss and found that that situation was not accounted for. This commit fixes that. Includes unit test. --- src/Tokenizers/PHP.php | 63 ++++++++++++------- .../ContextSensitiveKeywordsTest.inc | 1 + .../ContextSensitiveKeywordsTest.php | 1 + 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 4b0bf76550..68d838ff39 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -607,38 +607,57 @@ protected function tokenize($string) if ($tokenIsArray === true && isset(Util\Tokens::$contextSensitiveKeywords[$token[0]]) === true - && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true + && (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true + || $finalTokens[$lastNotEmptyToken]['content'] === '&') ) { - $preserveKeyword = false; + 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`, 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; - } + // `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; + } - // `namespace\` should be preserved - if ($token[0] === T_NAMESPACE) { - for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { - if (is_array($tokens[$i]) === false) { break; } + } + }//end if + + if ($finalTokens[$lastNotEmptyToken]['content'] === '&') { + $preserveKeyword = true; - if (isset(Util\Tokens::$emptyTokens[$tokens[$i][0]]) === true) { + for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) { + if (isset(Util\Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) { continue; } - if ($tokens[$i][0] === T_NS_SEPARATOR) { - $preserveKeyword = true; + if ($finalTokens[$i]['code'] === T_FUNCTION) { + $preserveKeyword = false; } break; diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc index 584ed407d8..b78062aa55 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc @@ -217,3 +217,4 @@ 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 index f78ca88925..de60ecbffe 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php @@ -124,6 +124,7 @@ public function dataStrings() ['/* testNamespaceNameIsString3 */'], ['/* testKeywordAfterFunctionShouldBeString */'], + ['/* testKeywordAfterFunctionByRefShouldBeString */'], ]; }//end dataStrings() From e9f6c43d7f81417f680588805be1824bde288f8d Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 13 Jun 2022 15:55:26 +1000 Subject: [PATCH 590/733] Changelog for #3575 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 0370ecb045..bfd031bea4 100644 --- a/package.xml +++ b/package.xml @@ -77,6 +77,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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
From d8313c6835a3a2ee9138994497f45e84c82a89c8 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 13 Jun 2022 16:16:15 +1000 Subject: [PATCH 591/733] Changelog for #3604 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index c9a59c9e7e..aaa0a7e18b 100644 --- a/package.xml +++ b/package.xml @@ -79,6 +79,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 From a2cd51b45bcaef9c1f2a4bda48f2dd2fa2b95563 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 13 Jun 2022 16:31:38 +1000 Subject: [PATCH 592/733] Prepare for 3.7.0 release --- package.xml | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index aaa0a7e18b..4d639c2b79 100644 --- a/package.xml +++ b/package.xml @@ -14,8 +14,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> gsherwood@squiz.net yes - 2021-12-13 - + 2022-06-13 + 3.7.0 3.7.0 @@ -2296,6 +2296,75 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 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.BlockCommentS + -- Squiz.Commenting.DocCommentAlignment + -- Squiz.Commenting.VariableComment + -- Squiz.WhiteSpace.ScopeKeywordSpacing + -- Thanks to Juliette Reinders Folmer for the patches + - The parallel feature is now more efficent and runs faster in some situations due to improved process managment + -- 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 From 5f7890041c016f2012039becdfc4ad70828700a6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 13 Jun 2022 11:41:09 +0200 Subject: [PATCH 593/733] Tokenizer/PHP: bug fix in improved context sensitive keyword support As reported in 3609, the `empty` keyword, as well as the `isset` and `unset` keywords, was not included in the list of context sensitive keyword. This is a regression compared to PHPCS 3.6.2. Fixed now, including unit tests. Note: I've now done a full comparison with the [reserved keyword list as per the PHP manual](https://www.php.net/manual/en/reserved.keywords.php). This should hopefully fix the remaining stranglers. Fixes 3609 --- src/Util/Tokens.php | 3 +++ .../Tokenizer/ContextSensitiveKeywordsTest.inc | 9 ++++++++- .../Tokenizer/ContextSensitiveKeywordsTest.php | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index 2368a90d3c..1208e4f059 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -687,6 +687,7 @@ final class Tokens 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, @@ -712,6 +713,7 @@ final class Tokens 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, @@ -732,6 +734,7 @@ final class Tokens 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, diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc index b78062aa55..82fe564382 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc @@ -22,6 +22,7 @@ class ContextSensitiveKeywords const /* testEcho */ ECHO = 'ECHO'; const /* testElse */ ELSE = 'ELSE'; const /* testElseIf */ ELSEIF = 'ELSEIF'; + const /* testEmpty */ EMPTY = 'EMPTY'; const /* testEndDeclare */ ENDDECLARE = 'ENDDECLARE'; const /* testEndFor */ ENDFOR = 'ENDFOR'; const /* testEndForeach */ ENDFOREACH = 'ENDFOREACH'; @@ -47,6 +48,7 @@ class ContextSensitiveKeywords const /* testInstanceOf */ INSTANCEOF = 'INSTANCEOF'; const /* testInsteadOf */ INSTEADOF = 'INSTEADOF'; const /* testInterface */ INTERFACE = 'INTERFACE'; + const /* testIsset */ ISSET = 'ISSET'; const /* testList */ LIST = 'LIST'; const /* testMatch */ MATCH = 'MATCH'; const /* testNamespace */ NAMESPACE = 'NAMESPACE'; @@ -66,6 +68,7 @@ class ContextSensitiveKeywords const /* testThrows */ THROW = 'THROW'; const /* testTrait */ TRAIT = 'TRAIT'; const /* testTry */ TRY = 'TRY'; + const /* testUnset */ UNSET = 'UNSET'; const /* testUse */ USE = 'USE'; const /* testVar */ VAR = 'VAR'; const /* testWhile */ WHILE = 'WHILE'; @@ -121,7 +124,7 @@ $object = /* testNewIsKeyword */ new SomeClass(); $object /* testInstanceOfIsKeyword */ instanceof SomeClass; $copy = /* testCloneIsKeyword */ clone $object; -/* testIfIsKeyword */ if (true): +/* testIfIsKeyword */ if (/* testEmptyIsKeyword */ empty($a)): /* testElseIfIsKeyword */ elseif (false): /* testElseIsKeyword */ else: /* testEndIfIsKeyword */ endif; @@ -170,6 +173,10 @@ die($foo); eval(' Date: Mon, 13 Jun 2022 20:31:55 +1000 Subject: [PATCH 594/733] Changelog for #3609 (ref #3610) --- package.xml | 65 +++++--------------------------------------------- src/Config.php | 2 +- 2 files changed, 7 insertions(+), 60 deletions(-) diff --git a/package.xml b/package.xml index 4d639c2b79..26a7ea1859 100644 --- a/package.xml +++ b/package.xml @@ -17,8 +17,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2022-06-13 - 3.7.0 - 3.7.0 + 3.7.1 + 3.7.1 stable @@ -26,61 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause 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.BlockCommentS - -- Squiz.Commenting.DocCommentAlignment - -- Squiz.Commenting.VariableComment - -- Squiz.WhiteSpace.ScopeKeywordSpacing - -- Thanks to Juliette Reinders Folmer for the patches - - The parallel feature is now more efficent and runs faster in some situations due to improved process managment - -- 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 + - Fixed bug #3609: Method with name empty is always reported as error + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch @@ -2332,12 +2279,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Support for new PHP 8.1 readonly keyword has been added to the following sniffs: -- Generic.PHP.LowerCaseKeyword -- PSR2.Classes.PropertyDeclaration - -- Squiz.Commenting.BlockCommentS + -- 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 efficent and runs faster in some situations due to improved process managment + - 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 diff --git a/src/Config.php b/src/Config.php index 13eda3e61b..915ad00303 100644 --- a/src/Config.php +++ b/src/Config.php @@ -80,7 +80,7 @@ class Config * * @var string */ - const VERSION = '3.7.0'; + const VERSION = '3.7.1'; /** * Package stability; either stable, beta or alpha. From 0f02e3e492ae596c568473afbbcdf49c5dfed4f5 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 14 Jun 2022 09:09:53 +1000 Subject: [PATCH 595/733] Updated changelog message for #3609 --- package.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 26a7ea1859..c98fc5bd3f 100644 --- a/package.xml +++ b/package.xml @@ -26,7 +26,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - - Fixed bug #3609: Method with name empty is always reported as error + - Fixed bug #3609: Methods/constants with name empty/isset/unset are always reported as error -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From 1359e176e9307e906dc3d890bcc9603ff6d90619 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sat, 18 Jun 2022 17:21:10 +1000 Subject: [PATCH 596/733] Prepare for 3.7.1 release --- package.xml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index c98fc5bd3f..cfb4853359 100644 --- a/package.xml +++ b/package.xml @@ -14,8 +14,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> gsherwood@squiz.net yes - 2022-06-13 - + 2022-06-18 + 3.7.1 3.7.1 @@ -2243,6 +2243,22 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + 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 From 760990b61489dad1c433399318cca6f005c7b0c1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 23 Jun 2022 11:50:20 +0200 Subject: [PATCH 597/733] Tokens::$functionNameTokens: include the `parent` keyword Follow up to PR 3546, which changed how the `parent` keyword in a `new parent` snippet was tokenized from `T_STRING` to `T_PARENT`. The `T_PARENT` keyword token, however, was not included in the `Tokens::$functionNameTokens` array, which was the underlying cause for the bug reported in 3618. Fixed now. Tested by adding additional tests to the `Generic.WhiteSpace.ArbitraryParenthesesSpacing` sniff. These tests passed in PHPCS 3.6.2 and started failing in PHPCS 3.7.0. Once this fix has been merged, the tests will pass again. --- .../ArbitraryParenthesesSpacingUnitTest.inc | 12 ++++++++++++ .../ArbitraryParenthesesSpacingUnitTest.inc.fixed | 12 ++++++++++++ src/Util/Tokens.php | 1 + 3 files changed, 25 insertions(+) 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/Util/Tokens.php b/src/Util/Tokens.php index 1208e4f059..bb1fb2ca99 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -628,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, ]; From bb293b1707063d267b67316119a31008d294c493 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 27 Jun 2022 21:45:42 +0200 Subject: [PATCH 598/733] Squiz/DisallowComparisonAssignment: bug fix - ignore match structures Fixes 3616 Note: I've changed the `findNext()` call from an exhaustive call to the end of the statement to a faster check for the first non-empty token, which I believe was the actual _intention_ of the function call. As no tests are failing, I'm fairly certain my suspicion is correct ;-) --- .../PHP/DisallowComparisonAssignmentSniff.php | 13 +++++++------ .../PHP/DisallowComparisonAssignmentUnitTest.inc | 10 ++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php index 7c7e2246f5..48a9f7f237 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/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', + }; +} From 3caeec939ba710f7d62414bfb519cf39f1842871 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 28 Jun 2022 08:22:50 +1000 Subject: [PATCH 599/733] Removed for consistency --- .../Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php index 48a9f7f237..9eb2124233 100644 --- a/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php @@ -61,8 +61,8 @@ public function process(File $phpcsFile, $stackPtr) ); if ($nextNonEmpty !== false - && ($tokens[$nextNonEmpty]['code'] === \T_ARRAY - || $tokens[$nextNonEmpty]['code'] === \T_MATCH) + && ($tokens[$nextNonEmpty]['code'] === T_ARRAY + || $tokens[$nextNonEmpty]['code'] === T_MATCH) ) { return; } From 8d65f45a9f2778bc0a71a4ae3bb8c341dc1e1e3c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 28 Jun 2022 08:24:22 +1000 Subject: [PATCH 600/733] CHangelog for #3616 (ref #3624) --- package.xml | 6 +++--- src/Config.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.xml b/package.xml index cfb4853359..6a3e88afae 100644 --- a/package.xml +++ b/package.xml @@ -17,8 +17,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2022-06-18 - 3.7.1 - 3.7.1 + 3.7.2 + 3.7.2 stable @@ -26,7 +26,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - - Fixed bug #3609: Methods/constants with name empty/isset/unset are always reported as error + - Fixed bug #3616: Squiz.PHP.DisallowComparisonAssignment false positive for PHP 8 match expression -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch diff --git a/src/Config.php b/src/Config.php index 915ad00303..bfca9e1d18 100644 --- a/src/Config.php +++ b/src/Config.php @@ -80,7 +80,7 @@ class Config * * @var string */ - const VERSION = '3.7.1'; + const VERSION = '3.7.2'; /** * Package stability; either stable, beta or alpha. From f3a83428055642b09d14ec216ceae7107817c117 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 28 Jun 2022 08:52:37 +1000 Subject: [PATCH 601/733] Changelog for #3618 (ref #3619) --- package.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 6a3e88afae..2c593d512a 100644 --- a/package.xml +++ b/package.xml @@ -26,7 +26,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License - - Fixed bug #3616: Squiz.PHP.DisallowComparisonAssignment false positive for PHP 8 match expression + - 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 @@ -2255,7 +2257,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2022-06-18 BSD License - - Fixed bug #3609: Methods/constants with name empty/isset/unset are always reported as error + - Fixed bug #3609 : Methods/constants with name empty/isset/unset are always reported as error -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From 50fa77e28c339d140b3c301d6e36242dfeeaba53 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 10 Jul 2022 03:04:53 +0200 Subject: [PATCH 602/733] Runner: show actionable information when PHPCS runs out of memory As discussed in 3621, this PR adds a new function to show a "friendly" error message if/when PHPCS runs out of memory. This functionality can't be tested via the unit/integration test suite as the PHP shutdown can't be mocked. To test the functionality, I used a test run of PHPCS over its own code base with the following command: ```bash phpcs -d memory_limit 32M ``` Fixes 3621 Co-authored-by: Alain Schlesser --- src/Runner.php | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/Runner.php b/src/Runner.php index fa68ff51bf..2f44988c8c 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -53,6 +53,8 @@ class Runner */ public function runPHPCS() { + $this->register_out_of_memory_shutdown_message('phpcs'); + try { Util\Timing::startTiming(); Runner::checkRequirements(); @@ -153,6 +155,8 @@ public function runPHPCS() */ public function runPHPCBF() { + $this->register_out_of_memory_shutdown_message('phpcbf'); + if (defined('PHP_CODESNIFFER_CBF') === false) { define('PHP_CODESNIFFER_CBF', true); } @@ -886,4 +890,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 register_out_of_memory_shutdown_message($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 register_out_of_memory_shutdown_message() + + }//end class From 382ad27fbd8c8e7c1b5704d4feeb34b9c98660ce Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 14 Jul 2022 15:42:34 +0200 Subject: [PATCH 603/733] Tokenizer/PHP: bug fix for short list tokens in control structures without braces Originally reported in sirbrillig/phpcs-variable-analysis 263. When the short list syntax would be used as the first contents within a control structure without braces, the square brackets would be tokenized as plain square brackets, not as short array brackets. ```php if ( true ) [ $a ] = [ 'hi' ]; // The first `[` in this line was tokenized incorrectly. return $a ?? ''; ``` Fixed now by checking whether the preceding parenthesis closer has an owner and if that owner could be a scope owner. Includes unit tests. Includes updating the data providers to use named data sets to allow for easier debugging. --- src/Tokenizers/PHP.php | 9 +++- tests/Core/Tokenizer/ShortArrayTest.inc | 14 ++++++ tests/Core/Tokenizer/ShortArrayTest.php | 63 +++++++++++++------------ 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 63fbcc48a8..1dde9d1e82 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2688,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; 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() From 9cc8d43616c22806eb3fda46b712a78b8232e11a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 29 Jul 2022 17:11:22 +0200 Subject: [PATCH 604/733] PHP 7.3 | Generic/DisallowTabIndent: add support for flexible heredocs/nowdocs Since PHP 7.3, heredoc/nowdoc closers may be indented. This indent can use either tabs or spaces and the indent is included in the `T_END_HEREDOC`/`T_END_NOWDOC` token contents as received from the PHP native tokenizer. However, these tokens where not included in the tokens to look at for the `Generic.WhiteSpace.DisallowTabIndent` sniff, which could lead to false negatives. Fixed now, includes tests. --- package.xml | 2 ++ .../WhiteSpace/DisallowTabIndentSniff.php | 2 ++ .../DisallowTabIndentUnitTest.3.inc | 13 ++++++++++++ .../DisallowTabIndentUnitTest.3.inc.fixed | 13 ++++++++++++ .../WhiteSpace/DisallowTabIndentUnitTest.php | 20 ++++++++++++++----- 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.3.inc create mode 100644 src/Standards/Generic/Tests/WhiteSpace/DisallowTabIndentUnitTest.3.inc.fixed diff --git a/package.xml b/package.xml index 2c593d512a..1e527ca69c 100644 --- a/package.xml +++ b/package.xml @@ -785,6 +785,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + 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/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() From bdf5c493d9a7be338b7e5d3077504dd192a38792 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 29 Jul 2022 16:58:57 +0200 Subject: [PATCH 605/733] Tokenizer: apply tab replacement to heredoc/nowdoc closers Since PHP 7.3, heredoc/nowdoc closers may be indented. This indent can use either tabs or spaces and the indent is included in the `T_END_HEREDOC`/`T_END_NOWDOC` token contents as received from the PHP native tokenizer. However, the PHPCS `Tokenizer` did no execute tab replacement on these token leading to unexpected `'content'` and incorrect `'length'` values in the `File::$tokens` array, which in turn could lead to incorrect sniff results and incorrect fixes. This commit adds the `T_END_HEREDOC`/`T_END_NOWDOC` tokens to the array of tokens for which to do tab replacement to make them more consistent with the rest of PHPCS. I also considered splitting the token into a `T_WHITESPACE` token and the `T_END_HEREDOC`/`T_END_NOWDOC` token, but that could potentially break sniffs which expect the `T_END_HEREDOC`/`T_END_NOWDOC` token directly after the last `T_HEREDOC`/`T_NOWDOC` token. The current fix does not contain that risk. Includes unit tests safeguarding this change. The tests will only run on PHP 7.3+ as flexible heredoc/nowdocs don't tokenize correctly in PHP < 7.3. --- package.xml | 6 + src/Tokenizers/Tokenizer.php | 2 + .../Tokenizer/HeredocNowdocCloserTest.inc | 43 +++++ .../Tokenizer/HeredocNowdocCloserTest.php | 150 ++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 tests/Core/Tokenizer/HeredocNowdocCloserTest.inc create mode 100644 tests/Core/Tokenizer/HeredocNowdocCloserTest.php diff --git a/package.xml b/package.xml index 2c593d512a..44a50d158f 100644 --- a/package.xml +++ b/package.xml @@ -143,6 +143,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2116,6 +2118,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2220,6 +2224,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + 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/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 From 9f7cbd9d1bfe8d73edd69a0150be5ba46816338f Mon Sep 17 00:00:00 2001 From: enl Date: Tue, 9 Aug 2022 17:50:54 +0300 Subject: [PATCH 606/733] Handle child process failing When a child process fails, e.g. with a FatalError, it might still return empty list of errors. Although, child process failure is definitely not a thing we want to ignore. Therefore, we check exit status of each child process and make sure parent process fails (and returns non-zero status code) as well. --- src/Runner.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Runner.php b/src/Runner.php index fa68ff51bf..7de0d0e393 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -713,14 +713,22 @@ private function processChildProcs($childProcs) $numProcessed = 0; $totalBatches = count($childProcs); - $success = true; + $success = true; + $childProcessFailed = false; while (count($childProcs) > 0) { $pid = pcntl_waitpid(0, $status); + if ($pid <= 0) { continue; } + $childProcessStatus = pcntl_wexitstatus($status); + + if ($childProcessStatus !== 0) { + $childProcessFailed = true; + } + $out = $childProcs[$pid]; unset($childProcs[$pid]); if (file_exists($out) === false) { @@ -768,7 +776,7 @@ private function processChildProcs($childProcs) $this->printProgress($file, $totalBatches, $numProcessed); }//end while - return $success; + return $success && !$childProcessFailed; }//end processChildProcs() From 378785b41d0765f38ca894281c83d7ddc104d1fc Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sat, 13 Aug 2022 17:13:58 +1000 Subject: [PATCH 607/733] Simplified logic for #3645 --- src/Runner.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Runner.php b/src/Runner.php index 7de0d0e393..cd6312421e 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -713,20 +713,17 @@ private function processChildProcs($childProcs) $numProcessed = 0; $totalBatches = count($childProcs); - $success = true; - $childProcessFailed = false; + $success = true; while (count($childProcs) > 0) { $pid = pcntl_waitpid(0, $status); - if ($pid <= 0) { continue; } $childProcessStatus = pcntl_wexitstatus($status); - if ($childProcessStatus !== 0) { - $childProcessFailed = true; + $success = false; } $out = $childProcs[$pid]; @@ -776,7 +773,7 @@ private function processChildProcs($childProcs) $this->printProgress($file, $totalBatches, $numProcessed); }//end while - return $success && !$childProcessFailed; + return $success; }//end processChildProcs() From 53e0f59fff0657e03c2dfab631db30f18abf41f3 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sat, 13 Aug 2022 17:15:31 +1000 Subject: [PATCH 608/733] Changelog for #3645 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 2c593d512a..2170929227 100644 --- a/package.xml +++ b/package.xml @@ -30,6 +30,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 #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 From c0a8bb759a51cca1f6b3f13bb1c338b235af8fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Wed, 24 Aug 2022 21:16:34 +0200 Subject: [PATCH 609/733] Fixed false positives for match() in OperatorSpacingSniff --- .../Sniffs/WhiteSpace/OperatorSpacingSniff.php | 13 +++++++------ .../Tests/WhiteSpace/OperatorSpacingUnitTest.inc | 6 ++++++ .../WhiteSpace/OperatorSpacingUnitTest.inc.fixed | 6 ++++++ 3 files changed, 19 insertions(+), 6 deletions(-) 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/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 + From 8848a641ff9a8f991134c5a130d159cb0ef7eb9e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Sep 2022 15:33:55 +0200 Subject: [PATCH 610/733] Generic/LowerCaseType: examine types in arrow function declarations PHP 7.4 arrow functions were not yet being taken into account for this sniff. Fixed now. Includes unit tests. --- src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php | 1 + src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc | 3 +++ .../Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed | 3 +++ src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php | 1 + 4 files changed, 8 insertions(+) diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index 96331f11db..4d463f28fb 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -52,6 +52,7 @@ public function register() $tokens = Tokens::$castTokens; $tokens[] = T_FUNCTION; $tokens[] = T_CLOSURE; + $tokens[] = T_FN; $tokens[] = T_VARIABLE; return $tokens; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index ac2a1f9e95..011adcd530 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -89,3 +89,6 @@ function (): NeVeR { 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 e3cc993b33..d866101b6f 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -89,3 +89,6 @@ function (): never { 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 d95872a7dd..fa05aba6a3 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php @@ -66,6 +66,7 @@ public function getErrorList() 78 => 3, 82 => 2, 85 => 1, + 94 => 5, ]; }//end getErrorList() From 701cc81603c5ddded9415958ae70d1da21733225 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sat, 10 Sep 2022 16:44:29 +1000 Subject: [PATCH 611/733] Changelog for #3660 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 2170929227..97f1c5c9b9 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - 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() From 1e3efe8fb77a44f0e583802cbda2e137384b502f Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Sun, 4 Sep 2022 16:07:26 +0100 Subject: [PATCH 612/733] Add "static analysis" Composer keyword As per https://getcomposer.org/doc/04-schema.md#keywords by including "static analysis" as a keyword in the `composer.json` file, Composer 2.4.0-RC1 and later will prompt users if the package is installed with `composer require` instead of `composer require --dev`. See https://github.com/composer/composer/pull/10960 for more info. --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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", From 195176e5b08f5164dbac99af7897e739914ba42a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sat, 10 Sep 2022 17:18:23 +1000 Subject: [PATCH 613/733] Changelog for #3655 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 97f1c5c9b9..40279e023d 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - Newer versions of Composer will now suggest installing PHPCS using require-dev instead of require + -- Thanks to Gary Jones (@GaryJones) for the patch - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3616 : Squiz.PHP.DisallowComparisonAssignment false positive for PHP 8 match expression From f3e3bcc3e7f8a14dec4412c6f5d8e43bd5001d2f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sat, 10 Sep 2022 17:26:02 +1000 Subject: [PATCH 614/733] Squiz.Formatting.OperatorBracket no longer reports false positives in match() structures --- package.xml | 1 + .../Squiz/Sniffs/Formatting/OperatorBracketSniff.php | 1 + .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc | 6 ++++++ .../Tests/Formatting/OperatorBracketUnitTest.inc.fixed | 6 ++++++ 4 files changed, 14 insertions(+) diff --git a/package.xml b/package.xml index 40279e023d..22973e4f82 100644 --- a/package.xml +++ b/package.xml @@ -30,6 +30,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Gary Jones (@GaryJones) 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() diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index 60b113d3a9..e2e34eb530 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) { diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc index 2a480bb926..bd19dd9e1a 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc @@ -193,3 +193,9 @@ $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, +}; diff --git a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed index 669b16b2bc..a4ea626f08 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed @@ -193,3 +193,9 @@ $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, +}; From b860f758b87bf43b7e5a7e0ebe1804f2f9771f1b Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sat, 10 Sep 2022 17:27:09 +1000 Subject: [PATCH 615/733] Changelog for #3653 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 22973e4f82..4e9276fb2a 100644 --- a/package.xml +++ b/package.xml @@ -37,6 +37,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 From a5adf67bb17110a03f604145d096d45c86f011c3 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 16 Sep 2022 08:02:22 +1000 Subject: [PATCH 616/733] Renamed method for consistency (ref #3630) --- src/Runner.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Runner.php b/src/Runner.php index 6a6b3a3fb1..e0cbca7160 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -53,7 +53,7 @@ class Runner */ public function runPHPCS() { - $this->register_out_of_memory_shutdown_message('phpcs'); + $this->registerOutOfMemoryShutdownMessage('phpcs'); try { Util\Timing::startTiming(); @@ -155,7 +155,7 @@ public function runPHPCS() */ public function runPHPCBF() { - $this->register_out_of_memory_shutdown_message('phpcbf'); + $this->registerOutOfMemoryShutdownMessage('phpcbf'); if (defined('PHP_CODESNIFFER_CBF') === false) { define('PHP_CODESNIFFER_CBF', true); @@ -902,7 +902,7 @@ public function printProgress(File $file, $numFiles, $numProcessed) * * @return void */ - private function register_out_of_memory_shutdown_message($command) + 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; @@ -930,7 +930,7 @@ static function () use ( } ); - }//end register_out_of_memory_shutdown_message() + }//end registerOutOfMemoryShutdownMessage() }//end class From 70aee78e813b0cb7904a6a222ab1f9022be2a4e4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 16 Sep 2022 08:12:05 +1000 Subject: [PATCH 617/733] Changelog for #3630 --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index 4e9276fb2a..a81c8dbd5f 100644 --- a/package.xml +++ b/package.xml @@ -28,6 +28,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 From 19855395ad42a9692542be3f7cd68f000edcce5d Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 16 Sep 2022 08:19:54 +1000 Subject: [PATCH 618/733] Changelog for #3632 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index a81c8dbd5f..6ff88d1860 100644 --- a/package.xml +++ b/package.xml @@ -38,6 +38,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 #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 From 9445108a57b46f4e84a890788de5d2388346460a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 19 Sep 2022 14:46:18 +1000 Subject: [PATCH 619/733] Fixed bug #3666 : PEAR.Functions.FunctionCallSignature incorrect indent fix when checking mixed HTML/PHP files --- package.xml | 1 + .../Functions/FunctionCallSignatureSniff.php | 5 ++++- .../FunctionCallSignatureUnitTest.inc | 19 +++++++++++++++++++ .../FunctionCallSignatureUnitTest.inc.fixed | 19 +++++++++++++++++++ .../FunctionCallSignatureUnitTest.php | 3 +++ 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/package.xml b/package.xml index 6ff88d1860..28f507e395 100644 --- a/package.xml +++ b/package.xml @@ -44,6 +44,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 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/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() From 8d52a6045842417b9a7a7dec3feba14f6c3b2a17 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 20 Sep 2022 13:11:11 +0200 Subject: [PATCH 620/733] PSR12/ClassInstantiation: fix regression for `new parent` Related to 3546 which fixed an inconsistency after 3484. The change of the tokenization from `T_STRING` to `T_PARENT` for the `parent` keyword in `new parent` caused a regression in the `PSR12.Classes.ClassInstantiation` sniff. Fixed now. Includes unit tests. --- src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php | 1 + .../PSR12/Tests/Classes/ClassInstantiationUnitTest.inc | 3 +++ .../PSR12/Tests/Classes/ClassInstantiationUnitTest.inc.fixed | 3 +++ .../PSR12/Tests/Classes/ClassInstantiationUnitTest.php | 1 + 4 files changed, 8 insertions(+) diff --git a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php index e4ebd576a3..2298da39f4 100644 --- a/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php +++ b/src/Standards/PSR12/Sniffs/Classes/ClassInstantiationSniff.php @@ -48,6 +48,7 @@ public function process(File $phpcsFile, $stackPtr) T_NS_SEPARATOR => 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/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() From 13921de764ad2734550ed0ed6949c08610b4bc8d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 20 Sep 2022 13:30:34 +0200 Subject: [PATCH 621/733] Generic/FunctionCallArgumentSpacing: fix regression for `new parent` Related to 3546 which fixed an inconsistency after 3484. The change of the tokenization from `T_STRING` to `T_PARENT` for the `parent` keyword in `new parent` caused a regression in the `Generic.Functions.FunctionCallArgumentSpacing` sniff. Fixed now. Includes unit tests. --- .../Functions/FunctionCallArgumentSpacingSniff.php | 1 + .../Functions/FunctionCallArgumentSpacingUnitTest.inc | 9 +++++++++ .../FunctionCallArgumentSpacingUnitTest.inc.fixed | 9 +++++++++ .../Functions/FunctionCallArgumentSpacingUnitTest.php | 1 + 4 files changed, 20 insertions(+) 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/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() From 37ead6e6f0e1e977fdafeb60e9931accbc9a126b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 20 Sep 2022 13:35:58 +0200 Subject: [PATCH 622/733] Squiz/OperatorBracket: fix regression for `new parent` Related to 3546 which fixed an inconsistency after 3484. The change of the tokenization from `T_STRING` to `T_PARENT` for the `parent` keyword caused a regression in the `Squiz.Formatting.OperatorBracket` sniff. Fixed now. Includes unit tests. --- src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php | 1 + .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc | 2 ++ .../Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php index e2e34eb530..8becb74a4e 100644 --- a/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php +++ b/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php @@ -142,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/Tests/Formatting/OperatorBracketUnitTest.inc b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc index bd19dd9e1a..8e62896387 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc @@ -199,3 +199,5 @@ match ($a) { '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 a4ea626f08..9fa0216cb6 100644 --- a/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Formatting/OperatorBracketUnitTest.inc.fixed @@ -199,3 +199,5 @@ match ($a) { 'b', 'c', 'd' => -2, default => -3, }; + +$cntPages = ceil(count($items) / parent::ON_PAGE); From c08491dc0d9e1be85b12d3c0208ee9d161992cb4 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 23 Sep 2022 08:31:56 +1000 Subject: [PATCH 623/733] Changelog for #3668 (ref #3669, #3670, #3671) --- package.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.xml b/package.xml index 28f507e395..8f816a2ce4 100644 --- a/package.xml +++ b/package.xml @@ -45,6 +45,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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
From e189e3a450ddd4244a6a1df8ebc6a977a95fe863 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 11 Jun 2022 18:08:25 +0200 Subject: [PATCH 624/733] Docs: fix invalid XML docs for three sniffs Each of these three sniffs had a `` block containing _three_ `` elements instead of two. Discovered by validating the docs against the XSD schema created by the amazing dingo-d. The XSD schema is now available in [PHPCSDevTools](https://github.com/PHPCSStandards/PHPCSDevTools) 1.2.0 as discussed in 3585. If you're interested, I could refresh (a variation of) PR 2872 to add XML doc validation against the schema to the GH Actions checks. --- .../Generic/Docs/Classes/OpeningBraceSameLineStandard.xml | 8 ++++++++ .../Generic/Docs/Formatting/SpaceAfterNotStandard.xml | 7 ++----- .../WhiteSpace/ArbitraryParenthesesSpacingStandard.xml | 7 ++----- 3 files changed, 12 insertions(+), 10 deletions(-) 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); ]]> - + - - - Date: Mon, 26 Sep 2022 12:14:32 +1000 Subject: [PATCH 625/733] Fixed bug #3672 : Incorrect ScopeIndent.IncorrectExact report for match inside array literal --- package.xml | 1 + .../Sniffs/WhiteSpace/ScopeIndentSniff.php | 28 +++++++++++++------ .../WhiteSpace/ScopeIndentUnitTest.1.inc | 7 +++++ .../ScopeIndentUnitTest.1.inc.fixed | 7 +++++ .../WhiteSpace/ScopeIndentUnitTest.2.inc | 7 +++++ .../ScopeIndentUnitTest.2.inc.fixed | 7 +++++ .../Tests/WhiteSpace/ScopeIndentUnitTest.php | 8 +++--- 7 files changed, 52 insertions(+), 13 deletions(-) diff --git a/package.xml b/package.xml index 8f816a2ce4..35d544d28b 100644 --- a/package.xml +++ b/package.xml @@ -48,6 +48,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 diff --git a/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php b/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php index 92d2ed8b84..e91d628e5c 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']; @@ -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; } diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc index caeb503aa8..4061aff567 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc @@ -1572,6 +1572,13 @@ switch ($foo) { 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! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed index 6fd9e5fd3a..7b5efea36a 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed @@ -1572,6 +1572,13 @@ switch ($foo) { 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! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc index b8bf72ad5f..e7253141d4 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc @@ -1572,6 +1572,13 @@ switch ($foo) { 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! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed index 83324391de..57caa29175 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed @@ -1572,6 +1572,13 @@ switch ($foo) { 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! */ ?> diff --git a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php index 700fc5b81d..6b0a71028e 100644 --- a/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php +++ b/src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php @@ -187,10 +187,10 @@ public function getErrorList($testFile='ScopeIndentUnitTest.inc') 1527 => 1, 1529 => 1, 1530 => 1, - 1583 => 1, - 1584 => 1, - 1585 => 1, - 1586 => 1, + 1590 => 1, + 1591 => 1, + 1592 => 1, + 1593 => 1, ]; }//end getErrorList() From 3f194e7a2582cf36ad3a7307d23fa4d9de201fe7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 6 Oct 2022 23:41:34 +0200 Subject: [PATCH 626/733] PHP 8.2 | File::getClassProperties(): add support for readonly classes PHP 8.2 introduces `readonly` classes. The `readonly` keyword can be combined with the `abstract` or `final` keyword. See: https://3v4l.org/VIXgD Includes adding a full set of tests for the `File::getClassProperties()` method, which was so far untested. Ref: * https://wiki.php.net/rfc/readonly_classes --- package.xml | 6 + src/Files/File.php | 8 + tests/Core/File/GetClassPropertiesTest.inc | 58 +++++++ tests/Core/File/GetClassPropertiesTest.php | 188 +++++++++++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 tests/Core/File/GetClassPropertiesTest.inc create mode 100644 tests/Core/File/GetClassPropertiesTest.php diff --git a/package.xml b/package.xml index 35d544d28b..7f435c96be 100644 --- a/package.xml +++ b/package.xml @@ -100,6 +100,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2083,6 +2085,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2187,6 +2191,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Files/File.php b/src/Files/File.php index a15af762fc..cdc11a98c8 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1947,6 +1947,7 @@ public function getMemberProperties($stackPtr) * array( * 'is_abstract' => false, // true if the abstract keyword was found. * 'is_final' => false, // true if the final keyword was found. + * 'is_readonly' => false, // true if the readonly keyword was found. * ); * * @@ -1966,6 +1967,7 @@ public function getClassProperties($stackPtr) $valid = [ T_FINAL => T_FINAL, T_ABSTRACT => T_ABSTRACT, + T_READONLY => T_READONLY, T_WHITESPACE => T_WHITESPACE, T_COMMENT => T_COMMENT, T_DOC_COMMENT => T_DOC_COMMENT, @@ -1973,6 +1975,7 @@ public function getClassProperties($stackPtr) $isAbstract = false; $isFinal = false; + $isReadonly = false; for ($i = ($stackPtr - 1); $i > 0; $i--) { if (isset($valid[$this->tokens[$i]['code']]) === false) { @@ -1987,12 +1990,17 @@ public function getClassProperties($stackPtr) case T_FINAL: $isFinal = true; break; + + case T_READONLY: + $isReadonly = true; + break; } }//end for return [ 'is_abstract' => $isAbstract, 'is_final' => $isFinal, + 'is_readonly' => $isReadonly, ]; }//end getClassProperties() diff --git a/tests/Core/File/GetClassPropertiesTest.inc b/tests/Core/File/GetClassPropertiesTest.inc new file mode 100644 index 0000000000..2490a09657 --- /dev/null +++ b/tests/Core/File/GetClassPropertiesTest.inc @@ -0,0 +1,58 @@ + + * @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\File; + +use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest; + +class GetClassPropertiesTest extends AbstractMethodUnitTest +{ + + + /** + * Test receiving an expected exception when a non class token is passed. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $tokenType The type of token to look for after the marker. + * + * @dataProvider dataNotAClassException + * + * @expectedException PHP_CodeSniffer\Exceptions\RuntimeException + * @expectedExceptionMessage $stackPtr must be of type T_CLASS + * + * @return void + */ + public function testNotAClassException($testMarker, $tokenType) + { + $target = $this->getTargetToken($testMarker, $tokenType); + self::$phpcsFile->getClassProperties($target); + + }//end testNotAClassException() + + + /** + * Data provider. + * + * @see testNotAClassException() For the array format. + * + * @return array + */ + public function dataNotAClassException() + { + return [ + 'interface' => [ + '/* testNotAClass */', + \T_INTERFACE, + ], + 'anon-class' => [ + '/* testAnonClass */', + \T_ANON_CLASS, + ], + 'enum' => [ + '/* testEnum */', + \T_ENUM, + ], + ]; + + }//end dataNotAClassException() + + + /** + * Test retrieving the properties for a class declaration. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expected Expected function output. + * + * @dataProvider dataGetClassProperties + * + * @return void + */ + public function testGetClassProperties($testMarker, $expected) + { + $class = $this->getTargetToken($testMarker, \T_CLASS); + $result = self::$phpcsFile->getClassProperties($class); + $this->assertSame($expected, $result); + + }//end testGetClassProperties() + + + /** + * Data provider. + * + * @see testGetClassProperties() For the array format. + * + * @return array + */ + public function dataGetClassProperties() + { + return [ + 'no-properties' => [ + '/* testClassWithoutProperties */', + [ + 'is_abstract' => false, + 'is_final' => false, + 'is_readonly' => false, + ], + ], + 'abstract' => [ + '/* testAbstractClass */', + [ + 'is_abstract' => true, + 'is_final' => false, + 'is_readonly' => false, + ], + ], + 'final' => [ + '/* testFinalClass */', + [ + 'is_abstract' => false, + 'is_final' => true, + 'is_readonly' => false, + ], + ], + 'readonly' => [ + '/* testReadonlyClass */', + [ + 'is_abstract' => false, + 'is_final' => false, + 'is_readonly' => true, + ], + ], + 'final-readonly' => [ + '/* testFinalReadonlyClass */', + [ + 'is_abstract' => false, + 'is_final' => true, + 'is_readonly' => true, + ], + ], + 'readonly-final' => [ + '/* testReadonlyFinalClass */', + [ + 'is_abstract' => false, + 'is_final' => true, + 'is_readonly' => true, + ], + ], + 'abstract-readonly' => [ + '/* testAbstractReadonlyClass */', + [ + 'is_abstract' => true, + 'is_final' => false, + 'is_readonly' => true, + ], + ], + 'readonly-abstract' => [ + '/* testReadonlyAbstractClass */', + [ + 'is_abstract' => true, + 'is_final' => false, + 'is_readonly' => true, + ], + ], + 'comments-and-new-lines' => [ + '/* testWithCommentsAndNewLines */', + [ + 'is_abstract' => true, + 'is_final' => false, + 'is_readonly' => false, + ], + ], + 'no-properties-with-docblock' => [ + '/* testWithDocblockWithoutProperties */', + [ + 'is_abstract' => false, + 'is_final' => false, + 'is_readonly' => false, + ], + ], + 'abstract-final-parse-error' => [ + '/* testParseErrorAbstractFinal */', + [ + 'is_abstract' => true, + 'is_final' => true, + 'is_readonly' => false, + ], + ], + ]; + + }//end dataGetClassProperties() + + +}//end class From c4eefdf768fe050769ca4f67693037a936d7a8b3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 16 May 2021 18:08:09 +0200 Subject: [PATCH 627/733] ScopeSettingWithNamespaceOperatorTest: fix unintentional parse error --- tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 08a98ad3f75d72dc0f7f4a53f8e300c34938e448 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 4 Oct 2022 07:21:17 +0200 Subject: [PATCH 628/733] IsReferenceTest: fix two unintentional parse errors in the test case file --- tests/Core/File/IsReferenceTest.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ) {} From 0d79f8fc32d7888ebfedb43982e44f5bb52e9740 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 4 Oct 2022 23:52:32 +0200 Subject: [PATCH 629/733] GetMemberPropertiesTest: sync the order of the data sets with the test case file --- tests/Core/File/GetMemberPropertiesTest.php | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index 69dfde1e73..934d8e2890 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -210,6 +210,17 @@ public function dataGetMemberProperties() 'nullable_type' => false, ], ], + [ + '/* testNoPrefix */', + [ + 'scope' => 'public', + 'scope_specified' => false, + 'is_static' => false, + 'is_readonly' => false, + 'type' => '', + 'nullable_type' => false, + ], + ], [ '/* testPublicStaticWithDocblock */', [ @@ -287,17 +298,6 @@ public function dataGetMemberProperties() 'nullable_type' => true, ], ], - [ - '/* testNoPrefix */', - [ - 'scope' => 'public', - 'scope_specified' => false, - 'is_static' => false, - 'is_readonly' => false, - 'type' => '', - 'nullable_type' => false, - ], - ], [ '/* testGroupProtectedStatic 1 */', [ From f48210dea6f3c59c6675f27231c43407b8696399 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 5 Oct 2022 19:18:22 +0200 Subject: [PATCH 630/733] FindEndOfStatementTest: fix broken test The `return 0;` statement belongs with the `testStaticArrowFunction` test and was introduced to test a specific bug in 2749. This brings the test back to its correct state. --- tests/Core/File/FindEndOfStatementTest.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; From 294cc8eb7299623adf31b3dea16c0ae7193c96f8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 5 Oct 2022 23:43:19 +0200 Subject: [PATCH 631/733] GetMethodParametersTest: sync the order of the data sets with the test case file (while the diff may look weird, in reality, it just moves three tests up) --- tests/Core/File/GetMethodParametersTest.php | 112 ++++++++++---------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index f70d64f2d1..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() /** From a7cf635b9cd506efd47576818e45b67e0d6792fc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 10 Sep 2022 11:17:49 +0200 Subject: [PATCH 632/733] GetMethodPropertiesTest: remove stray docblock opener --- tests/Core/File/GetMethodPropertiesTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php index 2f552bfecd..66f4eea3ea 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -774,7 +774,6 @@ public function testPHP81NullableNeverType() }//end testPHP81NullableNeverType() - /** /** * Verify recognition of PHP8.1 intersection type declaration. * From 1495f9ef706b731c7a256058d067073593bfff8d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Aug 2019 04:48:29 +0200 Subject: [PATCH 633/733] Various minor code tweaks .. picked up along the way. * Remove unused `use` statements. * Use `use` statements. * No need for a combined assignment operator. --- .../Generic/Sniffs/PHP/UpperCaseConstantSniff.php | 2 -- .../Sniffs/ControlStructures/ForLoopDeclarationSniff.php | 2 +- tests/Core/Autoloader/DetermineLoadedClassTest.php | 9 +++++---- tests/Core/Tokenizer/AttributesTest.php | 1 - 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php b/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php index f5f72b5581..2740884bb4 100644 --- a/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php @@ -10,8 +10,6 @@ namespace PHP_CodeSniffer\Standards\Generic\Sniffs\PHP; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Tokens; class UpperCaseConstantSniff extends LowerCaseConstantSniff { 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/tests/Core/Autoloader/DetermineLoadedClassTest.php b/tests/Core/Autoloader/DetermineLoadedClassTest.php index 65542b670b..2188fd700a 100644 --- a/tests/Core/Autoloader/DetermineLoadedClassTest.php +++ b/tests/Core/Autoloader/DetermineLoadedClassTest.php @@ -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/Tokenizer/AttributesTest.php b/tests/Core/Tokenizer/AttributesTest.php index b10a1efb95..9d2746a9b8 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 { From e0f53ea9402e36d913b0c3b520afa2dfa44dd6d1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 14 Oct 2022 19:55:38 +0200 Subject: [PATCH 634/733] GH Actions: fix use of deprecated `set-output` GitHub has deprecated the use of `set-output` (and `set-state`) in favour of new environment files. This commit updates workflows to use the new methodology. Refs: * https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ * https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 04c7cba00a..f9366e074f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,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::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::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::error_reporting=-1, display_errors=On' + echo 'PHP_INI=error_reporting=-1, display_errors=On' >> $GITHUB_OUTPUT fi - name: Install PHP From dc61947e353014a6b9849ec0026d681b407ea2fc Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 16 Oct 2022 07:30:11 +0200 Subject: [PATCH 635/733] PHP 8.1 | Generic/SpreadOperatorSpacingAfter: ignore spread operator in first class callables ... as in that case the spread operator does not apply to a variable/function call and the likelihood of the sniff conflicting with other sniffs which examine the spacing on the inside of parenthesis is high. Includes unit tests. Ref: * https://wiki.php.net/rfc/first_class_callable_syntax * https://3v4l.org/YTTO1 --- .../Sniffs/WhiteSpace/SpreadOperatorSpacingAfterSniff.php | 5 +++++ .../Tests/WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc | 6 ++++++ .../WhiteSpace/SpreadOperatorSpacingAfterUnitTest.inc.fixed | 6 ++++++ 3 files changed, 17 insertions(+) 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/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( ... From 19179abc1548420393bb90b5ac7a2afab6638ee3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 23 Apr 2022 03:53:52 +0200 Subject: [PATCH 636/733] NamedFunctionCallArgumentsTest: add test with enum keyword --- tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc | 3 +++ tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php | 1 + 2 files changed, 4 insertions(+) diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc index 4497f5c380..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); diff --git a/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php b/tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php index 0a878c1d3b..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', From 75bb43d6ead1b517adbf34cf2e312247831ba2b3 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 21 Oct 2022 16:38:21 +1100 Subject: [PATCH 637/733] Changelog for #3640 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 1d00d45451..9c6bb95ce9 100644 --- a/package.xml +++ b/package.xml @@ -40,6 +40,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 #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 From cd5acaa651df870e8a3207926f236400361219e0 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Fri, 21 Oct 2022 16:57:32 +1100 Subject: [PATCH 638/733] Changelog for #3639 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index fd02405083..50b562a931 100644 --- a/package.xml +++ b/package.xml @@ -40,6 +40,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 From da8898b7c3e103c57ce1b52cff351a29f4576f42 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 26 Oct 2022 13:51:17 +0200 Subject: [PATCH 639/733] Tokenizer/PHP: add tests for consistent tokenization heredocs with interpolated strings Similar to previous PR #3604 which fixed the tokenization for complex double quoted strings with interpolated variables/expressions and added tests for it. --- package.xml | 6 + tests/Core/Tokenizer/HeredocStringTest.inc | 193 +++++++++++++++++++++ tests/Core/Tokenizer/HeredocStringTest.php | 153 ++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100644 tests/Core/Tokenizer/HeredocStringTest.inc create mode 100644 tests/Core/Tokenizer/HeredocStringTest.php diff --git a/package.xml b/package.xml index 50b562a931..75b78ea153 100644 --- a/package.xml +++ b/package.xml @@ -168,6 +168,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2145,6 +2147,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2251,6 +2255,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + 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 From 679621528cb60100954fb234c56080ca67074722 Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Mon, 21 Nov 2022 14:29:46 +0100 Subject: [PATCH 640/733] Fix `Squiz.Commenting.FunctionComment.InvalidNoReturn` false positive when return type is `never` --- .../Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 9 ++++++--- .../Squiz/Tests/Commenting/FunctionCommentUnitTest.inc | 5 +++++ .../Tests/Commenting/FunctionCommentUnitTest.inc.fixed | 5 +++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index 38170d22e4..3c79a7a8c4 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -143,9 +143,12 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) } } }//end if - } else if ($returnType !== 'mixed' && in_array('void', $typeNames, true) === false) { - // If return type is not void, there needs to be a return statement - // somewhere in the function that returns something. + } else if ($returnType !== 'mixed' + && $returnType !== 'never' + && in_array('void', $typeNames, true) === false + ) { + // If return type is not void, never, or mixed, there needs to be a + // return statement somewhere in the function that returns something. if (isset($tokens[$stackPtr]['scope_closer']) === true) { $endToken = $tokens[$stackPtr]['scope_closer']; for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) { diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc index 55e0fe9bb9..a44f5e0e2e 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc @@ -1129,3 +1129,8 @@ public function variableCaseTest( public function variableOrderMismatch($bar, $baz, $foo) { return; } + +/** + * @return never + */ +function foo() {} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 22474caf85..3d2e10fd6a 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -1129,3 +1129,8 @@ public function variableCaseTest( public function variableOrderMismatch($bar, $baz, $foo) { return; } + +/** + * @return never + */ +function foo() {} From 0488aecf955ed6acc89ef756f509bf4e62792614 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Sep 2022 15:30:48 +0200 Subject: [PATCH 641/733] PHP 8.2 | PSR1/SideEffects: allow for readonly classes Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_classes --- src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php | 4 +++- src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php index 3f8c5e0d56..7ad52fa708 100644 --- a/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php +++ b/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php @@ -169,7 +169,9 @@ private function searchForConflict($phpcsFile, $start, $end, $tokens) } // Ignore function/class prefixes. - if (isset(Tokens::$methodPrefixes[$tokens[$i]['code']]) === true) { + if (isset(Tokens::$methodPrefixes[$tokens[$i]['code']]) === true + || $tokens[$i]['code'] === T_READONLY + ) { continue; } diff --git a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc index ca538823c1..b7e1dc9a0a 100644 --- a/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc +++ b/src/Standards/PSR1/Tests/Files/SideEffectsUnitTest.1.inc @@ -76,4 +76,12 @@ namespace { defined('APP_BASE_URL') or define('APP_BASE_URL', '/'); +readonly class Foo { + public function __construct( + private string $foo, + private string $bar, + ) { + } +} + ?> From c732fec6a77ffa72cdd2426d9919264a3241453c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Mon, 19 Dec 2022 09:46:53 +1100 Subject: [PATCH 642/733] Changelog for #3694 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 50b562a931..1486e19b90 100644 --- a/package.xml +++ b/package.xml @@ -53,6 +53,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- 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 From 53aadb336690af9ccf0f67bed5bd21eb3f1ff1d7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 4 Jan 2023 23:56:37 +0100 Subject: [PATCH 643/733] FileComment: update year in test files The regular annual update to make sure the build still passes ;-) --- .../Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed | 2 +- .../Squiz/Tests/Commenting/FileCommentUnitTest.1.js.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.1.inc.fixed index e42e037c70..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 2022 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 90046c7102..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 2022 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 * From 206690ddf62d9fb65a6e0a9cdc7ea896c1553afe Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 17 Feb 2023 00:44:29 +0100 Subject: [PATCH 644/733] http to https for hyperlink --- src/Config.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Config.php b/src/Config.php index bfca9e1d18..4859756b69 100644 --- a/src/Config.php +++ b/src/Config.php @@ -690,7 +690,7 @@ public function processShortArgument($arg, $pos) /** - * Processes a long (--example) command line argument. + * Processes a long (--example) command-line argument. * * @param string $arg The command line argument. * @param int $pos The position of the argument on the command line. @@ -709,7 +709,7 @@ public function processLongArgument($arg, $pos) throw new DeepExitException($output, 0); case 'version': $output = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') '; - $output .= 'by Squiz (http://www.squiz.net)'.PHP_EOL; + $output .= 'by Squiz (https://www.squiz.net)'.PHP_EOL; throw new DeepExitException($output, 0); case 'colors': if (isset(self::$overriddenDefaults['colors']) === true) { From ed8e00df0a83aa96acf703f8c2979ff33341f879 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 23 Feb 2023 10:07:41 +1100 Subject: [PATCH 645/733] Prepare for 3.7.2 release --- package.xml | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/package.xml b/package.xml index 32a6f6ecee..9338b46e79 100644 --- a/package.xml +++ b/package.xml @@ -2284,6 +2284,49 @@ 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 From acbb0b7d31d1920c2061046e03dab8fadc6f51c2 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 21 Nov 2022 03:40:29 +0100 Subject: [PATCH 646/733] Runner::processFile(): make internal error more informative When sniffs use traits/utility classes/helper functions, the message for the `Internal.Exception` can be unclear as it may point to the trait/utility classes/helper function as the culprit without any indication of which sniff caused the error. This can cause bugs in sniffs to be reported by end-users in the wrong repository, but even when reported in the right repository, it makes the report unclear and means the maintainer will need to do more work to try and reproduce the issue. This PR attempts to make the `Internal.Exception` error message more informative by pinpointing the sniff which caused the issue to occur. While this _may_ result in duplicate information when no traits/utility classes/helper functions are involved, for those cases when those are involved, I believe this will be a useful change. Example output for a (simulated) error _without_ this change: ``` ------------------------------------------------------------------------------------------------------------------------ FOUND 1 ERROR AFFECTING 1 LINE ------------------------------------------------------------------------------------------------------------------------ 1 | ERROR | An error occurred during processing; checking has been aborted. The error message was: Undefined variable | | $tabWidth in | | path/to/PHP_CodeSniffer/src/Standards/Generic/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php | | on line 64 (Internal.Exception) ------------------------------------------------------------------------------------------------------------------------ ``` Example output for the same (simulated) error _with_ this change: ``` ------------------------------------------------------------------------------------------------------------------------ FOUND 1 ERROR AFFECTING 1 LINE ------------------------------------------------------------------------------------------------------------------------ 1 | ERROR | An error occurred during processing; checking has been aborted. The error message was: Undefined variable | | $tabWidth in | | path/to/PHP_CodeSniffer/src/Standards/Generic/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php | | on line 64 | | The error originated in the Generic.WhiteSpace.DisallowSpaceIndent sniff on line 64. | | (Internal.Exception) ------------------------------------------------------------------------------------------------------------------------ ``` Example output for a (simulated) error _without_ this change for a sniff using a helper function: ``` ------------------------------------------------------------------------------------------------------------------------ FOUND 1 ERROR AFFECTING 1 LINE ------------------------------------------------------------------------------------------------------------------------ 1 | ERROR | An error occurred during processing; checking has been aborted. The error message was: The | | PHPCSUtils\Tokens\Collections::functionDeclarationTokensBC() method is deprecated since PHPCSUtils | | 1.0.0-alpha4. Use the PHPCSUtils\Tokens\Collections::functionDeclarationTokens() method instead. in | | path/to/PHPCSUtils/PHPCSUtils/Tokens/Collections.php on line 753 | | (Internal.Exception) ------------------------------------------------------------------------------------------------------------------------ ``` Example output for the same (simulated) error _with_ this change for a sniff using a helper function: ``` ------------------------------------------------------------------------------------------------------------------------ FOUND 1 ERROR AFFECTING 1 LINE ------------------------------------------------------------------------------------------------------------------------ 1 | ERROR | An error occurred during processing; checking has been aborted. The error message was: The | | PHPCSUtils\Tokens\Collections::functionDeclarationTokensBC() method is deprecated since PHPCSUtils | | 1.0.0-alpha4. Use the PHPCSUtils\Tokens\Collections::functionDeclarationTokens() method instead. in | | path/to/PHPCSUtils/PHPCSUtils/Tokens/Collections.php on line 753 | | The error originated in the PHPCompatibility.Interfaces.NewInterfaces sniff on line 201. | | (Internal.Exception) ------------------------------------------------------------------------------------------------------------------------ ``` --- src/Runner.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Runner.php b/src/Runner.php index e0cbca7160..2f21edf482 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -652,6 +652,39 @@ public function processFile($file) } } catch (\Exception $e) { $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage(); + + // Determine which sniff caused the error. + $sniffStack = null; + $nextStack = null; + foreach ($e->getTrace() as $step) { + if (isset($step['file']) === false) { + continue; + } + + if (empty($sniffStack) === false) { + $nextStack = $step; + break; + } + + if (substr($step['file'], -9) === 'Sniff.php') { + $sniffStack = $step; + continue; + } + } + + if (empty($sniffStack) === false) { + if (empty($nextStack) === false + && isset($nextStack['class']) === true + && substr($nextStack['class'], -5) === 'Sniff' + ) { + $sniffCode = Common::getSniffCode($nextStack['class']); + } else { + $sniffCode = substr(strrchr(str_replace('\\', '/', $sniffStack['file']), '/'), 1); + } + + $error .= sprintf(PHP_EOL.'The error originated in the %s sniff on line %s.', $sniffCode, $sniffStack['line']); + } + $file->addErrorOnLine($error, 1, 'Internal.Exception'); }//end try From 38178f4c8e37037eec26841bacbe2c78991965ae Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 5 Dec 2022 03:56:24 +0100 Subject: [PATCH 647/733] Tokenizer/PHP: bug fix for potential "Uninitialized string offset 1" ... in the backfill for the PHP 8.1 explicit octal notation. Includes unit tests. --- src/Tokenizers/PHP.php | 1 + tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc | 3 +++ tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 3fc67b0c81..6b4ae22bc6 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -750,6 +750,7 @@ protected function tokenize($string) && (isset($tokens[($stackPtr + 1)]) === true && is_array($tokens[($stackPtr + 1)]) === true && $tokens[($stackPtr + 1)][0] === T_STRING + && isset($tokens[($stackPtr + 1)][1][0], $tokens[($stackPtr + 1)][1][1]) === true && 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 diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc index 154d489506..eb907ed390 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.inc @@ -26,3 +26,6 @@ $foo = 0o28_2; /* testInvalid6 */ $foo = 0o2_82; + +/* testInvalid7 */ +$foo = 0o; diff --git a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php index e1c55bd934..ddc3ff10b6 100644 --- a/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php +++ b/tests/Core/Tokenizer/BackfillExplicitOctalNotationTest.php @@ -106,6 +106,12 @@ public function dataExplicitOctalNotation() 'nextToken' => T_STRING, 'nextContent' => '_82', ], + [ + 'marker' => '/* testInvalid7 */', + 'value' => '0', + 'nextToken' => T_STRING, + 'nextContent' => 'o', + ], ]; }//end dataExplicitOctalNotation() From c9829af5a0abc89e549a335a711d79507e8f1abb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 4 Nov 2022 19:36:40 +0100 Subject: [PATCH 648/733] GH Actions: bust the cache semi-regularly Caches used in GH Actions do not get updated, they can only be replaced by a different cache with a different cache key. Now the predefined Composer install action this repo is using already creates a pretty comprehensive cache key: > `ramsey/composer-install` will auto-generate a cache key which is composed of the following elements: > * The OS image name, like `ubuntu-latest`. > * The exact PHP version, like `8.1.11`. > * The options passed via `composer-options`. > * The dependency version setting as per `dependency-versions`. > * The working directory as per `working-directory`. > * A hash of the `composer.json` and/or `composer.lock` files. This means that aside from other factors, the cache will always be busted when changes are made to the (committed) `composer.json` or the `composer.lock` file (if the latter exists in the repo). For packages running on recent versions of PHP, it also means that the cache will automatically be busted once a month when a new PHP version comes out. ### The problem For runs on older PHP versions which don't receive updates anymore, the cache will not be busted via new PHP version releases, so effectively, the cache will only be busted when a change is made to the `composer.json`/`composer.lock` file - which may not happen that frequently on low-traffic repos. But... packages _in use_ on those older PHP versions - especially dependencies of declared dependencies - may still release new versions and those new versions will not exist in the cache and will need to be downloaded each time the action is run and over time the cache gets less and less relevant as more and more packages will need to be downloaded for each run. ### The solution To combat this issue, a new `custom-cache-suffix` option has been added to the Composer install action in version 2.2.0. This new option allows for providing some extra information to add to the cache key, which allows for busting the cache based on your own additional criteria. This commit implements the use of this `custom-cache-suffix` option for all relevant workflows in this repo. Refs: * https://github.com/ramsey/composer-install/#custom-cache-suffix * https://github.com/ramsey/composer-install/releases/tag/2.2.0 --- .github/workflows/phpstan.yml | 3 +++ .github/workflows/test.yml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 65e9a6427b..19d45d1ee4 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -34,6 +34,9 @@ jobs: # @link https://github.com/marketplace/actions/install-composer-dependencies - name: Install Composer dependencies uses: "ramsey/composer-install@v2" + with: + # Bust the cache at least once a month - output format: YYYY-MM. + custom-cache-suffix: $(date -u "+%Y-%m") - name: Run PHPStan run: phpstan analyse --configuration=phpstan.neon diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9366e074f..53bd6bf782 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -117,6 +117,9 @@ jobs: - name: Install Composer dependencies - normal if: ${{ matrix.php < '8.0' }} uses: "ramsey/composer-install@v2" + with: + # Bust the cache at least once a month - output format: YYYY-MM. + custom-cache-suffix: $(date -u "+%Y-%m") # For PHP 8.0+, we need to install with ignore platform reqs as PHPUnit 7 is still used. - name: Install Composer dependencies - with ignore platform @@ -124,6 +127,7 @@ jobs: uses: "ramsey/composer-install@v2" with: composer-options: --ignore-platform-reqs + custom-cache-suffix: $(date -u "+%Y-%m") # Note: The code style check is run multiple times against every PHP version # as it also acts as an integration test. From 1d87180d0284f4ac661b1ce9c10b64807abeaf23 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Sun, 5 Mar 2023 14:32:34 +0000 Subject: [PATCH 649/733] Fix case when jshint is not available --- src/Standards/Generic/Sniffs/Debug/JSHintSniff.php | 2 +- src/Standards/Generic/Tests/Debug/JSHintUnitTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Debug/JSHintSniff.php b/src/Standards/Generic/Sniffs/Debug/JSHintSniff.php index 06de35940a..738ab67ebe 100644 --- a/src/Standards/Generic/Sniffs/Debug/JSHintSniff.php +++ b/src/Standards/Generic/Sniffs/Debug/JSHintSniff.php @@ -52,7 +52,7 @@ public function process(File $phpcsFile, $stackPtr) { $rhinoPath = Config::getExecutablePath('rhino'); $jshintPath = Config::getExecutablePath('jshint'); - if ($rhinoPath === null && $jshintPath === null) { + if ($jshintPath === null) { return; } diff --git a/src/Standards/Generic/Tests/Debug/JSHintUnitTest.php b/src/Standards/Generic/Tests/Debug/JSHintUnitTest.php index 618465a916..6a10e2ad8b 100644 --- a/src/Standards/Generic/Tests/Debug/JSHintUnitTest.php +++ b/src/Standards/Generic/Tests/Debug/JSHintUnitTest.php @@ -25,7 +25,7 @@ protected function shouldSkipTest() { $rhinoPath = Config::getExecutablePath('rhino'); $jshintPath = Config::getExecutablePath('jshint'); - if ($rhinoPath === null && $jshintPath === null) { + if ($jshintPath === null) { return true; } From 272aa59c4793f0e3b489262f31faf593ea68fb1e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Mar 2023 00:47:26 +0100 Subject: [PATCH 650/733] Squiz/NonExecutableCode: remove redundant line `$prev` is already defined in exactly the same way on line 66. No need to re-define it. --- src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 343d9b29e3..592472683e 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -139,7 +139,6 @@ public function process(File $phpcsFile, $stackPtr) // This token may be part of an inline condition. // If we find a closing parenthesis that belongs to a condition // we should ignore this token. - $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); if (isset($tokens[$prev]['parenthesis_owner']) === true) { $owner = $tokens[$prev]['parenthesis_owner']; $ignore = [ From 7fc2e8735cb3ebc4210c7415eb8c8173acb24bfd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Mar 2023 00:44:25 +0100 Subject: [PATCH 651/733] Squiz/NonExecutableCode: only ignore inline statements when PHP allows them Aside from `T_EXIT`, PHP (prior to PHP 8.0) does not allow for the inline use of the other tokens this sniff registers. While using those tokens inline would result in a parse error, that is not the concern of this sniff. By ignoring these, the sniff causes false negatives. This commit makes the sniff more selective and only ignores inline statements for tokens which are allowed in inline statements. Includes unit test. --- .../Sniffs/PHP/NonExecutableCodeSniff.php | 22 +++++++++++++++---- .../Tests/PHP/NonExecutableCodeUnitTest.1.inc | 11 ++++++++++ .../Tests/PHP/NonExecutableCodeUnitTest.php | 2 ++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 592472683e..043847ea89 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -16,6 +16,16 @@ class NonExecutableCodeSniff implements Sniff { + /** + * Tokens for terminating expressions, which can be used inline. + * + * This is in contrast to terminating statements, which cannot be used inline + * and would result in a parse error (which is not the concern of this sniff). + * + * @var array + */ + private $expressionTokens = [T_EXIT => T_EXIT]; + /** * Returns an array of tokens this test wants to listen for. @@ -49,11 +59,15 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - // If this token is preceded with an "or", it only relates to one line - // and should be ignored. For example: fopen() or die(). $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); - if ($tokens[$prev]['code'] === T_LOGICAL_OR || $tokens[$prev]['code'] === T_BOOLEAN_OR) { - return; + + // Tokens which can be used in inline expressions need special handling. + if (isset($this->expressionTokens[$tokens[$stackPtr]['code']]) === true) { + // If this token is preceded with an "or", it only relates to one line + // and should be ignored. For example: fopen() or die(). + if ($tokens[$prev]['code'] === T_LOGICAL_OR || $tokens[$prev]['code'] === T_BOOLEAN_OR) { + return; + } } // Check if this token is actually part of a one-line IF or ELSE statement. diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc index ed7f01151c..148a5995c9 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc @@ -297,5 +297,16 @@ class TestAlternativeControlStructures { $var_after_class_in_global_space = 1; do_something_else(); +// These are parse errors, but that's not the concern of the sniff. +function parseError1() { + defined('FOO') or return 'foo'; + echo 'unreachable'; +} + +function parseError2() { + defined('FOO') || continue; + echo 'unreachable'; +} + // Intentional syntax error. return array_map( diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php index f5f90c0b92..787f4ca4e3 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php @@ -74,6 +74,8 @@ public function getWarningList($testFile='') 252 => 1, 253 => 1, 254 => 2, + 303 => 1, + 308 => 1, ]; break; case 'NonExecutableCodeUnitTest.2.inc': From 4c094ed951b27ac56fae13f6ccacf7d00fc06afb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Mar 2023 00:34:17 +0100 Subject: [PATCH 652/733] Squiz/NonExecutableCode: bug fix - allow for all logic operators The sniff, as it was, only allowed for `or` and `||` logical operators before a termination expression, not for `and`, `&&` or `xor`, while PHP allows these too. See: https://3v4l.org/OWS4n#veol Fixed now. Includes unit tests. --- .../Squiz/Sniffs/PHP/NonExecutableCodeSniff.php | 4 ++-- .../Tests/PHP/NonExecutableCodeUnitTest.1.inc | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 043847ea89..9a4b971f6d 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -63,9 +63,9 @@ public function process(File $phpcsFile, $stackPtr) // Tokens which can be used in inline expressions need special handling. if (isset($this->expressionTokens[$tokens[$stackPtr]['code']]) === true) { - // If this token is preceded with an "or", it only relates to one line + // If this token is preceded by a logical operator, it only relates to one line // and should be ignored. For example: fopen() or die(). - if ($tokens[$prev]['code'] === T_LOGICAL_OR || $tokens[$prev]['code'] === T_BOOLEAN_OR) { + if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { return; } } diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc index 148a5995c9..64284cbe1f 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc @@ -308,5 +308,20 @@ function parseError2() { echo 'unreachable'; } +// All logical operators are allowed with inline expressions (but this was not correctly handled by the sniff). +function exitExpressionsWithLogicalOperators() { + $condition = false; + $condition || exit(); + $condition or die(); + + $condition = true; + $condition && die(); + $condition and exit; + + $condition xor die(); + + echo 'still executable as exit, in all of the above cases, is used as part of an expression'; +} + // Intentional syntax error. return array_map( From 2048a01f4ed4f75e0f63033227329b56aca8b12d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Mar 2023 01:54:25 +0100 Subject: [PATCH 653/733] Squiz/NonExecutableCode: bug fix - expressions in ternary The `T_EXIT` token can be used in ternaries without affecting code after the ternary expression. See: https://3v4l.org/oMWuA#veol Fixed now. Includes unit test. Fixes 2857 --- .../Squiz/Sniffs/PHP/NonExecutableCodeSniff.php | 5 +++++ .../Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 9a4b971f6d..527978493f 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -68,6 +68,11 @@ public function process(File $phpcsFile, $stackPtr) if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { return; } + + // Expressions are allowed in the `else` clause of ternaries. + if ($tokens[$prev]['code'] === T_INLINE_THEN || $tokens[$prev]['code'] === T_INLINE_ELSE) { + return; + } } // Check if this token is actually part of a one-line IF or ELSE statement. diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc index 64284cbe1f..c9057853c0 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc @@ -323,5 +323,17 @@ function exitExpressionsWithLogicalOperators() { echo 'still executable as exit, in all of the above cases, is used as part of an expression'; } +// Inline expressions are allowed in ternaries. +function exitExpressionsInTernary() { + $value = $myValue ? $myValue : exit(); + $value = $myValue ?: exit(); + $value = $var == 'foo' ? 'bar' : die( 'world' ); + + $value = (!$myValue ) ? exit() : $myValue; + $value = $var != 'foo' ? die( 'world' ) : 'bar'; + + echo 'still executable'; +} + // Intentional syntax error. return array_map( From b38437270bff005016fecfaf91ae1e056aefe4c8 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Mar 2023 01:36:56 +0100 Subject: [PATCH 654/733] Squiz/NonExecutableCode: bug fix - expressions after PHP 7.0/7.4 null coalesce (equals) PHP 7.0 introduced the null coalesce operator. PHP 7.4 introduced the null coalesce equals operator. The `T_EXIT` token can be used in combination with those without affecting the code directly following it. See: https://3v4l.org/C218d#veol Fixed now. Includes unit test. Related to 2857 --- src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php | 5 +++++ .../Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 527978493f..bd4d3ac14e 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -73,6 +73,11 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$prev]['code'] === T_INLINE_THEN || $tokens[$prev]['code'] === T_INLINE_ELSE) { return; } + + // Expressions are allowed with PHP 7.0+ null coalesce and PHP 7.4+ null coalesce equals. + if ($tokens[$prev]['code'] === T_COALESCE || $tokens[$prev]['code'] === T_COALESCE_EQUAL) { + return; + } } // Check if this token is actually part of a one-line IF or ELSE statement. diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc index c9057853c0..88df30166b 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc @@ -335,5 +335,12 @@ function exitExpressionsInTernary() { echo 'still executable'; } +// Inline expressions are allowed with null coalesce and null coalesce equals. +function exitExpressionsWithNullCoalesce() { + $value = $nullableValue ?? exit(); + $value ??= die(); + echo 'still executable'; +} + // Intentional syntax error. return array_map( From c17d7c7b440170c05ee96f88f8459a371c9ba2a3 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Mar 2023 01:25:17 +0100 Subject: [PATCH 655/733] Squiz/NonExecutableCode: bug fix - expressions in PHP 7.4 arrow functions PHP 7.4 introduced arrow functions. The `T_EXIT` token can be used in those without affecting the code following it. See: https://3v4l.org/gSrt4#veol Fixed now. Includes unit test. --- src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php | 7 ++++++- .../Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index bd4d3ac14e..ffc6743295 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -78,7 +78,12 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$prev]['code'] === T_COALESCE || $tokens[$prev]['code'] === T_COALESCE_EQUAL) { return; } - } + + // Expressions are allowed in arrow functions. + if ($tokens[$prev]['code'] === T_FN_ARROW) { + return; + } + }//end if // Check if this token is actually part of a one-line IF or ELSE statement. for ($i = ($stackPtr - 1); $i > 0; $i--) { diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc index 88df30166b..6da15d1e6b 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc @@ -342,5 +342,11 @@ function exitExpressionsWithNullCoalesce() { echo 'still executable'; } +// Inline expressions are allowed in arrow functions. +function exitExpressionsInArrowFunction() { + $callable = fn() => die(); + echo 'still executable'; +} + // Intentional syntax error. return array_map( From 0f2c50ff4e399edcf3afae081ca50e841b69b190 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Mar 2023 00:44:32 +0100 Subject: [PATCH 656/733] Squiz/NonExecutableCode: bug fix - PHP 8.0 inline throw expressions PHP 8.0 introduced the ability to use `throw` as an expression instead of as a statement. Ref: https://wiki.php.net/rfc/throw_expression When used as an expression, the `throw` does not necessarily affect the code after it. See: https://3v4l.org/AmMf2 Fixed now. Includes unit tests. Fixes 3592 --- .../Sniffs/PHP/NonExecutableCodeSniff.php | 13 ++++- .../Tests/PHP/NonExecutableCodeUnitTest.1.inc | 48 +++++++++++++++++++ .../Tests/PHP/NonExecutableCodeUnitTest.php | 6 +++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index ffc6743295..12c8881145 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -22,9 +22,15 @@ class NonExecutableCodeSniff implements Sniff * This is in contrast to terminating statements, which cannot be used inline * and would result in a parse error (which is not the concern of this sniff). * + * `throw` can be used as an expression since PHP 8.0. + * {@link https://wiki.php.net/rfc/throw_expression} + * * @var array */ - private $expressionTokens = [T_EXIT => T_EXIT]; + private $expressionTokens = [ + T_EXIT => T_EXIT, + T_THROW => T_THROW, + ]; /** @@ -65,7 +71,10 @@ public function process(File $phpcsFile, $stackPtr) if (isset($this->expressionTokens[$tokens[$stackPtr]['code']]) === true) { // If this token is preceded by a logical operator, it only relates to one line // and should be ignored. For example: fopen() or die(). - if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { + // Note: There is one exception: throw expressions can not be used with xor. + if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true + && ($tokens[$stackPtr]['code'] === T_THROW && $tokens[$prev]['code'] === T_LOGICAL_XOR) === false + ) { return; } diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc index 6da15d1e6b..051b6c6b11 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc @@ -348,5 +348,53 @@ function exitExpressionsInArrowFunction() { echo 'still executable'; } +// PHP 8.0+: throw expressions which don't stop execution. +function nonStoppingThrowExpressions() { + $callable = fn() => throw new Exception(); + + $value = $myValue ? 'something' : throw new Exception(); + $value = $myValue ?: throw new Exception(); + $value = $myValue ? throw new Exception() : 'something'; + + $value = $nullableValue ?? throw new Exception(); + $value ??= throw new Exception(); + + $condition && throw new Exception(); + $condition || throw new Exception(); + $condition and throw new Exception(); + $condition or throw new Exception(); + + echo 'still executable as throw, in all of the above cases, is used as part of an expression'; + + throw new Exception(); + echo 'non-executable'; +} + +// PHP 8.0+: throw expressions which do stop execution. +function executionStoppingThrowExpressionsA() { + $condition xor throw new Exception(); + echo 'non-executable'; +} + +function executionStoppingThrowExpressionsB() { + throw $userIsAuthorized ? new ForbiddenException() : new UnauthorizedException(); + echo 'non-executable'; +} + +function executionStoppingThrowExpressionsC() { + throw $condition1 && $condition2 ? new Exception1() : new Exception2(); + echo 'non-executable'; +} + +function executionStoppingThrowExpressionsD() { + throw $exception ??= new Exception(); + echo 'non-executable'; +} + +function executionStoppingThrowExpressionsE() { + throw $maybeNullException ?? new Exception(); + echo 'non-executable'; +} + // Intentional syntax error. return array_map( diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php index 787f4ca4e3..4348df1318 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php @@ -76,6 +76,12 @@ public function getWarningList($testFile='') 254 => 2, 303 => 1, 308 => 1, + 370 => 1, + 376 => 1, + 381 => 1, + 386 => 1, + 391 => 1, + 396 => 1, ]; break; case 'NonExecutableCodeUnitTest.2.inc': From 693476f66cb19e61b209951209c02d488377cc7d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 20 Mar 2023 22:50:01 +0100 Subject: [PATCH 657/733] Squiz/LowercasePHPFunctions: bug fix for class names in attributes Attributes cannot contain function calls and in most cases, `T_STRING`s in an attribute will be a class name, not a function name. As things were, `T_STRING`'s in attributes which _looked_ like function calls (but in actual fact are class instantiations) would lead to false positives. Fixed now by ignoring all code within attributes. Includes unit test. Fixes 3778 --- .../Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php | 5 +++++ .../Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc | 7 +++++++ .../Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php b/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php index 519b7f6ccc..37f1e40e7b 100644 --- a/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php @@ -75,6 +75,11 @@ public function process(File $phpcsFile, $stackPtr) } // Make sure this is a function call or a use statement. + if (empty($tokens[$stackPtr]['nested_attributes']) === false) { + // Class instantiation in attribute, not function call. + return; + } + $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); if ($next === false) { // Not a function call. diff --git a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc index c67381ad8b..702b13de0a 100644 --- a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc @@ -41,3 +41,10 @@ $callToNamespacedFunction = namespace\STR_REPEAT($a, 2); // Could potentially be $filePath = new \File($path); $count = $object?->Count(); + +class AttributesShouldBeIgnored +{ + #[Putenv('FOO', 'foo')] + public function foo(): void + {} +} diff --git a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed index 40507c0404..281425c595 100644 --- a/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/PHP/LowercasePHPFunctionsUnitTest.inc.fixed @@ -41,3 +41,10 @@ $callToNamespacedFunction = namespace\STR_REPEAT($a, 2); // Could potentially be $filePath = new \File($path); $count = $object?->Count(); + +class AttributesShouldBeIgnored +{ + #[Putenv('FOO', 'foo')] + public function foo(): void + {} +} From 4d2d3d431942f02f3fe3fcfba30a22c20c4539bd Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 20 Mar 2023 22:51:08 +0100 Subject: [PATCH 658/733] Generic/ForbiddenFunctions: bug fix for class names in attributes Attributes cannot contain function calls and in most cases, `T_STRING`s in an attribute will be a class name, not a function name. As things were, `T_STRING`'s in attributes which _looked_ like function calls (but in actual fact are class instantiations) would lead to false positives. Fixed now by ignoring all code within attributes. Includes unit test. --- src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php | 5 +++++ .../Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc | 3 +++ .../Squiz/Tests/PHP/DiscouragedFunctionsUnitTest.inc | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php b/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php index 1a10b15320..8717189f0e 100644 --- a/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php @@ -163,6 +163,11 @@ public function process(File $phpcsFile, $stackPtr) return; } + if (empty($tokens[$stackPtr]['nested_attributes']) === false) { + // Class instantiation in attribute, not function call. + return; + } + $function = strtolower($tokens[$stackPtr]['content']); $pattern = null; diff --git a/src/Standards/Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc b/src/Standards/Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc index b30f0dde94..060da6128c 100644 --- a/src/Standards/Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/ForbiddenFunctionsUnitTest.inc @@ -55,3 +55,6 @@ function mymodule_form_callback(SizeOf $sizeof) { } $size = $class?->sizeof($array); + +#[SizeOf(10)] +function doSomething() {} diff --git a/src/Standards/Squiz/Tests/PHP/DiscouragedFunctionsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/DiscouragedFunctionsUnitTest.inc index ca457a2e9a..3c875d0983 100644 --- a/src/Standards/Squiz/Tests/PHP/DiscouragedFunctionsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/DiscouragedFunctionsUnitTest.inc @@ -2,4 +2,6 @@ error_log('test'); print_r($array); var_dump($array); -?> + +#[Var_Dump(10)] +function debugMe() {} From cae56aff37bf346d468ca46430f8313752b850ef Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Wed, 29 Mar 2023 17:17:02 +0100 Subject: [PATCH 659/733] Add test to demonstrate bug --- .../Squiz/Tests/Commenting/FunctionCommentUnitTest.inc | 8 ++++++++ .../Tests/Commenting/FunctionCommentUnitTest.inc.fixed | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc index 4f59f60b71..d57017daf5 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc @@ -1046,3 +1046,11 @@ public function ignored() { * @return void * @throws Exception If any other error occurs. */ function throwCommentOneLine() {} + +/** + * When two adjacent pipe symbols are used (by mistake), the sniff should not throw a PHP Fatal error + * + * @param stdClass||null $object While invalid, this should not throw a PHP Fatal error. + * @return void + */ +function doublePipeFatalError(?stdClass $object) {} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 21a4103eb5..330563b0d5 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -1046,3 +1046,11 @@ public function ignored() { * @return void * @throws Exception If any other error occurs. */ function throwCommentOneLine() {} + +/** + * When two adjacent pipe symbols are used (by mistake), the sniff should not throw a PHP Fatal error + * + * @param stdClass||null $object While invalid, this should not throw a PHP Fatal error. + * @return void + */ +function doublePipeFatalError(?stdClass $object) {} From 8acfadab7f5a8419e287cfe73150707389b8b1a4 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Wed, 29 Mar 2023 17:17:17 +0100 Subject: [PATCH 660/733] Fix bug --- src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index ba3e1710f0..91f5693ef4 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -406,7 +406,7 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) foreach ($typeNames as $typeName) { // Strip nullable operator. - if ($typeName[0] === '?') { + if (strlen($typeName) > 1 && $typeName[0] === '?') { $typeName = substr($typeName, 1); } From c38bb12175691e6e48a02c00ccb69dc5da1ae158 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Wed, 29 Mar 2023 18:48:38 +0100 Subject: [PATCH 661/733] Use isset() over strlen() for better performance --- src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index 91f5693ef4..1101165cbd 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -406,7 +406,7 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) foreach ($typeNames as $typeName) { // Strip nullable operator. - if (strlen($typeName) > 1 && $typeName[0] === '?') { + if (isset($typeName[0]) === true && $typeName[0] === '?') { $typeName = substr($typeName, 1); } From 6213153c2079407d9d81234887acfc0de3052a47 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 30 Mar 2023 14:21:44 +0200 Subject: [PATCH 662/733] PEAR/Squiz/[MultiLine]FunctionDeclaration: allow for PHP 8.1 new in initializers Since PHP 8.1, `new ClassName()` expressions can be used as the default value of a parameter in a function declaration. `new ClassName()` expressions should be treated as function _calls_, which have their own indentation rules, so the `PEAR.Functions.FunctionDeclaration` and the `Squiz.Functions.MultiLineFunctionDeclaration` sniffs should skip over these and not attempt to change the indentation of parameters in those function calls. This commit implements this change. Includes unit tests. Refs: * https://wiki.php.net/rfc/new_in_initializers * https://www.php.net/manual/en/migration81.new-features.php#migration81.new-features.core.new-in-initializer * https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.new Fixes 3786 --- .../Functions/FunctionDeclarationSniff.php | 13 +++++ .../Functions/FunctionDeclarationUnitTest.inc | 47 +++++++++++++++++++ .../FunctionDeclarationUnitTest.inc.fixed | 47 +++++++++++++++++++ .../MultiLineFunctionDeclarationUnitTest.inc | 47 +++++++++++++++++++ ...iLineFunctionDeclarationUnitTest.inc.fixed | 47 +++++++++++++++++++ 5 files changed, 201 insertions(+) diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php index bd59a7cd90..14479e2c0a 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php @@ -496,6 +496,19 @@ public function processArgumentList($phpcsFile, $stackPtr, $indent, $type='funct $lastLine = $tokens[$i]['line']; }//end if + if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS + && isset($tokens[$i]['parenthesis_closer']) === true + ) { + $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), null, true); + if ($tokens[$prevNonEmpty]['code'] !== T_USE) { + // Since PHP 8.1, a default value can contain a class instantiation. + // Skip over these "function calls" as they have their own indentation rules. + $i = $tokens[$i]['parenthesis_closer']; + $lastLine = $tokens[$i]['line']; + continue; + } + } + if ($tokens[$i]['code'] === T_ARRAY || $tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { // Skip arrays as they have their own indentation rules. if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc index 02e0a20dbf..fb7cb52f4d 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc @@ -418,3 +418,50 @@ class ConstructorPropertyPromotionMultiLineAttributesIncorrectIndent // Do something. } } + +// PHP 8.1: new in initializers means that class instantiations with parameters can occur in a function declaration. +function usingNewInInitializersCallParamsIndented( + int $paramA, + string $paramB, + object $paramC = new SomeClass( + new InjectedDependencyA(), + new InjectedDependencyB + ) +) {} + +function usingNewInInitializersCallParamsNotIndented( + int $paramA, + string $paramB, + object $paramC = new SomeClass( + new InjectedDependencyA, + new InjectedDependencyB() + ) +) {} + +function usingNewInInitializersCallParamsIncorrectlyIndentedShouldNotBeFlaggedNorFixed( + int $paramA, + string $paramB, + object $paramC = new SomeClass( +new InjectedDependencyA(), new InjectedDependencyB() +) +) {} + +class UsingNewInInitializers { + public function doSomething( + object $paramA, + stdClass $paramB = new stdClass(), + Exception $paramC = new Exception( + new ExceptionMessage(), + new ExceptionCode(), + ), + ) { + } + + public function callParamsIncorrectlyIndentedShouldNotBeFlaggedNorFixed( + Exception $param = new Exception( +new ExceptionMessage(), + new ExceptionCode(), + ), + ) { + } +} diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed index 0d67e9f758..f82b5665a5 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed @@ -416,3 +416,50 @@ class ConstructorPropertyPromotionMultiLineAttributesIncorrectIndent // Do something. } } + +// PHP 8.1: new in initializers means that class instantiations with parameters can occur in a function declaration. +function usingNewInInitializersCallParamsIndented( + int $paramA, + string $paramB, + object $paramC = new SomeClass( + new InjectedDependencyA(), + new InjectedDependencyB + ) +) {} + +function usingNewInInitializersCallParamsNotIndented( + int $paramA, + string $paramB, + object $paramC = new SomeClass( + new InjectedDependencyA, + new InjectedDependencyB() + ) +) {} + +function usingNewInInitializersCallParamsIncorrectlyIndentedShouldNotBeFlaggedNorFixed( + int $paramA, + string $paramB, + object $paramC = new SomeClass( +new InjectedDependencyA(), new InjectedDependencyB() +) +) {} + +class UsingNewInInitializers { + public function doSomething( + object $paramA, + stdClass $paramB = new stdClass(), + Exception $paramC = new Exception( + new ExceptionMessage(), + new ExceptionCode(), + ), + ) { + } + + public function callParamsIncorrectlyIndentedShouldNotBeFlaggedNorFixed( + Exception $param = new Exception( +new ExceptionMessage(), + new ExceptionCode(), + ), + ) { + } +} diff --git a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc index fce0b237ba..811c56ec14 100644 --- a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc +++ b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc @@ -255,3 +255,50 @@ private string $private, ) { } } + +// PHP 8.1: new in initializers means that class instantiations with parameters can occur in a function declaration. +function usingNewInInitializersCallParamsIndented( + int $paramA, + string $paramB, + object $paramC = new SomeClass( + new InjectedDependencyA(), + new InjectedDependencyB + ) +) {} + +function usingNewInInitializersCallParamsNotIndented( + int $paramA, + string $paramB, + object $paramC = new SomeClass( + new InjectedDependencyA, + new InjectedDependencyB() + ) +) {} + +function usingNewInInitializersCallParamsIncorrectlyIndentedShouldNotBeFlaggedNorFixed( + int $paramA, + string $paramB, + object $paramC = new SomeClass( +new InjectedDependencyA(), new InjectedDependencyB() +) +) {} + +class UsingNewInInitializers { + public function doSomething( + object $paramA, + stdClass $paramB = new stdClass(), + Exception $paramC = new Exception( + new ExceptionMessage(), + new ExceptionCode(), + ), + ) { + } + + public function callParamsIncorrectlyIndentedShouldNotBeFlaggedNorFixed( + Exception $param = new Exception( +new ExceptionMessage(), + new ExceptionCode(), + ), + ) { + } +} diff --git a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc.fixed index b927a001b3..c38e3ecc0a 100644 --- a/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Functions/MultiLineFunctionDeclarationUnitTest.inc.fixed @@ -267,3 +267,50 @@ class ConstructorPropertyPromotionMultiLineDocblockAndAttributeIncorrectIndent ) { } } + +// PHP 8.1: new in initializers means that class instantiations with parameters can occur in a function declaration. +function usingNewInInitializersCallParamsIndented( + int $paramA, + string $paramB, + object $paramC = new SomeClass( + new InjectedDependencyA(), + new InjectedDependencyB + ) +) {} + +function usingNewInInitializersCallParamsNotIndented( + int $paramA, + string $paramB, + object $paramC = new SomeClass( + new InjectedDependencyA, + new InjectedDependencyB() + ) +) {} + +function usingNewInInitializersCallParamsIncorrectlyIndentedShouldNotBeFlaggedNorFixed( + int $paramA, + string $paramB, + object $paramC = new SomeClass( +new InjectedDependencyA(), new InjectedDependencyB() +) +) {} + +class UsingNewInInitializers { + public function doSomething( + object $paramA, + stdClass $paramB = new stdClass(), + Exception $paramC = new Exception( + new ExceptionMessage(), + new ExceptionCode(), + ), + ) { + } + + public function callParamsIncorrectlyIndentedShouldNotBeFlaggedNorFixed( + Exception $param = new Exception( +new ExceptionMessage(), + new ExceptionCode(), + ), + ) { + } +} From 577b2b81bca2ea60ecf5c2452a97d61d8d6046b5 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 2 Apr 2023 19:39:29 +0200 Subject: [PATCH 663/733] Tokenizer/PHP: bug fix for match within ternary This fixes the mis-identification of a `T_DEFAULT` token within a (not yet retokenized) `match` structure as a `switch` `default` token, which resulted in a `T_COLON` token incorrectly not being retokenized to `T_INLINE_ELSE`. As a complete test file for ternary tokenization does not exist (yet), I've added a unit test to the `Squiz.Objects.ObjectInstantiation` sniff. The code in the test would cause a false positive for the `ObjectInstantiation` sniff without this fix. Fixes 3789 --- .../Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc | 7 +++++++ src/Tokenizers/PHP.php | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc b/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc index 41c881289d..725c60ed69 100644 --- a/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc +++ b/src/Standards/Squiz/Tests/Objects/ObjectInstantiationUnitTest.inc @@ -41,6 +41,13 @@ function nonAssignmentTernary() { } } +// Test for tokenizer issue #3789. +$a = $b !== null + ? match ($c) { + default => 5, + } + : new Foo; + // Intentional parse error. This must be the last test in the file. function new ?> diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 3fc67b0c81..ad93547ac2 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2249,11 +2249,12 @@ function return types. We want to keep the parenthesis map clean, if (is_array($tokens[$i]) === false && ($tokens[$i] === ';' - || $tokens[$i] === '{') + || $tokens[$i] === '{' + || $tokens[$i] === '}') ) { break; } - } + }//end for }//end if if ($isInlineIf === true) { From 487164fa5b6a5314479a395fbc26715e99b2f477 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 15 Apr 2023 10:12:49 +0200 Subject: [PATCH 664/733] File::getMethodParameters(): allow for readonly properties without visibility Follow up on PR 3516 which was included in PHPCS 3.7.0. Turns out that constructor property promotion also allows for declaring properties with the `readonly` keyword, but without explicit visibility set. See: https://3v4l.org/nli62 Readonly properties without explicit visibility are already handled correctly in the `File::getMemberProperties()` method, but were not handled correctly in the `File::getMethodParameters()` method. Fixed now. Includes updated documentation and a unit test. --- src/Files/File.php | 29 ++++++++++------ tests/Core/File/GetMethodParametersTest.inc | 5 +++ tests/Core/File/GetMethodParametersTest.php | 37 +++++++++++++++++++++ 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/Files/File.php b/src/Files/File.php index ad1ca0b236..d240df2295 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1309,10 +1309,12 @@ public function getDeclarationName($stackPtr) * 'default_equal_token' => integer, // The stack pointer to the equals sign. * * 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. + * 'property_visibility' => string, // The property visibility as declared. + * 'visibility_token' => integer|false, // The stack pointer to the visibility modifier token + * // or FALSE if the visibility is not explicitly declared. + * 'property_readonly' => boolean, // TRUE if the readonly keyword was found. + * 'readonly_token' => integer, // The stack pointer to the readonly modifier token. + * // This index will only be set if the property is readonly. * * @param int $stackPtr The position in the stack of the function token * to acquire the parameters for. @@ -1530,15 +1532,20 @@ public function getMethodParameters($stackPtr) $vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken; $vars[$paramCount]['nullable_type'] = $nullableType; - if ($visibilityToken !== null) { - $vars[$paramCount]['property_visibility'] = $this->tokens[$visibilityToken]['content']; - $vars[$paramCount]['visibility_token'] = $visibilityToken; + if ($visibilityToken !== null || $readonlyToken !== null) { + $vars[$paramCount]['property_visibility'] = 'public'; + $vars[$paramCount]['visibility_token'] = false; $vars[$paramCount]['property_readonly'] = false; - } - if ($readonlyToken !== null) { - $vars[$paramCount]['property_readonly'] = true; - $vars[$paramCount]['readonly_token'] = $readonlyToken; + if ($visibilityToken !== null) { + $vars[$paramCount]['property_visibility'] = $this->tokens[$visibilityToken]['content']; + $vars[$paramCount]['visibility_token'] = $visibilityToken; + } + + if ($readonlyToken !== null) { + $vars[$paramCount]['property_readonly'] = true; + $vars[$paramCount]['readonly_token'] = $readonlyToken; + } } if ($this->tokens[$i]['code'] === T_COMMA) { diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index dc46549140..c0ef6e0621 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -113,6 +113,11 @@ class ConstructorPropertyPromotionWithReadOnly { public function __construct(public readonly ?int $promotedProp, readonly private string|bool &$promotedToo) {} } +class ConstructorPropertyPromotionWithOnlyReadOnly { + /* testPHP81ConstructorPropertyPromotionWithOnlyReadOnly */ + public function __construct(readonly Foo&Bar $promotedProp, readonly ?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) {} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index ba4d754485..b5bd61813c 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -846,6 +846,43 @@ public function testPHP81ConstructorPropertyPromotionWithReadOnly() }//end testPHP81ConstructorPropertyPromotionWithReadOnly() + /** + * Verify recognition of PHP8 constructor with property promotion using PHP 8.1 readonly + * keyword without explicit visibility. + * + * @return void + */ + public function testPHP81ConstructorPropertyPromotionWithOnlyReadOnly() + { + $expected = []; + $expected[0] = [ + 'name' => '$promotedProp', + 'content' => 'readonly Foo&Bar $promotedProp', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'Foo&Bar', + 'nullable_type' => false, + 'property_visibility' => 'public', + 'property_readonly' => true, + ]; + $expected[1] = [ + 'name' => '$promotedToo', + 'content' => 'readonly ?bool $promotedToo', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?bool', + 'nullable_type' => true, + 'property_visibility' => 'public', + 'property_readonly' => true, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81ConstructorPropertyPromotionWithOnlyReadOnly() + + /** * Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax. * From cb764c7d613feddc7984c959e693e3652884209c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 13 Apr 2023 02:56:37 +0200 Subject: [PATCH 665/733] Tokenizer/PHP: more context sensitive keyword fixes PHPCS re-tokenizes the `self`, `parent`, `true`, `false` and `null` keywords to a PHPCS native token. This re-tokenization did not take the following situations into account: * Those keywords being used as function names when the function is declared to return by reference. * Those keywords being used as a function call. Additionally, the PHP native `T_STATIC` token would not be (re-)tokenized to `T_STRING` when used as a function call, though it was tokenized correctly when used as a method call.. While using the `static` keyword for a global function declaration is illegal in PHP, the tokenization in PHPCS should still be consistent. This commit fixes those issues. Includes additional unit tests. --- src/Tokenizers/PHP.php | 98 +++- .../ContextSensitiveKeywordsTest.inc | 491 ++++++++++-------- .../ContextSensitiveKeywordsTest.php | 82 ++- 3 files changed, 419 insertions(+), 252 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 3fc67b0c81..11802c0d34 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -681,6 +681,36 @@ protected function tokenize($string) } }//end if + /* + Special case for `static` used as a function name, i.e. `static()`. + */ + + if ($tokenIsArray === true + && $token[0] === T_STATIC + && $finalTokens[$lastNotEmptyToken]['code'] !== T_NEW + ) { + for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { + if (is_array($tokens[$i]) === true + && isset(Util\Tokens::$emptyTokens[$tokens[$i][0]]) === true + ) { + continue; + } + + if ($tokens[$i][0] === '(') { + $finalTokens[$newStackPtr] = [ + 'code' => T_STRING, + 'type' => 'T_STRING', + 'content' => $token[1], + ]; + + $newStackPtr++; + continue 2; + } + + break; + } + }//end if + /* Parse doc blocks into something that can be easily iterated over. */ @@ -2103,38 +2133,60 @@ function return types. We want to keep the parenthesis map clean, } } else { // 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'; + if ($tokenIsArray === true && $token[0] === T_STRING) { + $preserveTstring = false; + + if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true) { + $preserveTstring = 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') + ) { + $preserveTstring = false; } + } else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') { + // Function names for functions declared to return by reference. + for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) { + if (isset(Util\Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) { + continue; + } + + if ($finalTokens[$i]['code'] === T_FUNCTION) { + $preserveTstring = true; + } - if ($tokenContentLower === 'parent') { - $finalTokens[$newStackPtr]['code'] = T_PARENT; - $finalTokens[$newStackPtr]['type'] = 'T_PARENT'; + break; } } else { + // Keywords with special PHPCS token when used as a function call. + for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { + if (is_array($tokens[$i]) === true + && isset(Util\Tokens::$emptyTokens[$tokens[$i][0]]) === true + ) { + continue; + } + + if ($tokens[$i][0] === '(') { + $preserveTstring = true; + } + + break; + } + }//end if + + if ($preserveTstring === true) { $finalTokens[$newStackPtr] = [ - 'content' => $token[1], 'code' => T_STRING, 'type' => 'T_STRING', + 'content' => $token[1], ]; - } - $newStackPtr++; - continue; + $newStackPtr++; + continue; + } }//end if $newToken = null; diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc index 82fe564382..bc98c49f3d 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc @@ -1,227 +1,264 @@ - '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() {} + '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() {} + +function /* testKeywordSelfAfterFunctionByRefShouldBeString */ &self() {} +function /* testKeywordStaticAfterFunctionByRefShouldBeString */ &static() {} +function /* testKeywordParentAfterFunctionByRefShouldBeString */ &parent() {} +function /* testKeywordFalseAfterFunctionByRefShouldBeString */ &false() {} +function /* testKeywordTrueAfterFunctionByRefShouldBeString */ & true () {} +function /* testKeywordNullAfterFunctionByRefShouldBeString */ &NULL() {} + +/* testKeywordAsFunctionCallNameShouldBeStringSelf */ self(); +/* testKeywordAsFunctionCallNameShouldBeStringStatic */ static(); +$obj-> /* testKeywordAsMethodCallNameShouldBeStringStatic */ static(); +/* testKeywordAsFunctionCallNameShouldBeStringParent */ parent(); +/* testKeywordAsFunctionCallNameShouldBeStringFalse */ false(); +/* testKeywordAsFunctionCallNameShouldBeStringTrue */ True (); +/* testKeywordAsFunctionCallNameShouldBeStringNull */ null /*comment*/ (); + +$instantiated4 = new /* testClassInstantiationFalseIsString */ False(); +$instantiated5 = new /* testClassInstantiationTrueIsString */ true (); +$instantiated6 = new /* testClassInstantiationNullIsString */ null(); + +$function = /* testStaticIsKeywordBeforeClosure */ static function(/* testStaticIsKeywordWhenParamType */ static $param) {}; +$arrow = /* testStaticIsKeywordBeforeArrow */ static fn(): /* testStaticIsKeywordWhenReturnType */ static => 10; + +function standAloneFalseTrueNullTypesAndMore( + /* testFalseIsKeywordAsParamType */ false $paramA, + /* testTrueIsKeywordAsParamType */ true $paramB, + /* testNullIsKeywordAsParamType */ null $paramC, +) /* testFalseIsKeywordAsReturnType */ false | /* testTrueIsKeywordAsReturnType */ true | /* testNullIsKeywordAsReturnType */ null { + if ($a === /* testFalseIsKeywordInComparison */ false + || $a === /* testTrueIsKeywordInComparison */ true + || $a === /* testNullIsKeywordInComparison */ null + ) {} +} diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php index a747e573c2..3f077ca639 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php @@ -30,7 +30,7 @@ public function testStrings($testMarker) { $tokens = self::$phpcsFile->getTokens(); - $token = $this->getTargetToken($testMarker, (Tokens::$contextSensitiveKeywords + [T_STRING])); + $token = $this->getTargetToken($testMarker, (Tokens::$contextSensitiveKeywords + [T_STRING, T_NULL, T_FALSE, T_TRUE, T_PARENT, T_SELF])); $this->assertSame(T_STRING, $tokens[$token]['code']); $this->assertSame('T_STRING', $tokens[$token]['type']); @@ -120,6 +120,9 @@ public function dataStrings() ['/* testAnd */'], ['/* testOr */'], ['/* testXor */'], + ['/* testFalse */'], + ['/* testTrue */'], + ['/* testNull */'], ['/* testKeywordAfterNamespaceShouldBeString */'], ['/* testNamespaceNameIsString1 */'], @@ -128,6 +131,24 @@ public function dataStrings() ['/* testKeywordAfterFunctionShouldBeString */'], ['/* testKeywordAfterFunctionByRefShouldBeString */'], + ['/* testKeywordSelfAfterFunctionByRefShouldBeString */'], + ['/* testKeywordStaticAfterFunctionByRefShouldBeString */'], + ['/* testKeywordParentAfterFunctionByRefShouldBeString */'], + ['/* testKeywordFalseAfterFunctionByRefShouldBeString */'], + ['/* testKeywordTrueAfterFunctionByRefShouldBeString */'], + ['/* testKeywordNullAfterFunctionByRefShouldBeString */'], + + ['/* testKeywordAsFunctionCallNameShouldBeStringSelf */'], + ['/* testKeywordAsFunctionCallNameShouldBeStringStatic */'], + ['/* testKeywordAsMethodCallNameShouldBeStringStatic */'], + ['/* testKeywordAsFunctionCallNameShouldBeStringParent */'], + ['/* testKeywordAsFunctionCallNameShouldBeStringFalse */'], + ['/* testKeywordAsFunctionCallNameShouldBeStringTrue */'], + ['/* testKeywordAsFunctionCallNameShouldBeStringNull */'], + + ['/* testClassInstantiationFalseIsString */'], + ['/* testClassInstantiationTrueIsString */'], + ['/* testClassInstantiationNullIsString */'], ]; }//end dataStrings() @@ -148,7 +169,10 @@ 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])); + $token = $this->getTargetToken( + $testMarker, + (Tokens::$contextSensitiveKeywords + [T_ANON_CLASS, T_MATCH_DEFAULT, T_PARENT, T_SELF, T_STRING, T_NULL, T_FALSE, T_TRUE]) + ); $this->assertSame(constant($expectedTokenType), $tokens[$token]['code']); $this->assertSame($expectedTokenType, $tokens[$token]['type']); @@ -501,6 +525,60 @@ public function dataKeywords() '/* testNamespaceInNameIsKeyword */', 'T_NAMESPACE', ], + + [ + '/* testStaticIsKeywordBeforeClosure */', + 'T_STATIC', + ], + [ + '/* testStaticIsKeywordWhenParamType */', + 'T_STATIC', + ], + [ + '/* testStaticIsKeywordBeforeArrow */', + 'T_STATIC', + ], + [ + '/* testStaticIsKeywordWhenReturnType */', + 'T_STATIC', + ], + + [ + '/* testFalseIsKeywordAsParamType */', + 'T_FALSE', + ], + [ + '/* testTrueIsKeywordAsParamType */', + 'T_TRUE', + ], + [ + '/* testNullIsKeywordAsParamType */', + 'T_NULL', + ], + [ + '/* testFalseIsKeywordAsReturnType */', + 'T_FALSE', + ], + [ + '/* testTrueIsKeywordAsReturnType */', + 'T_TRUE', + ], + [ + '/* testNullIsKeywordAsReturnType */', + 'T_NULL', + ], + [ + '/* testFalseIsKeywordInComparison */', + 'T_FALSE', + ], + [ + '/* testTrueIsKeywordInComparison */', + 'T_TRUE', + ], + [ + '/* testNullIsKeywordInComparison */', + 'T_NULL', + ], ]; }//end dataKeywords() From 2dd54f064ced73708e54607889874f5d14d09f2c Mon Sep 17 00:00:00 2001 From: Daimona Eaytoy Date: Wed, 19 Apr 2023 14:25:20 +0200 Subject: [PATCH 666/733] Make InnerFunctionsSniff detect functions inside closures --- src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php | 7 +++++-- src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc | 7 +++++++ src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php index 4e3ee2152c..c8afb57dfa 100644 --- a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php @@ -43,8 +43,11 @@ public function process(File $phpcsFile, $stackPtr) $function = $phpcsFile->getCondition($stackPtr, T_FUNCTION); if ($function === false) { - // Not a nested function. - return; + $function = $phpcsFile->getCondition($stackPtr, T_CLOSURE); + if ($function === false) { + // Not a nested function. + return; + } } $class = $phpcsFile->getCondition($stackPtr, T_ANON_CLASS, false); diff --git a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc index dd851461b9..0c69e06794 100644 --- a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc @@ -48,3 +48,10 @@ new class { } } }; + +$outerClosure = function () +{ + // Functions inside closures are not allowed. + function innerFunction() { + } +}; diff --git a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php index 3c9ad07bd3..606d23563c 100644 --- a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php @@ -28,6 +28,7 @@ public function getErrorList() return [ 5 => 1, 46 => 1, + 55 => 1, ]; }//end getErrorList() From 07c94c7e43d5d3b454fa95406d1dd3dc6316e9e9 Mon Sep 17 00:00:00 2001 From: Daimona Eaytoy Date: Wed, 19 Apr 2023 18:30:09 +0200 Subject: [PATCH 667/733] Allow methods inside OOP structures within functions --- .../Squiz/Sniffs/PHP/InnerFunctionsSniff.php | 42 +++++++++++++------ .../Tests/PHP/InnerFunctionsUnitTest.inc | 21 ++++++++++ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php index c8afb57dfa..efcf06c65a 100644 --- a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php @@ -41,25 +41,41 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $function = $phpcsFile->getCondition($stackPtr, T_FUNCTION); - if ($function === false) { - $function = $phpcsFile->getCondition($stackPtr, T_CLOSURE); - if ($function === false) { - // Not a nested function. - return; + if (isset($tokens[$stackPtr]['conditions']) === false) { + return; + } + + $conditions = $tokens[$stackPtr]['conditions']; + + $outerFuncToken = null; + foreach ($conditions as $condToken => $condition) { + if ($condition === T_FUNCTION || $condition === T_CLOSURE) { + $outerFuncToken = $condToken; + break; } } - $class = $phpcsFile->getCondition($stackPtr, T_ANON_CLASS, false); - if ($class !== false && $class > $function) { - // Ignore methods in anon classes. + if ($outerFuncToken === null) { + // Not a nested function. return; } - $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); - if ($tokens[$prev]['code'] === T_EQUAL) { - // Ignore closures. - return; + $reversedConditions = array_reverse($conditions, true); + $allowedOOPConditions = [ + T_ANON_CLASS => true, + T_CLASS => true, + T_TRAIT => true, + T_INTERFACE => true, + ]; + foreach ($reversedConditions as $condToken => $condition) { + if ($condToken <= $outerFuncToken) { + break; + } + + if (\array_key_exists($condition, $allowedOOPConditions) === true) { + // Ignore methods in OOP structures defined within functions. + return; + } } $error = 'The use of inner functions is forbidden'; diff --git a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc index 0c69e06794..6f78608809 100644 --- a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc @@ -55,3 +55,24 @@ $outerClosure = function () function innerFunction() { } }; + +// Allow methods in classes/traits/interfaces defined inside functions +function foo() { + if (class_exists('MyClass') === false) { + class MyClass { + function foo() {} + } + } + + if (trait_exists('MyTrait') === false) { + trait MyTrait { + function foo() {} + } + } + + if (interface_exists('MyInterface') === false) { + interface MyInterface { + function foo(); + } + } +} From d027abb2a56fdfd6f59f11f61b810115465a47fd Mon Sep 17 00:00:00 2001 From: Daimona Eaytoy Date: Wed, 19 Apr 2023 23:26:50 +0200 Subject: [PATCH 668/733] Simplify and improve OOP scope check for nested functions --- .../Squiz/Sniffs/PHP/InnerFunctionsSniff.php | 28 ++++++------------- .../Tests/PHP/InnerFunctionsUnitTest.inc | 9 ++++++ .../Tests/PHP/InnerFunctionsUnitTest.php | 1 + 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php index efcf06c65a..721c0aeda8 100644 --- a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Tokens; class InnerFunctionsSniff implements Sniff { @@ -45,14 +46,19 @@ public function process(File $phpcsFile, $stackPtr) return; } - $conditions = $tokens[$stackPtr]['conditions']; + $reversedConditions = array_reverse($tokens[$stackPtr]['conditions'], true); $outerFuncToken = null; - foreach ($conditions as $condToken => $condition) { + foreach ($reversedConditions as $condToken => $condition) { if ($condition === T_FUNCTION || $condition === T_CLOSURE) { $outerFuncToken = $condToken; break; } + + if (\array_key_exists($condition, Tokens::$ooScopeTokens) === true) { + // Ignore methods in OOP structures defined within functions. + return; + } } if ($outerFuncToken === null) { @@ -60,24 +66,6 @@ public function process(File $phpcsFile, $stackPtr) return; } - $reversedConditions = array_reverse($conditions, true); - $allowedOOPConditions = [ - T_ANON_CLASS => true, - T_CLASS => true, - T_TRAIT => true, - T_INTERFACE => true, - ]; - foreach ($reversedConditions as $condToken => $condition) { - if ($condToken <= $outerFuncToken) { - break; - } - - if (\array_key_exists($condition, $allowedOOPConditions) === true) { - // Ignore methods in OOP structures defined within functions. - return; - } - } - $error = 'The use of inner functions is forbidden'; $phpcsFile->addError($error, $stackPtr, 'NotAllowed'); diff --git a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc index 6f78608809..d16c7f2eb6 100644 --- a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc +++ b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.inc @@ -75,4 +75,13 @@ function foo() { function foo(); } } + + // But disallow functions nested inside those methods + if (class_exists('NestedFunctionInMethod') === false) { + class NestedFunctionInMethod { + function foo() { + function innerFunction() {} + } + } + } } diff --git a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php index 606d23563c..b0b13b6147 100644 --- a/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/InnerFunctionsUnitTest.php @@ -29,6 +29,7 @@ public function getErrorList() 5 => 1, 46 => 1, 55 => 1, + 83 => 1, ]; }//end getErrorList() From 47b9cbb75585db5c1c8917f649806f5f7294952e Mon Sep 17 00:00:00 2001 From: Daimona Eaytoy Date: Wed, 19 Apr 2023 23:43:02 +0200 Subject: [PATCH 669/733] Micro-optimize array_reverse call --- src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php index 721c0aeda8..e42ef5a16a 100644 --- a/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php @@ -46,7 +46,8 @@ public function process(File $phpcsFile, $stackPtr) return; } - $reversedConditions = array_reverse($tokens[$stackPtr]['conditions'], true); + $conditions = $tokens[$stackPtr]['conditions']; + $reversedConditions = array_reverse($conditions, true); $outerFuncToken = null; foreach ($reversedConditions as $condToken => $condition) { From af44889ad0cb71e5dd360b88b5bbcad1d449eebe Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 21 Apr 2023 11:50:01 +0200 Subject: [PATCH 670/733] Reports/GitBlame: Fix invocation path of git blame After we change into the files directory, invocation of git blame with full path will end in fatal error. --- src/Reports/Gitblame.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Reports/Gitblame.php b/src/Reports/Gitblame.php index 6427567f8d..0919e0e0ba 100644 --- a/src/Reports/Gitblame.php +++ b/src/Reports/Gitblame.php @@ -70,7 +70,7 @@ protected function getBlameContent($filename) $cwd = getcwd(); chdir(dirname($filename)); - $command = 'git blame --date=short "'.$filename.'" 2>&1'; + $command = 'git blame --date=short "'.basename($filename).'" 2>&1'; $handle = popen($command, 'r'); if ($handle === false) { $error = 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL; From 6891c046f1b30af622aa5bca14ea27dc42e476d6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 3 May 2023 14:09:20 +0200 Subject: [PATCH 671/733] PSR12/FileHeader: bug fix - false positives on PHP 8.2+ readonly classes As reported in https://github.com/squizlabs/PHP_CodeSniffer/issues/3799#issuecomment-1532870980, the `FileHeader` sniff did not take the new PHP 8.2+ `readonly` OO modifier keyword into account. Fixed now. Includes test. --- package.xml | 1 + .../PSR12/Sniffs/Files/FileHeaderSniff.php | 1 + .../PSR12/Tests/Files/FileHeaderUnitTest.18.inc | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.18.inc diff --git a/package.xml b/package.xml index 9338b46e79..3336ca16e3 100644 --- a/package.xml +++ b/package.xml @@ -1233,6 +1233,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php b/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php index 8a8255cf47..c3d0d0cadc 100644 --- a/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php +++ b/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php @@ -188,6 +188,7 @@ public function getHeaderLines(File $phpcsFile, $stackPtr) if (isset($commentOpeners[$tokens[$docToken]['code']]) === false && isset(Tokens::$methodPrefixes[$tokens[$docToken]['code']]) === false + && $tokens[$docToken]['code'] !== T_READONLY ) { // Check for an @var annotation. $annotation = false; diff --git a/src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.18.inc b/src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.18.inc new file mode 100644 index 0000000000..be7c11ac91 --- /dev/null +++ b/src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.18.inc @@ -0,0 +1,16 @@ + Date: Thu, 4 May 2023 16:02:32 +1000 Subject: [PATCH 672/733] Changelog for #3722 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 9338b46e79..f1d96ae606 100644 --- a/package.xml +++ b/package.xml @@ -55,6 +55,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 + - Fixed bug #3722 : Potential "Uninitialized string offset 1" in octal notation backfill + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From 934756b7d6db221cb20a11fb0b0ace2ae18944df Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 4 May 2023 16:19:26 +1000 Subject: [PATCH 673/733] Changelog for #3789 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index f1d96ae606..ef9a6a3147 100644 --- a/package.xml +++ b/package.xml @@ -57,6 +57,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3722 : Potential "Uninitialized string offset 1" in octal notation backfill -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3789 : Incorrect tokenization for ternary operator with match inside of it + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From 73dafe5f2d93f77ccb6c1dc9251e6b6f3e370d3f Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 4 May 2023 16:30:11 +1000 Subject: [PATCH 674/733] Changelog for #3797 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index ef9a6a3147..221e016544 100644 --- a/package.xml +++ b/package.xml @@ -59,6 +59,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3789 : Incorrect tokenization for ternary operator with match inside of it -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3797 : Tokenizer/PHP: more context sensitive keyword fixes + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From ce123ff6598b6cb9c66a35ef9bd23151e6593479 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 4 May 2023 17:14:24 +1000 Subject: [PATCH 675/733] Changelog for #3716 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 221e016544..c8aa266b9e 100644 --- a/package.xml +++ b/package.xml @@ -31,6 +31,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 + - Sniff error messages are now more informative to help bugs get reported to the correct project + -- Thanks to Juliette Reinders Folmer (@jrfnl) 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 From afe2026b3664929461ebeb8ba958fc8be6e04860 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 4 May 2023 17:45:59 +1000 Subject: [PATCH 676/733] Changelog for #3779 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index c8aa266b9e..0b295c3787 100644 --- a/package.xml +++ b/package.xml @@ -59,6 +59,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3722 : Potential "Uninitialized string offset 1" in octal notation backfill -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3779 : Squiz/LowercasePHPFunctions + Generic/ForbiddenFunctions: bug fix for class names in attributes + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3789 : Incorrect tokenization for ternary operator with match inside of it -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3797 : Tokenizer/PHP: more context sensitive keyword fixes From 2f09ce7e88f217fc6ce390fc2d33e23332ad6066 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 19 Apr 2023 08:57:48 +0200 Subject: [PATCH 677/733] Generic/FunctionCallArgumentSpacing: prevent fixer conflict over PHP 7.3+ trailing comma's in function calls As of PHP 7.3, trailing comma's in function calls are allowed. This sniff should leave those alone to prevent fixer conflicts with sniffs dealing with the spacing around the parentheses of a function call, like the `PEAR.Functions.FunctionCallSignature` sniff, which this sniff is often combined with. While the sniff did take trailing comma's into account for multi-line function calls, it did not handle them correctly for single-line function calls. Note: too _much_ space can still be removed, but space should be added. Fixed now. Includes tests. --- .../Functions/FunctionCallArgumentSpacingSniff.php | 11 +++++++---- .../Functions/FunctionCallArgumentSpacingUnitTest.inc | 10 ++++++++++ .../FunctionCallArgumentSpacingUnitTest.inc.fixed | 10 ++++++++++ .../Functions/FunctionCallArgumentSpacingUnitTest.php | 1 + 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php index 136a1d4ae9..39fdae9526 100644 --- a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php +++ b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php @@ -156,10 +156,13 @@ public function checkSpacing(File $phpcsFile, $stackPtr, $openBracket) }//end if if ($tokens[($nextSeparator + 1)]['code'] !== T_WHITESPACE) { - $error = 'No space found after comma in argument list'; - $fix = $phpcsFile->addFixableError($error, $nextSeparator, 'NoSpaceAfterComma'); - if ($fix === true) { - $phpcsFile->fixer->addContent($nextSeparator, ' '); + // Ignore trailing comma's after last argument as that's outside the scope of this sniff. + if (($nextSeparator + 1) !== $closeBracket) { + $error = 'No space found after comma in argument list'; + $fix = $phpcsFile->addFixableError($error, $nextSeparator, 'NoSpaceAfterComma'); + if ($fix === true) { + $phpcsFile->fixer->addContent($nextSeparator, ' '); + } } } else { // If there is a newline in the space, then they must be formatting diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc index 7c86a89f01..8bd067b3f9 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc @@ -162,3 +162,13 @@ class Testing extends Bar $a = new parent($foo ,$bar); } } + +// Ignore spacing after PHP 7.3+ trailing comma in single-line function calls to prevent fixer conflicts. +// This is something which should be decided by a sniff dealing with the function call parentheses. +$foo = new MyClass($obj, 'getMethod',); +$foo = new MyClass($obj, 'getMethod', ); +$foo = new MyClass($obj, 'getMethod', ); +$foo = new MyClass( + $obj, + 'getMethod', +); diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed index 8c9800725a..69676524dc 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.inc.fixed @@ -162,3 +162,13 @@ class Testing extends Bar $a = new parent($foo, $bar); } } + +// Ignore spacing after PHP 7.3+ trailing comma in single-line function calls to prevent fixer conflicts. +// This is something which should be decided by a sniff dealing with the function call parentheses. +$foo = new MyClass($obj, 'getMethod',); +$foo = new MyClass($obj, 'getMethod', ); +$foo = new MyClass($obj, 'getMethod', ); +$foo = new MyClass( + $obj, + 'getMethod', +); diff --git a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php index 789b60b37c..5f87962e62 100644 --- a/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php +++ b/src/Standards/Generic/Tests/Functions/FunctionCallArgumentSpacingUnitTest.php @@ -55,6 +55,7 @@ public function getErrorList() 154 => 2, 155 => 1, 162 => 2, + 170 => 1, ]; }//end getErrorList() From c1a30b1f8b9aedf630633c30437c351949fd66a9 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 4 May 2023 17:53:46 +1000 Subject: [PATCH 678/733] Updated changelog for #3777 and #3805 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index 0b295c3787..b955ac1647 100644 --- a/package.xml +++ b/package.xml @@ -59,12 +59,16 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3722 : Potential "Uninitialized string offset 1" in octal notation backfill -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3777 : Squiz/NonExecutableCode: slew of bug fixes, mostly related to modern PHP + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3779 : Squiz/LowercasePHPFunctions + Generic/ForbiddenFunctions: bug fix for class names in attributes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3789 : Incorrect tokenization for ternary operator with match inside of it -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3797 : Tokenizer/PHP: more context sensitive keyword fixes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3805 : Generic/FunctionCallArgumentSpacing: prevent fixer conflict over PHP 7.3+ trailing comma's in function calls + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From c85ac6ad4c1005070122cb9382b105185ac40a8a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 4 May 2023 18:02:35 +1000 Subject: [PATCH 679/733] Changelog for #3787 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index b955ac1647..594a24b428 100644 --- a/package.xml +++ b/package.xml @@ -63,6 +63,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3779 : Squiz/LowercasePHPFunctions + Generic/ForbiddenFunctions: bug fix for class names in attributes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3787 : PEAR/Squiz/[MultiLine]FunctionDeclaration: allow for PHP 8.1 new in initializers + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3789 : Incorrect tokenization for ternary operator with match inside of it -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3797 : Tokenizer/PHP: more context sensitive keyword fixes From 3b093de505cb978603d3f48b6cc0f94d3a5ba93c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 8 Jul 2022 22:36:00 +0200 Subject: [PATCH 680/733] PHP 8.2 | Ruleset: prevent notices about dynamic properties being set ### :warning: This PR contains two - albeit small - BC-breaks! :warning: The BC-breaks can be broken down as follows: ### For end-users If a property is set via a ruleset for an **_individual_** sniff and that sniff does not have the property explicitly declared, nor does the sniff declare the PHP magic `__set()` method or `extend stdClass`, the ruleset will now be regarded as invalid. In practice, this means the PHPCS run will halt and will show an informative notice to the end-user about the invalid property setting and the PHPCS run will exit with exit code `3`. For properties being set for a complete category of sniffs or a complete standard, the PHPCS run will not halt, nor show an error when the property isn't supported for all sniffs in the category/standard. In that case, the property will only be set on those sniffs which support it (i.e. either have the property explicitly declared on the sniff, or have the magic `__set()` method declared on the sniff, or `extend stdClass`) and will be silently ignored for all other sniffs. :point_right: Aside from possibly surfacing some incidental typos in property settings in rulesets, I expect this change to go mostly unnoticed by end-users. ### For developers of PHPCS standards/integrations 1. The format of the `$ruleset['sniff code']['properties']['property name']` sub-array has changed from a `array|string` type property value to a sub-array containing two entries: 1. `'value'` - containing the `array|string` type property value. 2. `'scope'` - containing a string, either `sniff` or `standard`, to indicate whether the ruleset property directive was for an individual sniff or for a whole standard/category of sniffs. 2. The third parameter for the `Ruleset::setSniffProperty()` method has been changed to expect the above mentioned array of `$settings` instead of just a property value. This change is necessary to allow for throwing the error notice when an invalid property is being set on an invalid sniff versus silently ignoring the invalid property if the ruleset specified it on a standard/category. :point_right: The impact of this BC-break is expected to be small, as it would require for an external standard or integration of PHPCS to: * `extend` the `Ruleset` class **and** overload the `setSniffProperty()` method to have any impact; * or to call the `setSniffProperty()` method on the `Ruleset` object directly (which _may_ happen in custom test frameworks for external standards). Note: * The change to the `processRule()` method cannot be overloaded as it is a `private` method. * The change in the `populateTokenListeners()` method is only cosmetic and has no functional impact. --- ### Technical choices made: For consistent handling of properties set in a (custom) ruleset across PHP versions, I have chosen to only support setting properties when: * A property is explicitly declared on a sniff class. * A sniff class explicitly declares (or inherits) the magic `__set()` method. * A sniff class `extends` `stdClass`. Note: no additional check has been added to verify that a declared property is `public`. If that is not the case a PHP native error would be thrown previously and still will be now. This behaviour has not changed. I have chosen **not** to respect the `#[\AllowDynamicProperties]` attribute as it is not possible (without tokenizing the sniff files which would create a lot of overhead) to determine whether that attribute exists on a class when PHPCS runs on PHP < 8.0 as the Reflection method needed to detect the attribute is only available on PHP 8.0+. In other words, adding support for the attribute would introduce an inconsistency in how properties set in a ruleset are handled based on the PHP version on which PHPCS is being run. It would also mean that the error notice for invalid properties set on individual sniffs would only be available on PHP 8.2+, which would greatly reduce the usefulness of the error notice. In my opinion, consistency here is more important than supporting the attribute, especially as there are three fully supported ways to allow for supporting properties to be set from a ruleset. The three above mentioned ways to allow for setting properties from a ruleset for a sniff are all fully supported on all PHP versions currently supported by PHPCS, so there is no compelling reason to also allow for the attribute. ### Suggested changelog entry > PHP 8.2: prevent deprecation notices for properties set in a (custom) ruleset for complete standards/complete sniff categories > - Invalid properties set for individual sniffs will now result in an error and halt the execution of PHPCS. > - Properties set for complete standards/complete sniff categories will now only be set on sniffs which explicitly support the property. > The property will be silently ignored for those sniffs which do not support the property. > - For sniff developers: it is strongly recommended for sniffs to explicitly declare any user-adjustable `public` properties. > If dynamic properties need to be supported for a sniff, either declare the magic `__set()`/`__get()`/`__isset()`/`__unset()` methods on the sniff or let the sniff extend `stdClass`. > Note: The `#[\AllowDynamicProperties]` attribute will have **no effect** for properties which are being set in rulesets. > - Sniff developers/integrators of PHPCS may need to make some small adjustments to allow for changes to the PHPCS internals. See the description of PR 3629 for full details. ### Other notes The changes are accompanied by a full set of tests covering the change. There is a little overlap between the `RuleInclusionTest` class and the new `SetSniffPropertyTest` class, but for the most part, the tests compliment each other. Includes: * Correct handling of properties being set via `phpcs:set` annotations. * The `RuleInclusionTest` XML fixture and test expectations have been adjusted to allow for these changes. 1. As we now need "settable" properties to exist on sniffs to use for the `testSettingProperties()` test, the `PSR1` ruleset is not sufficient as the sniffs in that ruleset don't have public properties. For that reason, the "set property for complete standard" test has been changed to use `PSR2` instead and set the `indent` property. The test expectations has been adjusted to match. 2. Along the same lines, the `Zend.NamingConventions` sniffs don't have public properties, so the "setting property for complete category" test has been switched to `PSR12.Operators` and set the `ignoreSpacingBeforeAssignments` property. The test expectations has been adjusted to match. 3. In both these cases, the XML `` now contains both a property which is valid for at least some sniffs in the standard/category and a property which is invalid in for all sniffs. For the _invalid_ properties, a separate `testSettingInvalidPropertiesOnStandardsAndCategoriesSilentlyFails()` test with data provider has been added to verify these will not be set. 4. As the ruleset has changed, the expectations for the `testHasSniffCodes()` test and the `testRegisteredSniffCodes()` test have been updated to match. 5. The test descriptions for the test cases in the `dataSettingProperties()` have also been updated to make it more explicit what each test case is testing. * The PHPCS ruleset has been updated to exclude sniffs which function as test fixtures from the PHPCS check for this repo. * A minor tweak has been made to the `ValidatePEARPackageXML` script to prevent a warning about sniffs which function as test fixtures. These test fixtures should receive the `role="test"` attribute, not a `role="php"` attribute. Fixes 3489 --- package.xml | 45 ++++ phpcs.xml.dist | 1 + .../ValidatePEAR/ValidatePEARPackageXML.php | 5 +- src/Files/File.php | 14 +- src/Ruleset.php | 64 ++++- .../SetPropertyAllowedAsDeclaredSniff.php | 28 ++ .../SetPropertyAllowedViaMagicMethodSniff.php | 40 +++ .../SetPropertyAllowedViaStdClassSniff.php | 26 ++ ...SetPropertyNotAllowedViaAttributeSniff.php | 27 ++ tests/Core/Ruleset/RuleInclusionTest.php | 244 ++++++++++++++++-- tests/Core/Ruleset/RuleInclusionTest.xml | 9 +- .../SetPropertyAllowedAsDeclaredTest.xml | 16 ++ .../SetPropertyAllowedViaMagicMethodTest.xml | 16 ++ .../SetPropertyAllowedViaStdClassTest.xml | 16 ++ ...PropertyToMultipleSniffsInCategoryTest.xml | 9 + ...nInvalidPropertyWhenSetForCategoryTest.xml | 9 + ...nInvalidPropertyWhenSetForStandardTest.xml | 9 + .../SetPropertyNotAllowedViaAttributeTest.xml | 10 + ...opertyThrowsErrorOnInvalidPropertyTest.xml | 9 + tests/Core/Ruleset/SetSniffPropertyTest.php | 233 +++++++++++++++++ 20 files changed, 783 insertions(+), 47 deletions(-) create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedAsDeclaredSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedViaMagicMethodSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedViaStdClassSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyNotAllowedViaAttributeSniff.php create mode 100644 tests/Core/Ruleset/SetPropertyAllowedAsDeclaredTest.xml create mode 100644 tests/Core/Ruleset/SetPropertyAllowedViaMagicMethodTest.xml create mode 100644 tests/Core/Ruleset/SetPropertyAllowedViaStdClassTest.xml create mode 100644 tests/Core/Ruleset/SetPropertyAppliesPropertyToMultipleSniffsInCategoryTest.xml create mode 100644 tests/Core/Ruleset/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCategoryTest.xml create mode 100644 tests/Core/Ruleset/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForStandardTest.xml create mode 100644 tests/Core/Ruleset/SetPropertyNotAllowedViaAttributeTest.xml create mode 100644 tests/Core/Ruleset/SetPropertyThrowsErrorOnInvalidPropertyTest.xml create mode 100644 tests/Core/Ruleset/SetSniffPropertyTest.php diff --git a/package.xml b/package.xml index 594a24b428..1ef124d7b0 100644 --- a/package.xml +++ b/package.xml @@ -138,6 +138,16 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + + + @@ -145,6 +155,15 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + + @@ -2128,6 +2147,19 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + + + + + + @@ -2236,6 +2268,19 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + + + + + + diff --git a/phpcs.xml.dist b/phpcs.xml.dist index c9dfc72ee8..3c54610094 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -10,6 +10,7 @@ */src/Standards/*/Tests/*\.(inc|css|js)$ */tests/Core/*/*\.(inc|css|js)$ + */tests/Core/*/Fixtures/*\.php$ diff --git a/scripts/ValidatePEAR/ValidatePEARPackageXML.php b/scripts/ValidatePEAR/ValidatePEARPackageXML.php index 11d73bc87b..5c1d2d1f0f 100644 --- a/scripts/ValidatePEAR/ValidatePEARPackageXML.php +++ b/scripts/ValidatePEAR/ValidatePEARPackageXML.php @@ -232,10 +232,11 @@ protected function checkFileTag($tag, $currentDirectory='') $valid = false; } else { // Limited validation of the "role" tags. - if (strpos($name, 'Test.') !== false && $role !== 'test') { + if ((strpos($name, 'tests/') === 0 || strpos($name, 'Test.') !== false) && $role !== 'test') { echo "- Test files should have the role 'test'. Found: '$role' for file '{$name}'.".PHP_EOL; $valid = false; - } else if ((strpos($name, 'Standard.xml') !== false || strpos($name, 'Sniff.php') !== false) + } else if (strpos($name, 'tests/') !== 0 + && (strpos($name, 'Standard.xml') !== false || strpos($name, 'Sniff.php') !== false) && $role !== 'php' ) { echo "- Sniff files, including sniff documentation files should have the role 'php'. Found: '$role' for file '{$name}'.".PHP_EOL; diff --git a/src/Files/File.php b/src/Files/File.php index ad1ca0b236..3266d6438d 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -379,9 +379,12 @@ public function process() if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) { $listenerCode = array_shift($parts); $propertyCode = array_shift($parts); - $propertyValue = rtrim(implode(' ', $parts), " */\r\n"); + $settings = [ + 'value' => rtrim(implode(' ', $parts), " */\r\n"), + 'scope' => 'sniff', + ]; $listenerClass = $this->ruleset->sniffCodes[$listenerCode]; - $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue); + $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $settings); } } } @@ -403,9 +406,12 @@ public function process() $listenerCode = $token['sniffCode']; if (isset($this->ruleset->sniffCodes[$listenerCode]) === true) { $propertyCode = $token['sniffProperty']; - $propertyValue = $token['sniffPropertyValue']; + $settings = [ + 'value' => $token['sniffPropertyValue'], + 'scope' => 'sniff', + ]; $listenerClass = $this->ruleset->sniffCodes[$listenerCode]; - $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue); + $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $settings); } } }//end if diff --git a/src/Ruleset.php b/src/Ruleset.php index f90e7b6d2b..dd9a7f4104 100644 --- a/src/Ruleset.php +++ b/src/Ruleset.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Util; +use stdClass; class Ruleset { @@ -960,6 +961,11 @@ private function processRule($rule, $newSniffs, $depth=0) if (isset($rule->properties) === true && $this->shouldProcessElement($rule->properties) === true ) { + $propertyScope = 'standard'; + if ($code === $ref || substr($ref, -9) === 'Sniff.php') { + $propertyScope = 'sniff'; + } + foreach ($rule->properties->property as $prop) { if ($this->shouldProcessElement($prop) === false) { continue; @@ -980,9 +986,9 @@ private function processRule($rule, $newSniffs, $depth=0) $values = []; if (isset($prop['extend']) === true && (string) $prop['extend'] === 'true' - && isset($this->ruleset[$code]['properties'][$name]) === true + && isset($this->ruleset[$code]['properties'][$name]['value']) === true ) { - $values = $this->ruleset[$code]['properties'][$name]; + $values = $this->ruleset[$code]['properties'][$name]['value']; } if (isset($prop->element) === true) { @@ -1017,7 +1023,10 @@ private function processRule($rule, $newSniffs, $depth=0) } }//end if - $this->ruleset[$code]['properties'][$name] = $values; + $this->ruleset[$code]['properties'][$name] = [ + 'value' => $values, + 'scope' => $propertyScope, + ]; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> array property \"$name\" set to \"$printValue\""; @@ -1028,7 +1037,10 @@ private function processRule($rule, $newSniffs, $depth=0) echo PHP_EOL; } } else { - $this->ruleset[$code]['properties'][$name] = (string) $prop['value']; + $this->ruleset[$code]['properties'][$name] = [ + 'value' => (string) $prop['value'], + 'scope' => $propertyScope, + ]; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"'; @@ -1218,8 +1230,8 @@ public function populateTokenListeners() // Set custom properties. if (isset($this->ruleset[$sniffCode]['properties']) === true) { - foreach ($this->ruleset[$sniffCode]['properties'] as $name => $value) { - $this->setSniffProperty($sniffClass, $name, $value); + foreach ($this->ruleset[$sniffCode]['properties'] as $name => $settings) { + $this->setSniffProperty($sniffClass, $name, $settings); } } @@ -1286,18 +1298,48 @@ public function populateTokenListeners() * * @param string $sniffClass The class name of the sniff. * @param string $name The name of the property to change. - * @param string $value The new value of the property. + * @param array $settings Array with the new value of the property and the scope of the property being set. * * @return void + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException When attempting to set a non-existent property on a sniff + * which doesn't declare the property or explicitly supports + * dynamic properties. */ - public function setSniffProperty($sniffClass, $name, $value) + public function setSniffProperty($sniffClass, $name, $settings) { // Setting a property for a sniff we are not using. if (isset($this->sniffs[$sniffClass]) === false) { return; } - $name = trim($name); + $name = trim($name); + $propertyName = $name; + if (substr($propertyName, -2) === '[]') { + $propertyName = substr($propertyName, 0, -2); + } + + $isSettable = false; + $sniffObject = $this->sniffs[$sniffClass]; + if (property_exists($sniffObject, $propertyName) === true + || ($sniffObject instanceof stdClass) === true + || method_exists($sniffObject, '__set') === true + ) { + $isSettable = true; + } + + if ($isSettable === false) { + if ($settings['scope'] === 'sniff') { + $notice = "Ruleset invalid. Property \"$propertyName\" does not exist on sniff "; + $notice .= array_search($sniffClass, $this->sniffCodes, true); + throw new RuntimeException($notice); + } + + return; + } + + $value = $settings['value']; + if (is_string($value) === true) { $value = trim($value); } @@ -1312,7 +1354,7 @@ public function setSniffProperty($sniffClass, $name, $value) } else if ($value === 'false') { $value = false; } else if (substr($name, -2) === '[]') { - $name = substr($name, 0, -2); + $name = $propertyName; $values = []; if ($value !== null) { foreach (explode(',', $value) as $val) { @@ -1328,7 +1370,7 @@ public function setSniffProperty($sniffClass, $name, $value) $value = $values; } - $this->sniffs[$sniffClass]->$name = $value; + $sniffObject->$name = $value; }//end setSniffProperty() diff --git a/tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedAsDeclaredSniff.php b/tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedAsDeclaredSniff.php new file mode 100644 index 0000000000..7eeed212f3 --- /dev/null +++ b/tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedAsDeclaredSniff.php @@ -0,0 +1,28 @@ +magic[$name] = $value; + } + + public function __get($name) + { + if (isset($this->magic[$name])) { + return $this->magic[$name]; + } + + return null; + } + + public function register() + { + return [T_WHITESPACE]; + } + + public function process(File $phpcsFile, $stackPtr) + { + // Do something. + } +} diff --git a/tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedViaStdClassSniff.php b/tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedViaStdClassSniff.php new file mode 100644 index 0000000000..6ffffad3e2 --- /dev/null +++ b/tests/Core/Ruleset/Fixtures/Sniffs/Category/SetPropertyAllowedViaStdClassSniff.php @@ -0,0 +1,26 @@ +assertObjectHasAttribute('sniffCodes', self::$ruleset); - $this->assertCount(14, self::$ruleset->sniffCodes); + $this->assertCount(48, self::$ruleset->sniffCodes); }//end testHasSniffCodes() /** - * Test that sniffs are correctly registered, independently on the syntax used to include the sniff. + * Test that sniffs are correctly registered, independently of the syntax used to include the sniff. * * @param string $key Expected array key. * @param string $value Expected array value. @@ -147,6 +147,54 @@ public function testRegisteredSniffCodes($key, $value) public function dataRegisteredSniffCodes() { return [ + [ + 'PSR2.Classes.ClassDeclaration', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes\ClassDeclarationSniff', + ], + [ + 'PSR2.Classes.PropertyDeclaration', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes\PropertyDeclarationSniff', + ], + [ + 'PSR2.ControlStructures.ControlStructureSpacing', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\ControlStructures\ControlStructureSpacingSniff', + ], + [ + 'PSR2.ControlStructures.ElseIfDeclaration', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\ControlStructures\ElseIfDeclarationSniff', + ], + [ + 'PSR2.ControlStructures.SwitchDeclaration', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\ControlStructures\SwitchDeclarationSniff', + ], + [ + 'PSR2.Files.ClosingTag', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Files\ClosingTagSniff', + ], + [ + 'PSR2.Files.EndFileNewline', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Files\EndFileNewlineSniff', + ], + [ + 'PSR2.Methods.FunctionCallSignature', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods\FunctionCallSignatureSniff', + ], + [ + 'PSR2.Methods.FunctionClosingBrace', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods\FunctionClosingBraceSniff', + ], + [ + 'PSR2.Methods.MethodDeclaration', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods\MethodDeclarationSniff', + ], + [ + 'PSR2.Namespaces.NamespaceDeclaration', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Namespaces\NamespaceDeclarationSniff', + ], + [ + 'PSR2.Namespaces.UseDeclaration', + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Namespaces\UseDeclarationSniff', + ], [ 'PSR1.Classes.ClassDeclaration', 'PHP_CodeSniffer\Standards\PSR1\Sniffs\Classes\ClassDeclarationSniff', @@ -180,8 +228,100 @@ public function dataRegisteredSniffCodes() 'PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\UpperCaseConstantNameSniff', ], [ - 'Zend.NamingConventions.ValidVariableName', - 'PHP_CodeSniffer\Standards\Zend\Sniffs\NamingConventions\ValidVariableNameSniff', + 'Generic.Files.LineEndings', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineEndingsSniff', + ], + [ + 'Generic.Files.LineLength', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff', + ], + [ + 'Squiz.WhiteSpace.SuperfluousWhitespace', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\SuperfluousWhitespaceSniff', + ], + [ + 'Generic.Formatting.DisallowMultipleStatements', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\Formatting\DisallowMultipleStatementsSniff', + ], + [ + 'Generic.WhiteSpace.ScopeIndent', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\WhiteSpace\ScopeIndentSniff', + ], + [ + 'Generic.WhiteSpace.DisallowTabIndent', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\WhiteSpace\DisallowTabIndentSniff', + ], + [ + 'Generic.PHP.LowerCaseKeyword', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\LowerCaseKeywordSniff', + ], + [ + 'Generic.PHP.LowerCaseConstant', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\LowerCaseConstantSniff', + ], + [ + 'Squiz.Scope.MethodScope', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\Scope\MethodScopeSniff', + ], + [ + 'Squiz.WhiteSpace.ScopeKeywordSpacing', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\ScopeKeywordSpacingSniff', + ], + [ + 'Squiz.Functions.FunctionDeclaration', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\Functions\FunctionDeclarationSniff', + ], + [ + 'Squiz.Functions.LowercaseFunctionKeywords', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\Functions\LowercaseFunctionKeywordsSniff', + ], + [ + 'Squiz.Functions.FunctionDeclarationArgumentSpacing', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\Functions\FunctionDeclarationArgumentSpacingSniff', + ], + [ + 'PEAR.Functions.ValidDefaultValue', + 'PHP_CodeSniffer\Standards\PEAR\Sniffs\Functions\ValidDefaultValueSniff', + ], + [ + 'Squiz.Functions.MultiLineFunctionDeclaration', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\Functions\MultiLineFunctionDeclarationSniff', + ], + [ + 'Generic.Functions.FunctionCallArgumentSpacing', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\FunctionCallArgumentSpacingSniff', + ], + [ + 'Squiz.ControlStructures.ControlSignature', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\ControlStructures\ControlSignatureSniff', + ], + [ + 'Squiz.WhiteSpace.ControlStructureSpacing', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\ControlStructureSpacingSniff', + ], + [ + 'Squiz.WhiteSpace.ScopeClosingBrace', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\ScopeClosingBraceSniff', + ], + [ + 'Squiz.ControlStructures.ForEachLoopDeclaration', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\ControlStructures\ForEachLoopDeclarationSniff', + ], + [ + 'Squiz.ControlStructures.ForLoopDeclaration', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\ControlStructures\ForLoopDeclarationSniff', + ], + [ + 'Squiz.ControlStructures.LowercaseDeclaration', + 'PHP_CodeSniffer\Standards\Squiz\Sniffs\ControlStructures\LowercaseDeclarationSniff', + ], + [ + 'Generic.ControlStructures.InlineControlStructure', + 'PHP_CodeSniffer\Standards\Generic\Sniffs\ControlStructures\InlineControlStructureSniff', + ], + [ + 'PSR12.Operators.OperatorSpacing', + 'PHP_CodeSniffer\Standards\PSR12\Sniffs\Operators\OperatorSpacingSniff', ], [ 'Generic.Arrays.ArrayIndent', @@ -191,10 +331,6 @@ public function dataRegisteredSniffCodes() 'Generic.Metrics.CyclomaticComplexity', 'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\CyclomaticComplexitySniff', ], - [ - 'Generic.Files.LineLength', - 'PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff', - ], [ 'Generic.NamingConventions.CamelCapsFunctionName', 'PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\CamelCapsFunctionNameSniff', @@ -242,49 +378,54 @@ public function testSettingProperties($sniffClass, $propertyName, $expectedValue public function dataSettingProperties() { return [ - 'ClassDeclarationSniff' => [ - 'PHP_CodeSniffer\Standards\PSR1\Sniffs\Classes\ClassDeclarationSniff', - 'setforallsniffs', - true, + 'Set property for complete standard: PSR2 ClassDeclaration' => [ + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes\ClassDeclarationSniff', + 'indent', + '20', ], - 'SideEffectsSniff' => [ - 'PHP_CodeSniffer\Standards\PSR1\Sniffs\Files\SideEffectsSniff', - 'setforallsniffs', - true, + 'Set property for complete standard: PSR2 SwitchDeclaration' => [ + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\ControlStructures\SwitchDeclarationSniff', + 'indent', + '20', ], - 'ValidVariableNameSniff' => [ - 'PHP_CodeSniffer\Standards\Zend\Sniffs\NamingConventions\ValidVariableNameSniff', - 'setforallincategory', - true, + 'Set property for complete standard: PSR2 FunctionCallSignature' => [ + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods\FunctionCallSignatureSniff', + 'indent', + '20', ], - 'ArrayIndentSniff' => [ + 'Set property for complete category: PSR12 OperatorSpacing' => [ + 'PHP_CodeSniffer\Standards\PSR12\Sniffs\Operators\OperatorSpacingSniff', + 'ignoreSpacingBeforeAssignments', + false, + ], + 'Set property for individual sniff: Generic ArrayIndent' => [ 'PHP_CodeSniffer\Standards\Generic\Sniffs\Arrays\ArrayIndentSniff', 'indent', '2', ], - 'LineLengthSniff' => [ + 'Set property for individual sniff using sniff file inclusion: Generic LineLength' => [ 'PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff', 'lineLimit', '10', ], - 'CamelCapsFunctionNameSniff' => [ + 'Set property for individual sniff using sniff file inclusion: CamelCapsFunctionName' => [ 'PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\CamelCapsFunctionNameSniff', 'strict', false, ], - 'NestingLevelSniff-nestingLevel' => [ + 'Set property for individual sniff via included ruleset: NestingLevel - nestingLevel' => [ 'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\NestingLevelSniff', 'nestingLevel', '2', ], - 'NestingLevelSniff-setforsniffsinincludedruleset' => [ + 'Set property for all sniffs in an included ruleset: NestingLevel - absoluteNestingLevel' => [ 'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\NestingLevelSniff', - 'setforsniffsinincludedruleset', + 'absoluteNestingLevel', true, ], // Testing that setting a property at error code level does *not* work. - 'CyclomaticComplexitySniff' => [ + 'Set property for error code will not change the sniff property value: CyclomaticComplexity' => [ 'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\CyclomaticComplexitySniff', 'complexity', 10, @@ -294,4 +435,53 @@ public function dataSettingProperties() }//end dataSettingProperties() + /** + * Test that setting properties for standards, categories on sniffs which don't support the property will + * silently ignore the property and not set it. + * + * @param string $sniffClass The name of the sniff class. + * @param string $propertyName The name of the property which should not be set. + * + * @dataProvider dataSettingInvalidPropertiesOnStandardsAndCategoriesSilentlyFails + * + * @return void + */ + public function testSettingInvalidPropertiesOnStandardsAndCategoriesSilentlyFails($sniffClass, $propertyName) + { + $this->assertObjectHasAttribute('sniffs', self::$ruleset, 'Ruleset does not have property sniffs'); + $this->assertArrayHasKey($sniffClass, self::$ruleset->sniffs, 'Sniff class '.$sniffClass.' not listed in registered sniffs'); + + $sniffObject = self::$ruleset->sniffs[$sniffClass]; + $this->assertObjectNotHasAttribute($propertyName, $sniffObject, 'Property '.$propertyName.' registered for sniff '.$sniffClass.' which does not support it'); + + }//end testSettingInvalidPropertiesOnStandardsAndCategoriesSilentlyFails() + + + /** + * Data provider. + * + * @see self::testSettingInvalidPropertiesOnStandardsAndCategoriesSilentlyFails() + * + * @return array + */ + public function dataSettingInvalidPropertiesOnStandardsAndCategoriesSilentlyFails() + { + return [ + 'Set property for complete standard: PSR2 ClassDeclaration' => [ + 'PHP_CodeSniffer\Standards\PSR1\Sniffs\Classes\ClassDeclarationSniff', + 'setforallsniffs', + ], + 'Set property for complete standard: PSR2 FunctionCallSignature' => [ + 'PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods\FunctionCallSignatureSniff', + 'setforallsniffs', + ], + 'Set property for complete category: PSR12 OperatorSpacing' => [ + 'PHP_CodeSniffer\Standards\PSR12\Sniffs\Operators\OperatorSpacingSniff', + 'setforallincategory', + ], + ]; + + }//end dataSettingInvalidPropertiesOnStandardsAndCategoriesSilentlyFails() + + }//end class diff --git a/tests/Core/Ruleset/RuleInclusionTest.xml b/tests/Core/Ruleset/RuleInclusionTest.xml index 06ce040e71..e1812bbba5 100644 --- a/tests/Core/Ruleset/RuleInclusionTest.xml +++ b/tests/Core/Ruleset/RuleInclusionTest.xml @@ -1,15 +1,17 @@ - + + - + + @@ -38,8 +40,9 @@ + - + diff --git a/tests/Core/Ruleset/SetPropertyAllowedAsDeclaredTest.xml b/tests/Core/Ruleset/SetPropertyAllowedAsDeclaredTest.xml new file mode 100644 index 0000000000..21dba9da8f --- /dev/null +++ b/tests/Core/Ruleset/SetPropertyAllowedAsDeclaredTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/tests/Core/Ruleset/SetPropertyAllowedViaMagicMethodTest.xml b/tests/Core/Ruleset/SetPropertyAllowedViaMagicMethodTest.xml new file mode 100644 index 0000000000..03424db26e --- /dev/null +++ b/tests/Core/Ruleset/SetPropertyAllowedViaMagicMethodTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/tests/Core/Ruleset/SetPropertyAllowedViaStdClassTest.xml b/tests/Core/Ruleset/SetPropertyAllowedViaStdClassTest.xml new file mode 100644 index 0000000000..4e4fd59e65 --- /dev/null +++ b/tests/Core/Ruleset/SetPropertyAllowedViaStdClassTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/tests/Core/Ruleset/SetPropertyAppliesPropertyToMultipleSniffsInCategoryTest.xml b/tests/Core/Ruleset/SetPropertyAppliesPropertyToMultipleSniffsInCategoryTest.xml new file mode 100644 index 0000000000..cb4f489d37 --- /dev/null +++ b/tests/Core/Ruleset/SetPropertyAppliesPropertyToMultipleSniffsInCategoryTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/Core/Ruleset/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCategoryTest.xml b/tests/Core/Ruleset/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCategoryTest.xml new file mode 100644 index 0000000000..044414c0f5 --- /dev/null +++ b/tests/Core/Ruleset/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCategoryTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/Core/Ruleset/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForStandardTest.xml b/tests/Core/Ruleset/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForStandardTest.xml new file mode 100644 index 0000000000..759c6c655a --- /dev/null +++ b/tests/Core/Ruleset/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForStandardTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/Core/Ruleset/SetPropertyNotAllowedViaAttributeTest.xml b/tests/Core/Ruleset/SetPropertyNotAllowedViaAttributeTest.xml new file mode 100644 index 0000000000..d9f808e383 --- /dev/null +++ b/tests/Core/Ruleset/SetPropertyNotAllowedViaAttributeTest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/Core/Ruleset/SetPropertyThrowsErrorOnInvalidPropertyTest.xml b/tests/Core/Ruleset/SetPropertyThrowsErrorOnInvalidPropertyTest.xml new file mode 100644 index 0000000000..435601affb --- /dev/null +++ b/tests/Core/Ruleset/SetPropertyThrowsErrorOnInvalidPropertyTest.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/Core/Ruleset/SetSniffPropertyTest.php b/tests/Core/Ruleset/SetSniffPropertyTest.php new file mode 100644 index 0000000000..4fa59f305a --- /dev/null +++ b/tests/Core/Ruleset/SetSniffPropertyTest.php @@ -0,0 +1,233 @@ + + * @copyright 2022 Juliette Reinders Folmer. All rights reserved. + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Ruleset; + +use PHP_CodeSniffer\Config; +use PHP_CodeSniffer\Ruleset; +use PHPUnit\Framework\TestCase; + +/** + * These tests specifically focus on the changes made to work around the PHP 8.2 dynamic properties deprecation. + * + * @covers \PHP_CodeSniffer\Ruleset::setSniffProperty + */ +class SetSniffPropertyTest extends TestCase +{ + + + /** + * Initialize the test. + * + * @return void + */ + public function setUp() + { + if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === true) { + // PEAR installs test and sniff files into different locations + // so these tests will not pass as they directly reference files + // by relative location. + $this->markTestSkipped('Test cannot run from a PEAR install'); + } + + }//end setUp() + + + /** + * Test that setting a property via the ruleset works in all situations which allow for it. + * + * @param string $name Name of the test. Used for the sniff name, the ruleset file name etc. + * + * @dataProvider dataSniffPropertiesGetSetWhenAllowed + * + * @return void + */ + public function testSniffPropertiesGetSetWhenAllowed($name) + { + $sniffCode = "Fixtures.Category.{$name}"; + $sniffClass = 'Fixtures\Sniffs\Category\\'.$name.'Sniff'; + $properties = [ + 'arbitrarystring' => 'arbitraryvalue', + 'arbitraryarray' => [ + 'mykey' => 'myvalue', + 'otherkey' => 'othervalue', + ], + ]; + + // Set up the ruleset. + $standard = __DIR__."/{$name}Test.xml"; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + // Verify that the sniff has been registered. + $this->assertGreaterThan(0, count($ruleset->sniffCodes), 'No sniff codes registered'); + + // Verify that our target sniff has been registered. + $this->assertArrayHasKey($sniffCode, $ruleset->sniffCodes, 'Target sniff not registered'); + $this->assertSame($sniffClass, $ruleset->sniffCodes[$sniffCode], 'Target sniff not registered with the correct class'); + + // Test that the property as declared in the ruleset has been set on the sniff. + $this->assertArrayHasKey($sniffClass, $ruleset->sniffs, 'Sniff class not listed in registered sniffs'); + + $sniffObject = $ruleset->sniffs[$sniffClass]; + foreach ($properties as $name => $expectedValue) { + $this->assertSame($expectedValue, $sniffObject->$name, 'Property value not set to expected value'); + } + + }//end testSniffPropertiesGetSetWhenAllowed() + + + /** + * Data provider. + * + * @see self::testSniffPropertiesGetSetWhenAllowed() + * + * @return array + */ + public function dataSniffPropertiesGetSetWhenAllowed() + { + return [ + 'Property allowed as explicitly declared' => ['SetPropertyAllowedAsDeclared'], + 'Property allowed as sniff extends stdClass' => ['SetPropertyAllowedViaStdClass'], + 'Property allowed as sniff has magic __set() method' => ['SetPropertyAllowedViaMagicMethod'], + ]; + + }//end dataSniffPropertiesGetSetWhenAllowed() + + + /** + * Test that setting a property for a category will apply it correctly to those sniffs which support the + * property, but won't apply it to sniffs which don't. + * + * Note: this test intentionally uses the `PEAR.Functions` category as two sniffs in that category + * have a public property with the same name (`indent`) and one sniff doesn't, which makes it a great + * test case for this. + * + * @return void + */ + public function testSetPropertyAppliesPropertyToMultipleSniffsInCategory() + { + $propertyName = 'indent'; + $expectedValue = '10'; + + // Set up the ruleset. + $standard = __DIR__.'/SetPropertyAppliesPropertyToMultipleSniffsInCategoryTest.xml'; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + // Test that the two sniffs which support the property have received the value. + $sniffClass = 'PHP_CodeSniffer\Standards\PEAR\Sniffs\Functions\FunctionCallSignatureSniff'; + $this->assertArrayHasKey($sniffClass, $ruleset->sniffs, 'Sniff class '.$sniffClass.' not listed in registered sniffs'); + $sniffObject = $ruleset->sniffs[$sniffClass]; + $this->assertSame($expectedValue, $sniffObject->$propertyName, 'Property value not set to expected value for '.$sniffClass); + + $sniffClass = 'PHP_CodeSniffer\Standards\PEAR\Sniffs\Functions\FunctionDeclarationSniff'; + $this->assertArrayHasKey($sniffClass, $ruleset->sniffs, 'Sniff class '.$sniffClass.' not listed in registered sniffs'); + $sniffObject = $ruleset->sniffs[$sniffClass]; + $this->assertSame($expectedValue, $sniffObject->$propertyName, 'Property value not set to expected value for '.$sniffClass); + + // Test that the property doesn't get set for the one sniff which doesn't support the property. + $sniffClass = 'PHP_CodeSniffer\Standards\PEAR\Sniffs\Functions\ValidDefaultValueSniff'; + $this->assertArrayHasKey($sniffClass, $ruleset->sniffs, 'Sniff class '.$sniffClass.' not listed in registered sniffs'); + $sniffObject = $ruleset->sniffs[$sniffClass]; + $this->assertObjectNotHasAttribute($propertyName, $sniffObject, 'Property registered for sniff '.$sniffClass.' which does not support it'); + + }//end testSetPropertyAppliesPropertyToMultipleSniffsInCategory() + + + /** + * Test that attempting to set a non-existent property directly on a sniff will throw an error + * when the sniff does not explicitly declare the property, extends stdClass or has magic methods. + * + * @return void + */ + public function testSetPropertyThrowsErrorOnInvalidProperty() + { + $exceptionClass = 'PHP_CodeSniffer\Exceptions\RuntimeException'; + $exceptionMsg = 'Ruleset invalid. Property "indentation" does not exist on sniff Generic.Arrays.ArrayIndent'; + if (method_exists($this, 'expectException') === true) { + $this->expectException($exceptionClass); + $this->expectExceptionMessage($exceptionMsg); + } else { + // PHPUnit < 5.2.0. + $this->setExpectedException($exceptionClass, $exceptionMsg); + } + + // Set up the ruleset. + $standard = __DIR__.'/SetPropertyThrowsErrorOnInvalidPropertyTest.xml'; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + }//end testSetPropertyThrowsErrorOnInvalidProperty() + + + /** + * Test that attempting to set a non-existent property directly on a sniff will throw an error + * when the sniff does not explicitly declare the property, extends stdClass or has magic methods, + * even though the sniff has the PHP 8.2 `#[AllowDynamicProperties]` attribute set. + * + * @return void + */ + public function testSetPropertyThrowsErrorWhenPropertyOnlyAllowedViaAttribute() + { + $exceptionClass = 'PHP_CodeSniffer\Exceptions\RuntimeException'; + $exceptionMsg = 'Ruleset invalid. Property "arbitrarystring" does not exist on sniff Fixtures.Category.SetPropertyNotAllowedViaAttribute'; + if (method_exists($this, 'expectException') === true) { + $this->expectException($exceptionClass); + $this->expectExceptionMessage($exceptionMsg); + } else { + // PHPUnit < 5.2.0. + $this->setExpectedException($exceptionClass, $exceptionMsg); + } + + // Set up the ruleset. + $standard = __DIR__.'/SetPropertyNotAllowedViaAttributeTest.xml'; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + }//end testSetPropertyThrowsErrorWhenPropertyOnlyAllowedViaAttribute() + + + /** + * Test that attempting to set a non-existent property on a sniff when the property directive is + * for the whole standard, does not yield an error. + * + * @doesNotPerformAssertions + * + * @return void + */ + public function testSetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForStandard() + { + // Set up the ruleset. + $standard = __DIR__.'/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForStandardTest.xml'; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + }//end testSetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForStandard() + + + /** + * Test that attempting to set a non-existent property on a sniff when the property directive is + * for a whole category, does not yield an error. + * + * @doesNotPerformAssertions + * + * @return void + */ + public function testSetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCategory() + { + // Set up the ruleset. + $standard = __DIR__.'/SetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCategoryTest.xml'; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + }//end testSetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCategory() + + +}//end class From 48c2ae364bca9e0e7a07aec306b77b39149ce472 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 4 May 2023 22:06:04 +0200 Subject: [PATCH 681/733] Ruleset::setSniffProperty(): add BC-layer for old format property values This commit adds a BC layer to handle property values passed to `Ruleset::setSniffProperty()` in the old (mixed) format. This BC-layer will never be hit when PHPCS is used from the CLI/with a ruleset. This BC-layer is only in place for integrations with PHPCS which may call the `Ruleset::setSniffProperty()` method directly. The `Ruleset::setSniffProperty()` will still handle properties passed in the old format correctly, but will also throw a deprecation notice to allow the maintainers of the integration to update their code. Includes dedicated tests to ensure this BC-layer works as intended. Note: this commit should **NOT** be ported to PHPCS 4.x. --- src/Ruleset.php | 28 +++ tests/Core/Ruleset/SetSniffPropertyTest.php | 180 ++++++++++++++++++++ 2 files changed, 208 insertions(+) diff --git a/src/Ruleset.php b/src/Ruleset.php index dd9a7f4104..5ba0d180fc 100644 --- a/src/Ruleset.php +++ b/src/Ruleset.php @@ -1319,6 +1319,34 @@ public function setSniffProperty($sniffClass, $name, $settings) $propertyName = substr($propertyName, 0, -2); } + /* + * BC-compatibility layer for $settings using the pre-PHPCS 3.8.0 format. + * + * Prior to PHPCS 3.8.0, `$settings` was expected to only contain the new _value_ + * for the property (which could be an array). + * Since PHPCS 3.8.0, `$settings` is expected to be an array with two keys: 'scope' + * and 'value', where 'scope' indicates whether the property should be set to the given 'value' + * for one individual sniff or for all sniffs in a standard. + * + * This BC-layer is only for integrations with PHPCS which may call this method directly + * and will be removed in PHPCS 4.0.0. + */ + + if (is_array($settings) === false + || isset($settings['scope'], $settings['value']) === false + ) { + // This will be an "old" format value. + $settings = [ + 'value' => $settings, + 'scope' => 'standard', + ]; + + trigger_error( + __FUNCTION__.': the format of the $settings parameter has changed from (mixed) $value to array(\'scope\' => \'sniff|standard\', \'value\' => $value). Please update your integration code. See PR #3629 for more information.', + E_USER_DEPRECATED + ); + } + $isSettable = false; $sniffObject = $this->sniffs[$sniffClass]; if (property_exists($sniffObject, $propertyName) === true diff --git a/tests/Core/Ruleset/SetSniffPropertyTest.php b/tests/Core/Ruleset/SetSniffPropertyTest.php index 4fa59f305a..9dbfc7bc88 100644 --- a/tests/Core/Ruleset/SetSniffPropertyTest.php +++ b/tests/Core/Ruleset/SetSniffPropertyTest.php @@ -230,4 +230,184 @@ public function testSetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCateg }//end testSetPropertyDoesNotThrowErrorOnInvalidPropertyWhenSetForCategory() + /** + * Test that setting a property via a direct call to the Ruleset::setSniffProperty() method + * sets the property correctly when using the new $settings array format. + * + * @return void + */ + public function testDirectCallWithNewArrayFormatSetsProperty() + { + $name = 'SetPropertyAllowedAsDeclared'; + $sniffCode = "Fixtures.Category.{$name}"; + $sniffClass = 'Fixtures\Sniffs\Category\\'.$name.'Sniff'; + + // Set up the ruleset. + $standard = __DIR__."/{$name}Test.xml"; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + $propertyName = 'arbitrarystring'; + $propertyValue = 'new value'; + + $ruleset->setSniffProperty( + $sniffClass, + $propertyName, + [ + 'scope' => 'sniff', + 'value' => $propertyValue, + ] + ); + + // Verify that the sniff has been registered. + $this->assertGreaterThan(0, count($ruleset->sniffCodes), 'No sniff codes registered'); + + // Verify that our target sniff has been registered. + $this->assertArrayHasKey($sniffCode, $ruleset->sniffCodes, 'Target sniff not registered'); + $this->assertSame($sniffClass, $ruleset->sniffCodes[$sniffCode], 'Target sniff not registered with the correct class'); + + // Test that the property as declared in the ruleset has been set on the sniff. + $this->assertArrayHasKey($sniffClass, $ruleset->sniffs, 'Sniff class not listed in registered sniffs'); + + $sniffObject = $ruleset->sniffs[$sniffClass]; + $this->assertSame($propertyValue, $sniffObject->$propertyName, 'Property value not set to expected value'); + + }//end testDirectCallWithNewArrayFormatSetsProperty() + + + /** + * Test that setting a property via a direct call to the Ruleset::setSniffProperty() method + * sets the property correctly when using the old $settings array format. + * + * Tested by silencing the deprecation notice as otherwise the test would fail on the deprecation notice. + * + * @param mixed $propertyValue Value for the property to set. + * + * @dataProvider dataDirectCallWithOldArrayFormatSetsProperty + * + * @return void + */ + public function testDirectCallWithOldArrayFormatSetsProperty($propertyValue) + { + $name = 'SetPropertyAllowedAsDeclared'; + $sniffCode = "Fixtures.Category.{$name}"; + $sniffClass = 'Fixtures\Sniffs\Category\\'.$name.'Sniff'; + + // Set up the ruleset. + $standard = __DIR__."/{$name}Test.xml"; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + $propertyName = 'arbitrarystring'; + + @$ruleset->setSniffProperty( + $sniffClass, + $propertyName, + $propertyValue + ); + + // Verify that the sniff has been registered. + $this->assertGreaterThan(0, count($ruleset->sniffCodes), 'No sniff codes registered'); + + // Verify that our target sniff has been registered. + $this->assertArrayHasKey($sniffCode, $ruleset->sniffCodes, 'Target sniff not registered'); + $this->assertSame($sniffClass, $ruleset->sniffCodes[$sniffCode], 'Target sniff not registered with the correct class'); + + // Test that the property as declared in the ruleset has been set on the sniff. + $this->assertArrayHasKey($sniffClass, $ruleset->sniffs, 'Sniff class not listed in registered sniffs'); + + $sniffObject = $ruleset->sniffs[$sniffClass]; + $this->assertSame($propertyValue, $sniffObject->$propertyName, 'Property value not set to expected value'); + + }//end testDirectCallWithOldArrayFormatSetsProperty() + + + /** + * Data provider. + * + * @see self::testDirectCallWithOldArrayFormatSetsProperty() + * + * @return array + */ + public function dataDirectCallWithOldArrayFormatSetsProperty() + { + return [ + 'Property value is not an array (boolean)' => [false], + 'Property value is not an array (string)' => ['a string'], + 'Property value is an empty array' => [[]], + 'Property value is an array without keys' => [ + [ + 'value', + false, + ], + ], + 'Property value is an array without the "scope" or "value" keys' => [ + [ + 'key1' => 'value', + 'key2' => false, + ], + ], + 'Property value is an array without the "scope" key' => [ + [ + 'key1' => 'value', + 'value' => true, + ], + ], + 'Property value is an array without the "value" key' => [ + [ + 'scope' => 'value', + 'key2' => 1234, + ], + ], + ]; + + }//end dataDirectCallWithOldArrayFormatSetsProperty() + + + /** + * Test that setting a property via a direct call to the Ruleset::setSniffProperty() method + * throws a deprecation notice when using the old $settings array format. + * + * Note: as PHPUnit stops as soon as it sees the deprecation notice, the setting of the property + * value is not tested here. + * + * @return void + */ + public function testDirectCallWithOldArrayFormatThrowsDeprecationNotice() + { + $exceptionClass = 'PHPUnit\Framework\Error\Deprecated'; + if (class_exists($exceptionClass) === false) { + $exceptionClass = 'PHPUnit_Framework_Error_Deprecated'; + } + + $exceptionMsg = 'the format of the $settings parameter has changed from (mixed) $value to array(\'scope\' => \'sniff|standard\', \'value\' => $value). Please update your integration code. See PR #3629 for more information.'; + + if (method_exists($this, 'expectException') === true) { + $this->expectException($exceptionClass); + $this->expectExceptionMessage($exceptionMsg); + } else { + // PHPUnit < 5.2.0. + $this->setExpectedException($exceptionClass, $exceptionMsg); + } + + $name = 'SetPropertyAllowedAsDeclared'; + $sniffCode = "Fixtures.Category.{$name}"; + $sniffClass = 'Fixtures\Sniffs\Category\\'.$name.'Sniff'; + + // Set up the ruleset. + $standard = __DIR__."/{$name}Test.xml"; + $config = new Config(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + $propertyName = 'arbitrarystring'; + + $ruleset->setSniffProperty( + $sniffClass, + 'arbitrarystring', + ['key' => 'value'] + ); + + }//end testDirectCallWithOldArrayFormatThrowsDeprecationNotice() + + }//end class From 8cdc1221e53a0c9b2f977ce36d87c8ff11dc125b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 23 Jul 2022 22:05:41 +0200 Subject: [PATCH 682/733] PHP 8.1 | PSR2/PropertyDeclaration: add check for visibility before readonly keyword The `PSR2.Classes.PropertyDeclaration` sniff includes a check to verify that the `static` keyword is _after_ the visibility in a property declaration. PHP 8.1 introduced the `readonly` keyword for properties. [PSR PER 2.0.0](https://www.php-fig.org/per/coding-style/#46-modifier-keywords) dictates that visibility is declared before the `readonly` keyword. All code samples in both the RFC as well as the PHP manual also use the `visibility - readonly` modifier keyword order, so it is likely that this will become the standard modifier keyword order. A search for PHP projects which have started to use the `readonly` keyword, also shows these predominantly use the `visibility - readonly` modifier keyword order. With that in mind, I'm proposing to add a check for this order to the `PSR2.Classes.PropertyDeclaration` sniff - this being the only PHPCS native sniff which checks the modifier keyword order for properties. As this sniff is included in PSR2 and PSR12, the new check will automatically apply the PSR-PER property modifier keyword order rules to PSR2/PSR12. Includes unit tests. Ref: * https://wiki.php.net/rfc/readonly_properties_v2 * https://www.php.net/manual/en/language.oop5.properties.php#language.oop5.properties.readonly-properties * https://sourcegraph.com/search?q=context:global+%5Cs%28public%7Cprotected%7Cprivate%29%5Cs%2Breadonly%5Cs%2B+lang:php+-file:%28%5E%7C/%29%28vendor%7Ctests%3F%29/+fork:no+archived:no&patternType=regexp (searching `visibility - readonly` order - > 500 results) * https://sourcegraph.com/search?q=context:global+%5Csreadonly%5Cs%2B%28public%7Cprotected%7Cprivate%29%5Cs%2B+lang:php+-file:%28%5E%7C/%29%28vendor%7Ctests%3F%29/+fork:no+archived:no&patternType=regexp (searching `readonly - visibility` order - 41 (non false positive) results) --- .../Classes/PropertyDeclarationSniff.php | 68 +++++++++++++++---- .../Classes/PropertyDeclarationUnitTest.inc | 4 ++ .../PropertyDeclarationUnitTest.inc.fixed | 4 ++ .../Classes/PropertyDeclarationUnitTest.php | 2 + 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php index efdbb43827..aca0be2d0d 100644 --- a/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php @@ -118,30 +118,70 @@ protected function processMemberVar(File $phpcsFile, $stackPtr) $phpcsFile->addError($error, $stackPtr, 'ScopeMissing', $data); } + /* + * Note: per PSR-PER section 4.6, the order should be: + * - Inheritance modifier: `abstract` or `final`. + * - Visibility modifier: `public`, `protected`, or `private`. + * - Scope modifier: `static`. + * - Mutation modifier: `readonly`. + * - Type declaration. + * - Name. + * + * Ref: https://www.php-fig.org/per/coding-style/#46-modifier-keywords + * + * At this time (PHP 8.2), inheritance modifiers cannot be applied to properties and + * the `static` and `readonly` modifiers are mutually exclusive and cannot be used together. + * + * Based on that, the below modifier keyword order checks are sufficient (for now). + */ + if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_static'] === true) { $scopePtr = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($stackPtr - 1)); $staticPtr = $phpcsFile->findPrevious(T_STATIC, ($stackPtr - 1)); - if ($scopePtr < $staticPtr) { - return; - } + if ($scopePtr > $staticPtr) { + $error = 'The static declaration must come after the visibility declaration'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'StaticBeforeVisibility'); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); - $error = 'The static declaration must come after the visibility declaration'; - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'StaticBeforeVisibility'); - if ($fix === true) { - $phpcsFile->fixer->beginChangeset(); + for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) { + if ($tokens[$i]['code'] !== T_WHITESPACE) { + break; + } - for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) { - if ($tokens[$i]['code'] !== T_WHITESPACE) { - break; + $phpcsFile->fixer->replaceToken($i, ''); } - $phpcsFile->fixer->replaceToken($i, ''); + $phpcsFile->fixer->replaceToken($scopePtr, ''); + $phpcsFile->fixer->addContentBefore($staticPtr, $propertyInfo['scope'].' '); + + $phpcsFile->fixer->endChangeset(); } + } + }//end if + + if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_readonly'] === true) { + $scopePtr = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($stackPtr - 1)); + $readonlyPtr = $phpcsFile->findPrevious(T_READONLY, ($stackPtr - 1)); + if ($scopePtr > $readonlyPtr) { + $error = 'The readonly declaration must come after the visibility declaration'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'ReadonlyBeforeVisibility'); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) { + if ($tokens[$i]['code'] !== T_WHITESPACE) { + break; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } - $phpcsFile->fixer->replaceToken($scopePtr, ''); - $phpcsFile->fixer->addContentBefore($staticPtr, $propertyInfo['scope'].' '); + $phpcsFile->fixer->replaceToken($scopePtr, ''); + $phpcsFile->fixer->addContentBefore($readonlyPtr, $propertyInfo['scope'].' '); - $phpcsFile->fixer->endChangeset(); + $phpcsFile->fixer->endChangeset(); + } } }//end if diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc index 33bec44e70..3e086c6f22 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc @@ -80,4 +80,8 @@ class ReadOnlyProp { protected readonly ?string $foo; readonly array $foo; + + readonly public int $wrongOrder1; + + readonly protected ?string $wrongOrder2; } diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed index df83112af2..c4e22fc18b 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.inc.fixed @@ -77,4 +77,8 @@ class ReadOnlyProp { protected readonly ?string $foo; readonly array $foo; + + public readonly int $wrongOrder1; + + protected readonly ?string $wrongOrder2; } diff --git a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php index f1dd0194d2..6a1778b904 100644 --- a/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/Classes/PropertyDeclarationUnitTest.php @@ -49,6 +49,8 @@ public function getErrorList() 76 => 1, 80 => 1, 82 => 1, + 84 => 1, + 86 => 1, ]; }//end getErrorList() From 8bdbd5671e6e1be6531c1dddd017c7dc7ddcaae7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 15 Feb 2023 23:52:01 +0000 Subject: [PATCH 683/733] PHP 8.1 | Config: fix passing null to non-nullable notice As per issue 3760, if the `stty size` command errors out, `shell_exec()` will return `null`. Per the [documentation](https://www.php.net/manual/en/function.shell-exec.php#refsect1-function.shell-exec-returnvalues), the `shell_exec()` command can also return `false`, which would also be an unusable value (`preg_match()` will not match, so `'auto'` would be cast to int, resulting in `0` being set as the value). If the output of `shell_exec()` would be `null`, PHP as of PHP 8.1 will throw a "passing null to non-nullable" notice. Additionally, if the output from `shell_exec()` would be `false`, the original input would be an invalid non-numeric value or the original input would be `auto`, but the `shell_exec()` command is disabled, the code as-it-was, would set the `reportWidth` to `0`, which is not that helpful. See: https://3v4l.org/GE7sK I've now changed the logic to: * If the received value is `auto`, check if a valid CLI width can be determined and only then use that value. * In all other cases, including when the value is set directly on the object, the value will be validated and if not valid, a default value of width `80` will be used, which is in line with the default width as stated in [the documentation](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options#setting-the-default-report-width). Fixes 3760 --- src/Config.php | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Config.php b/src/Config.php index bfca9e1d18..8d3fb173e6 100644 --- a/src/Config.php +++ b/src/Config.php @@ -89,6 +89,13 @@ class Config */ const STABILITY = 'stable'; + /** + * Default report width when no report width is provided and 'auto' does not yield a valid width. + * + * @var int + */ + const DEFAULT_REPORT_WIDTH = 80; + /** * An array of settings that PHPCS and PHPCBF accept. * @@ -223,13 +230,20 @@ public function __set($name, $value) switch ($name) { case 'reportWidth' : // Support auto terminal width. - if ($value === 'auto' - && function_exists('shell_exec') === true - && preg_match('|\d+ (\d+)|', shell_exec('stty size 2>&1'), $matches) === 1 - ) { - $value = (int) $matches[1]; - } else { + if ($value === 'auto' && function_exists('shell_exec') === true) { + $dimensions = shell_exec('stty size 2>&1'); + if (is_string($dimensions) === true && preg_match('|\d+ (\d+)|', $dimensions, $matches) === 1) { + $value = (int) $matches[1]; + break; + } + } + + if (is_int($value) === true) { + $value = abs($value); + } else if (is_string($value) === true && preg_match('`^\d+$`', $value) === 1) { $value = (int) $value; + } else { + $value = self::DEFAULT_REPORT_WIDTH; } break; case 'standards' : From e8e1d25324e758418c109bfd8ba8a9939c44315c Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sun, 7 May 2023 14:41:47 +1000 Subject: [PATCH 684/733] Changelog for #3637 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index 594a24b428..24be16c836 100644 --- a/package.xml +++ b/package.xml @@ -35,6 +35,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - PSR2.Classes.PropertyDeclaration now enforces that the readonly modifier comes after the visibility modifier + - PSR2 and PSR12 do not have documented rules for this as they pre-date the readonly modifier + - PSR-PER has been used to confirm the order of this keyword so it can be applied to PSR2 and PSR12 correctly + -- 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 From a26c071d00b415bba26cedd8f835fca6288cf6b9 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Sun, 7 May 2023 14:45:11 +1000 Subject: [PATCH 685/733] Changelog for #3761 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 24be16c836..855f19b560 100644 --- a/package.xml +++ b/package.xml @@ -31,6 +31,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 + - When using auto report width (the default) a value of 80 columns will be used if an auto width cannot be determined + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Sniff error messages are now more informative to help bugs get reported to the correct project -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions From 193f9a590c73e64da6e9e5cec7627352dcea5c59 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 5 May 2023 12:12:37 +0200 Subject: [PATCH 686/733] Config: add tests for the `reportWidth` setting Follow up on merged PR 3761 While definitively not a comprehensive set of tests for the `Config` class, this commit adds an initial set of tests for the `Config` class verifying that the `reportWidth` property is handled correctly. Note: these tests have been set up to already be compatible with PR 3803, which widens the supported PHPUnit version. :point_right: I have a separate commit ready for these tests for PHPCS 4.x as some changes are needed for PHPCS 4.x. --- package.xml | 5 + tests/Core/Config/ReportWidthTest.php | 304 ++++++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 tests/Core/Config/ReportWidthTest.php diff --git a/package.xml b/package.xml index 855f19b560..6baf8d84b4 100644 --- a/package.xml +++ b/package.xml @@ -119,6 +119,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + @@ -2109,6 +2112,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + @@ -2217,6 +2221,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/tests/Core/Config/ReportWidthTest.php b/tests/Core/Config/ReportWidthTest.php new file mode 100644 index 0000000000..aa5375a42c --- /dev/null +++ b/tests/Core/Config/ReportWidthTest.php @@ -0,0 +1,304 @@ + + * @copyright 2006-2023 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\Config; + +use PHP_CodeSniffer\Config; +use PHPUnit\Framework\TestCase; +use ReflectionProperty; + +class ReportWidthTest extends TestCase +{ + + + /** + * Set static properties in the Config class to prevent tests influencing each other. + * + * @before + * + * @return void + */ + public static function cleanConfig() + { + // Set to the property's default value to clear out potentially set values from other tests. + self::setStaticProperty('executablePaths', []); + + // Set to a usable value to circumvent Config trying to find a phpcs.xml config file. + self::setStaticProperty('overriddenDefaults', ['standards' => ['PSR1']]); + + // Set to values which prevent the test-runner user's `CodeSniffer.conf` file + // from being read and influencing the tests. + self::setStaticProperty('configData', []); + self::setStaticProperty('configDataFile', ''); + + }//end cleanConfig() + + + /** + * Clean up after each finished test. + * + * @after + * + * @return void + */ + public function resetConfig() + { + $_SERVER['argv'] = []; + + }//end resetConfig() + + + /** + * Reset the static properties in the Config class to their true defaults to prevent this class + * from unfluencing other tests. + * + * @afterClass + * + * @return void + */ + public static function resetConfigToDefaults() + { + self::setStaticProperty('overriddenDefaults', []); + self::setStaticProperty('executablePaths', []); + self::setStaticProperty('configData', null); + self::setStaticProperty('configDataFile', null); + $_SERVER['argv'] = []; + + }//end resetConfigToDefaults() + + + /** + * Test that report width without overrules will always be set to a non-0 positive integer. + * + * @return void + */ + public function testReportWidthDefault() + { + $config = new Config(); + + // Can't test the exact value as "auto" will resolve differently depending on the machine running the tests. + $this->assertTrue(is_int($config->reportWidth), 'Report width is not an integer'); + $this->assertGreaterThan(0, $config->reportWidth, 'Report width is not greater than 0'); + + }//end testReportWidthDefault() + + + /** + * Test that the report width will be set to a non-0 positive integer when not found in the CodeSniffer.conf file. + * + * @return void + */ + public function testReportWidthWillBeSetFromAutoWhenNotFoundInConfFile() + { + $phpCodeSnifferConfig = [ + 'default_standard' => 'PSR2', + 'show_warnings' => '0', + ]; + + $this->setStaticProperty('configData', $phpCodeSnifferConfig); + + $config = new Config(); + + // Can't test the exact value as "auto" will resolve differently depending on the machine running the tests. + $this->assertTrue(is_int($config->reportWidth), 'Report width is not an integer'); + $this->assertGreaterThan(0, $config->reportWidth, 'Report width is not greater than 0'); + + }//end testReportWidthWillBeSetFromAutoWhenNotFoundInConfFile() + + + /** + * Test that the report width will be set correctly when found in the CodeSniffer.conf file. + * + * @return void + */ + public function testReportWidthCanBeSetFromConfFile() + { + $phpCodeSnifferConfig = [ + 'default_standard' => 'PSR2', + 'report_width' => '120', + ]; + + $this->setStaticProperty('configData', $phpCodeSnifferConfig); + + $config = new Config(); + $this->assertSame(120, $config->reportWidth); + + }//end testReportWidthCanBeSetFromConfFile() + + + /** + * Test that the report width will be set correctly when passed as a CLI argument. + * + * @return void + */ + public function testReportWidthCanBeSetFromCLI() + { + $_SERVER['argv'] = [ + 'phpcs', + '--report-width=100', + ]; + + $config = new Config(); + $this->assertSame(100, $config->reportWidth); + + }//end testReportWidthCanBeSetFromCLI() + + + /** + * Test that the report width will be set correctly when multiple report widths are passed on the CLI. + * + * @return void + */ + public function testReportWidthWhenSetFromCLIFirstValuePrevails() + { + $_SERVER['argv'] = [ + 'phpcs', + '--report-width=100', + '--report-width=200', + ]; + + $config = new Config(); + $this->assertSame(100, $config->reportWidth); + + }//end testReportWidthWhenSetFromCLIFirstValuePrevails() + + + /** + * Test that a report width passed as a CLI argument will overrule a report width set in a CodeSniffer.conf file. + * + * @return void + */ + public function testReportWidthSetFromCLIOverrulesConfFile() + { + $phpCodeSnifferConfig = [ + 'default_standard' => 'PSR2', + 'report_format' => 'summary', + 'show_warnings' => '0', + 'show_progress' => '1', + 'report_width' => '120', + ]; + + $this->setStaticProperty('configData', $phpCodeSnifferConfig); + + $cliArgs = [ + 'phpcs', + '--report-width=180', + ]; + + $config = new Config($cliArgs); + $this->assertSame(180, $config->reportWidth); + + }//end testReportWidthSetFromCLIOverrulesConfFile() + + + /** + * Test that the report width will be set to a non-0 positive integer when set to "auto". + * + * @return void + */ + public function testReportWidthInputHandlingForAuto() + { + $config = new Config(); + $config->reportWidth = 'auto'; + + // Can't test the exact value as "auto" will resolve differently depending on the machine running the tests. + $this->assertTrue(is_int($config->reportWidth), 'Report width is not an integer'); + $this->assertGreaterThan(0, $config->reportWidth, 'Report width is not greater than 0'); + + }//end testReportWidthInputHandlingForAuto() + + + /** + * Test that the report width will be set correctly for various types of input. + * + * @param mixed $input Input value received. + * @param int $expected Expected report width. + * + * @dataProvider dataReportWidthInputHandling + * + * @return void + */ + public function testReportWidthInputHandling($input, $expected) + { + $config = new Config(); + $config->reportWidth = $input; + + $this->assertSame($expected, $config->reportWidth); + + }//end testReportWidthInputHandling() + + + /** + * Data provider. + * + * @return array + */ + public function dataReportWidthInputHandling() + { + return [ + 'No value (empty string)' => [ + 'value' => '', + 'expected' => Config::DEFAULT_REPORT_WIDTH, + ], + 'Value: invalid input type null' => [ + 'value' => null, + 'expected' => Config::DEFAULT_REPORT_WIDTH, + ], + 'Value: invalid input type false' => [ + 'value' => false, + 'expected' => Config::DEFAULT_REPORT_WIDTH, + ], + 'Value: invalid input type float' => [ + 'value' => 100.50, + 'expected' => Config::DEFAULT_REPORT_WIDTH, + ], + 'Value: invalid string value "invalid"' => [ + 'value' => 'invalid', + 'expected' => Config::DEFAULT_REPORT_WIDTH, + ], + 'Value: invalid string value, non-integer string "50.25"' => [ + 'value' => '50.25', + 'expected' => Config::DEFAULT_REPORT_WIDTH, + ], + 'Value: valid numeric string value' => [ + 'value' => '250', + 'expected' => 250, + ], + 'Value: valid int value' => [ + 'value' => 220, + 'expected' => 220, + ], + 'Value: negative int value becomes positive int' => [ + 'value' => -180, + 'expected' => 180, + ], + ]; + + }//end dataReportWidthInputHandling() + + + /** + * Helper function to set a static property on the Config class. + * + * @param string $name The name of the property to set. + * @param mixed $value The value to set the propert to. + * + * @return void + */ + public static function setStaticProperty($name, $value) + { + $property = new ReflectionProperty('PHP_CodeSniffer\Config', $name); + $property->setAccessible(true); + $property->setValue($value); + $property->setAccessible(false); + + }//end setStaticProperty() + + +}//end class From d439d9870cb45f4e5b9f64d1dcda9d302a026e47 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 16 May 2023 18:34:56 +0200 Subject: [PATCH 687/733] Add a pull request template This pull requests template has been set up to attempt to gather all information needed by the maintainers to evaluate the PR. It also is intended to act as a reminder for contributors to make sure the PR is complete and ready for review. --- .github/pull_request_template.md | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..4afe79d762 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,51 @@ + + + + +## Description + + + +### Suggested changelog entry + + + +### Related issues/external references + +Fixes # + + +## Types of changes + +- [ ] Bug fix _(non-breaking change which fixes an issue)_ +- [ ] New feature _(non-breaking change which adds functionality)_ +- [ ] Breaking change _(fix or feature that would cause existing functionality to change)_ + - [ ] This change is only breaking for integrators, not for external standards or end-users. +- [ ] Documentation improvement + + +## PR checklist + +- [ ] I have checked there is no other PR open for the same change. +- [ ] I have read the [Contribution Guidelines](.github/CONTRIBUTING.md). +- [ ] I grant the project the right to include and distribute the code under the BSD-3-Clause license (and I have the right to grant these rights). +- [ ] I have added tests to cover my changes. +- [ ] I have verified that the code complies with the projects coding standards. +- [ ] [Required for new sniffs] I have added XML documentation for the sniff. +- [ ] [Required for new files] I have added any new files to the `package.xml` file. + + From 70fb023393b552627e042331ee27854e13d67fe9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 16 May 2023 18:45:05 +0200 Subject: [PATCH 688/733] Bug report template: add notice about external standards ... in hopes of reducing the number of issues which are incorrectly reported to this repo. Includes automating the assignment of initial issue labels. --- .github/ISSUE_TEMPLATE/bug_report.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 00f6f43f12..de31018098 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,11 +2,22 @@ name: Bug report about: Create a report to help us improve title: '' -labels: '' +labels: 'Status: triage', 'Type: bug' assignees: '' --- + + **Describe the bug** A clear and concise description of what the bug is. From b206485fa18039358eb880e072f2a25f40e0cd63 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 18 May 2023 09:02:41 +0200 Subject: [PATCH 689/733] Bug template: fix yaml syntax --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index de31018098..2f740a4755 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: 'Status: triage', 'Type: bug' +labels: ['Status: triage', 'Type: bug'] assignees: '' --- From 87bdec693426d4ec1af1bd9dca2b1d7471114701 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 18 May 2023 17:17:41 +1000 Subject: [PATCH 690/733] Changelog for #3629 --- package.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/package.xml b/package.xml index 777a5163a9..69091451ec 100644 --- a/package.xml +++ b/package.xml @@ -26,6 +26,21 @@ http://pear.php.net/dtd/package-2.0.xsd"> BSD 3-Clause License + - Changes have been made to the way PHPCS handles invalid sniff properties being set in a custom ruleset + -- Fixes PHP 8.2 deprecation notices for properties set in a (custom) ruleset for complete standards/complete sniff categories + -- Invalid sniff properties set for individual sniffs will now result in an error and halt the execution of PHPCS + --- A descriptive error message is provided to allow users to fix their ruleset + -- Sniff properties set for complete standards/complete sniff categories will now only be set on sniffs which explicitly support the property + --- The property will be silently ignored for those sniffs which do not support the property + -- For sniff developers, it is strongly recommended for sniffs to explicitly declare any user-adjustable public properties + --- If dynamic properties need to be supported for a sniff, either declare the magic __set()/__get()/__isset()/__unset() methods on the sniff or let the sniff extend stdClass + --- Note: The #[\AllowDynamicProperties] attribute will have no effect for properties which are being set in rulesets. + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - The third parameter for the Ruleset::setSniffProperty() method has been changed to expect an array + -- Sniff developers/integrators of PHPCS may need to make some small adjustments to allow for this change + -- Existing code will continue to work but will throw a deprecation error + -- The backwards compatiblity layer will be removed in PHPCS 4.0 + -- Thanks to Juliette Reinders Folmer (@jrfnl) 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 From dd8ede12a571f496fc3dfa874c5497224d91e244 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 7 May 2023 03:57:33 +0200 Subject: [PATCH 691/733] GH Actions: automate some label management This is a quite straight-forward workflow to just remove some labels which should only be on open issues/open PRs and which should be removed once an issue or PR has been closed/merged. Just attempting to reduce yet some more manual labour. --- .github/workflows/manage-labels.yml | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/manage-labels.yml diff --git a/.github/workflows/manage-labels.yml b/.github/workflows/manage-labels.yml new file mode 100644 index 0000000000..f2b074e8c2 --- /dev/null +++ b/.github/workflows/manage-labels.yml @@ -0,0 +1,59 @@ +name: Remove outdated labels + +on: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target + issues: + types: + - closed + pull_request_target: + types: + - closed + +jobs: + on-issue-close: + runs-on: ubuntu-latest + if: github.repository_owner == 'squizlabs' && github.event.issue.state == 'closed' + + name: Clean up labels on issue close + + steps: + - uses: mondeja/remove-labels-gh-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + labels: | + Status: awaiting feedback + Status: close candidate + Status: needs investigation + Status: triage + + on-pr-merge: + runs-on: ubuntu-latest + if: github.repository_owner == 'squizlabs' && github.event.pull_request.merged == true + + name: Clean up labels on PR merge + + steps: + - uses: mondeja/remove-labels-gh-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + labels: | + Status: awaiting feedback + Status: close candidate + Status: needs investigation + Status: triage + + on-pr-close: + runs-on: ubuntu-latest + if: github.repository_owner == 'squizlabs' && github.event_name == 'pull_request_target' && github.event.pull_request.merged == false + + name: Clean up labels on PR close + + steps: + - uses: mondeja/remove-labels-gh-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + labels: | + Status: awaiting feedback + Status: close candidate + Status: needs investigation + Status: triage From 2922ea108200ab68dfa2e357d484238aad7fb47e Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 18 May 2023 17:51:58 +1000 Subject: [PATCH 692/733] Changelog for #3801 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 238d4498ff..59b2412381 100644 --- a/package.xml +++ b/package.xml @@ -90,6 +90,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3797 : Tokenizer/PHP: more context sensitive keyword fixes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3801 : File::getMethodParameters(): allow for readonly promoted properties without visibility + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3805 : Generic/FunctionCallArgumentSpacing: prevent fixer conflict over PHP 7.3+ trailing comma's in function calls -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From 4df1d5ed79aa649fcd062d5fd478bedeecc9dff1 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 18 May 2023 18:14:03 +1000 Subject: [PATCH 693/733] Changelog for #3686 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 450bcd325f..13b42874e7 100644 --- a/package.xml +++ b/package.xml @@ -50,6 +50,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Sniff error messages are now more informative to help bugs get reported to the correct project -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Added support for readonly classes to File::getClassProperties() through a new is_readonly array index in the return value + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - PSR2.Classes.PropertyDeclaration now enforces that the readonly modifier comes after the visibility modifier From 9eee9f5d5bf9afa0a5c70dde6edda6c00eddbc4f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 00:18:09 +0200 Subject: [PATCH 694/733] PHP 8.2 | Generic/UnnecessaryFinalModifier: allow for readonly classes Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_classes --- .../CodeAnalysis/UnnecessaryFinalModifierSniff.php | 10 +++------- .../CodeAnalysis/UnnecessaryFinalModifierUnitTest.inc | 5 +++++ .../CodeAnalysis/UnnecessaryFinalModifierUnitTest.php | 2 ++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php index bed67c93c5..6472273798 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php @@ -24,7 +24,6 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Tokens; class UnnecessaryFinalModifierSniff implements Sniff { @@ -56,16 +55,13 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); $token = $tokens[$stackPtr]; - // Skip for-statements without body. + // Skip for statements without body. if (isset($token['scope_opener']) === false) { return; } - // Fetch previous token. - $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); - - // Skip for non final class. - if ($prev === false || $tokens[$prev]['code'] !== T_FINAL) { + if ($phpcsFile->getClassProperties($stackPtr)['is_final'] === false) { + // This class is not final so we don't need to check it. return; } diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnnecessaryFinalModifierUnitTest.inc b/src/Standards/Generic/Tests/CodeAnalysis/UnnecessaryFinalModifierUnitTest.inc index 0ba5df778f..026cf48424 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnnecessaryFinalModifierUnitTest.inc +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnnecessaryFinalModifierUnitTest.inc @@ -27,3 +27,8 @@ final class Bar_Foo { protected function foo() {} private function Bar() {} } + +final readonly class Foo_Bar { + public final function fooBar() {} + final protected function fool() {} +} diff --git a/src/Standards/Generic/Tests/CodeAnalysis/UnnecessaryFinalModifierUnitTest.php b/src/Standards/Generic/Tests/CodeAnalysis/UnnecessaryFinalModifierUnitTest.php index 732f30318c..33bb160a04 100644 --- a/src/Standards/Generic/Tests/CodeAnalysis/UnnecessaryFinalModifierUnitTest.php +++ b/src/Standards/Generic/Tests/CodeAnalysis/UnnecessaryFinalModifierUnitTest.php @@ -45,6 +45,8 @@ public function getWarningList() 12 => 1, 15 => 1, 18 => 1, + 32 => 1, + 33 => 1, ]; }//end getWarningList() From 358362cf087b795d24861fce0df1fbec3a10abde Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 00:24:18 +0200 Subject: [PATCH 695/733] Generic/UnnecessaryFinalModifier: make the sniff more efficient No need to token walk the contents of functions as the `final` keyword cannot be used in them anyway. --- .../Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php b/src/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php index 6472273798..02ae2e7a32 100644 --- a/src/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php +++ b/src/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php @@ -73,6 +73,13 @@ public function process(File $phpcsFile, $stackPtr) $error = 'Unnecessary FINAL modifier in FINAL class'; $phpcsFile->addWarning($error, $next, 'Found'); } + + // Skip over the contents of functions as those can't contain the `final` keyword anyway. + if ($tokens[$next]['code'] === T_FUNCTION + && isset($tokens[$next]['scope_closer']) === true + ) { + $next = $tokens[$next]['scope_closer']; + } } }//end process() From 39b1956db3dd70a7394b30600c77f175e806a862 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 00:59:45 +0200 Subject: [PATCH 696/733] PHP 8.2 | PEAR/ClassComment: allow for readonly classes Includes improving the accuracy of the tokens to skip over. Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_classes --- .../Sniffs/Commenting/ClassCommentSniff.php | 9 ++++++--- .../Tests/Commenting/ClassCommentUnitTest.inc | 18 ++++++++++++++++++ .../Tests/Commenting/ClassCommentUnitTest.php | 2 ++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php index a01ea2cc7e..2e9b59b7bf 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php @@ -10,7 +10,6 @@ namespace PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Tokens; class ClassCommentSniff extends FileCommentSniff { @@ -48,8 +47,12 @@ public function process(File $phpcsFile, $stackPtr) $type = strtolower($tokens[$stackPtr]['content']); $errorData = [$type]; - $find = Tokens::$methodPrefixes; - $find[T_WHITESPACE] = T_WHITESPACE; + $find = [ + T_ABSTRACT => T_ABSTRACT, + T_FINAL => T_FINAL, + T_READONLY => T_READONLY, + T_WHITESPACE => T_WHITESPACE, + ]; for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) { if (isset($find[$tokens[$commentEnd]['code']]) === true) { diff --git a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc index da53b99e87..e340ff0041 100644 --- a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc +++ b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.inc @@ -143,3 +143,21 @@ enum Empty_Enum_Doc class TodoController extends AbstractController implements MustBeLoggedInInterface { } + +/** + * Docblock + * + * @category PHP + * @package PHP_CodeSniffer + * @author Greg Sherwood + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @link http://pear.php.net/package/PHP_CodeSniffer + */ +abstract readonly class AbstractReadonlyWithDocblock {} + +/* + * Docblock + */ +readonly class ReadonlyWrongStyle {} + +readonly final class ReadonlyFinalWithoutDocblock {} diff --git a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php index 004a064b35..73465fadc9 100644 --- a/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php +++ b/src/Standards/PEAR/Tests/Commenting/ClassCommentUnitTest.php @@ -45,6 +45,8 @@ public function getErrorList() 106 => 5, 116 => 5, 126 => 5, + 161 => 1, + 163 => 1, ]; }//end getErrorList() From af87457e0e034a0ba455b2fb98b915796c9faf4b Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 01:14:46 +0200 Subject: [PATCH 697/733] PHP 8.2 | PEAR/FileComment: allow for readonly classes Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_classes --- package.xml | 1 + src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php | 1 + .../PEAR/Tests/Commenting/FileCommentUnitTest.4.inc | 7 +++++++ .../PEAR/Tests/Commenting/FileCommentUnitTest.php | 3 +-- 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.4.inc diff --git a/package.xml b/package.xml index 13b42874e7..26b98d8133 100644 --- a/package.xml +++ b/package.xml @@ -1077,6 +1077,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php index 6a44584686..0009f804a6 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php @@ -170,6 +170,7 @@ public function process(File $phpcsFile, $stackPtr) T_FINAL, T_STATIC, T_ABSTRACT, + T_READONLY, T_CONST, T_PROPERTY, ]; diff --git a/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.4.inc b/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.4.inc new file mode 100644 index 0000000000..3c4f63598c --- /dev/null +++ b/src/Standards/PEAR/Tests/Commenting/FileCommentUnitTest.4.inc @@ -0,0 +1,7 @@ + 1]; - case 'FileCommentUnitTest.3.inc': + case 'FileCommentUnitTest.4.inc': return [1 => 1]; default: From 3441ef92b5b352db82d706028788ab67d2f1918f Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 01:29:30 +0200 Subject: [PATCH 698/733] PHP 8.2 | PSR2/ClassDeclaration: allow for readonly classes Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_classes --- .../PSR2/Sniffs/Classes/ClassDeclarationSniff.php | 3 ++- .../PSR2/Tests/Classes/ClassDeclarationUnitTest.inc | 9 +++++++++ .../Tests/Classes/ClassDeclarationUnitTest.inc.fixed | 8 ++++++++ .../PSR2/Tests/Classes/ClassDeclarationUnitTest.php | 2 ++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php index f96b004906..c620582d41 100644 --- a/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php @@ -71,7 +71,7 @@ public function processOpen(File $phpcsFile, $stackPtr) $blankSpace = substr($prevContent, strpos($prevContent, $phpcsFile->eolChar)); $spaces = strlen($blankSpace); - if (in_array($tokens[($stackPtr - 2)]['code'], [T_ABSTRACT, T_FINAL], true) === true + if (in_array($tokens[($stackPtr - 2)]['code'], [T_ABSTRACT, T_FINAL, T_READONLY], true) === true && $spaces !== 1 ) { $prevContent = strtolower($tokens[($stackPtr - 2)]['content']); @@ -89,6 +89,7 @@ public function processOpen(File $phpcsFile, $stackPtr) } } else if ($tokens[($stackPtr - 2)]['code'] === T_ABSTRACT || $tokens[($stackPtr - 2)]['code'] === T_FINAL + || $tokens[($stackPtr - 2)]['code'] === T_READONLY ) { $prevContent = strtolower($tokens[($stackPtr - 2)]['content']); $error = 'Expected 1 space between %s and %s keywords; newline found'; diff --git a/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.inc b/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.inc index 13e596e17a..dd78809366 100644 --- a/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.inc +++ b/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.inc @@ -239,3 +239,12 @@ C8 foo(new class { }); + +readonly +class Test +{ +} + +readonly class Test +{ +} diff --git a/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.inc.fixed b/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.inc.fixed index 3ee394e789..3be473639e 100644 --- a/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.inc.fixed @@ -232,3 +232,11 @@ class C8 foo(new class { }); + +readonly class Test +{ +} + +readonly class Test +{ +} diff --git a/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.php b/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.php index 635ac710ba..95c225edb4 100644 --- a/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.php +++ b/src/Standards/PSR2/Tests/Classes/ClassDeclarationUnitTest.php @@ -63,6 +63,8 @@ public function getErrorList() 216 => 1, 231 => 2, 235 => 1, + 244 => 1, + 248 => 1, ]; }//end getErrorList() From 3f7b9ba8544073fb2ff7c879c70d036090ebd95a Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 01:33:17 +0200 Subject: [PATCH 699/733] PHP 8.2 | Squiz/ClassDeclaration: allow for readonly classes Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_classes --- .../Squiz/Sniffs/Classes/ClassDeclarationSniff.php | 1 + .../Squiz/Tests/Classes/ClassDeclarationUnitTest.inc | 9 +++++++++ .../Tests/Classes/ClassDeclarationUnitTest.inc.fixed | 8 ++++++++ .../Squiz/Tests/Classes/ClassDeclarationUnitTest.php | 2 ++ 4 files changed, 20 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Classes/ClassDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Classes/ClassDeclarationSniff.php index 3d2c4db1aa..69188326d9 100644 --- a/src/Standards/Squiz/Sniffs/Classes/ClassDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Classes/ClassDeclarationSniff.php @@ -65,6 +65,7 @@ public function processOpen(File $phpcsFile, $stackPtr) if ($tokens[($stackPtr - 2)]['code'] !== T_ABSTRACT && $tokens[($stackPtr - 2)]['code'] !== T_FINAL + && $tokens[($stackPtr - 2)]['code'] !== T_READONLY ) { if ($spaces !== 0) { $type = strtolower($tokens[$stackPtr]['content']); diff --git a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc index 43ef6c9cdd..2af42d3721 100644 --- a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc @@ -119,3 +119,12 @@ class IncorrectCodeBeforeClosingBrace { echo phpinfo();} + +readonly +class Test +{ +} + +readonly class Test +{ +} diff --git a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc.fixed index 9083633fa9..5d01b68e0a 100644 --- a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.inc.fixed @@ -130,3 +130,11 @@ class IncorrectCodeBeforeClosingBrace echo phpinfo(); } + +readonly class Test +{ +} + +readonly class Test +{ +} diff --git a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.php b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.php index 914e6c87ef..23d89ec77a 100644 --- a/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.php +++ b/src/Standards/Squiz/Tests/Classes/ClassDeclarationUnitTest.php @@ -61,6 +61,8 @@ public function getErrorList() 116 => 1, 118 => 1, 121 => 1, + 124 => 2, + 128 => 2, ]; }//end getErrorList() From 358c476da830f19bf3479e92a499eb8493550330 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 00:07:40 +0200 Subject: [PATCH 700/733] PHP 8.2 | Squiz/LowercaseClassKeywords: add support for readonly classes/properties Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_properties_v2 * https://wiki.php.net/rfc/readonly_classes --- .../Squiz/Sniffs/Classes/LowercaseClassKeywordsSniff.php | 1 + .../Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc | 2 +- .../Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed | 2 +- .../Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Classes/LowercaseClassKeywordsSniff.php b/src/Standards/Squiz/Sniffs/Classes/LowercaseClassKeywordsSniff.php index 5f4f78c0a2..2ea5ad8bc2 100644 --- a/src/Standards/Squiz/Sniffs/Classes/LowercaseClassKeywordsSniff.php +++ b/src/Standards/Squiz/Sniffs/Classes/LowercaseClassKeywordsSniff.php @@ -29,6 +29,7 @@ public function register() $targets[] = T_IMPLEMENTS; $targets[] = T_ABSTRACT; $targets[] = T_FINAL; + $targets[] = T_READONLY; $targets[] = T_VAR; $targets[] = T_CONST; diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc index ea8cd89ec5..eb724505eb 100644 --- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc +++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc @@ -5,7 +5,7 @@ Interface MyInterface {} Trait MyTrait {} Enum MyEnum IMPLEMENTS Colorful {} -class MyClass +ReadOnly class MyClass { Var $myVar = null; Const myConst = true; diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed index f573905217..672fdfb3c4 100644 --- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.inc.fixed @@ -5,7 +5,7 @@ interface MyInterface {} trait MyTrait {} enum MyEnum implements Colorful {} -class MyClass +readonly class MyClass { var $myVar = null; const myConst = true; diff --git a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php index 8c4d10c79a..b4d5aae5a1 100644 --- a/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php +++ b/src/Standards/Squiz/Tests/Classes/LowercaseClassKeywordsUnitTest.php @@ -31,6 +31,7 @@ public function getErrorList() 4 => 1, 5 => 1, 6 => 2, + 8 => 1, 10 => 1, 11 => 1, 14 => 1, From 81f322e7cd6ac2d45ea895b0bd13e684216be373 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 00:40:09 +0200 Subject: [PATCH 701/733] PHP 8.2 | Squiz/ClassComment: allow for readonly classes Includes improving the accuracy of the tokens to skip over. Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_classes --- .../Squiz/Sniffs/Commenting/ClassCommentSniff.php | 9 ++++++--- .../Squiz/Tests/Commenting/ClassCommentUnitTest.inc | 12 ++++++++++++ .../Squiz/Tests/Commenting/ClassCommentUnitTest.php | 10 ++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php index 0da4ef2450..3f30036a8d 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php @@ -19,7 +19,6 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Tokens; class ClassCommentSniff implements Sniff { @@ -49,8 +48,12 @@ public function register() public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $find = Tokens::$methodPrefixes; - $find[T_WHITESPACE] = T_WHITESPACE; + $find = [ + T_ABSTRACT => T_ABSTRACT, + T_FINAL => T_FINAL, + T_READONLY => T_READONLY, + T_WHITESPACE => T_WHITESPACE, + ]; $previousContent = null; for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) { diff --git a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc index 28aff226b0..8de3d0a702 100644 --- a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.inc @@ -131,3 +131,15 @@ class Space_At_end class AllGood { } + +/** + * Docblock + */ +abstract readonly class AbstractReadonlyWithDocblock {} + +/* + * Docblock + */ +readonly class ReadonlyWrongStyle {} + +readonly final class ReadonlyFinalWithoutDocblock {} diff --git a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.php index 80a404be07..b370d547c0 100644 --- a/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/ClassCommentUnitTest.php @@ -26,10 +26,12 @@ class ClassCommentUnitTest extends AbstractSniffUnitTest public function getErrorList() { return [ - 2 => 1, - 15 => 1, - 31 => 1, - 54 => 1, + 2 => 1, + 15 => 1, + 31 => 1, + 54 => 1, + 143 => 1, + 145 => 1, ]; }//end getErrorList() From 4ee40b318c49bbb97bee000e6a97d0b2ab7acbbe Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 01:02:44 +0200 Subject: [PATCH 702/733] PHP 8.2 | Squiz/DocCommentAlignment: add test for readonly classes Adjust existing unit test to confirm this is already handled correctly. --- .../Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc | 2 +- .../Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc index e42cf8ab27..d95acd2ce1 100644 --- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc @@ -31,7 +31,7 @@ class MyClass * Some info about the class here * */ -class MyClass +readonly class MyClass { /** * Some info about the function here. diff --git a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed index 6182b539cd..ea6488a02b 100644 --- a/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/DocCommentAlignmentUnitTest.inc.fixed @@ -31,7 +31,7 @@ class MyClass * Some info about the class here * */ -class MyClass +readonly class MyClass { /** * Some info about the function here. From b7604d75f196a0d1f982522922f7858c56645bf0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 01:10:19 +0200 Subject: [PATCH 703/733] PHP 8.2 | Squiz/FileComment: allow for readonly classes Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_classes --- package.xml | 1 + .../Squiz/Sniffs/Commenting/FileCommentSniff.php | 1 + .../Tests/Commenting/FileCommentUnitTest.10.inc | 12 ++++++++++++ .../Squiz/Tests/Commenting/FileCommentUnitTest.php | 1 + 4 files changed, 15 insertions(+) create mode 100644 src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.10.inc diff --git a/package.xml b/package.xml index 26b98d8133..6b6e785486 100644 --- a/package.xml +++ b/package.xml @@ -1719,6 +1719,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php index 08aaae29a6..a1c79c6a25 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php @@ -104,6 +104,7 @@ public function process(File $phpcsFile, $stackPtr) T_FINAL, T_STATIC, T_ABSTRACT, + T_READONLY, T_CONST, T_PROPERTY, T_INCLUDE, diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.10.inc b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.10.inc new file mode 100644 index 0000000000..1f82abfeaf --- /dev/null +++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.10.inc @@ -0,0 +1,12 @@ + + * @copyright 2010-2014 Squiz Pty Ltd (ABN 77 084 670 600) + */ + +readonly class Foo { +} diff --git a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php index ee81369ab7..1402432aec 100644 --- a/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FileCommentUnitTest.php @@ -46,6 +46,7 @@ public function getErrorList($testFile='FileCommentUnitTest.inc') case 'FileCommentUnitTest.6.inc': case 'FileCommentUnitTest.7.inc': case 'FileCommentUnitTest.9.inc': + case 'FileCommentUnitTest.10.inc': return [1 => 1]; case 'FileCommentUnitTest.5.inc': From 97bd66f4edaa1b79ec042b78169e60939f45b192 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 7 Oct 2022 01:19:10 +0200 Subject: [PATCH 704/733] PHP 8.1/8.2 | Squiz/InlineComment: allow for readonly classes and properties Includes unit test. Ref: * https://wiki.php.net/rfc/readonly_properties_v2 * https://wiki.php.net/rfc/readonly_classes --- .../Squiz/Sniffs/Commenting/InlineCommentSniff.php | 1 + .../Tests/Commenting/InlineCommentUnitTest.inc | 13 +++++++++++++ .../Commenting/InlineCommentUnitTest.inc.fixed | 13 +++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php index 8ce950414a..e3ef5c0dc1 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php @@ -83,6 +83,7 @@ public function process(File $phpcsFile, $stackPtr) T_FINAL, T_STATIC, T_ABSTRACT, + T_READONLY, T_CONST, T_PROPERTY, T_INCLUDE, diff --git a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc index 10a0b4b4c6..024876842e 100644 --- a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc @@ -173,6 +173,19 @@ enum MyEnum { } +/** + * Comment should be ignored. + * + */ +readonly class MyClass +{ + /** + * Comment should be ignored. + * + */ + readonly $property = 10; +} + /* * 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 97ae01490d..949a9ff949 100644 --- a/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/InlineCommentUnitTest.inc.fixed @@ -166,6 +166,19 @@ enum MyEnum { } +/** + * Comment should be ignored. + * + */ +readonly class MyClass +{ + /** + * Comment should be ignored. + * + */ + readonly $property = 10; +} + /* * 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 From 2dc7b5981488502fed92df99c1460530d741e666 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Thu, 18 May 2023 18:37:09 +1000 Subject: [PATCH 705/733] Changelog for #3826 --- package.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/package.xml b/package.xml index 6b6e785486..a5c8053d05 100644 --- a/package.xml +++ b/package.xml @@ -52,6 +52,18 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Added support for readonly classes to File::getClassProperties() through a new is_readonly array index in the return value -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Added support for readonly class to a number of sniffs + -- Generic.CodeAnalysis.UnnecessaryFinalModifier + -- PEAR.Commenting.ClassComment + -- PEAR.Commenting.FileComment + -- PSR2.Classes.ClassDeclaration + -- Squiz.Classes.ClassDeclaration + -- Squiz.Classes.LowercaseClassKeywords + -- Squiz.Commenting.ClassComment + -- Squiz.Commenting.DocCommentAlignment + -- Squiz.Commenting.FileComment + -- Squiz.Commenting.InlineComment + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - PSR2.Classes.PropertyDeclaration now enforces that the readonly modifier comes after the visibility modifier From 1a555836f7858d890ea47a77013fec5408636a0a Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 23 May 2023 09:09:00 +1000 Subject: [PATCH 706/733] Changelog for #3728 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index a5c8053d05..d488ab236b 100644 --- a/package.xml +++ b/package.xml @@ -94,6 +94,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3722 : Potential "Uninitialized string offset 1" in octal notation backfill -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3728 : PHP 8.2 | PSR1/SideEffects: allow for readonly classes + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3777 : Squiz/NonExecutableCode: slew of bug fixes, mostly related to modern PHP -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3779 : Squiz/LowercasePHPFunctions + Generic/ForbiddenFunctions: bug fix for class names in attributes From 2e59060b93b3720ff3aab2b57efda0c3ada069b0 Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 23 May 2023 09:17:44 +1000 Subject: [PATCH 707/733] Changelog for #3816 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 006fc34582..7718fa15e7 100644 --- a/package.xml +++ b/package.xml @@ -110,6 +110,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3805 : Generic/FunctionCallArgumentSpacing: prevent fixer conflict over PHP 7.3+ trailing comma's in function calls -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3816 : PSR12/FileHeader: bug fix - false positives on PHP 8.2+ readonly classes + -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From 70476e52ee0f70b588b26f0d0552831d62f35d33 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 18 May 2023 15:56:07 +0200 Subject: [PATCH 708/733] Bug report template: various tweaks * Improve readability of reports by using headers. * Improve readability of the versions list by using a table. * Explicitly say that screenshots of code are not acceptable as a code sample. * Added an "Install type" entry to the versions list * Added an extra checklist for bug reporters in an attempt to reduce the number of issues which should never have been opened in the first place. --- .github/ISSUE_TEMPLATE/bug_report.md | 34 ++++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2f740a4755..edb4b4c75c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -18,15 +18,15 @@ Please report bugs for externally maintained sniffs to the appropriate external standard repository (not here). --> -**Describe the bug** +## Describe the bug A clear and concise description of what the bug is. -**Code sample** +### Code sample ```php -echo "A short code snippet that can be used to reproduce the bug"; +echo "A short code snippet that can be used to reproduce the bug. Do NOT paste screenshots of code!"; ``` -**Custom ruleset** +### Custom ruleset ```xml @@ -34,7 +34,7 @@ echo "A short code snippet that can be used to reproduce the bug"; ``` -**To reproduce** +### To reproduce Steps to reproduce the behavior: 1. Create a file called `test.php` with the code sample above... 2. Run `phpcs test.php ...` @@ -43,14 +43,24 @@ Steps to reproduce the behavior: PHPCS output here ``` -**Expected behavior** +## Expected behavior A clear and concise description of what you expected to happen. -**Versions (please complete the following information):** - - OS: [e.g., Windows 10, MacOS 10.15] - - PHP: [e.g., 7.2, 7.4] - - PHPCS: [e.g., 3.5.5, master] - - Standard: [e.g., PSR2, PSR12, Squiz] +## Versions (please complete the following information) -**Additional context** +| | | +|-------------------------|------------------------------------------------------------------------------| +| Operating System | [e.g., Windows 10, MacOS 10.15] | +| PHP version | [e.g., 7.2, 7.4] | +| PHP_CodeSniffer version | [e.g., 3.5.5, master] | +| Standard | [e.g., PSR2, PSR12, Squiz, custom] | +| Install type | [e.g. Composer (global/local), PHAR, PEAR, git clone, other (please expand)] | + +## Additional context Add any other context about the problem here. + +## Please confirm: + +- [ ] I have searched the issue list and am not opening a duplicate issue. +- [ ] I confirm that this bug is a bug in PHP_CodeSniffer and not in one of the external standards. +- [ ] I have verified the issue still exists in the `master` branch of PHP_CodeSniffer. From d148febc2a2eb82972121d7f962883f7a5697b55 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Fri, 26 May 2023 23:32:02 +0100 Subject: [PATCH 709/733] Reduce width of table in issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index edb4b4c75c..5036ca4dcd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -48,13 +48,13 @@ A clear and concise description of what you expected to happen. ## Versions (please complete the following information) -| | | -|-------------------------|------------------------------------------------------------------------------| -| Operating System | [e.g., Windows 10, MacOS 10.15] | -| PHP version | [e.g., 7.2, 7.4] | -| PHP_CodeSniffer version | [e.g., 3.5.5, master] | -| Standard | [e.g., PSR2, PSR12, Squiz, custom] | -| Install type | [e.g. Composer (global/local), PHAR, PEAR, git clone, other (please expand)] | +| | | +|-|-| +| Operating System | [e.g., Windows 10, MacOS 10.15] +| PHP version | [e.g., 7.2, 7.4] +| PHP_CodeSniffer version | [e.g., 3.5.5, master] +| Standard | [e.g., PSR2, PSR12, Squiz, custom] +| Install type | [e.g. Composer (global/local), PHAR, PEAR, git clone, other (please expand)] ## Additional context Add any other context about the problem here. From 8c84123220a4c870f898e8a0c33024e490b479cf Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Thu, 8 Jun 2023 16:24:30 +0100 Subject: [PATCH 710/733] Skip empty types --- .../Squiz/Sniffs/Commenting/FunctionCommentSniff.php | 6 +++++- .../Tests/Commenting/FunctionCommentUnitTest.inc.fixed | 2 +- .../Squiz/Tests/Commenting/FunctionCommentUnitTest.php | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index 1101165cbd..f63d09c43b 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -405,8 +405,12 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $suggestedTypeNames = []; foreach ($typeNames as $typeName) { + if ($typeName === '') { + continue; + } + // Strip nullable operator. - if (isset($typeName[0]) === true && $typeName[0] === '?') { + if ($typeName[0] === '?') { $typeName = substr($typeName, 1); } diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 330563b0d5..10e939709b 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -1050,7 +1050,7 @@ function throwCommentOneLine() {} /** * When two adjacent pipe symbols are used (by mistake), the sniff should not throw a PHP Fatal error * - * @param stdClass||null $object While invalid, this should not throw a PHP Fatal error. + * @param stdClass|null $object While invalid, this should not throw a PHP Fatal error. * @return void */ function doublePipeFatalError(?stdClass $object) {} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php index 632b7c51b2..06ba08f72a 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php @@ -116,6 +116,7 @@ public function getErrorList() 1004 => 2, 1006 => 1, 1029 => 1, + 1053 => 1, ]; // Scalar type hints only work from PHP 7 onwards. From edda91acbf129ec2b1a19d791ee4796067190330 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 Jul 2023 08:33:12 +0200 Subject: [PATCH 711/733] Changelog for #3776 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 7718fa15e7..89114b4b2a 100644 --- a/package.xml +++ b/package.xml @@ -96,6 +96,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3728 : PHP 8.2 | PSR1/SideEffects: allow for readonly classes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3776 : Generic/JSHint: error when JSHint is not available + -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3777 : Squiz/NonExecutableCode: slew of bug fixes, mostly related to modern PHP -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3779 : Squiz/LowercasePHPFunctions + Generic/ForbiddenFunctions: bug fix for class names in attributes From 6067ef4a3a0c73064a025c2d6d5328e4772ad764 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 Jul 2023 08:53:23 +0200 Subject: [PATCH 712/733] Changelog for #3785 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 89114b4b2a..3c093f3b26 100644 --- a/package.xml +++ b/package.xml @@ -102,6 +102,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3779 : Squiz/LowercasePHPFunctions + Generic/ForbiddenFunctions: bug fix for class names in attributes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3785 : Squiz/FunctionComment: potential "Uninitialized string offset 0" when a type contains a duplicate pipe symbol + -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3787 : PEAR/Squiz/[MultiLine]FunctionDeclaration: allow for PHP 8.1 new in initializers -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3789 : Incorrect tokenization for ternary operator with match inside of it From 22edcb30467b730cc1f7e3678a29ff464a8f241b Mon Sep 17 00:00:00 2001 From: Atsushi Okui Date: Tue, 11 Jul 2023 16:06:56 +0900 Subject: [PATCH 713/733] Added PSR-2 documentation XML files (#3832) Documentation for the following PSR2 sniffs: * `PSR2.Files.ClosingTag` * `PSR2.Methods.FunctionCallSignature` * `PSR2.Methods.FunctionClosingBrace` --- package.xml | 3 + .../PSR2/Docs/Files/ClosingTagStandard.xml | 23 ++++ .../Methods/FunctionCallSignatureStandard.xml | 107 ++++++++++++++++++ .../Methods/FunctionClosingBraceStandard.xml | 26 +++++ 4 files changed, 159 insertions(+) create mode 100644 src/Standards/PSR2/Docs/Files/ClosingTagStandard.xml create mode 100644 src/Standards/PSR2/Docs/Methods/FunctionCallSignatureStandard.xml create mode 100644 src/Standards/PSR2/Docs/Methods/FunctionClosingBraceStandard.xml diff --git a/package.xml b/package.xml index 3c093f3b26..c799c7f6e3 100644 --- a/package.xml +++ b/package.xml @@ -1378,9 +1378,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + diff --git a/src/Standards/PSR2/Docs/Files/ClosingTagStandard.xml b/src/Standards/PSR2/Docs/Files/ClosingTagStandard.xml new file mode 100644 index 0000000000..60d5e7fbaa --- /dev/null +++ b/src/Standards/PSR2/Docs/Files/ClosingTagStandard.xml @@ -0,0 +1,23 @@ + + + + + + + + ]]> + + + ?> + ]]> + + + diff --git a/src/Standards/PSR2/Docs/Methods/FunctionCallSignatureStandard.xml b/src/Standards/PSR2/Docs/Methods/FunctionCallSignatureStandard.xml new file mode 100644 index 0000000000..257bcab0b2 --- /dev/null +++ b/src/Standards/PSR2/Docs/Methods/FunctionCallSignatureStandard.xml @@ -0,0 +1,107 @@ + + + + + + + ($bar, $baz); + ]]> + + + ( $bar, $baz ); + ]]> + + + + + $bar, + $baz +); + ]]> + + + $bar, + $baz +); + ]]> + + + + + ); + ]]> + + + ); + ]]> + + + + + $bar, + $baz +); + ]]> + + + $bar, + $baz +); + ]]> + + + + + $baz +); + ]]> + + + $baz +); + ]]> + + + + + + + + + $baz +); + ]]> + + + diff --git a/src/Standards/PSR2/Docs/Methods/FunctionClosingBraceStandard.xml b/src/Standards/PSR2/Docs/Methods/FunctionClosingBraceStandard.xml new file mode 100644 index 0000000000..3b1b6555e5 --- /dev/null +++ b/src/Standards/PSR2/Docs/Methods/FunctionClosingBraceStandard.xml @@ -0,0 +1,26 @@ + + + + + + + } + ]]> + + + +} + ]]> + + + From 653f3904f28740626c2632e694feb87e8df908f4 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 Jul 2023 09:07:44 +0200 Subject: [PATCH 714/733] Changelog for #3832 --- package.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.xml b/package.xml index c799c7f6e3..85881aaa03 100644 --- a/package.xml +++ b/package.xml @@ -71,6 +71,11 @@ http://pear.php.net/dtd/package-2.0.xsd"> - PSR-PER has been used to confirm the order of this keyword so it can be applied to PSR2 and PSR12 correctly -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Squiz.Formatting.OperatorBracket no longer reports false positives in match() structures + - Documentation has been added for the following sniffs: + -- PSR2.Files.ClosingTag + -- PSR2.Methods.FunctionCallSignature + -- PSR2.Methods.FunctionClosingBrace + -- Thanks to Atsushi Okui (@blue32a) for the patch - 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() From 5ce46a84c0109a40d66fca030c82e6a1865d4d45 Mon Sep 17 00:00:00 2001 From: Volker Dusch <247397+edorian@users.noreply.github.com> Date: Tue, 11 Jul 2023 09:17:50 +0200 Subject: [PATCH 715/733] Allow array unpacking in ArrayDeclaration multiline Sniff (#3843) Ignore array unpacking when determining whether an array is a list- or dict-shaped. Includes tests. Fixes #3557 --- .../Sniffs/Arrays/ArrayDeclarationSniff.php | 23 +++++++-- .../Arrays/ArrayDeclarationUnitTest.1.inc | 47 ++++++++++++++++- .../ArrayDeclarationUnitTest.1.inc.fixed | 47 ++++++++++++++++- .../Arrays/ArrayDeclarationUnitTest.2.inc | 50 +++++++++++++++++++ .../ArrayDeclarationUnitTest.2.inc.fixed | 50 +++++++++++++++++++ .../Tests/Arrays/ArrayDeclarationUnitTest.php | 8 +++ 6 files changed, 216 insertions(+), 9 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php index b45a4709de..64d5d64da2 100644 --- a/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -430,9 +430,13 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array } if ($keyUsed === true && $tokens[$lastToken]['code'] === T_COMMA) { - $error = 'No key specified for array entry; first entry specifies key'; - $phpcsFile->addError($error, $nextToken, 'NoKeySpecified'); - return; + $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($lastToken + 1), null, true); + // Allow for PHP 7.4+ array unpacking within an array declaration. + if ($tokens[$nextToken]['code'] !== T_ELLIPSIS) { + $error = 'No key specified for array entry; first entry specifies key'; + $phpcsFile->addError($error, $nextToken, 'NoKeySpecified'); + return; + } } if ($keyUsed === false) { @@ -470,8 +474,17 @@ public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $array true ); - $indices[] = ['value' => $valueContent]; - $singleUsed = true; + $indices[] = ['value' => $valueContent]; + $usesArrayUnpacking = $phpcsFile->findPrevious( + Tokens::$emptyTokens, + ($nextToken - 2), + null, + true + ); + if ($tokens[$usesArrayUnpacking]['code'] !== T_ELLIPSIS) { + // Don't decide if an array is key => value indexed or not when PHP 7.4+ array unpacking is used. + $singleUsed = true; + } }//end if $lastToken = $nextToken; diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc index 2774660c0c..b96aec7bde 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc @@ -475,14 +475,57 @@ yield array( static fn () : string => '', ); -$foo = [ +$foo = array( 'foo' => match ($anything) { 'foo' => 'bar', default => null, }, - ]; + ); // Intentional syntax error. $a = array( 'a' => ); + +// Safeguard correct errors for key/no key when PHP 7.4+ array unpacking is encountered. +$x = array( + ...$a, + 'foo' => 'bar', + ); + +$x = array( + 'foo' => 'bar', + ...$a, + ); + +$x = array( + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + ); + +$x = array( + ...$a, + 'foo' => 'bar', // OK. + 'bar', // NoKeySpecified Error (based on second entry). + ); + +$x = array( + ...$a, + 'bar', // OK. + 'foo' => 'bar', // KeySpecified Error (based on second entry). + ); + +$x = array( + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + 'bar', // NoKeySpecified Error (based on first entry). + ); + +$x = array( + 'bar', + ...$a, + 'bar', + 'baz' => 'bar', // KeySpecified (based on first entry). + ); diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed index b452006488..9da6a4279b 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.1.inc.fixed @@ -511,14 +511,57 @@ yield array( static fn () : string => '', ); -$foo = [ +$foo = array( 'foo' => match ($anything) { 'foo' => 'bar', default => null, }, - ]; + ); // Intentional syntax error. $a = array( 'a' => ); + +// Safeguard correct errors for key/no key when PHP 7.4+ array unpacking is encountered. +$x = array( + ...$a, + 'foo' => 'bar', + ); + +$x = array( + 'foo' => 'bar', + ...$a, + ); + +$x = array( + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + ); + +$x = array( + ...$a, + 'foo' => 'bar', // OK. + 'bar', // NoKeySpecified Error (based on second entry). + ); + +$x = array( + ...$a, + 'bar', // OK. + 'foo' => 'bar', // KeySpecified Error (based on second entry). + ); + +$x = array( + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + 'bar', // NoKeySpecified Error (based on first entry). + ); + +$x = array( + 'bar', + ...$a, + 'bar', + 'baz' => 'bar', // KeySpecified (based on first entry). + ); diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc index 621970fa1d..0c8b48fc89 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc @@ -464,7 +464,57 @@ yield [ static fn () : string => '', ]; +$foo = [ + 'foo' => match ($anything) { + 'foo' => 'bar', + default => null, + }, + ]; + // Intentional syntax error. $a = [ 'a' => ]; + +// Safeguard correct errors for key/no key when PHP 7.4+ array unpacking is encountered. +$x = [ + ...$a, + 'foo' => 'bar', + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + ]; + +$x = [ + ...$a, + 'foo' => 'bar', // OK. + 'bar', // NoKeySpecified Error (based on second entry). + ]; + +$x = [ + ...$a, + 'bar', // OK. + 'foo' => 'bar', // KeySpecified Error (based on second entry). + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + 'bar', // NoKeySpecified Error (based on first entry). + ]; + +$x = [ + 'bar', + ...$a, + 'bar', + 'baz' => 'bar', // KeySpecified (based on first entry). + ]; diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed index efe4c450e9..4b09e2f234 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.2.inc.fixed @@ -498,7 +498,57 @@ yield [ static fn () : string => '', ]; +$foo = [ + 'foo' => match ($anything) { + 'foo' => 'bar', + default => null, + }, + ]; + // Intentional syntax error. $a = [ 'a' => ]; + +// Safeguard correct errors for key/no key when PHP 7.4+ array unpacking is encountered. +$x = [ + ...$a, + 'foo' => 'bar', + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + ]; + +$x = [ + ...$a, + 'foo' => 'bar', // OK. + 'bar', // NoKeySpecified Error (based on second entry). + ]; + +$x = [ + ...$a, + 'bar', // OK. + 'foo' => 'bar', // KeySpecified Error (based on second entry). + ]; + +$x = [ + 'foo' => 'bar', + ...$a, + 'baz' => 'bar', + 'bar', // NoKeySpecified Error (based on first entry). + ]; + +$x = [ + 'bar', + ...$a, + 'bar', + 'baz' => 'bar', // KeySpecified (based on first entry). + ]; diff --git a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php index 15ce0746ef..9d917b46b0 100644 --- a/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php +++ b/src/Standards/Squiz/Tests/Arrays/ArrayDeclarationUnitTest.php @@ -124,6 +124,10 @@ public function getErrorList($testFile='') 467 => 1, 471 => 1, 472 => 1, + 510 => 1, + 516 => 1, + 523 => 1, + 530 => 1, ]; case 'ArrayDeclarationUnitTest.2.inc': return [ @@ -210,6 +214,10 @@ public function getErrorList($testFile='') 456 => 1, 460 => 1, 461 => 1, + 499 => 1, + 505 => 1, + 512 => 1, + 519 => 1, ]; default: return []; From d4155005ebafba6c32cc08a6db3ba157cd5f747d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 Jul 2023 09:18:29 +0200 Subject: [PATCH 716/733] Changelog for #3843 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 85881aaa03..dadcb89a03 100644 --- a/package.xml +++ b/package.xml @@ -76,6 +76,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- PSR2.Methods.FunctionCallSignature -- PSR2.Methods.FunctionClosingBrace -- Thanks to Atsushi Okui (@blue32a) for the patch + - Fixed bug #3557 : Squiz.Arrays.ArrayDeclaration will now ignore PHP 7.4 array unpacking when determining whether an array is associative + -- Thanks to Volker Dusch (@edorian) for the patch - 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() From 84621dd4b5c33601ebb69d2940bdfe33a5ee1ca6 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Tue, 11 Jul 2023 09:27:53 +0100 Subject: [PATCH 717/733] Handle @param when var passed by reference --- .../Commenting/FunctionCommentSniff.php | 32 ++++++-- .../Commenting/FunctionCommentUnitTest.inc | 75 +++++++++++++++++++ .../FunctionCommentUnitTest.inc.fixed | 75 +++++++++++++++++++ .../Commenting/FunctionCommentUnitTest.php | 25 ++++++- 4 files changed, 199 insertions(+), 8 deletions(-) diff --git a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php index f63d09c43b..38170d22e4 100644 --- a/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php @@ -555,16 +555,38 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) // Make sure the param name is correct. if (isset($realParams[$pos]) === true) { - $realName = $realParams[$pos]['name']; - if ($realName !== $param['var']) { + $realName = $realParams[$pos]['name']; + $paramVarName = $param['var']; + + if ($param['var'][0] === '&') { + // Even when passed by reference, the variable name in $realParams does not have + // a leading '&'. This sniff will accept both '&$var' and '$var' in these cases. + $paramVarName = substr($param['var'], 1); + + // This makes sure that the 'MissingParamTag' check won't throw a false positive. + $foundParams[(count($foundParams) - 1)] = $paramVarName; + + if ($realParams[$pos]['pass_by_reference'] !== true && $realName === $paramVarName) { + // Don't complain about this unless the param name is otherwise correct. + $error = 'Doc comment for parameter %s is prefixed with "&" but parameter is not passed by reference'; + $code = 'ParamNameUnexpectedAmpersandPrefix'; + $data = [$paramVarName]; + + // We're not offering an auto-fix here because we can't tell if the docblock + // is wrong, or the parameter should be passed by reference. + $phpcsFile->addError($error, $param['tag'], $code, $data); + } + } + + if ($realName !== $paramVarName) { $code = 'ParamNameNoMatch'; $data = [ - $param['var'], + $paramVarName, $realName, ]; $error = 'Doc comment for parameter %s does not match '; - if (strtolower($param['var']) === strtolower($realName)) { + if (strtolower($paramVarName) === strtolower($realName)) { $error .= 'case of '; $code = 'ParamNameNoCaseMatch'; } @@ -572,7 +594,7 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $error .= 'actual variable name %s'; $phpcsFile->addError($error, $param['tag'], $code, $data); - } + }//end if } else if (substr($param['var'], -4) !== ',...') { // We must have an extra parameter comment. $error = 'Superfluous parameter comment'; diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc index d57017daf5..55e0fe9bb9 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc @@ -1054,3 +1054,78 @@ function throwCommentOneLine() {} * @return void */ function doublePipeFatalError(?stdClass $object) {} + +/** + * Test for passing variables by reference + * + * This sniff treats the '&' as optional for parameters passed by reference, but + * forbidden for parameters which are not passed by reference. + * + * Because mismatches may be in either direction, we cannot auto-fix these. + * + * @param string $foo A string passed in by reference. + * @param string &$bar A string passed in by reference. + * @param string $baz A string NOT passed in by reference. + * @param string &$qux A string NOT passed in by reference. + * @param string &$case1 A string passed in by reference with a case mismatch. + * @param string &$CASE2 A string NOT passed in by reference, also with a case mismatch. + * + * @return void + */ +public function variablesPassedByReference(&$foo, &$bar, $baz, $qux, &$CASE1, $case2) +{ + return; +} + +/** + * Test for param tag containing ref, but param in declaration not being by ref. + * + * @param string &$foo This should be flagged as (only) ParamNameUnexpectedAmpersandPrefix. + * @param string &$bar This should be flagged as (only) ParamNameNoMatch. + * @param string &$baz This should be flagged as (only) ParamNameNoCaseMatch. + * + * @return void + */ +function passedByRefMismatch($foo, $bra, $BAZ) { + return; +} + +/** + * Test variable case + * + * @param string $foo This parameter is lowercase. + * @param string $BAR This parameter is UPPERCASE. + * @param string $BazQux This parameter is TitleCase. + * @param string $corgeGrault This parameter is camelCase. + * @param string $GARPLY This parameter should be in lowercase. + * @param string $waldo This parameter should be in TitleCase. + * @param string $freD This parameter should be in UPPERCASE. + * @param string $PLUGH This parameter should be in TitleCase. + * + * @return void + */ +public function variableCaseTest( + $foo, + $BAR, + $BazQux, + $corgeGrault, + $garply, + $Waldo, + $FRED, + $PluGh +) { + return; +} + +/** + * Test variable order mismatch + * + * @param string $foo This is the third parameter. + * @param string $bar This is the first parameter. + * @param string $baz This is the second parameter. + * + * @return void + */ +public function variableOrderMismatch($bar, $baz, $foo) { + return; +} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 10e939709b..22474caf85 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -1054,3 +1054,78 @@ function throwCommentOneLine() {} * @return void */ function doublePipeFatalError(?stdClass $object) {} + +/** + * Test for passing variables by reference + * + * This sniff treats the '&' as optional for parameters passed by reference, but + * forbidden for parameters which are not passed by reference. + * + * Because mismatches may be in either direction, we cannot auto-fix these. + * + * @param string $foo A string passed in by reference. + * @param string &$bar A string passed in by reference. + * @param string $baz A string NOT passed in by reference. + * @param string &$qux A string NOT passed in by reference. + * @param string &$case1 A string passed in by reference with a case mismatch. + * @param string &$CASE2 A string NOT passed in by reference, also with a case mismatch. + * + * @return void + */ +public function variablesPassedByReference(&$foo, &$bar, $baz, $qux, &$CASE1, $case2) +{ + return; +} + +/** + * Test for param tag containing ref, but param in declaration not being by ref. + * + * @param string &$foo This should be flagged as (only) ParamNameUnexpectedAmpersandPrefix. + * @param string &$bar This should be flagged as (only) ParamNameNoMatch. + * @param string &$baz This should be flagged as (only) ParamNameNoCaseMatch. + * + * @return void + */ +function passedByRefMismatch($foo, $bra, $BAZ) { + return; +} + +/** + * Test variable case + * + * @param string $foo This parameter is lowercase. + * @param string $BAR This parameter is UPPERCASE. + * @param string $BazQux This parameter is TitleCase. + * @param string $corgeGrault This parameter is camelCase. + * @param string $GARPLY This parameter should be in lowercase. + * @param string $waldo This parameter should be in TitleCase. + * @param string $freD This parameter should be in UPPERCASE. + * @param string $PLUGH This parameter should be in TitleCase. + * + * @return void + */ +public function variableCaseTest( + $foo, + $BAR, + $BazQux, + $corgeGrault, + $garply, + $Waldo, + $FRED, + $PluGh +) { + return; +} + +/** + * Test variable order mismatch + * + * @param string $foo This is the third parameter. + * @param string $bar This is the first parameter. + * @param string $baz This is the second parameter. + * + * @return void + */ +public function variableOrderMismatch($bar, $baz, $foo) { + return; +} diff --git a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php index 06ba08f72a..ff1d10c487 100644 --- a/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/Squiz/Tests/Commenting/FunctionCommentUnitTest.php @@ -48,8 +48,7 @@ public function getErrorList() 138 => 4, 139 => 4, 143 => 2, - 152 => 1, - 155 => 2, + 155 => 1, 159 => 1, 166 => 1, 173 => 1, @@ -117,6 +116,22 @@ public function getErrorList() 1006 => 1, 1029 => 1, 1053 => 1, + 1058 => 2, + 1069 => 1, + 1070 => 1, + 1071 => 1, + 1080 => 2, + 1083 => 1, + 1084 => 1, + 1085 => 1, + 1093 => 4, + 1100 => 1, + 1101 => 1, + 1102 => 1, + 1103 => 1, + 1123 => 1, + 1124 => 1, + 1125 => 1, ]; // Scalar type hints only work from PHP 7 onwards. @@ -132,12 +147,16 @@ public function getErrorList() $errors[575] = 2; $errors[627] = 1; $errors[1002] = 1; + $errors[1075] = 6; + $errors[1089] = 3; + $errors[1107] = 8; + $errors[1129] = 3; } else { $errors[729] = 4; $errors[740] = 2; $errors[752] = 2; $errors[982] = 1; - } + }//end if // Object type hints only work from PHP 7.2 onwards. if (PHP_VERSION_ID >= 70200) { From 6c3f42a12f49d80de541f58213861af607fc9f85 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 Jul 2023 21:54:39 +0200 Subject: [PATCH 718/733] Changelog for #3807 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index dadcb89a03..6a3601c043 100644 --- a/package.xml +++ b/package.xml @@ -71,6 +71,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - PSR-PER has been used to confirm the order of this keyword so it can be applied to PSR2 and PSR12 correctly -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Squiz.Formatting.OperatorBracket no longer reports false positives in match() structures + - Squiz.PHP.InnerFunctions sniff no longer reports on OO methods for OO structures declared within a function or closure + -- Thanks to @Daimona for the patch - Documentation has been added for the following sniffs: -- PSR2.Files.ClosingTag -- PSR2.Methods.FunctionCallSignature @@ -121,6 +123,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3805 : Generic/FunctionCallArgumentSpacing: prevent fixer conflict over PHP 7.3+ trailing comma's in function calls -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3806 : Squiz.PHP.InnerFunctions sniff now correctly reports inner functions declared within a closure + -- Thanks to @Daimona for the patch - Fixed bug #3816 : PSR12/FileHeader: bug fix - false positives on PHP 8.2+ readonly classes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From 276f68cc74a3e4e1855bab6d01f0089337d00ae0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 Jul 2023 22:18:21 +0200 Subject: [PATCH 719/733] Changelog for #3813 --- package.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.xml b/package.xml index 6a3601c043..0688cd6610 100644 --- a/package.xml +++ b/package.xml @@ -70,6 +70,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - PSR2 and PSR12 do not have documented rules for this as they pre-date the readonly modifier - PSR-PER has been used to confirm the order of this keyword so it can be applied to PSR2 and PSR12 correctly -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Squiz/FunctionComment: new ParamNameUnexpectedAmpersandPrefix error for parameters annotated as passed by reference while the parameter is not passed by reference + -- Thanks to Dan Wallis (@fredden) for the patch - Squiz.Formatting.OperatorBracket no longer reports false positives in match() structures - Squiz.PHP.InnerFunctions sniff no longer reports on OO methods for OO structures declared within a function or closure -- Thanks to @Daimona for the patch @@ -125,6 +127,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3806 : Squiz.PHP.InnerFunctions sniff now correctly reports inner functions declared within a closure -- Thanks to @Daimona for the patch + - Fixed bug #3813 : Squiz/FunctionComment: false positive for parameter name mismatch on parameters annotated as passed by reference + -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3816 : PSR12/FileHeader: bug fix - false positives on PHP 8.2+ readonly classes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From b8d7e94fe41708c3b2d10f72d515fb56723176c9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 14 Jul 2023 08:20:15 +0200 Subject: [PATCH 720/733] Changelog for #3717 --- package.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package.xml b/package.xml index 0688cd6610..8fbb8961ba 100644 --- a/package.xml +++ b/package.xml @@ -70,7 +70,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - PSR2 and PSR12 do not have documented rules for this as they pre-date the readonly modifier - PSR-PER has been used to confirm the order of this keyword so it can be applied to PSR2 and PSR12 correctly -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - - Squiz/FunctionComment: new ParamNameUnexpectedAmpersandPrefix error for parameters annotated as passed by reference while the parameter is not passed by reference + - Squiz.Commenting.FunctionComment: new ParamNameUnexpectedAmpersandPrefix error for parameters annotated as passed by reference while the parameter is not passed by reference -- Thanks to Dan Wallis (@fredden) for the patch - Squiz.Formatting.OperatorBracket no longer reports false positives in match() structures - Squiz.PHP.InnerFunctions sniff no longer reports on OO methods for OO structures declared within a function or closure @@ -103,6 +103,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> - 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 + - Fixed bug #3717 : Squiz.Commenting.FunctionComment: fixed false positive for InvalidNoReturn when type is never + -- Thanks to Choraimy Kroonstuiver (@axlon) for the patch - Fixed bug #3722 : Potential "Uninitialized string offset 1" in octal notation backfill -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3728 : PHP 8.2 | PSR1/SideEffects: allow for readonly classes @@ -113,7 +115,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3779 : Squiz/LowercasePHPFunctions + Generic/ForbiddenFunctions: bug fix for class names in attributes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - - Fixed bug #3785 : Squiz/FunctionComment: potential "Uninitialized string offset 0" when a type contains a duplicate pipe symbol + - Fixed bug #3785 : Squiz.Commenting.FunctionComment: potential "Uninitialized string offset 0" when a type contains a duplicate pipe symbol -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3787 : PEAR/Squiz/[MultiLine]FunctionDeclaration: allow for PHP 8.1 new in initializers -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch @@ -127,7 +129,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3806 : Squiz.PHP.InnerFunctions sniff now correctly reports inner functions declared within a closure -- Thanks to @Daimona for the patch - - Fixed bug #3813 : Squiz/FunctionComment: false positive for parameter name mismatch on parameters annotated as passed by reference + - Fixed bug #3813 : Squiz.Commenting.FunctionComment: false positive for parameter name mismatch on parameters annotated as passed by reference -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3816 : PSR12/FileHeader: bug fix - false positives on PHP 8.2+ readonly classes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch From 354b4c5ee114a9d10b72f6d311d5d18152162ad0 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 16 Jul 2023 12:58:04 +0200 Subject: [PATCH 721/733] Changelog: remove 3.7.2 entries from 3.8.0 changelog ... and update the version number in the Config class. --- package.xml | 33 ++------------------------------- src/Config.php | 2 +- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/package.xml b/package.xml index 8fbb8961ba..00ecd60554 100644 --- a/package.xml +++ b/package.xml @@ -17,8 +17,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2022-06-18 - 3.7.2 - 3.7.2 + 3.8.0 + 3.8.0 stable @@ -41,11 +41,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Existing code will continue to work but will throw a deprecation error -- The backwards compatiblity layer will be removed in PHPCS 4.0 -- Thanks to Juliette Reinders Folmer (@jrfnl) 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 - When using auto report width (the default) a value of 80 columns will be used if an auto width cannot be determined -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Sniff error messages are now more informative to help bugs get reported to the correct project @@ -64,15 +59,12 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Squiz.Commenting.FileComment -- Squiz.Commenting.InlineComment -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - - Generic.PHP.LowerCaseType sniff now correctly examines types inside arrow functions - -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - PSR2.Classes.PropertyDeclaration now enforces that the readonly modifier comes after the visibility modifier - PSR2 and PSR12 do not have documented rules for this as they pre-date the readonly modifier - PSR-PER has been used to confirm the order of this keyword so it can be applied to PSR2 and PSR12 correctly -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Squiz.Commenting.FunctionComment: new ParamNameUnexpectedAmpersandPrefix error for parameters annotated as passed by reference while the parameter is not passed by reference -- Thanks to Dan Wallis (@fredden) for the patch - - Squiz.Formatting.OperatorBracket no longer reports false positives in match() structures - Squiz.PHP.InnerFunctions sniff no longer reports on OO methods for OO structures declared within a function or closure -- Thanks to @Daimona for the patch - Documentation has been added for the following sniffs: @@ -82,27 +74,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Atsushi Okui (@blue32a) for the patch - Fixed bug #3557 : Squiz.Arrays.ArrayDeclaration will now ignore PHP 7.4 array unpacking when determining whether an array is associative -- Thanks to Volker Dusch (@edorian) for the patch - - 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 - Fixed bug #3717 : Squiz.Commenting.FunctionComment: fixed false positive for InvalidNoReturn when type is never -- Thanks to Choraimy Kroonstuiver (@axlon) for the patch - Fixed bug #3722 : Potential "Uninitialized string offset 1" in octal notation backfill diff --git a/src/Config.php b/src/Config.php index 8d3fb173e6..1ae4c77ee3 100644 --- a/src/Config.php +++ b/src/Config.php @@ -80,7 +80,7 @@ class Config * * @var string */ - const VERSION = '3.7.2'; + const VERSION = '3.8.0'; /** * Package stability; either stable, beta or alpha. From 7a733cd458240c6fbf20d9e01def547e6aeedf8d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 1 Aug 2023 18:27:33 +0200 Subject: [PATCH 722/733] Changelog for #3809 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 00ecd60554..80d7c63d3f 100644 --- a/package.xml +++ b/package.xml @@ -100,6 +100,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3806 : Squiz.PHP.InnerFunctions sniff now correctly reports inner functions declared within a closure -- Thanks to @Daimona for the patch + - Fixed bug #3809 : GitBlame report was broken when passing a basepath + -- Thanks to Chris (@datengraben) for the patch - Fixed bug #3813 : Squiz.Commenting.FunctionComment: false positive for parameter name mismatch on parameters annotated as passed by reference -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3816 : PSR12/FileHeader: bug fix - false positives on PHP 8.2+ readonly classes From b6264f9f957e6898a25b535b51dc6807e0869b16 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Sun, 27 Aug 2023 17:24:36 +0200 Subject: [PATCH 723/733] Config: fix filter argument case in help texts (#3879) The filter names are case-sensitive (depending on OS), so the help texts should display the case which will be accepted on all OS-es. --- src/Config.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Config.php b/src/Config.php index 05f48c92a6..0320bdee87 100644 --- a/src/Config.php +++ b/src/Config.php @@ -1403,7 +1403,7 @@ public function printPHPCSUsage() echo ' e.g., module/php,es/js'.PHP_EOL; echo ' One or more files and/or directories to check'.PHP_EOL; echo ' A file containing a list of files and/or directories to check (one per line)'.PHP_EOL; - echo ' Use either the "gitmodified" or "gitstaged" filter,'.PHP_EOL; + echo ' Use either the "GitModified" or "GitStaged" filter,'.PHP_EOL; echo ' or specify the path to a custom filter class'.PHP_EOL; echo ' Use either the "HTML", "Markdown" or "Text" generator'.PHP_EOL; echo ' (forces documentation generation instead of checking)'.PHP_EOL; @@ -1465,7 +1465,7 @@ public function printPHPCBFUsage() echo ' e.g., module/php,es/js'.PHP_EOL; echo ' One or more files and/or directories to fix'.PHP_EOL; echo ' A file containing a list of files and/or directories to fix (one per line)'.PHP_EOL; - echo ' Use either the "gitmodified" or "gitstaged" filter,'.PHP_EOL; + echo ' Use either the "GitModified" or "GitStaged" filter,'.PHP_EOL; echo ' or specify the path to a custom filter class'.PHP_EOL; echo ' A comma separated list of patterns to ignore files and directories'.PHP_EOL; echo ' How many files should be fixed simultaneously (default is 1)'.PHP_EOL; From b0ecdf10fa5a15bbf341b6dd75d1b23bd803aaaf Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sun, 27 Aug 2023 17:25:51 +0200 Subject: [PATCH 724/733] Changelog for #3879 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 80d7c63d3f..a66c5b6256 100644 --- a/package.xml +++ b/package.xml @@ -106,6 +106,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3816 : PSR12/FileHeader: bug fix - false positives on PHP 8.2+ readonly classes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3877 : Filter names can be case-sensitive. The -h help text will now display the correct case for the available filters + -- Thanks to @simonsan for the patch From 7566b4d89de1d08d2a8ad85910507d6633a35d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olda=20=C5=A0=C3=A1lek?= Date: Mon, 11 Sep 2023 13:08:03 +0200 Subject: [PATCH 725/733] Fix invalid namespace in phpdoc (\PHP_CodeSniffer\Files\File) (#3885) --- src/Reports/Cbf.php | 8 ++++---- src/Reports/Checkstyle.php | 8 ++++---- src/Reports/Code.php | 8 ++++---- src/Reports/Csv.php | 8 ++++---- src/Reports/Diff.php | 8 ++++---- src/Reports/Emacs.php | 8 ++++---- src/Reports/Full.php | 8 ++++---- src/Reports/Info.php | 8 ++++---- src/Reports/Json.php | 8 ++++---- src/Reports/Junit.php | 8 ++++---- src/Reports/Notifysend.php | 8 ++++---- src/Reports/Report.php | 8 ++++---- src/Reports/Source.php | 8 ++++---- src/Reports/Summary.php | 8 ++++---- src/Reports/VersionControl.php | 8 ++++---- src/Reports/Xml.php | 8 ++++---- 16 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/Reports/Cbf.php b/src/Reports/Cbf.php index 0ecde76e67..c85353d988 100644 --- a/src/Reports/Cbf.php +++ b/src/Reports/Cbf.php @@ -28,10 +28,10 @@ class Cbf implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool * @throws \PHP_CodeSniffer\Exceptions\DeepExitException diff --git a/src/Reports/Checkstyle.php b/src/Reports/Checkstyle.php index 06a78e19fa..313e086710 100644 --- a/src/Reports/Checkstyle.php +++ b/src/Reports/Checkstyle.php @@ -23,10 +23,10 @@ class Checkstyle implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Code.php b/src/Reports/Code.php index 47c5581e25..4a7a27af82 100644 --- a/src/Reports/Code.php +++ b/src/Reports/Code.php @@ -23,10 +23,10 @@ class Code implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Csv.php b/src/Reports/Csv.php index 6db7ecfc18..1f02edbd85 100644 --- a/src/Reports/Csv.php +++ b/src/Reports/Csv.php @@ -22,10 +22,10 @@ class Csv implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Diff.php b/src/Reports/Diff.php index ce4b31fc08..68af0a0261 100644 --- a/src/Reports/Diff.php +++ b/src/Reports/Diff.php @@ -22,10 +22,10 @@ class Diff implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Emacs.php b/src/Reports/Emacs.php index 3555f55418..cae9d22d85 100644 --- a/src/Reports/Emacs.php +++ b/src/Reports/Emacs.php @@ -22,10 +22,10 @@ class Emacs implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Full.php b/src/Reports/Full.php index 084bc8aa58..5f6bcde80a 100644 --- a/src/Reports/Full.php +++ b/src/Reports/Full.php @@ -23,10 +23,10 @@ class Full implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Info.php b/src/Reports/Info.php index 3181bcc546..fa0698efdf 100644 --- a/src/Reports/Info.php +++ b/src/Reports/Info.php @@ -23,10 +23,10 @@ class Info implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Json.php b/src/Reports/Json.php index 59d8f305e5..f7aedb30a5 100644 --- a/src/Reports/Json.php +++ b/src/Reports/Json.php @@ -23,10 +23,10 @@ class Json implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Junit.php b/src/Reports/Junit.php index d3ede61d47..0b59604ae0 100644 --- a/src/Reports/Junit.php +++ b/src/Reports/Junit.php @@ -24,10 +24,10 @@ class Junit implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Notifysend.php b/src/Reports/Notifysend.php index 0841662234..71aed108ed 100644 --- a/src/Reports/Notifysend.php +++ b/src/Reports/Notifysend.php @@ -88,10 +88,10 @@ public function __construct() * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Report.php b/src/Reports/Report.php index 38f8a6298c..fd60185e94 100644 --- a/src/Reports/Report.php +++ b/src/Reports/Report.php @@ -22,10 +22,10 @@ interface Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Source.php b/src/Reports/Source.php index ce8c3cfec1..44f0d8748e 100644 --- a/src/Reports/Source.php +++ b/src/Reports/Source.php @@ -23,10 +23,10 @@ class Source implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Summary.php b/src/Reports/Summary.php index 8fe18e7640..cd5ca69476 100644 --- a/src/Reports/Summary.php +++ b/src/Reports/Summary.php @@ -23,10 +23,10 @@ class Summary implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/VersionControl.php b/src/Reports/VersionControl.php index 0f414567dc..e8e399a7dd 100644 --- a/src/Reports/VersionControl.php +++ b/src/Reports/VersionControl.php @@ -31,10 +31,10 @@ abstract class VersionControl implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ diff --git a/src/Reports/Xml.php b/src/Reports/Xml.php index 066383debe..c748dca726 100644 --- a/src/Reports/Xml.php +++ b/src/Reports/Xml.php @@ -23,10 +23,10 @@ class Xml implements Report * and FALSE if it ignored the file. Returning TRUE indicates that the file and * its data should be counted in the grand totals. * - * @param array $report Prepared report data. - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. - * @param bool $showSources Show sources? - * @param int $width Maximum allowed line width. + * @param array $report Prepared report data. + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being reported on. + * @param bool $showSources Show sources? + * @param int $width Maximum allowed line width. * * @return bool */ From ebb0a969a3e2ed1dd1560ba960bec011f8853714 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Thu, 12 Oct 2023 01:44:48 +0100 Subject: [PATCH 726/733] Squiz/NonExecutableCode: fix false positives when code goes in and out of PHP/HTML (#3770) Ignore HTML whitespace and PHP re-open tags when determining whether code should be flagged as non executable. --- package.xml | 1 + .../Sniffs/PHP/NonExecutableCodeSniff.php | 16 ++++- .../Tests/PHP/NonExecutableCodeUnitTest.2.inc | 12 ++++ .../Tests/PHP/NonExecutableCodeUnitTest.3.inc | 64 +++++++++++++++++++ .../Tests/PHP/NonExecutableCodeUnitTest.php | 12 ++++ 5 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.3.inc diff --git a/package.xml b/package.xml index a66c5b6256..b0d140c12f 100644 --- a/package.xml +++ b/package.xml @@ -1940,6 +1940,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + diff --git a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php index 12c8881145..31d9c5a351 100644 --- a/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php +++ b/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php @@ -241,8 +241,8 @@ public function process(File $phpcsFile, $stackPtr) $end = ($phpcsFile->numTokens - 1); }//end if - // Find the semicolon that ends this statement, skipping - // nested statements like FOR loops and closures. + // Find the semicolon or closing PHP tag that ends this statement, + // skipping nested statements like FOR loops and closures. for ($start = ($stackPtr + 1); $start < $phpcsFile->numTokens; $start++) { if ($start === $end) { break; @@ -262,7 +262,7 @@ public function process(File $phpcsFile, $stackPtr) continue; } - if ($tokens[$start]['code'] === T_SEMICOLON) { + if ($tokens[$start]['code'] === T_SEMICOLON || $tokens[$start]['code'] === T_CLOSE_TAG) { break; } }//end for @@ -295,6 +295,16 @@ public function process(File $phpcsFile, $stackPtr) continue; } + // Skip HTML whitespace. + if ($tokens[$i]['code'] === T_INLINE_HTML && \trim($tokens[$i]['content']) === '') { + continue; + } + + // Skip PHP re-open tag (eg, after inline HTML). + if ($tokens[$i]['code'] === T_OPEN_TAG) { + continue; + } + $line = $tokens[$i]['line']; if ($line > $lastLine) { $type = substr($tokens[$stackPtr]['type'], 2); diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc index c9bf052f34..9b7a22bcc9 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.2.inc @@ -58,4 +58,16 @@ $a = new class { } }; +// Multiple statements are still one line of unreachable code, so should get +// only one complaint from this sniff. (Well, technically two here since there +// are two 'exit()' statements above, so one complaint from each of those. So, +// two here, but not six.) +echo 'one'; echo 'two'; echo 'three'; + +// A single statement split across multiple lines. Here we get complaints for +// each line, even though they're all part of one statement. +echo 'one' . 'two' + . 'three' . 'four' + . 'five' . 'six'; + interface MyInterface { diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.3.inc b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.3.inc new file mode 100644 index 0000000000..6fe5c16c66 --- /dev/null +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.3.inc @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
non-executable
+ + + + + + + + +
non-executable
+ + + + + + + + +
non-executable
+ + + + + + + + + + + + + + + + + + + diff --git a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php index 4348df1318..40e4068e42 100644 --- a/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php +++ b/src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php @@ -92,8 +92,20 @@ public function getWarningList($testFile='') 10 => 2, 14 => 1, 54 => 2, + 65 => 2, + 69 => 2, + 70 => 2, + 71 => 2, ]; break; + case 'NonExecutableCodeUnitTest.3.inc': + return [ + 27 => 1, + 36 => 1, + 45 => 1, + 54 => 1, + 62 => 1, + ]; default: return []; break; From 8dfb63220e1fd5cfcce07f20bc24f75a5269597c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 12 Oct 2023 02:46:56 +0200 Subject: [PATCH 727/733] Changelog for #3770 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index b0d140c12f..f7b4f93ef3 100644 --- a/package.xml +++ b/package.xml @@ -80,6 +80,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3728 : PHP 8.2 | PSR1/SideEffects: allow for readonly classes -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch + - Fixed bug #3770 : Squiz/NonExecutableCode: prevent false positives for switching between PHP and HTML + -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3776 : Generic/JSHint: error when JSHint is not available -- Thanks to Dan Wallis (@fredden) for the patch - Fixed bug #3777 : Squiz/NonExecutableCode: slew of bug fixes, mostly related to modern PHP From 01167c3176a1071c740a9ab190c0f179b84add3f Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Sat, 28 Oct 2023 17:09:43 +0100 Subject: [PATCH 728/733] Fix bug in CSS tokenizer (#3906) ... causing a rogue "closing tag" token being inserted, which leads to problematic output when a fixer tries to manipulate the token. Includes test via the sniff which led to discovery of the issue. --- .../Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css | 7 +++++++ .../WhiteSpace/SuperfluousWhitespaceUnitTest.1.css.fixed | 7 +++++++ .../Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.php | 2 +- src/Tokenizers/CSS.php | 6 +++++- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css b/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css index 1dd1b6e6ba..e3f3f02918 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css +++ b/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css @@ -23,3 +23,10 @@ } /* phpcs:set Squiz.WhiteSpace.SuperfluousWhitespace ignoreBlankLines false */ +// /** +// * This text is in two types of comment: each line is commented out +// * individually, and the whole block is in what looks like a +// * docblock-comment. This sniff should ignore all this text as there +// * is no superfluous white-space here. +// */ + diff --git a/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css.fixed b/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css.fixed index 59ddddb084..11be21d5c9 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css.fixed +++ b/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.1.css.fixed @@ -21,3 +21,10 @@ float: left; } /* phpcs:set Squiz.WhiteSpace.SuperfluousWhitespace ignoreBlankLines false */ + +// /** +// * This text is in two types of comment: each line is commented out +// * individually, and the whole block is in what looks like a +// * docblock-comment. This sniff should ignore all this text as there +// * is no superfluous white-space here. +// */ diff --git a/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.php b/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.php index b9ff96fbc7..2f1ead2307 100644 --- a/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.php +++ b/src/Standards/Squiz/Tests/WhiteSpace/SuperfluousWhitespaceUnitTest.php @@ -84,7 +84,7 @@ public function getErrorList($testFile='SuperfluousWhitespaceUnitTest.inc') 8 => 1, 9 => 1, 11 => 1, - 25 => 1, + 32 => 1, ]; break; default: diff --git a/src/Tokenizers/CSS.php b/src/Tokenizers/CSS.php index b7c2018bcc..2f486d6cfa 100644 --- a/src/Tokenizers/CSS.php +++ b/src/Tokenizers/CSS.php @@ -196,7 +196,11 @@ public function tokenize($string) // The first and last tokens are the open/close tags. array_shift($commentTokens); - array_pop($commentTokens); + $closeTag = array_pop($commentTokens); + + while ($closeTag['content'] !== '?'.'>') { + $closeTag = array_pop($commentTokens); + } if ($leadingZero === true) { $commentTokens[0]['content'] = substr($commentTokens[0]['content'], 1); From 321d252dc9c180a085ecb6acc8a861aba8c4da2d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Sat, 28 Oct 2023 18:18:59 +0200 Subject: [PATCH 729/733] Changelog for #3906 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index f7b4f93ef3..557afb14dd 100644 --- a/package.xml +++ b/package.xml @@ -110,6 +110,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Fixed bug #3877 : Filter names can be case-sensitive. The -h help text will now display the correct case for the available filters -- Thanks to @simonsan for the patch + - Fixed bug #3906 : Tokenizer/CSS: fixed a bug related to the unsupported slash comment syntax + -- Thanks to Dan Wallis (@fredden) for the patch From 9a5b392b96f9ba38a99a8b54a879b5fe9738f25c Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Fri, 27 Oct 2023 18:08:49 +0100 Subject: [PATCH 730/733] Auto-fix PEAR.Commenting.FunctionComment.SpacingAfter --- .../Commenting/FunctionCommentSniff.php | 20 +++++++++-- .../Commenting/FunctionCommentUnitTest.inc | 34 +++++++++++++++++++ .../FunctionCommentUnitTest.inc.fixed | 21 +++++++++--- .../Commenting/FunctionCommentUnitTest.php | 2 ++ 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php index 408856fc5a..1bf9444784 100644 --- a/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php +++ b/src/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php @@ -129,11 +129,25 @@ public function process(File $phpcsFile, $stackPtr) && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] ) { $error = 'There must be no blank lines after the function comment'; - $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); + $fix = $phpcsFile->addFixableError($error, $commentEnd, 'SpacingAfter'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + while ($i < $stackPtr + && $tokens[$i]['code'] === T_WHITESPACE + && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] + ) { + $phpcsFile->fixer->replaceToken($i++, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + break; } - } - } + }//end for + }//end if $commentStart = $tokens[$commentEnd]['comment_opener']; foreach ($tokens[$commentStart]['comment_tags'] as $tag) { diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc index 5c3295fd60..a20ba3a720 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc @@ -476,3 +476,37 @@ class Something implements JsonSerializable { public function blankLineDetectionC() {} } + +class SpacingAfter { + /** + * There are multiple blank lines between this comment and the next function. + * + * @return void + */ + + + + + + + + + public function multipleBlankLines() {} + + /** + * There are multiple blank lines, and some "empty" lines with only + * spaces/tabs between this comment and the next function. + * + * @return void + */ + + + + + + + + + + public function multipleLinesSomeEmpty() {} +} diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index 751b09c665..fc6d4f7e71 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -453,7 +453,6 @@ class Something implements JsonSerializable { * * @return mixed */ - #[ReturnTypeWillChange] public function blankLineDetectionA() {} @@ -463,7 +462,6 @@ class Something implements JsonSerializable { * @return mixed */ #[ReturnTypeWillChange] - public function blankLineDetectionB() {} /** @@ -471,8 +469,23 @@ class Something implements JsonSerializable { * * @return mixed */ - #[ReturnTypeWillChange] - public function blankLineDetectionC() {} } + +class SpacingAfter { + /** + * There are multiple blank lines between this comment and the next function. + * + * @return void + */ + public function multipleBlankLines() {} + + /** + * There are multiple blank lines, and some "empty" lines with only + * spaces/tabs between this comment and the next function. + * + * @return void + */ + public function multipleLinesSomeEmpty() {} +} diff --git a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php index 734ff73e50..e7ec800d02 100644 --- a/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php +++ b/src/Standards/PEAR/Tests/Commenting/FunctionCommentUnitTest.php @@ -73,6 +73,8 @@ public function getErrorList() 455 => 1, 464 => 1, 473 => 1, + 485 => 1, + 501 => 1, ]; }//end getErrorList() From 7763e2e1f773cb0615ed8afa133189fc804f583d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 2 Nov 2023 01:47:31 +0100 Subject: [PATCH 731/733] Changelog for #3908 --- package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.xml b/package.xml index 557afb14dd..4120579390 100644 --- a/package.xml +++ b/package.xml @@ -65,6 +65,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Juliette Reinders Folmer (@jrfnl) for the patch - Squiz.Commenting.FunctionComment: new ParamNameUnexpectedAmpersandPrefix error for parameters annotated as passed by reference while the parameter is not passed by reference -- Thanks to Dan Wallis (@fredden) for the patch + - PEAR.Commenting.FunctionComment + Squiz.Commenting.FunctionComment: the SpacingAfter error can now be auto-fixed. + -- Thanks to Dan Wallis (@fredden) for the patch - Squiz.PHP.InnerFunctions sniff no longer reports on OO methods for OO structures declared within a function or closure -- Thanks to @Daimona for the patch - Documentation has been added for the following sniffs: From d23aaac98300839c3b1f6f0670f3c45c21ec3366 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 7 Nov 2023 02:08:29 +0100 Subject: [PATCH 732/733] Mark the repo as abandoned See: 3932 --- README.md | 7 +++++++ composer.json | 1 + 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 859de432c6..25c51c7cbf 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +> [!WARNING] +> This repository has been abandoned. Its successor is [PHPCSStandards/PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer/) +> +> See issue [#3932](https://github.com/squizlabs/PHP_CodeSniffer/issues/3932) for more information. +> + + ## About PHP_CodeSniffer is a set of two PHP scripts; the main `phpcs` script that tokenizes PHP, JavaScript and CSS files to detect violations of a defined coding standard, and a second `phpcbf` script to automatically correct coding standard violations. PHP_CodeSniffer is an essential development tool that ensures your code remains clean and consistent. diff --git a/composer.json b/composer.json index 37f41a0b80..fd3b70f831 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,7 @@ "require-dev": { "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, + "abandoned": "phpcsstandards/php_codesniffer", "bin": [ "bin/phpcs", "bin/phpcbf" From c6c65ca0dc8608ba87631523b97b2f8d5351a854 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 6 Dec 2023 11:28:34 +0100 Subject: [PATCH 733/733] Composer: don't mark as abandoned ... as the package name for the Composer package on Packagist will remain the same. --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index fd3b70f831..37f41a0b80 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,6 @@ "require-dev": { "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, - "abandoned": "phpcsstandards/php_codesniffer", "bin": [ "bin/phpcs", "bin/phpcbf"