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
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);
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);
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);
33
* Parses doc comments.
35
* This abstract parser handles the following tags:
38
* <li>The short description and the long description</li>
41
* <li>@deprecated</li>
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<tag_name>
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.
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.
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
70
abstract class PHP_CodeSniffer_CommentParser_AbstractParser
74
* The comment element that appears in the doc comment.
76
* @var PHP_CodeSniffer_CommentParser_CommentElement
78
protected $comment = null;
81
* The string content of the comment.
85
protected $commentString = '';
88
* The file that the comment exists in.
90
* @var PHP_CodeSniffer_File
92
protected $phpcsFile = null;
95
* The word tokens that appear in the comment.
97
* Whitespace tokens also appear in this stack, but are separate tokens
102
protected $words = array();
105
* The previous doc element that was processed.
107
* null if the current element being processed is the first element in the
110
* @var PHP_CodeSniffer_CommentParser_DocElement
112
protected $previousElement = null;
115
* A list of see elements that appear in this doc comment.
117
* @var array(PHP_CodeSniffer_CommentParser_SingleElement)
119
protected $sees = array();
122
* A list of see elements that appear in this doc comment.
124
* @var array(PHP_CodeSniffer_CommentParser_SingleElement)
126
protected $deprecated = null;
129
* A list of see elements that appear in this doc comment.
131
* @var array(PHP_CodeSniffer_CommentParser_SingleElement)
133
protected $links = array();
136
* A element to represent \@since tags.
138
* @var PHP_CodeSniffer_CommentParser_SingleElement
140
protected $since = null;
143
* True if the comment has been parsed.
147
private $_hasParsed = false;
150
* The tags that this class can process.
154
private static $_tags = array(
157
'deprecated' => true,
162
* An array of unknown tags.
166
public $unknown = array();
173
public $orders = array();
177
* Constructs a Doc Comment Parser.
179
* @param string $comment The comment to parse.
180
* @param PHP_CodeSniffer_File $phpcsFile The file that this comment is in.
182
public function __construct($comment, PHP_CodeSniffer_File $phpcsFile)
184
$this->commentString = $comment;
185
$this->phpcsFile = $phpcsFile;
191
* Initiates the parsing of the doc comment.
194
* @throws PHP_CodeSniffer_CommentParser_ParserException If the parser finds a
198
public function parse()
200
if ($this->_hasParsed === false) {
201
$this->_parse($this->commentString);
210
* @param string $comment The doc comment to parse.
215
private function _parse($comment)
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) {
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);
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);
240
$this->_parseWords();
246
* Parses each word within the doc comment.
250
* @throws PHP_CodeSniffer_CommentParser_ParserException If more than the allowed
251
* number of occurances of
254
private function _parseWords()
256
$allowedTags = (self::$_tags + $this->getAllowedTags());
257
$allowedTagNames = array_keys($allowedTags);
258
$foundTags = array();
260
$wordWasEmpty = true;
262
foreach ($this->words as $wordPos => $word) {
264
if (trim($word) !== '') {
265
$wordWasEmpty = false;
268
if ($word{0} === '@') {
270
$tag = substr($word, 1);
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)]) !== '') {
278
if (isset($this->words[($wordPos - 2)]) === false || $this->words[($wordPos - 2)] !== $this->phpcsFile->eolChar) {
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));
289
// There must have been a comment before this tag, so
290
// let's process that.
291
$this->parseTag('comment', 0, ($wordPos - 1));
294
$prevTagPos = $wordPos;
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.
317
if (in_array($tag, $knownTags) === false) {
318
$this->unknown[] = array(
320
'line' => $this->getLine($wordPos),
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));
335
// Process the last tag element.
336
$prevTag = substr($this->words[$prevTagPos], 1);
337
$this->parseTag($prevTag, $prevTagPos, count($this->words));
345
* Returns the line that the token exists on in the doc comment.
347
* @param int $tokenPos The position in the words stack to find the line
352
protected function getLine($tokenPos)
355
for ($i = 0; $i < $tokenPos; $i++) {
356
$newlines += substr_count($this->phpcsFile->eolChar, $this->words[$i]);
365
* Parses see tag element within the doc comment.
367
* @param array(string) $tokens The word tokens that comprise this element.
369
* @return DocElement The element that represents this see comment.
371
protected function parseSee($tokens)
373
$see = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'see', $this->phpcsFile);
374
$this->sees[] = $see;
381
* Parses the comment element that appears at the top of the doc comment.
383
* @param array(string) $tokens The word tokens that comprise tihs element.
385
* @return DocElement The element that represents this comment element.
387
protected function parseComment($tokens)
389
$this->comment = new PHP_CodeSniffer_CommentParser_CommentElement($this->previousElement, $tokens, $this->phpcsFile);
390
return $this->comment;
392
}//end parseComment()
396
* Parses \@deprecated tags.
398
* @param array(string) $tokens The word tokens that comprise tihs element.
400
* @return DocElement The element that represents this deprecated tag.
402
protected function parseDeprecated($tokens)
404
$this->deprecated = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'deprecated', $this->phpcsFile);
405
return $this->deprecated;
407
}//end parseDeprecated()
411
* Parses \@since tags.
413
* @param array(string) $tokens The word tokens that comprise this element.
415
* @return SingleElement The element that represents this since tag.
417
protected function parseSince($tokens)
419
$this->since = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'since', $this->phpcsFile);
426
* Parses \@link tags.
428
* @param array(string) $tokens The word tokens that comprise this element.
430
* @return SingleElement The element that represents this link tag.
432
protected function parseLink($tokens)
434
$link = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, 'link', $this->phpcsFile);
435
$this->links[] = $link;
442
* Returns the see elements that appear in this doc comment.
444
* @return array(SingleElement)
446
public function getSees()
454
* Returns the comment element that appears at the top of this doc comment.
456
* @return CommentElement
458
public function getComment()
460
return $this->comment;
466
* Returns the link elements found in this comment.
468
* Returns an empty array if no links are found in the comment.
470
* @return array(SingleElement)
472
public function getLinks()
480
* Returns the deprecated element found in this comment.
482
* Returns null if no element exists in the comment.
484
* @return SingleElement
486
public function getDeprecated()
488
return $this->deprecated;
490
}//end getDeprecated()
494
* Returns the since element found in this comment.
496
* Returns null if no element exists in the comment.
498
* @return SingleElement
500
public function getSince()
508
* Parses the specified tag.
510
* @param string $tag The tag name to parse (omitting the @ sybmol from
512
* @param int $start The position in the word tokens where this element
514
* @param int $end The position in the word tokens where this element
518
* @throws Exception If the process method for the tag cannot be found.
520
protected function parseTag($tag, $start, $end)
522
$tokens = array_slice($this->words, ($start + 1), ($end - $start));
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);
533
$this->previousElement = $this->$method($tokens);
535
$this->previousElement = new PHP_CodeSniffer_CommentParser_SingleElement($this->previousElement, $tokens, $tag, $this->phpcsFile);
538
$this->orders[] = $tag;
540
if ($this->previousElement === null || ($this->previousElement instanceof PHP_CodeSniffer_CommentParser_DocElement) === false) {
541
throw new Exception('Parse method must return a DocElement');
548
* Returns a list of tags that this comment parser allows for it's comment.
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.
555
* @return array(string => boolean)
557
protected abstract function getAllowedTags();
561
* Returns the tag orders (index => tagName).
565
public function getTagOrders()
567
return $this->orders;
569
}//end getTagOrders()
573
* Returns the unknown tags.
577
public function getUnknown()
579
return $this->unknown;