PageRenderTime 61ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/squizlabs/php_codesniffer/CodeSniffer/CommentParser/AbstractParser.php

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