1<?php 2/** 3 * Squiz_Sniffs_ControlStructures_LongConditionClosingCommentSniff. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @author Marc McIntyre <mmcintyre@squiz.net> 11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 12 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 13 * @link http://pear.php.net/package/PHP_CodeSniffer 14 */ 15 16/** 17 * Squiz_Sniffs_ControlStructures_LongConditionClosingCommentSniff. 18 * 19 * @category PHP 20 * @package PHP_CodeSniffer 21 * @author Greg Sherwood <gsherwood@squiz.net> 22 * @author Marc McIntyre <mmcintyre@squiz.net> 23 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 24 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 25 * @version Release: @package_version@ 26 * @link http://pear.php.net/package/PHP_CodeSniffer 27 */ 28class Squiz_Sniffs_Commenting_LongConditionClosingCommentSniff implements PHP_CodeSniffer_Sniff 29{ 30 31 /** 32 * A list of tokenizers this sniff supports. 33 * 34 * @var array 35 */ 36 public $supportedTokenizers = array( 37 'PHP', 38 'JS', 39 ); 40 41 /** 42 * The openers that we are interested in. 43 * 44 * @var array(int) 45 */ 46 private static $_openers = array( 47 T_SWITCH, 48 T_IF, 49 T_FOR, 50 T_FOREACH, 51 T_WHILE, 52 T_TRY, 53 T_CASE, 54 ); 55 56 /** 57 * The length that a code block must be before 58 * requiring a closing comment. 59 * 60 * @var int 61 */ 62 public $lineLimit = 20; 63 64 /** 65 * The format the end comment should be in. 66 * 67 * The placeholder %s will be replaced with the type of condition opener. 68 * 69 * @var string 70 */ 71 public $commentFormat = '//end %s'; 72 73 74 /** 75 * Returns an array of tokens this test wants to listen for. 76 * 77 * @return array 78 */ 79 public function register() 80 { 81 return array(T_CLOSE_CURLY_BRACKET); 82 83 }//end register() 84 85 86 /** 87 * Processes this test, when one of its tokens is encountered. 88 * 89 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 90 * @param int $stackPtr The position of the current token in the 91 * stack passed in $tokens. 92 * 93 * @return void 94 */ 95 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 96 { 97 $tokens = $phpcsFile->getTokens(); 98 99 if (isset($tokens[$stackPtr]['scope_condition']) === false) { 100 // No scope condition. It is a function closer. 101 return; 102 } 103 104 $startCondition = $tokens[$tokens[$stackPtr]['scope_condition']]; 105 $startBrace = $tokens[$tokens[$stackPtr]['scope_opener']]; 106 $endBrace = $tokens[$stackPtr]; 107 108 // We are only interested in some code blocks. 109 if (in_array($startCondition['code'], self::$_openers) === false) { 110 return; 111 } 112 113 if ($startCondition['code'] === T_IF) { 114 // If this is actually an ELSE IF, skip it as the brace 115 // will be checked by the original IF. 116 $else = $phpcsFile->findPrevious(T_WHITESPACE, ($tokens[$stackPtr]['scope_condition'] - 1), null, true); 117 if ($tokens[$else]['code'] === T_ELSE) { 118 return; 119 } 120 121 // IF statements that have an ELSE block need to use 122 // "end if" rather than "end else" or "end elseif". 123 do { 124 $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 125 if ($tokens[$nextToken]['code'] === T_ELSE || $tokens[$nextToken]['code'] === T_ELSEIF) { 126 // Check for ELSE IF (2 tokens) as opposed to ELSEIF (1 token). 127 if ($tokens[$nextToken]['code'] === T_ELSE 128 && isset($tokens[$nextToken]['scope_closer']) === false 129 ) { 130 $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($nextToken + 1), null, true); 131 if ($tokens[$nextToken]['code'] !== T_IF 132 || isset($tokens[$nextToken]['scope_closer']) === false 133 ) { 134 // Not an ELSE IF or is an inline ELSE IF. 135 break; 136 } 137 } 138 139 if (isset($tokens[$nextToken]['scope_closer']) === false) { 140 // There isn't going to be anywhere to print the "end if" comment 141 // because there is no closer. 142 return; 143 } 144 145 // The end brace becomes the ELSE's end brace. 146 $stackPtr = $tokens[$nextToken]['scope_closer']; 147 $endBrace = $tokens[$stackPtr]; 148 } else { 149 break; 150 }//end if 151 } while (isset($tokens[$nextToken]['scope_closer']) === true); 152 }//end if 153 154 if ($startCondition['code'] === T_TRY) { 155 // TRY statements need to check until the end of all CATCH statements. 156 do { 157 $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 158 if ($tokens[$nextToken]['code'] === T_CATCH) { 159 // The end brace becomes the CATCH's end brace. 160 $stackPtr = $tokens[$nextToken]['scope_closer']; 161 $endBrace = $tokens[$stackPtr]; 162 } else { 163 break; 164 } 165 } while (isset($tokens[$nextToken]['scope_closer']) === true); 166 } 167 168 $lineDifference = ($endBrace['line'] - $startBrace['line']); 169 170 $expected = sprintf($this->commentFormat, $startCondition['content']); 171 $comment = $phpcsFile->findNext(array(T_COMMENT), $stackPtr, null, false); 172 173 if (($comment === false) || ($tokens[$comment]['line'] !== $endBrace['line'])) { 174 if ($lineDifference >= $this->lineLimit) { 175 $error = 'End comment for long condition not found; expected "%s"'; 176 $data = array($expected); 177 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Missing', $data); 178 179 if ($fix === true) { 180 $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 181 if ($next !== false && $tokens[$next]['line'] === $tokens[$stackPtr]['line']) { 182 $expected .= $phpcsFile->eolChar; 183 } 184 185 $phpcsFile->fixer->addContent($stackPtr, $expected); 186 } 187 } 188 189 return; 190 } 191 192 if (($comment - $stackPtr) !== 1) { 193 $error = 'Space found before closing comment; expected "%s"'; 194 $data = array($expected); 195 $phpcsFile->addError($error, $stackPtr, 'SpacingBefore', $data); 196 } 197 198 if (trim($tokens[$comment]['content']) !== $expected) { 199 $found = trim($tokens[$comment]['content']); 200 $error = 'Incorrect closing comment; expected "%s" but found "%s"'; 201 $data = array( 202 $expected, 203 $found, 204 ); 205 206 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Invalid', $data); 207 if ($fix === true) { 208 $phpcsFile->fixer->replaceToken($comment, $expected.$phpcsFile->eolChar); 209 } 210 211 return; 212 } 213 214 }//end process() 215 216 217}//end class 218