1<?php 2/** 3 * Parses and verifies the file doc comment. 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 * Parses and verifies the file doc comment. 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 */ 28 29class Squiz_Sniffs_Commenting_FileCommentSniff implements PHP_CodeSniffer_Sniff 30{ 31 32 /** 33 * A list of tokenizers this sniff supports. 34 * 35 * @var array 36 */ 37 public $supportedTokenizers = array( 38 'PHP', 39 'JS', 40 ); 41 42 43 /** 44 * Returns an array of tokens this test wants to listen for. 45 * 46 * @return array 47 */ 48 public function register() 49 { 50 return array(T_OPEN_TAG); 51 52 }//end register() 53 54 55 /** 56 * Processes this test, when one of its tokens is encountered. 57 * 58 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 59 * @param int $stackPtr The position of the current token 60 * in the stack passed in $tokens. 61 * 62 * @return int 63 */ 64 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 65 { 66 $this->currentFile = $phpcsFile; 67 68 $tokens = $phpcsFile->getTokens(); 69 $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 70 71 if ($tokens[$commentStart]['code'] === T_COMMENT) { 72 $phpcsFile->addError('You must use "/**" style comments for a file comment', $commentStart, 'WrongStyle'); 73 $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes'); 74 return ($phpcsFile->numTokens + 1); 75 } else if ($commentStart === false || $tokens[$commentStart]['code'] !== T_DOC_COMMENT_OPEN_TAG) { 76 $phpcsFile->addError('Missing file doc comment', $stackPtr, 'Missing'); 77 $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'no'); 78 return ($phpcsFile->numTokens + 1); 79 } 80 81 $commentEnd = $tokens[$commentStart]['comment_closer']; 82 83 $nextToken = $phpcsFile->findNext( 84 T_WHITESPACE, 85 ($commentEnd + 1), 86 null, 87 true 88 ); 89 90 $ignore = array( 91 T_CLASS, 92 T_INTERFACE, 93 T_TRAIT, 94 T_FUNCTION, 95 T_CLOSURE, 96 T_PUBLIC, 97 T_PRIVATE, 98 T_PROTECTED, 99 T_FINAL, 100 T_STATIC, 101 T_ABSTRACT, 102 T_CONST, 103 T_PROPERTY, 104 T_INCLUDE, 105 T_INCLUDE_ONCE, 106 T_REQUIRE, 107 T_REQUIRE_ONCE, 108 ); 109 110 if (in_array($tokens[$nextToken]['code'], $ignore) === true) { 111 $phpcsFile->addError('Missing file doc comment', $stackPtr, 'Missing'); 112 $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'no'); 113 return ($phpcsFile->numTokens + 1); 114 } 115 116 $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes'); 117 118 // No blank line between the open tag and the file comment. 119 if ($tokens[$commentStart]['line'] > ($tokens[$stackPtr]['line'] + 1)) { 120 $error = 'There must be no blank lines before the file comment'; 121 $phpcsFile->addError($error, $stackPtr, 'SpacingAfterOpen'); 122 } 123 124 // Exactly one blank line after the file comment. 125 $next = $phpcsFile->findNext(T_WHITESPACE, ($commentEnd + 1), null, true); 126 if ($tokens[$next]['line'] !== ($tokens[$commentEnd]['line'] + 2)) { 127 $error = 'There must be exactly one blank line after the file comment'; 128 $phpcsFile->addError($error, $commentEnd, 'SpacingAfterComment'); 129 } 130 131 // Required tags in correct order. 132 $required = array( 133 '@package' => true, 134 '@subpackage' => true, 135 '@author' => true, 136 '@copyright' => true, 137 ); 138 139 $foundTags = array(); 140 foreach ($tokens[$commentStart]['comment_tags'] as $tag) { 141 $name = $tokens[$tag]['content']; 142 $isRequired = isset($required[$name]); 143 144 if ($isRequired === true && in_array($name, $foundTags) === true) { 145 $error = 'Only one %s tag is allowed in a file comment'; 146 $data = array($name); 147 $phpcsFile->addError($error, $tag, 'Duplicate'.ucfirst(substr($name, 1)).'Tag', $data); 148 } 149 150 $foundTags[] = $name; 151 152 if ($isRequired === false) { 153 continue; 154 } 155 156 $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd); 157 if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) { 158 $error = 'Content missing for %s tag in file comment'; 159 $data = array($name); 160 $phpcsFile->addError($error, $tag, 'Empty'.ucfirst(substr($name, 1)).'Tag', $data); 161 continue; 162 } 163 164 if ($name === '@author') { 165 if ($tokens[$string]['content'] !== 'Squiz Pty Ltd <products@squiz.net>') { 166 $error = 'Expected "Squiz Pty Ltd <products@squiz.net>" for author tag'; 167 $fix = $phpcsFile->addFixableError($error, $tag, 'IncorrectAuthor'); 168 if ($fix === true) { 169 $expected = 'Squiz Pty Ltd <products@squiz.net>'; 170 $phpcsFile->fixer->replaceToken($string, $expected); 171 } 172 } 173 } else if ($name === '@copyright') { 174 if (preg_match('/^([0-9]{4})(-[0-9]{4})? (Squiz Pty Ltd \(ABN 77 084 670 600\))$/', $tokens[$string]['content']) === 0) { 175 $error = 'Expected "xxxx-xxxx Squiz Pty Ltd (ABN 77 084 670 600)" for copyright declaration'; 176 $fix = $phpcsFile->addFixableError($error, $tag, 'IncorrectCopyright'); 177 if ($fix === true) { 178 $matches = array(); 179 preg_match('/^(([0-9]{4})(-[0-9]{4})?)?.*$/', $tokens[$string]['content'], $matches); 180 if (isset($matches[1]) === false) { 181 $matches[1] = date('Y'); 182 } 183 184 $expected = $matches[1].' Squiz Pty Ltd (ABN 77 084 670 600)'; 185 $phpcsFile->fixer->replaceToken($string, $expected); 186 } 187 } 188 }//end if 189 }//end foreach 190 191 // Check if the tags are in the correct position. 192 $pos = 0; 193 foreach ($required as $tag => $true) { 194 if (in_array($tag, $foundTags) === false) { 195 $error = 'Missing %s tag in file comment'; 196 $data = array($tag); 197 $phpcsFile->addError($error, $commentEnd, 'Missing'.ucfirst(substr($tag, 1)).'Tag', $data); 198 } 199 200 if (isset($foundTags[$pos]) === false) { 201 break; 202 } 203 204 if ($foundTags[$pos] !== $tag) { 205 $error = 'The tag in position %s should be the %s tag'; 206 $data = array( 207 ($pos + 1), 208 $tag, 209 ); 210 $phpcsFile->addError($error, $tokens[$commentStart]['comment_tags'][$pos], ucfirst(substr($tag, 1)).'TagOrder', $data); 211 } 212 213 $pos++; 214 }//end foreach 215 216 // Ignore the rest of the file. 217 return ($phpcsFile->numTokens + 1); 218 219 }//end process() 220 221 222}//end class 223