Subversion Repositories Applications.framework

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
5 aurelien 1
<?php
2
/**
3
 * Parses and verifies the doc comments for files.
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: FileCommentSniff.php,v 1.32 2009/02/10 06:01:46 squiz Exp $
14
 * @link      http://pear.php.net/package/PHP_CodeSniffer
15
 */
16
 
17
if (class_exists('PHP_CodeSniffer_CommentParser_ClassCommentParser', true) === false) {
18
    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_CommentParser_ClassCommentParser not found');
19
}
20
 
21
/**
22
 * Parses and verifies the doc comments for files.
23
 *
24
 * Verifies that :
25
 * <ul>
26
 *  <li>A doc comment exists.</li>
27
 *  <li>There is a blank newline after the short description.</li>
28
 *  <li>There is a blank newline between the long and short description.</li>
29
 *  <li>There is a blank newline between the long description and tags.</li>
30
 *  <li>A PHP version is specified.</li>
31
 *  <li>Check the order of the tags.</li>
32
 *  <li>Check the indentation of each tag.</li>
33
 *  <li>Check required and optional tags and the format of their content.</li>
34
 * </ul>
35
 *
36
 * @category  PHP
37
 * @package   PHP_CodeSniffer
38
 * @author    Greg Sherwood <gsherwood@squiz.net>
39
 * @author    Marc McIntyre <mmcintyre@squiz.net>
40
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
41
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
42
 * @version   Release: 1.2.0RC1
43
 * @link      http://pear.php.net/package/PHP_CodeSniffer
44
 */
45
 
