~ubuntu-branches/ubuntu/saucy/php-codesniffer/saucy

« back to all changes in this revision

Viewing changes to PHP_CodeSniffer-1.1.0/CodeSniffer/CommentParser/AbstractParser.php

  • Committer: Bazaar Package Importer
  • Author(s): Jack Bates
  • Date: 2008-10-01 17:39:43 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20081001173943-2dy06n1e8zwyw1o8
Tags: 1.1.0-1
* New upstream release
* Acknowledge NMU, thanks Jan

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Parses doc comments.
 
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: AbstractParser.php,v 1.19 2008/01/12 08:45:13 squiz Exp $
 
14
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 
15
 */
 
16
 
 
17
if (class_exists('PHP_CodeSniffer_CommentParser_SingleElement', true) === false) {
 
18
    $error = 'Class PHP_CodeSniffer_CommentParser_SingleElement not found';
 
19
    throw new PHP_CodeSniffer_Exception($error);
 
20
}
 
21
 
 
22
if (class_exists('PHP_CodeSniffer_CommentParser_CommentElement', true) === false) {
 
23
    $error = 'Class PHP_CodeSniffer_CommentParser_CommentElement not found';
 
24
    throw new PHP_CodeSniffer_Exception($error);
 
25
}
 
26
 
 
27
if (class_exists('PHP_CodeSniffer_CommentParser_ParserException', true) === false) {
 
28
    $error = 'Class PHP_CodeSniffer_CommentParser_ParserException not found';
 
29
    throw new PHP_CodeSniffer_Exception($error);
 
30
}
 
31
 
 
32
/**
 
33
 * Parses doc comments.
 
34
 *
 
35
 * This abstract parser handles the following tags:
 
36
 *
 
37
 * <ul>
 
38
 *  <li>The short description and the long description</li>
 
39
 *  <li>@see</li>
 
40
 *  <li>@link</li>
 
41
 *  <li>@deprecated</li>
 
42
 *  <li>@since</li>
 
43
 * </ul>
 
44
 *
 
45
 * Extending classes should implement the getAllowedTags() method to return the
 
46
 * tags that they wish to process, ommiting the tags that this base class
 
47
 * processes. When one of these tags in encountered, the process&lt;tag_name&gt;
 
48
 * method is called on that class. For example, if a parser's getAllowedTags()
 
49
 * method returns \@param as one of its tags, the processParam method will be
 
50
 * called so that the parser can process such a tag.
 
51
 *
 
52
 * The method is passed the tokens that comprise this tag. The tokens array
 
53
 * includes the whitespace that exists between the tokens, as seperate tokens.
 
54
 * It's up to the method to create a element that implements the DocElement
 
55
 * interface, which should be returned. The AbstractDocElement class is a helper
 
56
 * class that can be used to handle most of the parsing of the tokens into their
 
57
 * individual sub elements. It requires that you construct it with the element
 
58
 * previous to the element currently being processed, which can be acquired
 
59
 * with the protected $previousElement class member of this class.
 
60
 *
 
61
 * @category  PHP
 
62
 * @package   PHP_CodeSniffer
 
63
 * @author    Greg Sherwood <gsherwood@squiz.net>
 
64
 * @author    Marc McIntyre <mmcintyre@squiz.net>
 
65
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
 
66
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
 
67
 * @version   Release: 1.1.0
 
68
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 
69
 */
 
70
abstract class PHP_CodeSniffer_CommentParser_AbstractParser
 
