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 [];