46
class PEAR_Sniffs_Commenting_FileCommentSniff implements PHP_CodeSniffer_Sniff
47
{
48
 
49
    /**
50
     * The header comment parser for the current file.
51
     *
52
     * @var PHP_CodeSniffer_Comment_Parser_ClassCommentParser
53
     */
54
    protected $commentParser = null;
55
 
56
    /**
57
     * The current PHP_CodeSniffer_File object we are processing.
58
     *
59
     * @var PHP_CodeSniffer_File
60
     */
61
    protected $currentFile = null;
62
 
63
    /**
64
     * Tags in correct order and related info.
65
     *
66
     * @var array
67
     */
68
    protected $tags = array(
69
                       'category'   => array(
70
                                        'required'       => true,
71
                                        'allow_multiple' => false,
72
                                        'order_text'     => 'precedes @package',
73
                                       ),
74
                       'package'    => array(
75
                                        'required'       => true,
76
                                        'allow_multiple' => false,
77
                                        'order_text'     => 'follows @category',
78
                                       ),
79
                       'subpackage' => array(
80
                                        'required'       => false,
81
                                        'allow_multiple' => false,
82
                                        'order_text'     => 'follows @package',
83
                                       ),
84
                       'author'     => array(
85
                                        'required'       => true,
86
                                        'allow_multiple' => true,
87
                                        'order_text'     => 'follows @subpackage (if used) or @package',
88
                                       ),
89
                       'copyright'  => array(
90
                                        'required'       => false,
91
                                        'allow_multiple' => true,
92
                                        'order_text'     => 'follows @author',
93
                                       ),
94
                       'license'    => array(
95
                                        'required'       => true,
96
                                        'allow_multiple' => false,
97
                                        'order_text'     => 'follows @copyright (if used) or @author',
98
                                       ),
99
                       'version'    => array(
100
                                        'required'       => false,
101
                                        'allow_multiple' => false,
102
                                        'order_text'     => 'follows @license',
103
                                       ),
104
                       'link'       => array(
105
                                        'required'       => true,
106
                                        'allow_multiple' => true,
107
                                        'order_text'     => 'follows @version',
108
                                       ),
109
                       'see'        => array(
110
                                        'required'       => false,
111
                                        'allow_multiple' => true,
112
                                        'order_text'     => 'follows @link',
113
                                       ),
114
                       'since'      => array(
115
                                        'required'       => false,
116
                                        'allow_multiple' => false,
117
                                        'order_text'     => 'follows @see (if used) or @link',
118
                                       ),
119
                       'deprecated' => array(
120
                                        'required'       => false,
121
                                        'allow_multiple' => false,
122
                                        'order_text'     => 'follows @since (if used) or @see (if used) or @link',
123
                                       ),
124
                );
125
 
126
 
127
    /**
128
     * Returns an array of tokens this test wants to listen for.
129
     *
130
     * @return array
131
     */
132
    public function register()
133
    {
134
        return array(T_OPEN_TAG);
135
 
136
    }//end register()
137
 
138
 
139
    /**
140
     * Processes this test, when one of its tokens is encountered.
141
     *
142
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
143
     * @param int                  $stackPtr  The position of the current token
144
     *                                        in the stack passed in $tokens.
145
     *
146
     * @return void
147
     */
148
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
149
    {
150
        $this->currentFile = $phpcsFile;
151
 
152
        // We are only interested if this is the first open tag.
153
        if ($stackPtr !== 0) {
154
            if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) {
155
                return;
156
            }
157
        }
158
 
159
        $tokens = $phpcsFile->getTokens();
160
 
161
        // Find the next non whitespace token.
162
        $commentStart
163
            = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
164
 
165
        // Allow declare() statements at the top of the file.
166
        if ($tokens[$commentStart]['code'] === T_DECLARE) {
167
            $semicolon = $phpcsFile->findNext(T_SEMICOLON, ($commentStart + 1));
168
            $commentStart
169
                = $phpcsFile->findNext(T_WHITESPACE, ($semicolon + 1), null, true);
170
        }
171
 
172
        // Ignore vim header.
173
        if ($tokens[$commentStart]['code'] === T_COMMENT) {
174
            if (strstr($tokens[$commentStart]['content'], 'vim:') !== false) {
175
                $commentStart = $phpcsFile->findNext(
176
                    T_WHITESPACE,
177
                    ($commentStart + 1),
178
                    null,
179
                    true
180
                );
181
            }
182
        }
183
 
184
        $errorToken = ($stackPtr + 1);
185
        if (isset($tokens[$errorToken]) === false) {
186
            $errorToken--;
187
        }
188
 
189
        if ($tokens[$commentStart]['code'] === T_CLOSE_TAG) {
190
            // We are only interested if this is the first open tag.
191
            return;
192
        } else if ($tokens[$commentStart]['code'] === T_COMMENT) {
193
            $error = 'You must use "/**" style comments for a file comment';
194
            $phpcsFile->addError($error, $errorToken);
195
            return;
196
        } else if ($commentStart === false
197
            || $tokens[$commentStart]['code'] !== T_DOC_COMMENT
198
        ) {
199
            $phpcsFile->addError('Missing file doc comment', $errorToken);
200
            return;
201
        } else {
202
 
203
            // Extract the header comment docblock.
204
            $commentEnd = $phpcsFile->findNext(
205
                T_DOC_COMMENT,
206
                ($commentStart + 1),
207
                null,
208
                true
209
            );
210
 
211
            $commentEnd--;
212
 
213
            // Check if there is only 1 doc comment between the
214
            // open tag and class token.
215
            $nextToken   = array(
216
                            T_ABSTRACT,
217
                            T_CLASS,
218
                            T_FUNCTION,
219
                            T_DOC_COMMENT,
220
                           );
221
 
222
            $commentNext = $phpcsFile->findNext($nextToken, ($commentEnd + 1));
223
            if ($commentNext !== false
224
                && $tokens[$commentNext]['code'] !== T_DOC_COMMENT
225
            ) {
226
                // Found a class token right after comment doc block.
227
                $newlineToken = $phpcsFile->findNext(
228
                    T_WHITESPACE,
229
                    ($commentEnd + 1),
230
                    $commentNext,
231
                    false,
232
                    $phpcsFile->eolChar
233
                );
234
 
235
                if ($newlineToken !== false) {
236
                    $newlineToken = $phpcsFile->findNext(
237
                        T_WHITESPACE,
238
                        ($newlineToken + 1),
239
                        $commentNext,
240
                        false,
241
                        $phpcsFile->eolChar
242
                    );
243
 
244
                    if ($newlineToken === false) {
245
                        // No blank line between the class token and the doc block.
246
                        // The doc block is most likely a class comment.
247
                        $error = 'Missing file doc comment';
248
                        $phpcsFile->addError($error, $errorToken);
249
                        return;
250
                    }
251
                }
252
            }//end if
253
 
254
            $comment = $phpcsFile->getTokensAsString(
255
                $commentStart,
256
                ($commentEnd - $commentStart + 1)
257
            );
258
 
259
            // Parse the header comment docblock.
260
            try {
261
                $this->commentParser = new PHP_CodeSniffer_CommentParser_ClassCommentParser($comment, $phpcsFile);
262
                $this->commentParser->parse();
263
            } catch (PHP_CodeSniffer_CommentParser_ParserException $e) {
264
                $line = ($e->getLineWithinComment() + $commentStart);
265
                $phpcsFile->addError($e->getMessage(), $line);
266
                return;
267
            }
268
 
269
            $comment = $this->commentParser->getComment();
270
            if (is_null($comment) === true) {
271
                $error = 'File doc comment is empty';
272
                $phpcsFile->addError($error, $commentStart);
273
                return;
274
            }
275
 
276
            // No extra newline before short description.
277
            $short        = $comment->getShortComment();
278
            $newlineCount = 0;
279
            $newlineSpan  = strspn($short, $phpcsFile->eolChar);
280
            if ($short !== '' && $newlineSpan > 0) {
281
                $line  = ($newlineSpan > 1) ? 'newlines' : 'newline';
282
                $error = "Extra $line found before file comment short description";
283
                $phpcsFile->addError($error, ($commentStart + 1));
284
            }
285
 
286
            $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1);
287
 
288
            // Exactly one blank line between short and long description.
289
            $long = $comment->getLongComment();
290
            if (empty($long) === false) {
291
                $between        = $comment->getWhiteSpaceBetween();
292
                $newlineBetween = substr_count($between, $phpcsFile->eolChar);
293
                if ($newlineBetween !== 2) {
294
                    $error = 'There must be exactly one blank line between descriptions in file comment';
295
                    $phpcsFile->addError($error, ($commentStart + $newlineCount + 1));
296
                }
297
 
298
                $newlineCount += $newlineBetween;
299
            }
300
 
301
            // Exactly one blank line before tags.
302
            $tags = $this->commentParser->getTagOrders();
303
            if (count($tags) > 1) {
304
                $newlineSpan = $comment->getNewlineAfter();
305
                if ($newlineSpan !== 2) {
306
                    $error = 'There must be exactly one blank line before the tags in file comment';
307
                    if ($long !== '') {
308
                        $newlineCount += (substr_count($long, $phpcsFile->eolChar) - $newlineSpan + 1);
309
                    }
310
 
311
                    $phpcsFile->addError($error, ($commentStart + $newlineCount));
312
                    $short = rtrim($short, $phpcsFile->eolChar.' ');
313
                }
314
            }
315
 
316
            // Check the PHP Version.
317
            $this->processPHPVersion($commentStart, $commentEnd, $long);
318
 
319
            // Check each tag.
320
            $this->processTags($commentStart, $commentEnd);
321
        }//end if