71
{
 
72
 
 
73
    /**
 
74
     * The comment element that appears in the doc comment.
 
75
     *
 
76
     * @var PHP_CodeSniffer_CommentParser_CommentElement
 
77
     */
 
78
    protected $comment = null;
 
79
 
 
80
    /**
 
81
     * The string content of the comment.
 
82
     *
 
83
     * @var string
 
84
     */
 
85
    protected $commentString = '';
 
86
 
 
87
    /**
 
88
     * The file that the comment exists in.
 
89
     *
 
90
     * @var PHP_CodeSniffer_File
 
91
     */
 
92
    protected $phpcsFile = null;
 
93
 
 
94
    /**
 
95
     * The word tokens that appear in the comment.
 
96
     *
 
97
     * Whitespace tokens also appear in this stack, but are separate tokens
 
98
     * from words.
 
99
     *
 
100
     * @var array(string)
 
101
     */
 
102
    protected $words = array();
 
103
 
 
104
    /**
 
105
     * The previous doc element that was processed.
 
106
     *
 
107
     * null if the current element being processed is the first element in the
 
108
     * doc comment.
 
109
     *
 
110
     * @var PHP_CodeSniffer_CommentParser_DocElement
 
111
     */
 
112
    protected $previousElement = null;
 
113
 
 
114
    /**
 
115
     * A list of see elements that appear in this doc comment.
 
116
     *
 
117
     * @var array(PHP_CodeSniffer_CommentParser_SingleElement)
 
118
     */
 
119
    protected $sees = array();
 
120
 
 
121
    /**
 
122
     * A list of see elements that appear in this doc comment.
 
123
     *
 
124
     * @var array(PHP_CodeSniffer_CommentParser_SingleElement)
 
125
     */
 
126
    protected $deprecated = null;
 
127
 
 
128
    /**
 
129
     * A list of see elements that appear in this doc comment.
 
130
     *
 
131
     * @var array(PHP_CodeSniffer_CommentParser_SingleElement)
 
132
     */
 
133
    protected $links = array();
 
134
 
 
135
    /**
 
136
     * A element to represent \@since tags.
 
137
     *
 
138
     * @var PHP_CodeSniffer_CommentParser_SingleElement
 
139
     */
 
140
    protected $since = null;
 
141
 
 
142
    /**
 
143
     * True if the comment has been parsed.
 
144
     *
 
145
     * @var boolean
 
146
     */
 
147
    private $_hasParsed = false;
 
148
 
 
149
    /**
 
150
     * The tags that this class can process.
 
151
     *
 
152
     * @var array(string)
 
153
     */
 
154
    private static $_tags = array(
 
155
                             'see'        => false,
 
156
                             'link'       => false,
 
157
                             'deprecated' => true,
 
158
                             'since'      => true,
 
159
                            );
 
160
 
 
161
    /**
 
162
     * An array of unknown tags.
 
163
     *
 
164
     * @var array(string)
 
165
     */
 
166
    public $unknown = array();
 
167
 
 
168
    /**
 
169
     * The order of tags.
 
170
     *
 
171
     * @var array(string)
 
172
     */
 
173
    public $orders = array();
 
174
 
 
175
 
 
176
    /**
 
177
     * Constructs a Doc Comment Parser.
 
178
     *
 
179
     * @param string               $comment   The comment to parse.
 
180
     * @param PHP_CodeSniffer_File $phpcsFile The file that this comment is in.
 
181
     */
 
182
    public function __construct($comment, PHP_CodeSniffer_File $phpcsFile)
 
183
    {
 
184
        $this->commentString = $comment;
 
185
        $this->phpcsFile     = $phpcsFile;
 
186
 
 
187
    }//end __construct()
 
188
 
 
189
 
 
190
    /**
 
191
     * Initiates the parsing of the doc comment.
 
192
     *
 
193
     * @return void
 
194
     * @throws PHP_CodeSniffer_CommentParser_ParserException If the parser finds a
 
195
     *                                                       problem with the
 
196
     *                                                       comment.
 
197
     */
 
198
    public function parse()
 
199
    {
 
200
        if ($this->_hasParsed === false) {
 
201
            $this->_parse($this->commentString);
 
202
        }
 
203
 
 
204
    }//end parse()
 
205
 
 
206
 
 
207
    /**
 
208
     * Parse the comment.
 
209
     *
 
210
     * @param string $comment The doc comment to parse.
 
211
     *
 
212
     * @return void
 
213
     * @see _parseWords()
 
214
     */
 
215
    private function _parse($comment)
 
216
    {
 
217
        // Firstly, remove the comment tags and any stars from the left side.
 
218
        $lines = split($this->phpcsFile->eolChar, $comment);
 
219
        foreach ($lines as &$line) {
 
220
            $line = trim($line);
 
221
 
 
222
            if ($line !== '') {
 
223
                if (substr($line, 0, 3) === '/**') {
 
224
                    $line = substr($line, 3);
 
225
                } else if (substr($line, -2, 2) === '*/') {
 
226
                    $line = substr($line, 0, -2);
 
227
                } else if ($line{0} === '*') {
 
228
                    $line = substr($line, 1);
 
229
                }
 
230
 
 
231
                // Add the words to the stack, preserving newlines. Other parsers
 
232
                // might be interested in the spaces between words, so tokenize
 
233
                // spaces as well as separate tokens.
 
234
                $flags       = (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
 
235
                $words       = preg_split('|(\s+)|', $line.$this->phpcsFile->eolChar, -1, $flags);
 
236
                $this->words = array_merge($this->words, $words);
 
237
            }
 
238
        }//end foreach
 
239
 
 
240
        $this->_parseWords();
 
241
 
 
242
    }//end _parse()
 
243
 
 
244
 
 
245
    /**
 
246
     * Parses each word within the doc comment.
 
247
     *
 
248
     * @return void
 
249
     * @see _parse()
 
250
     * @throws PHP_CodeSniffer_CommentParser_ParserException If more than the allowed
 
251
     *                                                       number of occurances of
 
252
     *                                                       a tag is found.
 
253
     */
 
254
    private function _parseWords()
 
255
    {
 
256
        $allowedTags     = (self::$_tags + $this->getAllowedTags());
 
257
        $allowedTagNames = array_keys($allowedTags);
 
258
        $foundTags       = array();
 
259
        $prevTagPos      = false;
 
260
        $wordWasEmpty    = true;
 
261
 
 
262
        foreach ($this->words as $wordPos => $word) {
 
263
 
 
264
            if (trim($word) !== '') {
 
265
                $wordWasEmpty = false;
 
266
            }
 
267
 
 
268
            if ($word{0} === '@') {
 
269
 
 
270
                $tag = substr($word, 1);
 
271
 
 
272
                // Filter out @ tags in the comment description.
 
273
                // A real comment tag should have whitespace and a newline before it.
 
274
                if (isset($this->words[($wordPos - 1)]) === false || trim($this->words[($wordPos - 1)]) !== '') {
 
275
                    continue;
 
276
                }
 
277
 
 
278
                if (isset($this->words[($wordPos - 2)]) === false || $this->words[($wordPos - 2)] !== $this->phpcsFile->eolChar) {
 
279
                    continue;
 
280
                }
 
281
 
 
282
                $foundTags[] = $tag;
 
283
 
 
284
                if ($prevTagPos !== false) {
 
285
                    // There was a tag before this so let's process it.
 
286
                    $prevTag = substr($this->words[$prevTagPos], 1);
 
287
                    $this->parseTag($prevTag, $prevTagPos, ($wordPos - 1));
 
288
                } else {
 
289
                    // There must have been a comment before this tag, so
 
290
                    // let's process that.
 
291
                    $this->parseTag('comment', 0, ($wordPos - 1));
 
292
                }
 
293
 
 
294
                $prevTagPos = $wordPos;
 
295
 
 
296
                if (in_array($tag, $allowedTagNames) === false) {
 
297
                    // This is not a tag that we process, but let's check to
 
298
                    // see if it is a tag we know about. If we don't know about it,
 
299
                    // we add it to a list of unknown tags.
 
300
                    $knownTags = array(
 
301
                                  'abstract',
 
302
                                  'access',
 
303
                                  'example',
 
304
                                  'filesource',
 
305
                                  'global',
 
306
                                  'ignore',
 
307
                                  'internal',
 
308
                                  'name',
 
309
                                  'static',
 
310
                                  'staticvar',
 
311
                                  'todo',
 
312
                                  'tutorial',
 
313
                                  'uses',
 
314
                                  'package_version@',
 
315
                                 );
 
316
 
 
317
                    if (in_array($tag, $knownTags) === false) {
 
318
                        $this->unknown[] = array(
 
319
                                            'tag'  => $tag,
 
320
                                            'line' => $this->getLine($wordPos),
 
321
                                           );
 
322
                    }
 
323
 
 
324
                }//end if
 
325
 
 
326
            }//end if
 
327
        }//end foreach
 
328
 
 
329
        // Only process this tag if there was something to process.
 
330
        if ($wordWasEmpty === false) {
 
331
            if ($prevTagPos === false) {
 
332
                // There must only be a comment in this doc comment.
 
333
                $this->parseTag('comment', 0, count($this->words));
 
334
            } else {
 
335
                // Process the last tag element.
 
336
                $prevTag = substr($this->words[$prevTagPos], 1);
 
337
                $this->parseTag($prevTag, $prevTagPos, count($this->words));
 
338
            }
 
339
        }
 
340
 
 
341
    }//end _parseWords()
 
342
 
 
343
 
 
344
    /**
 
345
     * Returns the line that the token exists on in the doc comment.
 
346
     *
 
347
     * @param int $tokenPos The position in the words stack to find the line
 
348
     *                      number for.
 
349
     *
 
350
     * @return int
 
351
     */
 
352
    protected function getLine($tokenPos)
 
353
    {
 
354
        $newlines = 0;
 
355
        for ($i = 0; $i < $tokenPos; $i++) {
 
356
            $newlines += substr_count($this->phpcsFile->eolChar, $this->words[$i]);
 
357
        }
 
358
 
 
359
        return $newlines;
 
360
 
 
361
    }//end getLine()
 
362
 
 
363
 
 
364
    /**
 
365
     * Parses see tag element within the doc comment.
 
366
     *
 
367
     * @param array(string) $tokens The word tokens that comprise this element.
 
368
     *
 
369
     * @return DocElement The element that represents this see comment.
 
370
     */
 
371
    protected function parseSee($tokens)
 
372
    {
 
373
        $see          = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'see', $this->phpcsFile);
 
374
        $this->sees[] = $see;
 
375
        return $see;
 
376
 
 
377
    }//end parseSee()
 
378
 
 
379
 
 
380
    /**
 
381
     * Parses the comment element that appears at the top of the doc comment.
 
382
     *
 
383
     * @param array(string) $tokens The word tokens that comprise tihs element.
 
384
     *
 
385
     * @return DocElement The element that represents this comment element.
 
386
     */
 
387
    protected function parseComment($tokens)
 
388
    {
 
389
        $this->comment = new PHP_CodeSniffer_CommentParser_CommentElement($this->previousElement, $tokens, $this->phpcsFile);
 
390
        return $this->comment;
 
391
 
 
392
    }//end parseComment()
 
393
 
 
394
 
 
395
    /**
 
396
     * Parses \@deprecated tags.
 
397
     *
 
398
     * @param array(string) $tokens The word tokens that comprise tihs element.
 
399
     *
 
400
     * @return DocElement The element that represents this deprecated tag.
 
401
     */
 
402
    protected function parseDeprecated($tokens)
 
403
    {
 
404
        $this->deprecated = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'deprecated', $this->phpcsFile);
 
405
        return $this->deprecated;
 
406
 
 
407
    }//end parseDeprecated()
 
