diff --git a/src/Standards/PSR12/Docs/Operators/OperatorSpacingStandard.xml b/src/Standards/PSR12/Docs/Operators/OperatorSpacingStandard.xml index 981b1d97a1..dd30c3bd8f 100644 --- a/src/Standards/PSR12/Docs/Operators/OperatorSpacingStandard.xml +++ b/src/Standards/PSR12/Docs/Operators/OperatorSpacingStandard.xml @@ -20,6 +20,27 @@ if ($a===$b) { $foo=$bar??$a??$b; } elseif ($a>$b) { $variable=$foo?'foo':'bar'; +} + ]]> + + + + + + + + + + + diff --git a/src/Standards/PSR12/Sniffs/Operators/OperatorSpacingSniff.php b/src/Standards/PSR12/Sniffs/Operators/OperatorSpacingSniff.php index 824169438a..898f1a0e8d 100644 --- a/src/Standards/PSR12/Sniffs/Operators/OperatorSpacingSniff.php +++ b/src/Standards/PSR12/Sniffs/Operators/OperatorSpacingSniff.php @@ -17,6 +17,13 @@ class OperatorSpacingSniff extends SquizOperatorSpacingSniff { + /** + * The PER version to be compatible with. For backwards compatibility this is set to 1.0 by default. + * + * @var string + */ + public $perCompatible = '1.0'; + /** * Returns an array of tokens this test wants to listen for. @@ -75,6 +82,59 @@ public function process(File $phpcsFile, int $stackPtr) $operator = $tokens[$stackPtr]['content']; + // PER-CS 3.0: Exception to the rule for pipe operators in multi-catch blocks where no space is required. + // As union types didn't exist when PSR-12 was created, the pipe in catch statements + // was originally treated as a bitwise operator. This check changes the spacing requirement + // for that specific case when opting in to PER-CS 3.0 or higher. + if ($tokens[$stackPtr]['code'] === T_BITWISE_OR + && isset($tokens[$stackPtr]['nested_parenthesis']) === true + && version_compare($this->perCompatible, '3.0', '>=') === true + ) { + // Calling array_keys() on a nested sub-array can have memory leaks, assign to a variable first. + // See https://github.com/squizlabs/PHP_CodeSniffer/pull/2273 . + $parenthesis = $tokens[$stackPtr]['nested_parenthesis']; + $parenthesisKeys = array_keys($parenthesis); + $bracket = array_pop($parenthesisKeys); + if (isset($tokens[$bracket]['parenthesis_owner']) === true + && $tokens[$tokens[$bracket]['parenthesis_owner']]['code'] === T_CATCH + ) { + if ($tokens[($stackPtr - 1)]['code'] === T_WHITESPACE + && strpos($tokens[($stackPtr - 1)]['content'], $phpcsFile->eolChar) === false + && $tokens[($stackPtr - 1)]['column'] !== 1 + ) { + $error = 'Expected 0 spaces before "%s" in multi-catch statement; %s found'; + $data = [ + $operator, + $tokens[($stackPtr - 1)]['length'], + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'MultiCatchSpaceBefore', $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken(($stackPtr - 1), ''); + } + } + + if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE + && strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) === false + ) { + $error = 'Expected 0 spaces after "%s" in multi-catch statement; %s found'; + $data = [ + $operator, + $tokens[($stackPtr + 1)]['length'], + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'MultiCatchSpaceAfter', $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken(($stackPtr + 1), ''); + } + } + + // Now that this special case is handled, we can return early as we don't need to do + // further checks. + return; + } + } + $checkBefore = true; $checkAfter = true; diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.1.inc b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.1.inc index 14cf8e9dfd..25e0bd7103 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.1.inc +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.1.inc @@ -77,3 +77,91 @@ function setDefault(#[ImportValue( } declare(strict_types=1); + +// Valid. +try { + // nothing +} catch (Exception | RuntimeException $e) { +} + +// Valid because multiline formatting is undefined. +try { + // nothing +} catch ( + Exception + | RuntimeException + | KlausiException + | AnotherException + | ItNeverEndsException $e +) { +} + +// Valid because multiline formatting is undefined. +try { + // nothing +} catch ( + Exception | + RuntimeException | + KlausiException | + AnotherException | + ItNeverEndsException $e +) { +} + +// Valid because PSR 12 allows more than one space around operators. +try { + // nothing +} catch (Exception | RuntimeException $e) { +} + +// phpcs:set PSR12.Operators.OperatorSpacing perCompatible 3.0 + +// Valid. +try { + // nothing +} catch (Exception|RuntimeException $e) { +} + +// Valid because multiline formatting is undefined. +try { + // nothing +} catch ( + Exception + |RuntimeException + |KlausiException + |AnotherException + |ItNeverEndsException $e +) { +} + +// Valid because multiline formatting is undefined. +try { + // nothing +} catch ( + Exception| + RuntimeException| + KlausiException| + AnotherException| + ItNeverEndsException $e +) { +} + +// Invalid because of one space. +try { + // nothing +} catch (Exception | RuntimeException $e) { +} + +// Invalid because of multiple spaces. +try { + // nothing +} catch (Exception | RuntimeException $e) { +} + +// Testing that it works with future versions as well. +// phpcs:set PSR12.Operators.OperatorSpacing perCompatible 4.0 + +try { + // nothing +} catch (Exception | RuntimeException $e) { +} diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.1.inc.fixed b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.1.inc.fixed index 0f52f1cf74..f780cc6761 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.1.inc.fixed +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.1.inc.fixed @@ -77,3 +77,91 @@ function setDefault(#[ImportValue( } declare(strict_types=1); + +// Valid. +try { + // nothing +} catch (Exception | RuntimeException $e) { +} + +// Valid because multiline formatting is undefined. +try { + // nothing +} catch ( + Exception + | RuntimeException + | KlausiException + | AnotherException + | ItNeverEndsException $e +) { +} + +// Valid because multiline formatting is undefined. +try { + // nothing +} catch ( + Exception | + RuntimeException | + KlausiException | + AnotherException | + ItNeverEndsException $e +) { +} + +// Valid because PSR 12 allows more than one space around operators. +try { + // nothing +} catch (Exception | RuntimeException $e) { +} + +// phpcs:set PSR12.Operators.OperatorSpacing perCompatible 3.0 + +// Valid. +try { + // nothing +} catch (Exception|RuntimeException $e) { +} + +// Valid because multiline formatting is undefined. +try { + // nothing +} catch ( + Exception + |RuntimeException + |KlausiException + |AnotherException + |ItNeverEndsException $e +) { +} + +// Valid because multiline formatting is undefined. +try { + // nothing +} catch ( + Exception| + RuntimeException| + KlausiException| + AnotherException| + ItNeverEndsException $e +) { +} + +// Invalid because of one space. +try { + // nothing +} catch (Exception|RuntimeException $e) { +} + +// Invalid because of multiple spaces. +try { + // nothing +} catch (Exception|RuntimeException $e) { +} + +// Testing that it works with future versions as well. +// phpcs:set PSR12.Operators.OperatorSpacing perCompatible 4.0 + +try { + // nothing +} catch (Exception|RuntimeException $e) { +} diff --git a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.php b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.php index f0643ddcbd..21963420a5 100644 --- a/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.php +++ b/src/Standards/PSR12/Tests/Operators/OperatorSpacingUnitTest.php @@ -36,26 +36,29 @@ public function getErrorList($testFile = '') switch ($testFile) { case 'OperatorSpacingUnitTest.1.inc': return [ - 2 => 1, - 3 => 2, - 4 => 1, - 5 => 2, - 6 => 4, - 9 => 3, - 10 => 2, - 11 => 3, - 13 => 3, - 14 => 2, - 18 => 1, - 20 => 1, - 22 => 2, - 23 => 2, - 26 => 1, - 37 => 4, - 39 => 1, - 40 => 1, - 44 => 2, - 47 => 2, + 2 => 1, + 3 => 2, + 4 => 1, + 5 => 2, + 6 => 4, + 9 => 3, + 10 => 2, + 11 => 3, + 13 => 3, + 14 => 2, + 18 => 1, + 20 => 1, + 22 => 2, + 23 => 2, + 26 => 1, + 37 => 4, + 39 => 1, + 40 => 1, + 44 => 2, + 47 => 2, + 152 => 2, + 158 => 2, + 166 => 2, ]; default: return [];