1<?php 2/** 3 * This file is part of the CodeAnalysis add-on for PHP_CodeSniffer. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @author Manuel Pichler <mapi@manuel-pichler.de> 11 * @copyright 2007-2014 Manuel Pichler. All rights reserved. 12 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 13 * @link http://pear.php.net/package/PHP_CodeSniffer 14 */ 15 16/** 17 * Detects incrementer jumbling in for loops. 18 * 19 * This rule is based on the PMD rule catalog. The jumbling incrementer sniff 20 * detects the usage of one and the same incrementer into an outer and an inner 21 * loop. Even it is intended this is confusing code. 22 * 23 * <code> 24 * class Foo 25 * { 26 * public function bar($x) 27 * { 28 * for ($i = 0; $i < 10; $i++) 29 * { 30 * for ($k = 0; $k < 20; $i++) 31 * { 32 * echo 'Hello'; 33 * } 34 * } 35 * } 36 * } 37 * </code> 38 * 39 * @category PHP 40 * @package PHP_CodeSniffer 41 * @author Manuel Pichler <mapi@manuel-pichler.de> 42 * @copyright 2007-2014 Manuel Pichler. All rights reserved. 43 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 44 * @version Release: @package_version@ 45 * @link http://pear.php.net/package/PHP_CodeSniffer 46 */ 47class Generic_Sniffs_CodeAnalysis_JumbledIncrementerSniff implements PHP_CodeSniffer_Sniff 48{ 49 50 51 /** 52 * Registers the tokens that this sniff wants to listen for. 53 * 54 * @return int[] 55 */ 56 public function register() 57 { 58 return array(T_FOR); 59 60 }//end register() 61 62 63 /** 64 * Processes this test, when one of its tokens is encountered. 65 * 66 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 67 * @param int $stackPtr The position of the current token 68 * in the stack passed in $tokens. 69 * 70 * @return void 71 */ 72 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 73 { 74 $tokens = $phpcsFile->getTokens(); 75 $token = $tokens[$stackPtr]; 76 77 // Skip for-loop without body. 78 if (isset($token['scope_opener']) === false) { 79 return; 80 } 81 82 // Find incrementors for outer loop. 83 $outer = $this->findIncrementers($tokens, $token); 84 85 // Skip if empty. 86 if (count($outer) === 0) { 87 return; 88 } 89 90 // Find nested for loops. 91 $start = ++$token['scope_opener']; 92 $end = --$token['scope_closer']; 93 94 for (; $start <= $end; ++$start) { 95 if ($tokens[$start]['code'] !== T_FOR) { 96 continue; 97 } 98 99 $inner = $this->findIncrementers($tokens, $tokens[$start]); 100 $diff = array_intersect($outer, $inner); 101 102 if (count($diff) !== 0) { 103 $error = 'Loop incrementor (%s) jumbling with inner loop'; 104 $data = array(join(', ', $diff)); 105 $phpcsFile->addWarning($error, $stackPtr, 'Found', $data); 106 } 107 } 108 109 }//end process() 110 111 112 /** 113 * Get all used variables in the incrementer part of a for statement. 114 * 115 * @param array(integer=>array) $tokens Array with all code sniffer tokens. 116 * @param array(string=>mixed) $token Current for loop token 117 * 118 * @return string[] List of all found incrementer variables. 119 */ 120 protected function findIncrementers(array $tokens, array $token) 121 { 122 // Skip invalid statement. 123 if (isset($token['parenthesis_opener']) === false) { 124 return array(); 125 } 126 127 $start = ++$token['parenthesis_opener']; 128 $end = --$token['parenthesis_closer']; 129 130 $incrementers = array(); 131 $semicolons = 0; 132 for ($next = $start; $next <= $end; ++$next) { 133 $code = $tokens[$next]['code']; 134 if ($code === T_SEMICOLON) { 135 ++$semicolons; 136 } else if ($semicolons === 2 && $code === T_VARIABLE) { 137 $incrementers[] = $tokens[$next]['content']; 138 } 139 } 140 141 return $incrementers; 142 143 }//end findIncrementers() 144 145 146}//end class 147