408
 
 
409
 
 
410
    /**
 
411
     * Parses \@since tags.
 
412
     *
 
413
     * @param array(string) $tokens The word tokens that comprise this element.
 
414
     *
 
415
     * @return SingleElement The element that represents this since tag.
 
416
     */
 
417
    protected function parseSince($tokens)
 
418
    {
 
419
        $this->since = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'since', $this->phpcsFile);
 
420
        return $this->since;
 
421
 
 
422
    }//end parseSince()
 
423
 
 
424
 
 
425
    /**
 
426
     * Parses \@link tags.
 
427
     *
 
428
     * @param array(string) $tokens The word tokens that comprise this element.
 
429
     *
 
430
     * @return SingleElement The element that represents this link tag.
 
431
     */
 
432
    protected function parseLink($tokens)
 
433
    {
 
434
        $link          = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'link', $this->phpcsFile);
 
435
        $this->links[] = $link;
 
436
        return $link;
 
437
 
 
438
    }//end parseLink()
 
439
 
 
440
 
 
441
    /**
 
442
     * Returns the see elements that appear in this doc comment.
 
443
     *
 
444
     * @return array(SingleElement)
 
445
     */
 
446
    public function getSees()
 
447
    {
 
448
        return $this->sees;
 
449
 
 
450
    }//end getSees()
 
