PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/build/phpcs/Joomla/Sniffs/Commenting/FileCommentSniff.php

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