322
 
323
    }//end process()
324
 
325
 
326
    /**
327
     * Check that the PHP version is specified.
328
     *
329
     * @param int    $commentStart Position in the stack where the comment started.
330
     * @param int    $commentEnd   Position in the stack where the comment ended.
331
     * @param string $comment      The text of the function comment.
332
     *
333
     * @return void
334
     */
335
    protected function processPHPVersion($commentStart, $commentEnd, $commentText)
336
    {
337
        if (strstr(strtolower($commentText), 'php version') === false) {
338
            $error = 'PHP version not specified';
339
             $this->currentFile->addWarning($error, $commentEnd);
340
        }
341
 
342
    }//end processPHPVersion()
343
 
344
 
345
    /**
346
     * Processes each required or optional tag.
347
     *
348
     * @param int $commentStart Position in the stack where the comment started.
349
     * @param int $commentEnd   Position in the stack where the comment ended.
350
     *
351
     * @return void
352
     */
353
    protected function processTags($commentStart, $commentEnd)
354
    {
355
        $docBlock    = (get_class($this) === 'PEAR_Sniffs_Commenting_FileCommentSniff') ? 'file' : 'class';
356
        $foundTags   = $this->commentParser->getTagOrders();
357
        $orderIndex  = 0;
358
        $indentation = array();
359
        $longestTag  = 0;
360
        $errorPos    = 0;
361
 
362
        foreach ($this->tags as $tag => $info) {
363
 
364
            // Required tag missing.
365
            if ($info['required'] === true && in_array($tag, $foundTags) === false) {
366
                $error = "Missing @$tag tag in $docBlock comment";
367
                $this->currentFile->addError($error, $commentEnd);
368
                continue;
369
            }
370
 
371
             // Get the line number for current tag.
372
            $tagName = ucfirst($tag);
373
            if ($info['allow_multiple'] === true) {
374
                $tagName .= 's';
375
            }
376
 
377
            $getMethod  = 'get'.$tagName;
378
            $tagElement = $this->commentParser->$getMethod();
379
            if (is_null($tagElement) === true || empty($tagElement) === true) {
380
                continue;
381
            }
382
 
383
            $errorPos = $commentStart;
384
            if (is_array($tagElement) === false) {
385
                $errorPos = ($commentStart + $tagElement->getLine());
386
            }
387
 
388
            // Get the tag order.
389
            $foundIndexes = array_keys($foundTags, $tag);
390
 
391
            if (count($foundIndexes) > 1) {
392
                // Multiple occurance not allowed.
393
                if ($info['allow_multiple'] === false) {
394
                    $error = "Only 1 @$tag tag is allowed in a $docBlock comment";
395
                    $this->currentFile->addError($error, $errorPos);
396
                } else {
397
                    // Make sure same tags are grouped together.
398
                    $i     = 0;
399
                    $count = $foundIndexes[0];
400
                    foreach ($foundIndexes as $index) {
401
                        if ($index !== $count) {
402
                            $errorPosIndex
403
                                = ($errorPos + $tagElement[$i]->getLine());
404
                            $error = "@$tag tags must be grouped together";
405
                            $this->currentFile->addError($error, $errorPosIndex);
406
                        }
407
 
408
                        $i++;
409
                        $count++;
410
                    }
411
                }
412
            }//end if
413
 
414
            // Check tag order.
415
            if ($foundIndexes[0] > $orderIndex) {
416
                $orderIndex = $foundIndexes[0];
417
            } else {
418
                if (is_array($tagElement) === true && empty($tagElement) === false) {
419
                    $errorPos += $tagElement[0]->getLine();
420
                }
421
 
422
                $orderText = $info['order_text'];
423
                $error = "The @$tag tag is in the wrong order; the tag $orderText";
424
                $this->currentFile->addError($error, $errorPos);
425
            }
426
 
427
            // Store the indentation for checking.
428
            $len = strlen($tag);
429
            if ($len > $longestTag) {
430
                $longestTag = $len;
431
            }
432
 
433
            if (is_array($tagElement) === true) {
434
                foreach ($tagElement as $key => $element) {
435
                    $indentation[] = array(
436
                                      'tag'   => $tag,
437
                                      'space' => $this->getIndentation($tag, $element),
438
                                      'line'  => $element->getLine(),
439
                                     );
440
                }
441
            } else {
442
                $indentation[] = array(
443
                                  'tag'   => $tag,
444
                                  'space' => $this->getIndentation($tag, $tagElement),
445
                                 );
446
            }
447
 
448
            $method = 'process'.$tagName;
449
            if (method_exists($this, $method) === true) {
450
                // Process each tag if a method is defined.
451
                call_user_func(array($this, $method), $errorPos);
452
            } else {
453
                if (is_array($tagElement) === true) {
454
                    foreach ($tagElement as $key => $element) {
455
                        $element->process(
456
                            $this->currentFile,
457
                            $commentStart,
458
                            $docBlock
459
                        );
460
                    }
461
                } else {
462
                     $tagElement->process(
463
                         $this->currentFile,
464
                         $commentStart,
465
                         $docBlock
466
                     );
467
                }
468
            }
469
        }//end foreach
470
 
471
        foreach ($indentation as $indentInfo) {
472
            if ($indentInfo['space'] !== 0
473
                && $indentInfo['space'] !== ($longestTag + 1)
474
            ) {
475
                $expected = (($longestTag - strlen($indentInfo['tag'])) + 1);
476
                $space    = ($indentInfo['space'] - strlen($indentInfo['tag']));
477
                $error    = "@$indentInfo[tag] tag comment indented incorrectly. ";
478
                $error   .= "Expected $expected spaces but found $space.";
479
 
480
                $getTagMethod = 'get'.ucfirst($indentInfo['tag']);
481
 
482
                if ($this->tags[$indentInfo['tag']]['allow_multiple'] === true) {
483
                    $line = $indentInfo['line'];
484
                } else {
485
                    $tagElem = $this->commentParser->$getTagMethod();
486
                    $line    = $tagElem->getLine();
487
                }
488
 
489
                $this->currentFile->addError($error, ($commentStart + $line));
490
            }
491
        }
492
 
493
    }//end processTags()
