1<?php 2/** 3 * Parses and verifies the doc comments for classes. 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-2012 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 16if (class_exists('PHP_CodeSniffer_CommentParser_ClassCommentParser', true) === false) { 17 $error = 'Class PHP_CodeSniffer_CommentParser_ClassCommentParser not found'; 18 throw new PHP_CodeSniffer_Exception($error); 19} 20 21if (class_exists('PhOSCo_Sniffs_Commenting_FileCommentSniff', true) === false) { 22 $error = 'Class PhOSCo_Sniffs_Commenting_FileCommentSniff not found'; 23 throw new PHP_CodeSniffer_Exception($error); 24} 25 26/** 27 * Parses and verifies the doc comments for classes. 28 * 29 * Verifies that : 30 * <ul> 31 * <li>A doc comment exists.</li> 32 * <li>There is a blank newline after the short description.</li> 33 * <li>There is a blank newline between the long and short description.</li> 34 * <li>There is a blank newline between the long description and tags.</li> 35 * <li>Check the order of the tags.</li> 36 * <li>Check the indentation of each tag.</li> 37 * <li>Check required and optional tags and the format of their content.</li> 38 * </ul> 39 * 40 * @category PHP 41 * @package PHP_CodeSniffer 42 * @author Greg Sherwood <gsherwood@squiz.net> 43 * @author Marc McIntyre <mmcintyre@squiz.net> 44 * @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) 45 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 46 * @version Release: 1.5.1 47 * @link http://pear.php.net/package/PHP_CodeSniffer 48 */ 49class PhOSCo_Sniffs_Commenting_ClassCommentSniff extends PhOSCo_Sniffs_Commenting_FileCommentSniff { 50 51 /** 52 * Returns an array of tokens this test wants to listen for. 53 * 54 * @return array 55 */ 56 public function register() { 57 return array(T_CLASS, T_INTERFACE,); 58 59 }//end register() 60 61 /** 62 * Processes this test, when one of its tokens is encountered. 63 * 64 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 65 * @param int $stackPtr The position of the current token 66 * in the stack passed in $tokens. 67 * 68 * @return void 69 */ 70 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { 71 72 $this->currentFile = $phpcsFile; 73 74 $tokens = $phpcsFile->getTokens(); 75 $type = strtolower($tokens[$stackPtr]['content']); 76 $errorData = array($type); 77 $find = array(T_ABSTRACT, T_WHITESPACE, T_FINAL,); 78 79 // Extract the class comment docblock. 80 $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); 81 82 if ($commentEnd !== false && $tokens[$commentEnd]['code'] === T_COMMENT) { 83 $error = 'You must use "/**" style comments for a %s comment'; 84 $phpcsFile->addError($error, $stackPtr, 'WrongStyle', $errorData); 85 return; 86 } else if ($commentEnd === false || $tokens[$commentEnd]['code'] !== T_DOC_COMMENT) { 87 $phpcsFile->addError('Missing %s doc comment', $stackPtr, 'Missing', $errorData); 88 return; 89 } 90 91 $commentStart = ($phpcsFile->findPrevious(T_DOC_COMMENT, ($commentEnd - 1), null, true) + 1); 92 $commentNext = $phpcsFile->findPrevious(T_WHITESPACE, ($commentEnd + 1), $stackPtr, false, $phpcsFile->eolChar); 93 94 // Distinguish file and class comment. 95 $prevClassToken = $phpcsFile->findPrevious(T_CLASS, ($stackPtr - 1)); 96 if ($prevClassToken === false) { 97 // This is the first class token in this file, need extra checks. 98 $prevNonComment = $phpcsFile->findPrevious(T_DOC_COMMENT, ($commentStart - 1), null, true); 99 if ($prevNonComment !== false) { 100 $prevComment = $phpcsFile->findPrevious(T_DOC_COMMENT, ($prevNonComment - 1)); 101 if ($prevComment === false) { 102 // There is only 1 doc comment between open tag and class token. 103 $newlineToken = $phpcsFile->findNext(T_WHITESPACE, ($commentEnd + 1), $stackPtr, false, 104 $phpcsFile->eolChar); 105 if ($newlineToken !== false) { 106 $newlineToken = $phpcsFile->findNext(T_WHITESPACE, ($newlineToken + 1), $stackPtr, false, 107 $phpcsFile->eolChar); 108 109 if ($newlineToken !== false) { 110 // Blank line between the class and the doc block. 111 // The doc block is most likely a file comment. 112 $error = 'Missing %s doc comment'; 113 $phpcsFile->addError($error, ($stackPtr + 1), 'Missing', $errorData); 114 return; 115 } 116 }//end if 117 }//end if 118 }//end if 119 }//end if 120 121 $comment = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1)); 122 123 // Parse the class comment.docblock. 124 try { 125 $this->commentParser = new PHP_CodeSniffer_CommentParser_ClassCommentParser($comment, $phpcsFile); 126 $this->commentParser->parse(); 127 } catch (PHP_CodeSniffer_CommentParser_ParserException $e) { 128 $line = ($e->getLineWithinComment() + $commentStart); 129 $phpcsFile->addError($e->getMessage(), $line, 'FailedParse'); 130 return; 131 } 132 133 $comment = $this->commentParser->getComment(); 134 if (is_null($comment) === true) { 135 $error = 'Doc comment is empty for %s'; 136 $phpcsFile->addError($error, $commentStart, 'Empty', $errorData); 137 return; 138 } 139 140 // No extra newline before short description. 141 $short = $comment->getShortComment(); 142 $newlineCount = 0; 143 $newlineSpan = strspn($short, $phpcsFile->eolChar); 144 if ($short !== '' && $newlineSpan > 0) { 145 $error = 'Extra newline(s) found before %s comment short description'; 146 $phpcsFile->addError($error, ($commentStart + 1), 'SpacingBeforeShort', $errorData); 147 } 148 149 $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1); 150 151 // Exactly one blank line between short and long description. 152 $long = $comment->getLongComment(); 153 if (empty($long) === false) { 154 $between = $comment->getWhiteSpaceBetween(); 155 $newlineBetween = substr_count($between, $phpcsFile->eolChar); 156 if ($newlineBetween !== 2) { 157 $error = 'There must be exactly one blank line between descriptions in %s comments'; 158 $phpcsFile->addError($error, ($commentStart + $newlineCount + 1), 'SpacingAfterShort', $errorData); 159 } 160 161 $newlineCount += $newlineBetween; 162 } 163 164 // Exactly one blank line before tags. 165 $tags = $this->commentParser->getTagOrders(); 166 if (count($tags) > 1) { 167 $newlineSpan = $comment->getNewlineAfter(); 168 if ($newlineSpan !== 2) { 169 $error = 'There must be exactly one blank line before the tags in %s comments'; 170 if ($long !== '') { 171 $newlineCount += (substr_count($long, $phpcsFile->eolChar) - $newlineSpan + 1); 172 } 173 174 $phpcsFile->addError($error, ($commentStart + $newlineCount), 'SpacingBeforeTags', $errorData); 175 $short = rtrim($short, $phpcsFile->eolChar . ' '); 176 } 177 } 178 179 // Check each tag. 180 $this->processTags($commentStart, $commentEnd); 181 182 }//end process() 183 184 /** 185 * Process the version tag. 186 * 187 * @param int $errorPos The line number where the error occurs. 188 * 189 * @return void 190 */ 191 protected function processVersion($errorPos) { 192 $version = $this->commentParser->getVersion(); 193 if ($version !== null) { 194 $content = $version->getContent(); 195 $matches = array(); 196 if (empty($content) === true) { 197 $error = 'Content missing for @version tag in doc comment'; 198 $this->currentFile->addError($error, $errorPos, 'EmptyVersion'); 199 } else if ((strstr($content, 'Release:') === false)) { 200 $error = 'Invalid version "%s" in doc comment; consider "Release: <package_version>" instead'; 201 $data = array($content); 202 $this->currentFile->addWarning($error, $errorPos, 'InvalidVersion', $data); 203 } 204 } 205 206 }//end processVersion() 207 208}//end class 209 210?> 211