451
 
 
452
 
 
453
    /**
 
454
     * Returns the comment element that appears at the top of this doc comment.
 
455
     *
 
456
     * @return CommentElement
 
457
     */
 
458
    public function getComment()
 
459
    {
 
460
        return $this->comment;
 
461
 
 
462
    }//end getComment()
 
463
 
 
464
 
 
465
    /**
 
466
     * Returns the link elements found in this comment.
 
467
     *
 
468
     * Returns an empty array if no links are found in the comment.
 
469
     *
 
470
     * @return array(SingleElement)
 
471
     */
 
472
    public function getLinks()
 
473
    {
 
474
        return $this->links;
 
475
 
 
476
    }//end getLinks()
 
477
 
 
478
 
 
479
    /**
 
480
     * Returns the deprecated element found in this comment.
 
481
     *
 
482
     * Returns null if no element exists in the comment.
 
483
     *
 
484
     * @return SingleElement
 
485
     */
 
486
    public function getDeprecated()
 
487
    {
 
488
        return $this->deprecated;
 
489
 
 
490
    }//end getDeprecated()
 
491
 
 
492
 
 
493
    /**
 
494
     * Returns the since element found in this comment.
 
495
     *
 
496
     * Returns null if no element exists in the comment.
 
497
     *
 
498
     * @return SingleElement
 
499
     */
 