494
 
495
 
496
    /**
497
     * Get the indentation information of each tag.
498
     *
499
     * @param string                                   $tagName    The name of the
500
     *                                                             doc comment
501
     *                                                             element.
502
     * @param PHP_CodeSniffer_CommentParser_DocElement $tagElement The doc comment
503
     *                                                             element.
504
     *
505
     * @return void
506
     */
507
    protected function getIndentation($tagName, $tagElement)
508
    {
509
        if ($tagElement instanceof PHP_CodeSniffer_CommentParser_SingleElement) {
510
            if ($tagElement->getContent() !== '') {
511
                return (strlen($tagName) + substr_count($tagElement->getWhitespaceBeforeContent(), ' '));
512
            }
513
        } else if ($tagElement instanceof PHP_CodeSniffer_CommentParser_PairElement) {
514
            if ($tagElement->getValue() !== '') {
515
                return (strlen($tagName) + substr_count($tagElement->getWhitespaceBeforeValue(), ' '));
516
            }
517
        }
518
 
519
        return 0;
520
 
521
    }//end getIndentation()
522
 
523
 
524
    /**
525
     * Process the category tag.
526
     *
527
     * @param int $errorPos The line number where the error occurs.
528
     *
529
     * @return void
530
     */
531
    protected function processCategory($errorPos)
