Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * Parses and verifies the variable 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 Squiz Pty Ltd (ABN 77 084 670 600)
12
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
13
 * @version   CVS: $Id: VariableCommentSniff.php 253495 2008-02-21 23:14:17Z squiz $
14
 * @link      http://pear.php.net/package/PHP_CodeSniffer
15
 */
16
 
17
if (class_exists('PHP_CodeSniffer_Standards_AbstractVariableSniff', true) === false) {
18
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found');
19
}
20
 
21
if (class_exists('PHP_CodeSniffer_CommentParser_MemberCommentParser', true) === false) {
22
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CommentParser_MemberCommentParser not found');
23
}
24
 
25
/**
26
 * Parses and verifies the variable doc comment.
27
 *
28
 * Verifies that :
29
 * <ul>
30
 *  <li>A variable doc comment exists.</li>
31
 *  <li>Short description ends with a full stop.</li>
32
 *  <li>There is a blank line after the short description.</li>
33
 *  <li>There is a blank line between the description and the tags.</li>
34
 *  <li>Check the order, indentation and content of each tag.</li>
35
 * </ul>
36
 *
37
 * @category  PHP
38
 * @package   PHP_CodeSniffer
39
 * @author    Greg Sherwood <gsherwood@squiz.net>
40
 * @author    Marc McIntyre <mmcintyre@squiz.net>
41
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
42
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
43
 * @version   Release: 1.2.1
44
 * @link      http://pear.php.net/package/PHP_CodeSniffer
45
 */
46
 
47
class Squiz_Sniffs_Commenting_VariableCommentSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff
48
{
49
 
50
    /**
51
     * The header comment parser for the current file.
52
     *
53
     * @var PHP_CodeSniffer_Comment_Parser_ClassCommentParser
54
     */
55
    protected $commentParser = null;
56
 
57
 
58
    /**
59
     * Called to process class member vars.
60
     *
61
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
62
     * @param int                  $stackPtr  The position of the current token
63
     *                                        in the stack passed in $tokens.
64
     *
65
     * @return void
66
     */
67
    public function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
68
    {
69
        $this->currentFile = $phpcsFile;
70
        $tokens            = $phpcsFile->getTokens();
71
        $commentToken      = array(
72
                              T_COMMENT,
73
                              T_DOC_COMMENT,
74
                             );
75
 
76
        // Extract the var comment docblock.
77
        $commentEnd = $phpcsFile->findPrevious($commentToken, ($stackPtr - 3));
78
        if ($commentEnd !== false && $tokens[$commentEnd]['code'] === T_COMMENT) {
79
            $phpcsFile->addError('You must use "/**" style comments for a variable comment', $stackPtr);
80
            return;
81
        } else if ($commentEnd === false || $tokens[$commentEnd]['code'] !== T_DOC_COMMENT) {
82
            $phpcsFile->addError('Missing variable doc comment', $stackPtr);
83
            return;
84
        } else {
85
            // Make sure the comment we have found belongs to us.
86
            $commentFor = $phpcsFile->findNext(array(T_VARIABLE, T_CLASS, T_INTERFACE), ($commentEnd + 1));
87
            if ($commentFor !== $stackPtr) {
88
                $phpcsFile->addError('Missing variable doc comment', $stackPtr);
89
                return;
90
            }
91
        }
92
 
93
        $commentStart = ($phpcsFile->findPrevious(T_DOC_COMMENT, ($commentEnd - 1), null, true) + 1);
94
        $comment      = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1));
95
 
96
        // Parse the header comment docblock.
97
        try {
98
            $this->commentParser = new PHP_CodeSniffer_CommentParser_MemberCommentParser($comment, $phpcsFile);
99
            $this->commentParser->parse();
100
        } catch (PHP_CodeSniffer_CommentParser_ParserException $e) {
101
            $line = ($e->getLineWithinComment() + $commentStart);
102
            $phpcsFile->addError($e->getMessage(), $line);
103
            return;
104
        }
105
 
106
        $comment = $this->commentParser->getComment();
107
        if (is_null($comment) === true) {
108
            $error = 'Variable doc comment is empty';
109
            $phpcsFile->addError($error, $commentStart);
110
            return;
111
        }
112
 
113
        // Check for a comment description.
114
        $short = $comment->getShortComment();
115
        $long  = '';
116
        if (trim($short) === '') {
117
            $error = 'Missing short description in variable doc comment';
118
            $phpcsFile->addError($error, $commentStart);
119
            $newlineCount = 1;
120
        } else {
121
            // No extra newline before short description.
122
            $newlineCount = 0;
123
            $newlineSpan  = strspn($short, $phpcsFile->eolChar);
124
            if ($short !== '' && $newlineSpan > 0) {
125
                $line  = ($newlineSpan > 1) ? 'newlines' : 'newline';
126
                $error = "Extra $line found before variable comment short description";
127
                $phpcsFile->addError($error, ($commentStart + 1));
128
            }
129
 
130
            $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1);
