3
* Parses and verifies the variable doc comment.
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
16
if (class_exists('PHP_CodeSniffer_Standards_AbstractVariableSniff', true) === false) {
17
throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found');
20
if (class_exists('PHP_CodeSniffer_CommentParser_MemberCommentParser', true) === false) {
21
throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CommentParser_MemberCommentParser not found');
25
* Parses and verifies the variable doc comment.
29
* <li>A variable doc comment exists.</li>
30
* <li>Short description ends with a full stop.</li>
31
* <li>There is a blank line after the short description.</li>
32
* <li>There is a blank line between the description and the tags.</li>
33
* <li>Check the order, indentation and content of each tag.</li>
37
* @package PHP_CodeSniffer
38
* @author Greg Sherwood <gsherwood@squiz.net>
39
* @author Marc McIntyre <mmcintyre@squiz.net>
40
* @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600)
41
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
42
* @version Release: 1.5.0RC2
43
* @link http://pear.php.net/package/PHP_CodeSniffer
46
class Squiz_Sniffs_Commenting_VariableCommentSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff
50
* The header comment parser for the current file.
52
* @var PHP_CodeSniffer_Comment_Parser_ClassCommentParser
54
protected $commentParser = null;
58
* Called to process class member vars.
60
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
61
* @param int $stackPtr The position of the current token
62
* in the stack passed in $tokens.
66
public function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
68
$this->currentFile = $phpcsFile;
69
$tokens = $phpcsFile->getTokens();
70
$commentToken = array(
75
// Extract the var comment docblock.
76
$commentEnd = $phpcsFile->findPrevious($commentToken, ($stackPtr - 3));
77
if ($commentEnd !== false && $tokens[$commentEnd]['code'] === T_COMMENT) {
78
$phpcsFile->addError('You must use "/**" style comments for a variable comment', $stackPtr, 'WrongStyle');
80
} else if ($commentEnd === false || $tokens[$commentEnd]['code'] !== T_DOC_COMMENT) {
81
$phpcsFile->addError('Missing variable doc comment', $stackPtr, 'Missing');
84
// Make sure the comment we have found belongs to us.
85
$commentFor = $phpcsFile->findNext(array(T_VARIABLE, T_CLASS, T_INTERFACE), ($commentEnd + 1));
86
if ($commentFor !== $stackPtr) {
87
$phpcsFile->addError('Missing variable doc comment', $stackPtr, 'Missing');
92
$commentStart = ($phpcsFile->findPrevious(T_DOC_COMMENT, ($commentEnd - 1), null, true) + 1);
93
$commentString = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1));
95
// Parse the header comment docblock.
97
$this->commentParser = new PHP_CodeSniffer_CommentParser_MemberCommentParser($commentString, $phpcsFile);
98
$this->commentParser->parse();
99
} catch (PHP_CodeSniffer_CommentParser_ParserException $e) {
100
$line = ($e->getLineWithinComment() + $commentStart);
101
$phpcsFile->addError($e->getMessage(), $line, 'ErrorParsing');
105
$comment = $this->commentParser->getComment();
106
if (is_null($comment) === true) {
107
$error = 'Variable doc comment is empty';
108
$phpcsFile->addError($error, $commentStart, 'Empty');
112
// The first line of the comment should just be the /** code.
113
$eolPos = strpos($commentString, $phpcsFile->eolChar);
114
$firstLine = substr($commentString, 0, $eolPos);
115
if ($firstLine !== '/**') {
116
$error = 'The open comment tag must be the only content on the line';
117
$phpcsFile->addError($error, $commentStart, 'ContentAfterOpen');
120
// Check for a comment description.
121
$short = $comment->getShortComment();
123
if (trim($short) === '') {
124
$error = 'Missing short description in variable doc comment';
125
$phpcsFile->addError($error, $commentStart, 'MissingShort');
128
// No extra newline before short description.
130
$newlineSpan = strspn($short, $phpcsFile->eolChar);
131
if ($short !== '' && $newlineSpan > 0) {
132
$error = 'Extra newline(s) found before variable comment short description';
133
$phpcsFile->addError($error, ($commentStart + 1), 'SpacingBeforeShort');
136
$newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1);
138
// Exactly one blank line between short and long description.
139
$long = $comment->getLongComment();
140
if (empty($long) === false) {
141
$between = $comment->getWhiteSpaceBetween();
142
$newlineBetween = substr_count($between, $phpcsFile->eolChar);
143
if ($newlineBetween !== 2) {
144
$error = 'There must be exactly one blank line between descriptions in variable comment';
145
$phpcsFile->addError($error, ($commentStart + $newlineCount + 1), 'SpacingBetween');
148
$newlineCount += $newlineBetween;
150
$testLong = trim($long);
151
if (preg_match('|\p{Lu}|u', $testLong[0]) === 0) {
152
$error = 'Variable comment long description must start with a capital letter';
153
$phpcsFile->addError($error, ($commentStart + $newlineCount), 'LongNotCapital');
157
// Short description must be single line and end with a full stop.
158
$testShort = trim($short);
159
$lastChar = $testShort[(strlen($testShort) - 1)];
160
if (substr_count($testShort, $phpcsFile->eolChar) !== 0) {
161
$error = 'Variable comment short description must be on a single line';
162
$phpcsFile->addError($error, ($commentStart + 1), 'ShortSingleLine');
165
if (preg_match('|\p{Lu}|u', $testShort[0]) === 0) {
166
$error = 'Variable comment short description must start with a capital letter';
167
$phpcsFile->addError($error, ($commentStart + 1), 'ShortNotCapital');
170
if ($lastChar !== '.') {
171
$error = 'Variable comment short description must end with a full stop';
172
$phpcsFile->addError($error, ($commentStart + 1), 'ShortFullStop');
176
// Exactly one blank line before tags.
177
$tags = $this->commentParser->getTagOrders();
178
if (count($tags) > 1) {
179
$newlineSpan = $comment->getNewlineAfter();
180
if ($newlineSpan !== 2) {
181
$error = 'There must be exactly one blank line before the tags in variable comment';
183
$newlineCount += (substr_count($long, $phpcsFile->eolChar) - $newlineSpan + 1);
186
$phpcsFile->addError($error, ($commentStart + $newlineCount), 'SpacingBeforeTags');
187
$short = rtrim($short, $phpcsFile->eolChar.' ');
191
// Check for unknown/deprecated tags.
192
$unknownTags = $this->commentParser->getUnknown();
193
foreach ($unknownTags as $errorTag) {
194
// Unknown tags are not parsed, do not process further.
195
$error = '@%s tag is not allowed in variable comment';
196
$data = array($errorTag['tag']);
197
$phpcsFile->addWarning($error, ($commentStart + $errorTag['line']), 'TagNotAllowed', $data);
201
$this->processVar($commentStart, $commentEnd);
202
$this->processSees($commentStart);
204
// The last content should be a newline and the content before
205
// that should not be blank. If there is more blank space
206
// then they have additional blank lines at the end of the comment.
207
$words = $this->commentParser->getWords();
208
$lastPos = (count($words) - 1);
209
if (trim($words[($lastPos - 1)]) !== ''
210
|| strpos($words[($lastPos - 1)], $this->currentFile->eolChar) === false
211
|| trim($words[($lastPos - 2)]) === ''
213
$error = 'Additional blank lines found at end of variable comment';
214
$this->currentFile->addError($error, $commentEnd, 'SpacingAfter');
217
}//end processMemberVar()
221
* Process the var tag.
223
* @param int $commentStart The position in the stack where the comment started.
224
* @param int $commentEnd The position in the stack where the comment ended.
228
protected function processVar($commentStart, $commentEnd)
230
$var = $this->commentParser->getVar();
233
$errorPos = ($commentStart + $var->getLine());
234
$index = array_keys($this->commentParser->getTagOrders(), 'var');
236
if (count($index) > 1) {
237
$error = 'Only 1 @var tag is allowed in variable comment';
238
$this->currentFile->addError($error, $errorPos, 'DuplicateVar');
242
if ($index[0] !== 1) {
243
$error = 'The @var tag must be the first tag in a variable comment';
244
$this->currentFile->addError($error, $errorPos, 'VarOrder');
247
$content = $var->getContent();
248
if (empty($content) === true) {
249
$error = 'Var type missing for @var tag in variable comment';
250
$this->currentFile->addError($error, $errorPos, 'MissingVarType');
253
$suggestedType = PHP_CodeSniffer::suggestType($content);
254
if ($content !== $suggestedType) {
255
$error = 'Expected "%s"; found "%s" for @var tag in variable comment';
260
$this->currentFile->addError($error, $errorPos, 'IncorrectVarType', $data);
264
$spacing = substr_count($var->getWhitespaceBeforeContent(), ' ');
265
if ($spacing !== 1) {
266
$error = '@var tag indented incorrectly; expected 1 space but found %s';
267
$data = array($spacing);
268
$this->currentFile->addError($error, $errorPos, 'VarIndent', $data);
271
$error = 'Missing @var tag in variable comment';
272
$this->currentFile->addError($error, $commentEnd, 'MissingVar');
279
* Process the see tags.
281
* @param int $commentStart The position in the stack where the comment started.
285
protected function processSees($commentStart)
287
$sees = $this->commentParser->getSees();
288
if (empty($sees) === false) {
289
foreach ($sees as $see) {
290
$errorPos = ($commentStart + $see->getLine());
291
$content = $see->getContent();
292
if (empty($content) === true) {
293
$error = 'Content missing for @see tag in variable comment';
294
$this->currentFile->addError($error, $errorPos, 'EmptySees');
298
$spacing = substr_count($see->getWhitespaceBeforeContent(), ' ');
299
if ($spacing !== 1) {
300
$error = '@see tag indented incorrectly; expected 1 spaces but found %s';
301
$data = array($spacing);
302
$this->currentFile->addError($error, $errorPos, 'SeesIndent', $data);
311
* Called to process a normal variable.
313
* Not required for this sniff.
315
* @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found.
316
* @param int $stackPtr The position where the double quoted
321
protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
324
}//end processVariable()
328
* Called to process variables found in double quoted strings.
330
* Not required for this sniff.
332
* @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found.
333
* @param int $stackPtr The position where the double quoted
338
protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
341
}//end processVariableInString()