532
    {
533
        $category = $this->commentParser->getCategory();
534
        if ($category !== null) {
535
            $content = $category->getContent();
536
            if ($content !== '') {
537
                if (PHP_CodeSniffer::isUnderscoreName($content) !== true) {
538
                    $newContent = str_replace(' ', '_', $content);
539
                    $nameBits   = explode('_', $newContent);
540
                    $firstBit   = array_shift($nameBits);
541
                    $newName    = ucfirst($firstBit).'_';
542
                    foreach ($nameBits as $bit) {
543
                        $newName .= ucfirst($bit).'_';
544
                    }
545
 
546
                    $validName = trim($newName, '_');
547
                    $error     = "Category name \"$content\" is not valid; consider \"$validName\" instead";
548
                    $this->currentFile->addError($error, $errorPos);
549
                }
550
            } else {
551
                $error = '@category tag must contain a name';
552
                $this->currentFile->addError($error, $errorPos);
553
            }
554
        }
555
 
556
    }//end processCategory()
557
 
558
 
559
    /**
560
     * Process the package tag.
561
     *
562
     * @param int $errorPos The line number where the error occurs.
563
     *
564
     * @return void
565
     */
566
    protected function processPackage($errorPos)
567
    {
568
        $package = $this->commentParser->getPackage();
569
        if ($package !== null) {
570
            $content = $package->getContent();
571
            if ($content !== '') {
572
                if (PHP_CodeSniffer::isUnderscoreName($content) !== true) {
573
                    $newContent = str_replace(' ', '_', $content);
574
                    $nameBits   = explode('_', $newContent);
575
                    $firstBit   = array_shift($nameBits);
576
                    $newName    = strtoupper($firstBit{0}).substr($firstBit, 1).'_';
577
                    foreach ($nameBits as $bit) {
578
                        $newName .= strtoupper($bit{0}).substr($bit, 1).'_';
579
                    }
580
 
581
                    $validName = trim($newName, '_');
582
                    $error     = "Package name \"$content\" is not valid; consider \"$validName\" instead";
583
                    $this->currentFile->addError($error, $errorPos);
584
                }
585
            } else {
586
                $error = '@package tag must contain a name';
587
                $this->currentFile->addError($error, $errorPos);
588
            }
589
        }
590
 
591
    }//end processPackage()
