PHP_CodeSniffer
[ class tree: PHP_CodeSniffer ] [ index: PHP_CodeSniffer ] [ all elements ]

Source for file SwitchDeclarationSniff.php

Documentation is available at SwitchDeclarationSniff.php

  1. <?php
  2. /**
  3.  * Enforces switch statement formatting.
  4.  *
  5.  * @author    Greg Sherwood <gsherwood@squiz.net>
  6.  * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  7.  * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  8.  */
  9.  
  10. namespace PHP_CodeSniffer\Standards\Squiz\Sniffs\ControlStructures;
  11.  
  12. use PHP_CodeSniffer\Sniffs\Sniff;
  13. use PHP_CodeSniffer\Files\File;
  14. use PHP_CodeSniffer\Util\Tokens;
  15.  
  16. class SwitchDeclarationSniff implements Sniff
  17. {
  18.  
  19.     /**
  20.      * A list of tokenizers this sniff supports.
  21.      *
  22.      * @var array 
  23.      */
  24.     public $supportedTokenizers = array(
  25.                                    'PHP',
  26.                                    'JS',
  27.                                   );
  28.  
  29.     /**
  30.      * The number of spaces code should be indented.
  31.      *
  32.      * @var integer 
  33.      */
  34.     public $indent = 4;
  35.  
  36.  
  37.     /**
  38.      * Returns an array of tokens this test wants to listen for.
  39.      *
  40.      * @return array 
  41.      */
  42.     public function register()
  43.     {
  44.         return array(T_SWITCH);
  45.  
  46.     }//end register()
  47.  
  48.  
  49.     /**
  50.      * Processes this test, when one of its tokens is encountered.
  51.      *
  52.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  53.      * @param int                         $stackPtr  The position of the current token in the
  54.      *                                                stack passed in $tokens.
  55.      *
  56.      * @return void 
  57.      */
  58.     public function process(File $phpcsFile, $stackPtr)
  59.     {
  60.         $tokens = $phpcsFile->getTokens();
  61.  
  62.         // We can't process SWITCH statements unless we know where they start and end.
  63.         if (isset($tokens[$stackPtr]['scope_opener']) === false
  64.             || isset($tokens[$stackPtr]['scope_closer']) === false
  65.         ) {
  66.             return;
  67.         }
  68.  
  69.         $switch        = $tokens[$stackPtr];
  70.         $nextCase      = $stackPtr;
  71.         $caseAlignment = ($switch['column'] + $this->indent);
  72.         $caseCount     = 0;
  73.         $foundDefault  = false;
  74.  
  75.         while (($nextCase = $phpcsFile->findNext(array(T_CASE, T_DEFAULT, T_SWITCH), ($nextCase + 1), $switch['scope_closer'])) !== false) {
  76.             // Skip nested SWITCH statements; they are handled on their own.
  77.             if ($tokens[$nextCase]['code'] === T_SWITCH) {
  78.                 $nextCase = $tokens[$nextCase]['scope_closer'];
  79.                 continue;
  80.             }
  81.  
  82.             if ($tokens[$nextCase]['code'] === T_DEFAULT) {
  83.                 $type         = 'Default';
  84.                 $foundDefault = true;
  85.             } else {
  86.                 $type = 'Case';
  87.                 $caseCount++;
  88.             }
  89.  
  90.             if ($tokens[$nextCase]['content'] !== strtolower($tokens[$nextCase]['content'])) {
  91.                 $expected = strtolower($tokens[$nextCase]['content']);
  92.                 $error    = strtoupper($type).' keyword must be lowercase; expected "%s" but found "%s"';
  93.                 $data     = array(
  94.                              $expected,
  95.                              $tokens[$nextCase]['content'],
  96.                             );
  97.  
  98.                 $fix = $phpcsFile->addFixableError($error, $nextCase, $type.'NotLower', $data);
  99.                 if ($fix === true) {
  100.                     $phpcsFile->fixer->replaceToken($nextCase, $expected);
  101.                 }
  102.             }
  103.  
  104.             if ($tokens[$nextCase]['column'] !== $caseAlignment) {
  105.                 $error = strtoupper($type).' keyword must be indented '.$this->indent.' spaces from SWITCH keyword';
  106.                 $fix   = $phpcsFile->addFixableError($error, $nextCase, $type.'Indent');
  107.  
  108.                 if ($fix === true) {
  109.                     $padding = str_repeat(' ', ($caseAlignment - 1));
  110.                     if ($tokens[$nextCase]['column'] === 1
  111.                         || $tokens[($nextCase - 1)]['code'] !== T_WHITESPACE
  112.                     ) {
  113.                         $phpcsFile->fixer->addContentBefore($nextCase, $padding);
  114.                     } else {
  115.                         $phpcsFile->fixer->replaceToken(($nextCase - 1), $padding);
  116.                     }
  117.                 }
  118.             }
  119.  
  120.             if ($type === 'Case'
  121.                 && ($tokens[($nextCase + 1)]['type'] !== 'T_WHITESPACE'
  122.                 || $tokens[($nextCase + 1)]['content'] !== ' ')
  123.             ) {
  124.                 $error = 'CASE keyword must be followed by a single space';
  125.                 $fix   = $phpcsFile->addFixableError($error, $nextCase, 'SpacingAfterCase');
  126.                 if ($fix === true) {
  127.                     if ($tokens[($nextCase + 1)]['type'] !== 'T_WHITESPACE') {
  128.                         $phpcsFile->fixer->addContent($nextCase, ' ');
  129.                     } else {
  130.                         $phpcsFile->fixer->replaceToken(($nextCase + 1), ' ');
  131.                     }
  132.                 }
  133.             }
  134.  
  135.             if (isset($tokens[$nextCase]['scope_opener']) === false) {
  136.                 $error = 'Possible parse error: CASE missing opening colon';
  137.                 $phpcsFile->addWarning($error, $nextCase, 'MissingColon');
  138.                 continue;
  139.             }
  140.  
  141.             $opener = $tokens[$nextCase]['scope_opener'];
  142.             if ($tokens[($opener - 1)]['type'] === 'T_WHITESPACE') {
  143.                 $error = 'There must be no space before the colon in a '.strtoupper($type).' statement';
  144.                 $fix   = $phpcsFile->addFixableError($error, $nextCase, 'SpaceBeforeColon'.$type);
  145.                 if ($fix === true) {
  146.                     $phpcsFile->fixer->replaceToken(($opener - 1), '');
  147.                 }
  148.             }
  149.  
  150.             $nextBreak = $tokens[$nextCase]['scope_closer'];
  151.             if ($tokens[$nextBreak]['code'] === T_BREAK
  152.                 || $tokens[$nextBreak]['code'] === T_RETURN
  153.                 || $tokens[$nextBreak]['code'] === T_CONTINUE
  154.                 || $tokens[$nextBreak]['code'] === T_THROW
  155.                 || $tokens[$nextBreak]['code'] === T_EXIT
  156.             ) {
  157.                 if ($tokens[$nextBreak]['scope_condition'] === $nextCase) {
  158.                     // Only need to check a couple of things once, even if the
  159.                     // break is shared between multiple case statements, or even
  160.                     // the default case.
  161.                     if ($tokens[$nextBreak]['column'] !== $caseAlignment) {
  162.                         $error = 'Case breaking statement must be indented '.$this->indent.' spaces from SWITCH keyword';
  163.                         $fix   = $phpcsFile->addFixableError($error, $nextBreak, 'BreakIndent');
  164.  
  165.                         if ($fix === true) {
  166.                             $padding = str_repeat(' ', ($caseAlignment - 1));
  167.                             if ($tokens[$nextBreak]['column'] === 1
  168.                                 || $tokens[($nextBreak - 1)]['code'] !== T_WHITESPACE
  169.                             ) {
  170.                                 $phpcsFile->fixer->addContentBefore($nextBreak, $padding);
  171.                             } else {
  172.                                 $phpcsFile->fixer->replaceToken(($nextBreak - 1), $padding);
  173.                             }
  174.                         }
  175.                     }
  176.  
  177.                     $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($nextBreak - 1), $stackPtr, true);
  178.                     if ($tokens[$prev]['line'] !== ($tokens[$nextBreak]['line'] - 1)) {
  179.                         $error = 'Blank lines are not allowed before case breaking statements';
  180.                         $phpcsFile->addError($error, $nextBreak, 'SpacingBeforeBreak');
  181.                     }
  182.  
  183.                     $nextLine  = $tokens[$tokens[$stackPtr]['scope_closer']]['line'];
  184.                     $semicolon = $phpcsFile->findEndOfStatement($nextBreak);
  185.                     for ($i = ($semicolon + 1); $i < $tokens[$stackPtr]['scope_closer']; $i++) {
  186.                         if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  187.                             $nextLine = $tokens[$i]['line'];
  188.                             break;
  189.                         }
  190.                     }
  191.  
  192.                     if ($type === 'Case') {
  193.                         // Ensure the BREAK statement is followed by
  194.                         // a single blank line, or the end switch brace.
  195.                         if ($nextLine !== ($tokens[$semicolon]['line'] + 2) && $i !== $tokens[$stackPtr]['scope_closer']) {
  196.                             $error = 'Case breaking statements must be followed by a single blank line';
  197.                             $fix   = $phpcsFile->addFixableError($error, $nextBreak, 'SpacingAfterBreak');
  198.                             if ($fix === true) {
  199.                                 $phpcsFile->fixer->beginChangeset();
  200.                                 for ($i = ($semicolon + 1); $i <= $tokens[$stackPtr]['scope_closer']; $i++) {
  201.                                     if ($tokens[$i]['line'] === $nextLine) {
  202.                                         $phpcsFile->fixer->addNewlineBefore($i);
  203.                                         break;
  204.                                     }
  205.  
  206.                                     if ($tokens[$i]['line'] === $tokens[$semicolon]['line']) {
  207.                                         continue;
  208.                                     }
  209.  
  210.                                     $phpcsFile->fixer->replaceToken($i, '');
  211.                                 }
  212.  
  213.                                 $phpcsFile->fixer->endChangeset();
  214.                             }
  215.                         }//end if
  216.                     } else {
  217.                         // Ensure the BREAK statement is not followed by a blank line.
  218.                         if ($nextLine !== ($tokens[$semicolon]['line'] + 1)) {
  219.                             $error = 'Blank lines are not allowed after the DEFAULT case\'s breaking statement';
  220.                             $phpcsFile->addError($error, $nextBreak, 'SpacingAfterDefaultBreak');
  221.                         }
  222.                     }//end if
  223.  
  224.                     $caseLine = $tokens[$nextCase]['line'];
  225.                     $nextLine = $tokens[$nextBreak]['line'];
  226.                     for ($i = ($opener + 1); $i < $nextBreak; $i++) {
  227.                         if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  228.                             $nextLine = $tokens[$i]['line'];
  229.                             break;
  230.                         }
  231.                     }
  232.  
  233.                     if ($nextLine !== ($caseLine + 1)) {
  234.                         $error = 'Blank lines are not allowed after '.strtoupper($type).' statements';
  235.                         $phpcsFile->addError($error, $nextCase, 'SpacingAfter'.$type);
  236.                     }
  237.                 }//end if
  238.  
  239.                 if ($tokens[$nextBreak]['code'] === T_BREAK) {
  240.                     if ($type === 'Case') {
  241.                         // Ensure empty CASE statements are not allowed.
  242.                         // They must have some code content in them. A comment is not enough.
  243.                         // But count RETURN statements as valid content if they also
  244.                         // happen to close the CASE statement.
  245.                         $foundContent = false;
  246.                         for ($i = ($tokens[$nextCase]['scope_opener'] + 1); $i < $nextBreak; $i++) {
  247.                             if ($tokens[$i]['code'] === T_CASE) {
  248.                                 $i = $tokens[$i]['scope_opener'];
  249.                                 continue;
  250.                             }
  251.  
  252.                             if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) {
  253.                                 $foundContent = true;
  254.                                 break;
  255.                             }
  256.                         }
  257.  
  258.                         if ($foundContent === false) {
  259.                             $error = 'Empty CASE statements are not allowed';
  260.                             $phpcsFile->addError($error, $nextCase, 'EmptyCase');
  261.                         }
  262.                     } else {
  263.                         // Ensure empty DEFAULT statements are not allowed.
  264.                         // They must (at least) have a comment describing why
  265.                         // the default case is being ignored.
  266.                         $foundContent = false;
  267.                         for ($i = ($tokens[$nextCase]['scope_opener'] + 1); $i < $nextBreak; $i++) {
  268.                             if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  269.                                 $foundContent = true;
  270.                                 break;
  271.                             }
  272.                         }
  273.  
  274.                         if ($foundContent === false) {
  275.                             $error = 'Comment required for empty DEFAULT case';
  276.                             $phpcsFile->addError($error, $nextCase, 'EmptyDefault');
  277.                         }
  278.                     }//end if
  279.                 }//end if
  280.             } else if ($type === 'Default') {
  281.                 $error = 'DEFAULT case must have a breaking statement';
  282.                 $phpcsFile->addError($error, $nextCase, 'DefaultNoBreak');
  283.             }//end if
  284.         }//end while
  285.  
  286.         if ($foundDefault === false) {
  287.             $error = 'All SWITCH statements must contain a DEFAULT case';
  288.             $phpcsFile->addError($error, $stackPtr, 'MissingDefault');
  289.         }
  290.  
  291.         if ($tokens[$switch['scope_closer']]['column'] !== $switch['column']) {
  292.             $error = 'Closing brace of SWITCH statement must be aligned with SWITCH keyword';
  293.             $phpcsFile->addError($error, $switch['scope_closer'], 'CloseBraceAlign');
  294.         }
  295.  
  296.         if ($caseCount === 0) {
  297.             $error = 'SWITCH statements must contain at least one CASE statement';
  298.             $phpcsFile->addError($error, $stackPtr, 'MissingCase');
  299.         }
  300.  
  301.     }//end process()
  302.  
  303.  
  304. }//end class

Documentation generated on Mon, 11 Mar 2019 15:27:47 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.