Guest
Public paste!

SwitchDeclarationSniff

By: a guest | Mar 19th, 2010 | Syntax: PHP | Size: 12.75 KB | Hits: 61 | Expires: Never
Copy text to clipboard
  1. <?php
  2. /**
  3.  * Zend_Sniffs_ControlStructures_SwitchDeclarationSniff.
  4.  *
  5.  * PHP version 5
  6.  *
  7.  * @category  PHP
  8.  * @package   PHP_CodeSniffer
  9.  * @author    Juan Sotuyo <juansotuyo@gmail.com>
  10.  * @copyright 2010 Juan Sotuyo (Buenos Aires, Argentina)
  11.  * @license   BSD Licence
  12.  * @version   1.0
  13.  * @link      http://pear.php.net/package/PHP_CodeSniffer
  14.  */
  15.  
  16. /**
  17.  * Zend_Sniffs_ControlStructures_SwitchDeclarationSniff.
  18.  *
  19.  * Ensures all the breaks and cases are aligned correctly according to their
  20.  * parent switch's alignment and enforces other switch formatting.
  21.  *
  22.  * @category  PHP
  23.  * @package   PHP_CodeSniffer
  24.  * @author    Juan Sotuyo <juansotuyo@gmail.com>
  25.  * @copyright 2010 Juan Sotuyo (Buenos Aires, Argentina)
  26.  * @license   BSD Licence
  27.  * @version   Release: 1.2.2
  28.  * @link      http://pear.php.net/package/PHP_CodeSniffer
  29.  */
  30. class Zend_Sniffs_ControlStructures_SwitchDeclarationSniff implements PHP_CodeSniffer_Sniff
  31. {
  32.  
  33.     /**
  34.      * A list of tokenizers this sniff supports.
  35.      *
  36.      * @var array
  37.      */
  38.     public $supportedTokenizers = array(
  39.                                    'PHP',
  40.                                    'JS',
  41.                                   );
  42.  
  43.  
  44.     /**
  45.      * Returns an array of tokens this test wants to listen for.
  46.      *
  47.      * @return array
  48.      */
  49.     public function register()
  50.     {
  51.         return array(T_SWITCH);
  52.  
  53.     }//end register()
  54.  
  55.  
  56.     /**
  57.      * Processes this test, when one of its tokens is encountered.
  58.      *
  59.      * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
  60.      * @param int                  $stackPtr  The position of the current token in the
  61.      *                                        stack passed in $tokens.
  62.      *
  63.      * @return void
  64.      */
  65.     public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
  66.     {
  67.         $tokens = $phpcsFile->getTokens();
  68.  
  69.         $switch        = $tokens[$stackPtr];
  70.         $nextCase      = $stackPtr;
  71.         $caseAlignment = ($switch['column'] + 4);
  72.         $codeAlignment = $caseAlignment + 4;
  73.         $caseCount     = 0;
  74.  
  75.         while (($nextCase = $phpcsFile->findNext(array(T_CASE, 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.             $caseCount++;
  83.  
  84.             $content = $tokens[$nextCase]['content'];
  85.             if ($content !== strtolower($content)) {
  86.                 $expected = strtolower($content);
  87.                 $error    = "CASE keyword must be lowercase; expected \"$expected\" but found \"$content\"";
  88.                 $phpcsFile->addError($error, $nextCase);
  89.             }
  90.  
  91.             if ($tokens[$nextCase]['column'] !== $caseAlignment) {
  92.                 $error = 'CASE keyword must be indented 4 spaces from SWITCH keyword';
  93.                 $phpcsFile->addError($error, $nextCase);
  94.             }
  95.  
  96.             if ($tokens[($nextCase + 1)]['type'] !== 'T_WHITESPACE' || $tokens[($nextCase + 1)]['content'] !== ' ') {
  97.                 $error = 'CASE keyword must be followed by a single space';
  98.                 $phpcsFile->addError($error, $nextCase);
  99.             }
  100.  
  101.             $opener = $tokens[$nextCase]['scope_opener'];
  102.             if ($tokens[($opener - 1)]['type'] === 'T_WHITESPACE') {
  103.                 $error = 'There must be no space before the colon in a CASE statement';
  104.                 $phpcsFile->addError($error, $nextCase);
  105.             }
  106.  
  107.             $nextBreak = $phpcsFile->findNext(array(T_BREAK), ($nextCase + 1), $switch['scope_closer']);
  108.             if ($nextBreak !== false && isset($tokens[$nextBreak]['scope_condition']) === true) {
  109.                 // Only check this BREAK statement if it matches the current CASE
  110.                 // statement. This stops the same break (used for multiple CASEs) being
  111.                 // checked more than once.
  112.                 if ($tokens[$nextBreak]['scope_condition'] === $nextCase) {
  113.                     if ($tokens[$nextBreak]['column'] !== $codeAlignment) {
  114.                         $error = 'BREAK statement must be indented 4 spaces from CASE keyword';
  115.                         $phpcsFile->addError($error, $nextBreak);
  116.                     }
  117.  
  118.                     /*
  119.                         Ensure empty CASE statements are not allowed.
  120.                         They must have some code content in them. A comment is not
  121.                         enough.
  122.                     */
  123.  
  124.                     $foundContent = false;
  125.                     for ($i = ($tokens[$nextCase]['scope_opener'] + 1); $i < $nextBreak; $i++) {
  126.                         if ($tokens[$i]['code'] === T_CASE) {
  127.                             $i = $tokens[$i]['scope_opener'];
  128.                             continue;
  129.                         }
  130.  
  131.                         if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) {
  132.                             $foundContent = true;
  133.                             break;
  134.                         }
  135.                     }
  136.  
  137.                     if ($foundContent === false) {
  138.                         $error = 'Empty CASE statements are not allowed';
  139.                         $phpcsFile->addError($error, $nextCase);
  140.                     }
  141.  
  142.                     /*
  143.                         Ensure there is no blank line before
  144.                         the BREAK statement.
  145.                     */
  146.  
  147.                     $breakLine = $tokens[$nextBreak]['line'];
  148.                     $prevLine  = 0;
  149.                     for ($i = ($nextBreak - 1); $i > $stackPtr; $i--) {
  150.                         if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  151.                             $prevLine = $tokens[$i]['line'];
  152.                             break;
  153.                         }
  154.                     }
  155.  
  156.                     if ($prevLine !== ($breakLine - 1)) {
  157.                         $error = 'Blank lines are not allowed before BREAK statements';
  158.                         $phpcsFile->addError($error, $nextBreak);
  159.                     }
  160.  
  161.                     /*
  162.                         Ensure the BREAK statement is followed by
  163.                         a single blank line, or the end switch brace.
  164.                     */
  165.  
  166.                     $breakLine = $tokens[$nextBreak]['line'];
  167.                     $nextLine  = $tokens[$tokens[$stackPtr]['scope_closer']]['line'];
  168.                     $semicolon = $phpcsFile->findNext(T_SEMICOLON, $nextBreak);
  169.                     for ($i = ($semicolon + 1); $i < $tokens[$stackPtr]['scope_closer']; $i++) {
  170.                         if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  171.                             $nextLine = $tokens[$i]['line'];
  172.                             break;
  173.                         }
  174.                     }
  175.  
  176.                     if ($nextLine !== ($breakLine + 2) && $i !== $tokens[$stackPtr]['scope_closer']) {
  177.                         $error = 'BREAK statements must be followed by a single blank line';
  178.                         $phpcsFile->addError($error, $nextBreak);
  179.                     }
  180.                 }//end if
  181.             } else {
  182.                 $nextBreak = $tokens[$nextCase]['scope_closer'];
  183.             }//end if
  184.  
  185.             /*
  186.                 Ensure CASE statements are not followed by
  187.                 blank lines.
  188.             */
  189.  
  190.             $caseLine = $tokens[$nextCase]['line'];
  191.             $nextLine = $tokens[$nextBreak]['line'];
  192.             for ($i = ($opener + 1); $i < $nextBreak; $i++) {
  193.                 if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  194.                     $nextLine = $tokens[$i]['line'];
  195.                     break;
  196.                 }
  197.             }
  198.  
  199.             if ($nextLine !== ($caseLine + 1)) {
  200.                 $error = 'Blank lines are not allowed after CASE statements';
  201.                 $phpcsFile->addError($error, $nextCase);
  202.             }
  203.         }//end while
  204.  
  205.         $default = $phpcsFile->findPrevious(T_DEFAULT, $switch['scope_closer'], $switch['scope_opener']);
  206.  
  207.         // Make sure this default belongs to us.
  208.         if ($default !== false) {
  209.             $conditions = array_keys($tokens[$default]['conditions']);
  210.             $owner      = array_pop($conditions);
  211.             if ($owner !== $stackPtr) {
  212.                 $default = false;
  213.             }
  214.         }
  215.  
  216.         if ($default !== false) {
  217.             $content = $tokens[$default]['content'];
  218.             if ($content !== strtolower($content)) {
  219.                 $expected = strtolower($content);
  220.                 $error    = "DEFAULT keyword must be lowercase; expected \"$expected\" but found \"$content\"";
  221.                 $phpcsFile->addError($error, $default);
  222.             }
  223.  
  224.             $opener = $tokens[$default]['scope_opener'];
  225.             if ($tokens[($opener - 1)]['type'] === 'T_WHITESPACE') {
  226.                 $error = 'There must be no space before the colon in a DEFAULT statement';
  227.                 $phpcsFile->addError($error, $default);
  228.             }
  229.  
  230.             if ($tokens[$default]['column'] !== $caseAlignment) {
  231.                 $error = 'DEFAULT keyword must be indented 4 spaces from SWITCH keyword';
  232.                 $phpcsFile->addError($error, $default);
  233.             }
  234.  
  235.             $nextBreak = $phpcsFile->findNext(array(T_BREAK), ($default + 1), $switch['scope_closer']);
  236.             if ($nextBreak !== false) {
  237.                 if ($tokens[$nextBreak]['column'] !== $codeAlignment) {
  238.                     $error = 'BREAK statement must be indented 4 spaces from CASE keyword';
  239.                     $phpcsFile->addError($error, $nextBreak);
  240.                 }
  241.  
  242.                 /*
  243.                     Ensure the BREAK statement is not followed by
  244.                     a blank line.
  245.                 */
  246.  
  247.                 $breakLine = $tokens[$nextBreak]['line'];
  248.                 $nextLine  = $tokens[$tokens[$stackPtr]['scope_closer']]['line'];
  249.                 $semicolon = $phpcsFile->findNext(T_SEMICOLON, $nextBreak);
  250.                 for ($i = ($semicolon + 1); $i < $tokens[$stackPtr]['scope_closer']; $i++) {
  251.                     if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  252.                         $nextLine = $tokens[$i]['line'];
  253.                         break;
  254.                     }
  255.                 }
  256.  
  257.                 if ($nextLine !== ($breakLine + 1)) {
  258.                     $error = 'Blank lines are not allowed after the DEFAULT case\'s BREAK statement';
  259.                     $phpcsFile->addError($error, $nextBreak);
  260.                 }
  261.             } else {
  262.                 $error = 'DEFAULT case must have a BREAK statement';
  263.                 $phpcsFile->addError($error, $default);
  264.  
  265.                 $nextBreak = $tokens[$default]['scope_closer'];
  266.             }//end if
  267.  
  268.             /*
  269.                 Ensure empty DEFAULT statements are not allowed.
  270.                 They must (at least) have a comment describing why
  271.                 the default case is being ignored.
  272.             */
  273.  
  274.             $foundContent = false;
  275.             for ($i = ($tokens[$default]['scope_opener'] + 1); $i < $nextBreak; $i++) {
  276.                 if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  277.                     $foundContent = true;
  278.                     break;
  279.                 }
  280.             }
  281.  
  282.             if ($foundContent === false) {
  283.                 $error = 'Comment required for empty DEFAULT case';
  284.                 $phpcsFile->addError($error, $default);
  285.             }
  286.  
  287.             /*
  288.                 Ensure DEFAULT statements are not followed by
  289.                 blank lines.
  290.             */
  291.  
  292.             $defaultLine = $tokens[$default]['line'];
  293.             $nextLine    = $tokens[$nextBreak]['line'];
  294.             for ($i = ($opener + 1); $i < $nextBreak; $i++) {
  295.                 if ($tokens[$i]['type'] !== 'T_WHITESPACE') {
  296.                     $nextLine = $tokens[$i]['line'];
  297.                     break;
  298.                 }
  299.             }
  300.  
  301.             if ($nextLine !== ($defaultLine + 1)) {
  302.                 $error = 'Blank lines are not allowed after DEFAULT statements';
  303.                 $phpcsFile->addError($error, $default);
  304.             }
  305.  
  306.         } else {
  307.             $error = 'All SWITCH statements must contain a DEFAULT case';
  308.             $phpcsFile->addError($error, $stackPtr);
  309.         }//end if
  310.  
  311.         if ($tokens[$switch['scope_closer']]['column'] !== $switch['column']) {
  312.             $error = 'Closing brace of SWITCH statement must be aligned with SWITCH keyword';
  313.             $phpcsFile->addError($error, $switch['scope_closer']);
  314.         }
  315.  
  316.         if ($caseCount === 0) {
  317.             $error = 'SWITCH statements must contain at least one CASE statement';
  318.             $phpcsFile->addError($error, $stackPtr);
  319.         }
  320.  
  321.     }//end process()
  322.  
  323.  
  324. }//end class
  325.  
  326. ?>