592
 
593
 
594
    /**
595
     * Process the subpackage tag.
596
     *
597
     * @param int $errorPos The line number where the error occurs.
598
     *
599
     * @return void
600
     */
601
    protected function processSubpackage($errorPos)
602
    {
603
        $package = $this->commentParser->getSubpackage();
604
        if ($package !== null) {
605
            $content = $package->getContent();
606
            if ($content !== '') {
607
                if (PHP_CodeSniffer::isUnderscoreName($content) !== true) {
608
                    $newContent = str_replace(' ', '_', $content);
609
                    $nameBits   = explode('_', $newContent);
610
                    $firstBit   = array_shift($nameBits);
611
                    $newName    = strtoupper($firstBit{0}).substr($firstBit, 1).'_';
612
                    foreach ($nameBits as $bit) {
613
                        $newName .= strtoupper($bit{0}).substr($bit, 1).'_';
614
                    }
615
 
616
                    $validName = trim($newName, '_');
617
                    $error     = "Subpackage name \"$content\" is not valid; consider \"$validName\" instead";
618
                    $this->currentFile->addError($error, $errorPos);
619
                }
620
            } else {
621
                $error = '@subpackage tag must contain a name';
622
                $this->currentFile->addError($error, $errorPos);
623
            }
624
        }
625
 
626
    }//end processSubpackage()
627
 
628
 
629
    /**
630
     * Process the author tag(s) that this header comment has.
631
     *
632
     * This function is different from other _process functions
633
     * as $authors is an array of SingleElements, so we work out
634
     * the errorPos for each element separately
635
     *
636
     * @param int $commentStart The position in the stack where
637
     *                          the comment started.
638
     *
639
     * @return void
640
     */
641
    protected function processAuthors($commentStart)
642
    {
643
         $authors = $this->commentParser->getAuthors();
644
        // Report missing return.
645
        if (empty($authors) === false) {
646
            foreach ($authors as $author) {
647
                $errorPos = ($commentStart + $author->getLine());
648
                $content  = $author->getContent();
649
                if ($content !== '') {
650
                    $local = '\da-zA-Z-_+';
651
                    // Dot character cannot be the first or last character
652
                    // in the local-part.
653
                    $localMiddle = $local.'.\w';
654
                    if (preg_match('/^([^<]*)\s+<(['.$local.']['.$localMiddle.']*['.$local.']@[\da-zA-Z][-.\w]*[\da-zA-Z]\.[a-zA-Z]{2,7})>$/', $content) === 0) {
655
                        $error = 'Content of the @author tag must be in the form "Display Name <username@example.com>"';
656
                        $this->currentFile->addError($error, $errorPos);
657
                    }
658
                } else {
659
                    $docBlock = (get_class($this) === 'PEAR_Sniffs_Commenting_FileCommentSniff') ? 'file' : 'class';
660
                    $error = "Content missing for @author tag in $docBlock comment";
661
                    $this->currentFile->addError($error, $errorPos);
662
                }
663
            }
664
        }
665
 
666
    }//end processAuthors()
