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_CASET_DEFAULTT_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)$stackPtrtrue);
  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.