RemovedTernaryAssociativitySniff.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. /**
  3. * PHPCompatibility, an external standard for PHP_CodeSniffer.
  4. *
  5. * @package PHPCompatibility
  6. * @copyright 2012-2019 PHPCompatibility Contributors
  7. * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
  8. * @link https://github.com/PHPCompatibility/PHPCompatibility
  9. */
  10. namespace PHPCompatibility\Sniffs\Operators;
  11. use PHPCompatibility\Sniff;
  12. use PHPCompatibility\PHPCSHelper;
  13. use PHP_CodeSniffer_File as File;
  14. use PHP_CodeSniffer_Tokens as Tokens;
  15. /**
  16. * The left-associativity of the ternary operator is deprecated in PHP 7.4 and
  17. * removed in PHP 8.0.
  18. *
  19. * PHP version 7.4
  20. * PHP version 8.0
  21. *
  22. * @link https://www.php.net/manual/en/migration74.deprecated.php#migration74.deprecated.core.nested-ternary
  23. * @link https://wiki.php.net/rfc/ternary_associativity
  24. * @link https://github.com/php/php-src/pull/4017
  25. *
  26. * @since 9.2.0
  27. */
  28. class RemovedTernaryAssociativitySniff extends Sniff
  29. {
  30. /**
  31. * List of tokens with a lower operator precedence than ternary.
  32. *
  33. * @since 9.2.0
  34. *
  35. * @var array
  36. */
  37. private $tokensWithLowerPrecedence = array(
  38. 'T_YIELD_FROM' => true,
  39. 'T_YIELD' => true,
  40. 'T_LOGICAL_AND' => true,
  41. 'T_LOGICAL_OR' => true,
  42. 'T_LOGICAL_XOR' => true,
  43. );
  44. /**
  45. * Returns an array of tokens this test wants to listen for.
  46. *
  47. * @since 9.2.0
  48. *
  49. * @return array
  50. */
  51. public function register()
  52. {
  53. return array(\T_INLINE_THEN);
  54. }
  55. /**
  56. * Processes this test, when one of its tokens is encountered.
  57. *
  58. * @since 9.2.0
  59. *
  60. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  61. * @param int $stackPtr The position of the current token
  62. * in the stack passed in $tokens.
  63. *
  64. * @return void
  65. */
  66. public function process(File $phpcsFile, $stackPtr)
  67. {
  68. if ($this->supportsAbove('7.4') === false) {
  69. return;
  70. }
  71. $tokens = $phpcsFile->getTokens();
  72. $endOfStatement = PHPCSHelper::findEndOfStatement($phpcsFile, $stackPtr);
  73. if ($tokens[$endOfStatement]['code'] !== \T_SEMICOLON
  74. && $tokens[$endOfStatement]['code'] !== \T_COLON
  75. && $tokens[$endOfStatement]['code'] !== \T_COMMA
  76. && $tokens[$endOfStatement]['code'] !== \T_DOUBLE_ARROW
  77. && $tokens[$endOfStatement]['code'] !== \T_OPEN_TAG
  78. && $tokens[$endOfStatement]['code'] !== \T_CLOSE_TAG
  79. ) {
  80. // End of statement is last non-empty before close brace, so make sure we examine that token too.
  81. ++$endOfStatement;
  82. }
  83. $ternaryCount = 0;
  84. $elseCount = 0;
  85. $shortTernaryCount = 0;
  86. // Start at $stackPtr so we don't need duplicate code for short ternary determination.
  87. for ($i = $stackPtr; $i < $endOfStatement; $i++) {
  88. if (($tokens[$i]['code'] === \T_OPEN_SHORT_ARRAY
  89. || $tokens[$i]['code'] === \T_OPEN_SQUARE_BRACKET
  90. || $tokens[$i]['code'] === \T_OPEN_CURLY_BRACKET)
  91. && isset($tokens[$i]['bracket_closer'])
  92. ) {
  93. // Skip over short arrays, array access keys and curlies.
  94. $i = $tokens[$i]['bracket_closer'];
  95. continue;
  96. }
  97. if ($tokens[$i]['code'] === \T_OPEN_PARENTHESIS
  98. && isset($tokens[$i]['parenthesis_closer'])
  99. ) {
  100. // Skip over anything between parentheses.
  101. $i = $tokens[$i]['parenthesis_closer'];
  102. continue;
  103. }
  104. // Check for operators with lower operator precedence.
  105. if (isset(Tokens::$assignmentTokens[$tokens[$i]['code']])
  106. || isset($this->tokensWithLowerPrecedence[$tokens[$i]['code']])
  107. ) {
  108. break;
  109. }
  110. if ($tokens[$i]['code'] === \T_INLINE_THEN) {
  111. ++$ternaryCount;
  112. if ($this->isShortTernary($phpcsFile, $i) === true) {
  113. ++$shortTernaryCount;
  114. }
  115. continue;
  116. }
  117. if ($tokens[$i]['code'] === \T_INLINE_ELSE) {
  118. if (($ternaryCount - $elseCount) >= 2) {
  119. // This is the `else` for a ternary in the middle part of a previous ternary.
  120. --$ternaryCount;
  121. } else {
  122. ++$elseCount;
  123. }
  124. continue;
  125. }
  126. }
  127. if ($ternaryCount > 1 && $ternaryCount === $elseCount && $ternaryCount > $shortTernaryCount) {
  128. $message = 'The left-associativity of the ternary operator has been deprecated in PHP 7.4';
  129. $isError = false;
  130. if ($this->supportsAbove('8.0') === true) {
  131. $message .= ' and removed in PHP 8.0';
  132. $isError = true;
  133. }
  134. $message .= '. Multiple consecutive ternaries detected. Use parenthesis to clarify the order in which the operations should be executed';
  135. $this->addMessage($phpcsFile, $message, $stackPtr, $isError);
  136. }
  137. }
  138. }