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

/vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php

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