131
 
132
            // Exactly one blank line between short and long description.
133
            $long = $comment->getLongComment();
134
            if (empty($long) === false) {
135
                $between        = $comment->getWhiteSpaceBetween();
136
                $newlineBetween = substr_count($between, $phpcsFile->eolChar);
137
                if ($newlineBetween !== 2) {
138
                    $error = 'There must be exactly one blank line between descriptions in variable comment';
139
                    $phpcsFile->addError($error, ($commentStart + $newlineCount + 1));
140
                }
141
 
142
                $newlineCount += $newlineBetween;
143
 
144
                $testLong = trim($long);
145
                if (preg_match('|[A-Z]|', $testLong[0]) === 0) {
146
                    $error = 'Variable comment long description must start with a capital letter';
147
                    $phpcsFile->addError($error, ($commentStart + $newlineCount));
148
                }
149
            }//end if
150
 
151
            // Short description must be single line and end with a full stop.
152
            $testShort = trim($short);
153
            $lastChar  = $testShort[(strlen($testShort) - 1)];
154
            if (substr_count($testShort, $phpcsFile->eolChar) !== 0) {
155
                $error = 'Variable comment short description must be on a single line';
156
                $phpcsFile->addError($error, ($commentStart + 1));
157
            }
158
 
159
            if (preg_match('|[A-Z]|', $testShort[0]) === 0) {
160
                $error = 'Variable comment short description must start with a capital letter';
161
                $phpcsFile->addError($error, ($commentStart + 1));
162
            }
163
 
164
            if ($lastChar !== '.') {
165
                $error = 'Variable comment short description must end with a full stop';
166
                $phpcsFile->addError($error, ($commentStart + 1));
167
            }
168
        }//end if
169
 
170
        // Exactly one blank line before tags.
171
        $tags = $this->commentParser->getTagOrders();
172
        if (count($tags) > 1) {
173
            $newlineSpan = $comment->getNewlineAfter();
174
            if ($newlineSpan !== 2) {
175
                $error = 'There must be exactly one blank line before the tags in variable comment';
176
                if ($long !== '') {
177
                    $newlineCount += (substr_count($long, $phpcsFile->eolChar) - $newlineSpan + 1);
178
                }
179
 
180
                $phpcsFile->addError($error, ($commentStart + $newlineCount));
181
                $short = rtrim($short, $phpcsFile->eolChar.' ');
182
            }
183
        }
184
 
185
        // Check for unknown/deprecated tags.
186
        $unknownTags = $this->commentParser->getUnknown();
187
        foreach ($unknownTags as $errorTag) {
188
            // Unknown tags are not parsed, do not process further.
189
            $error = "@$errorTag[tag] tag is not allowed in variable comment";
190
            $phpcsFile->addWarning($error, ($commentStart + $errorTag['line']));
191
        }
192
 
193
        // Check each tag.
194
        $this->processVar($commentStart, $commentEnd);
195
        $this->processSince($commentStart, $commentEnd);
196
        $this->processSees($commentStart);
197
 
198
    }//end processMemberVar()
199
 
200
 
201
    /**
202
     * Process the var tag.
203
     *
204
     * @param int $commentStart The position in the stack where the comment started.
205
     * @param int $commentEnd   The position in the stack where the comment ended.
206
     *
207
     * @return void
208
     */
209
    protected function processVar($commentStart, $commentEnd)
210
    {
211
        $var = $this->commentParser->getVar();
212
 
213
        if ($var !== null) {
214
            $errorPos = ($commentStart + $var->getLine());
215
            $index    = array_keys($this->commentParser->getTagOrders(), 'var');
216
 
217
            if (count($index) > 1) {
218
                $error = 'Only 1 @var tag is allowed in variable comment';
219
                $this->currentFile->addError($error, $errorPos);
220
                return;
221
            }
222
 
223
            if ($index[0] !== 1) {
224
                $error = 'The @var tag must be the first tag in a variable comment';
225
                $this->currentFile->addError($error, $errorPos);
226
            }
227
 
228
            $content = $var->getContent();
229
            if (empty($content) === true) {
230
                $error = 'Var type missing for @var tag in variable comment';
231
                $this->currentFile->addError($error, $errorPos);
232
                return;
233
            } else {
234
                $suggestedType = PHP_CodeSniffer::suggestType($content);
235
                if ($content !== $suggestedType) {
236
                    $error = "Expected \"$suggestedType\"; found \"$content\" for @var tag in variable comment";
237
                    $this->currentFile->addError($error, $errorPos);
238
                }
239
            }
240
 
241
            $spacing = substr_count($var->getWhitespaceBeforeContent(), ' ');
242
            if ($spacing !== 3) {
243
                $error  = '@var tag indented incorrectly. ';
244
                $error .= "Expected 3 spaces but found $spacing.";
245
                $this->currentFile->addError($error, $errorPos);
246
            }
247
        } else {
248
            $error = 'Missing @var tag in variable comment';
249
            $this->currentFile->addError($error, $commentEnd);
250
        }//end if
251
 
252
    }//end processVar()