500
    public function getSince()
 
501
    {
 
502
        return $this->since;
 
503
 
 
504
    }//end getSince()
 
505
 
 
506
 
 
507
    /**
 
508
     * Parses the specified tag.
 
509
     *
 
510
     * @param string $tag   The tag name to parse (omitting the @ sybmol from
 
511
     *                      the tag)
 
512
     * @param int    $start The position in the word tokens where this element
 
513
     *                      started.
 
514
     * @param int    $end   The position in the word tokens where this element
 
515
     *                      ended.
 
516
     *
 
517
     * @return void
 
518
     * @throws Exception If the process method for the tag cannot be found.
 
519
     */
 
520
    protected function parseTag($tag, $start, $end)
 
521
    {
 
522
        $tokens = array_slice($this->words, ($start + 1), ($end - $start));
 
523
 
 
524
        $allowedTags     = (self::$_tags + $this->getAllowedTags());
 
525
        $allowedTagNames = array_keys($allowedTags);
 
526
        if ($tag === 'comment' || in_array($tag, $allowedTagNames) === true) {
 
527
            $method = 'parse'.$tag;
 
528
            if (method_exists($this, $method) === false) {
 
529
                $error = 'Method '.$method.' must be implemented to process '.$tag.' tags';
 
530
                throw new Exception($error);
 
531
            }
 
532
 
 
533
            $this->previousElement = $this->$method($tokens);
 
534
        } else {
 
535
            $this->previousElement = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, $tag, $this->phpcsFile);
 
536
        }
 
537
 
 
538
        $this->orders[] = $tag;
 
539
 
 
540
        if ($this->previousElement === null || ($this->previousElement instanceof PHP_CodeSniffer_CommentParser_DocElement) === false) {
 
541
            throw new Exception('Parse method must return a DocElement');
 
542
        }
 
543
 
 
544
    }//end parseTag()
 
545
 
 
546
 
 
547
    /**
 
548
     * Returns a list of tags that this comment parser allows for it's comment.
 
549
     *
 
550
     * Each tag should indicate if only one entry of this tag can exist in the
 
551
     * comment by specifying true as the array value, or false if more than one
 
552
     * is allowed. Each tag should ommit the @ symbol. Only tags other than
 
553
     * the standard tags should be returned.
 
554
     *
 
555
     * @return array(string => boolean)
 
556
     */
 
557
    protected abstract function getAllowedTags();
 
558
 
 
559
 
 
560
    /**
 
561
     * Returns the tag orders (index => tagName).
 
562
     *
 
563
     * @return array
 
564
     */
 
565
    public function getTagOrders()
 
566
    {
 
567
        return $this->orders;
 
568
 
 
569
    }//end getTagOrders()
 
570
 
 
571
 
 
572
    /**
 
573
     * Returns the unknown tags.
 
574
     *
 
575
     * @return array
 
576
     */
 
577
    public function getUnknown()
 
578
    {
 
579
        return $this->unknown;
 
580
 
 
581
    }//end getUnknown()
 
582
 
 
583
 
 
584
}//end class
 
585
 
 
586
?>