667
 
668
 
669
    /**
670
     * Process the copyright tags.
671
     *
672
     * @param int $commentStart The position in the stack where
673
     *                          the comment started.
674
     *
675
     * @return void
676
     */
677
    protected function processCopyrights($commentStart)
678
    {
679
        $copyrights = $this->commentParser->getCopyrights();
680
        foreach ($copyrights as $copyright) {
681
            $errorPos = ($commentStart + $copyright->getLine());
682
            $content  = $copyright->getContent();
683
            if ($content !== '') {
684
                $matches = array();
685
                if (preg_match('/^([0-9]{4})((.{1})([0-9]{4}))? (.+)$/', $content, $matches) !== 0) {
686
                    // Check earliest-latest year order.
687
                    if ($matches[3] !== '') {
688
                        if ($matches[3] !== '-') {
689
                            $error = 'A hyphen must be used between the earliest and latest year';
690
                            $this->currentFile->addError($error, $errorPos);
691
                        }
692
 
693
                        if ($matches[4] !== '' && $matches[4] < $matches[1]) {
694
                            $error = "Invalid year span \"$matches[1]$matches[3]$matches[4]\" found; consider \"$matches[4]-$matches[1]\" instead";
695
                            $this->currentFile->addWarning($error, $errorPos);
696
                        }
697
                    }
698
                } else {
699
                    $error = '@copyright tag must contain a year and the name of the copyright holder';
700
                    $this->currentFile->addError($error, $errorPos);
701
                }
702
            } else {
703
                $error = '@copyright tag must contain a year and the name of the copyright holder';
704
                $this->currentFile->addError($error, $errorPos);
705
            }//end if
706
        }//end if
707
 
708
    }//end processCopyrights()
709
 
710
 
711
    /**
712
     * Process the license tag.
713
     *
714
     * @param int $errorPos The line number where the error occurs.
715
     *
716
     * @return void
717
     */
718
    protected function processLicense($errorPos)
719
    {
720
        $license = $this->commentParser->getLicense();
721
        if ($license !== null) {
722
            $value   = $license->getValue();
723
            $comment = $license->getComment();
724
            if ($value === '' || $comment === '') {
725
                $error = '@license tag must contain a URL and a license name';
726
                $this->currentFile->addError($error, $errorPos);
727
            }
728
        }
729
 
730
    }//end processLicense()
731
 
732
 
733
    /**
734
     * Process the version tag.
735
     *
736
     * @param int $errorPos The line number where the error occurs.
737
     *
738
     * @return void
739
     */
740
    protected function processVersion($errorPos)
741
    {
742
        $version = $this->commentParser->getVersion();
743
        if ($version !== null) {
744
            $content = $version->getContent();
745
            $matches = array();
746
            if (empty($content) === true) {
747
                $error = 'Content missing for @version tag in file comment';
748
                $this->currentFile->addError($error, $errorPos);
749
            } else if (strstr($content, 'CVS:') === false
750
                && strstr($content, 'SVN:') === false
751
            ) {
752
                $error = "Invalid version \"$content\" in file comment; consider \"CVS: <cvs_id>\" or \"SVN: <svn_id>\" instead";
753
                $this->currentFile->addWarning($error, $errorPos);
754
            }
755
        }
756
 
757
    }//end processVersion()
758
 
759
 
760
}//end class
761
 
762
?>