253
 
254
 
255
    /**
256
     * Process the since tag.
257
     *
258
     * @param int $commentStart The position in the stack where the comment started.
259
     * @param int $commentEnd   The position in the stack where the comment ended.
260
     *
261
     * @return void
262
     */
263
    protected function processSince($commentStart, $commentEnd)
264
    {
265
        $since = $this->commentParser->getSince();
266
        if ($since !== null) {
267
            $errorPos  = ($commentStart + $since->getLine());
268
            $foundTags = $this->commentParser->getTagOrders();
269
            $index     = array_keys($foundTags, 'since');
270
            $var       = array_keys($foundTags, 'var');
271
 
272
            if (count($index) > 1) {
273
                $error = 'Only 1 @since tag is allowed in variable comment';
274
                $this->currentFile->addError($error, $errorPos);
275
                return;
276
            }
277
 
278
            // Only check order if there is one var tag in variable comment.
279
            if (count($var) === 1 && $index[0] !== 2) {
280
                $error = 'The order of @since tag is wrong in variable comment';
281
                $this->currentFile->addError($error, $errorPos);
282
            }
283
 
284
            $content = $since->getContent();
285
            if (empty($content) === true) {
286
                $error = 'Version number missing for @since tag in variable comment';
287
                $this->currentFile->addError($error, $errorPos);
288
                return;
289
            } else if ($content !== '%release_version%') {
290
                if (preg_match('/^([0-9]+)\.([0-9]+)\.([0-9]+)/', $content) === 0) {
291
                    $error = 'Expected version number to be in the form x.x.x in @since tag';
292
                    $this->currentFile->addError($error, $errorPos);
293
                }
294
            }
295
 
296
            $spacing = substr_count($since->getWhitespaceBeforeContent(), ' ');
297
            if ($spacing !== 1) {
298
                $error  = '@since tag indented incorrectly. ';
299
                $error .= "Expected 1 space but found $spacing.";
300
                $this->currentFile->addError($error, $errorPos);
301
            }
302
        } else {
303
            $error = 'Missing @since tag in variable comment';
304
            $this->currentFile->addError($error, $commentEnd);
305
        }//end if
306
 
307
    }//end processSince()
308
 
309
 
310
    /**
311
     * Process the see tags.
312
     *
313
     * @param int $commentStart The position in the stack where the comment started.
314
     *
315
     * @return void
316
     */
317
    protected function processSees($commentStart)
318
    {
319
        $sees = $this->commentParser->getSees();
320
        if (empty($sees) === false) {
321
            foreach ($sees as $see) {
322
                $errorPos = ($commentStart + $see->getLine());
323
                $content  = $see->getContent();
324
                if (empty($content) === true) {
325
                    $error = 'Content missing for @see tag in variable comment';
326
                    $this->currentFile->addError($error, $errorPos);
327
                    continue;
328
                }
329
 
330
                $spacing = substr_count($see->getWhitespaceBeforeContent(), ' ');
331
                if ($spacing !== 3) {
332
                    $error  = '@see tag indented incorrectly. ';
333
                    $error .= "Expected 3 spaces but found $spacing.";
334
                    $this->currentFile->addError($error, $errorPos);
335
                }
336
            }
337
        }
338
 
339
    }//end processSees()
340
 
341
 
342
    /**
343
     * Called to process a normal variable.
344
     *
345
     * Not required for this sniff.
346
     *
347
     * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found.
348
     * @param int                  $stackPtr  The position where the double quoted
349
     *                                        string was found.
350
     *
351
     * @return void
352
     */
353
    protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
354
    {
355
        return;
356
 
357
    }//end processVariable()
358
 
359
 
360
    /**
361
     * Called to process variables found in duoble quoted strings.
362
     *
363
     * Not required for this sniff.
364
     *
365
     * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found.
366
     * @param int                  $stackPtr  The position where the double quoted
367
     *                                        string was found.
368
     *
369
     * @return void
370
     */
371
    protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
372
    {
373
        return;
374
 
375
    }//end processVariableInString()
376
 
377
 
378
}//end class
379
?>