PageRenderTime 27ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/DocBlockAnalyzer.php

http://github.com/cakephp/api_generator
PHP | 430 lines | 217 code | 10 blank | 203 comment | 27 complexity | 99f121bf9cf0e7fde86b06201393f07f MD5 | raw file
  1. <?php
  2. /**
  3. * Docs analyzer - Uses a simple extensible rules system
  4. * to analzye doc block arrays and evaluate their 'goodness'
  5. *
  6. *
  7. * PHP 5
  8. *
  9. * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
  10. * Copyright 2006-2008, Cake Software Foundation, Inc.
  11. * 1785 E. Sahara Avenue, Suite 490-204
  12. * Las Vegas, Nevada 89104
  13. *
  14. * Licensed under The MIT License
  15. * Redistributions of files must retain the above copyright notice.
  16. *
  17. * @filesource
  18. * @copyright Copyright 2006-2008, Cake Software Foundation, Inc.
  19. * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
  20. * @package api_generator
  21. * @subpackage api_generator.vendors
  22. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  23. */
  24. class DocBlockAnalyzer {
  25. /**
  26. * Constructed Rules objects.
  27. *
  28. * @var array
  29. **/
  30. public $rules = array();
  31. /**
  32. * Rules classes that are going to be used
  33. *
  34. * @var array
  35. **/
  36. protected $_ruleNames = array();
  37. /**
  38. * Final score for analyzation
  39. *
  40. * @var int
  41. **/
  42. public $_finalScore = 0;
  43. /**
  44. * Total elements counted this run
  45. *
  46. * @var int
  47. **/
  48. protected $_totalElements = 0;
  49. /**
  50. * Running score for the current property
  51. *
  52. * @var int
  53. **/
  54. protected $_contentScore = 0;
  55. /**
  56. * Running total of all objects in the current property
  57. *
  58. * @var int
  59. **/
  60. protected $_contentObjectCount = 0;
  61. /**
  62. * Default rules
  63. *
  64. * @var string
  65. **/
  66. protected $_defaultRules = array(
  67. 'MissingLink', 'Empty', 'MissingParams', 'IncompleteTags'
  68. );
  69. /**
  70. * Current reflection objects being inspected.
  71. *
  72. * @var object
  73. **/
  74. protected $_reflection;
  75. /**
  76. * Constructor
  77. *
  78. * @param array $rules Names of DocBlockRule Classes you want to use.
  79. * @return void
  80. **/
  81. public function __construct($rules = array()) {
  82. if (empty($rules)) {
  83. $rules = $this->_defaultRules;
  84. }
  85. $this->_ruleNames = $rules;
  86. $this->_buildRules();
  87. }
  88. /**
  89. * Build the rules objects
  90. *
  91. * @return void
  92. **/
  93. protected function _buildRules() {
  94. foreach ($this->_ruleNames as $rule) {
  95. $className = $rule . 'DocBlockRule';
  96. if (!class_exists($className, false)) {
  97. trigger_error('Missing Rule Class ' . $className, E_USER_WARNING);
  98. continue;
  99. }
  100. $ruleObj = new $className();
  101. if ($ruleObj instanceof DocBlockRule) {
  102. $this->rules[$rule] = $ruleObj;
  103. }
  104. }
  105. }
  106. /**
  107. * Get the Descriptions and Names of the Rules being used.
  108. *
  109. * @return array
  110. **/
  111. public function getRules() {
  112. $out = array();
  113. foreach ($this->rules as $name => $rule) {
  114. $out[$name] = $rule->description;
  115. }
  116. return $out;
  117. }
  118. /**
  119. * Set the source for the Analyzation. Expects either a ClassDocumentor or FunctionDocumentor instance.
  120. *
  121. * @param object $reflector Reflection Object to be inspected.
  122. * @return boolean Success of setting source.
  123. * @throws RuntimeException
  124. **/
  125. public function setSource($reflector) {
  126. if (!($reflector instanceof ClassDocumentor) && !($reflector instanceof FunctionDocumentor)) {
  127. throw new RuntimeException(sprintf(
  128. 'DocBlockAnalyzer::setSource() - Expects an instance of ClassDocumentor or FunctionDocumentor, %s was given',
  129. get_class($reflector)
  130. ));
  131. return false;
  132. }
  133. $reflector->getAll();
  134. $this->_reflection = $reflector;
  135. return true;
  136. }
  137. /**
  138. * Analyze a Reflection object.
  139. *
  140. * Options
  141. * - includeParent Whether or not you want to include parent methods/properties. (default to false)
  142. *
  143. * @param object $reflector Reflection Object to be inspected (optional)
  144. * @param array $options Array of options to use.
  145. * @return array Array of scoring information
  146. **/
  147. public function analyze($reflector = null, $options = array()) {
  148. $this->reset();
  149. $options = array_merge(array('includeParent' => false), $options);
  150. if ($reflector !== null) {
  151. $valid = $this->setSource($reflector);
  152. if (!$valid) {
  153. return array();
  154. }
  155. }
  156. $lookAt = array('classInfo', 'properties', 'methods');
  157. $results = array();
  158. $totalElements = $finalScore = 0;
  159. foreach ($lookAt as $property) {
  160. $content = $this->_reflection->{$property};
  161. if (!is_array($content)) {
  162. continue;
  163. }
  164. $this->resetCounts();
  165. $results[$property] = array();
  166. if ($property == 'classInfo') {
  167. $result = $this->_scoreElement($content);
  168. $results[$property] = array(
  169. 'subject' => $property,
  170. 'scores' => $result['scores'],
  171. 'totalScore' => $result['totalScore']
  172. );
  173. } else {
  174. foreach ($content as $element) {
  175. $isParent = (isset($element['declaredInClass']) &&
  176. $element['declaredInClass'] != $this->_reflection->getName()
  177. );
  178. if (!$options['includeParent'] && $isParent) {
  179. continue;
  180. }
  181. $results[$property][] = $this->_scoreElement($element);
  182. }
  183. }
  184. $this->_calculateSectionTotals($results, $property);
  185. }
  186. $results['finalScore'] = ($this->_finalScore / $this->_totalElements);
  187. return $results;
  188. }
  189. /**
  190. * Reset the docblock analyzer
  191. *
  192. * @return boolean true;
  193. **/
  194. public function reset() {
  195. $this->_finalScore = 0;
  196. $this->_totalElements = 0;
  197. $this->resetCounts();
  198. return true;
  199. }
  200. /**
  201. * Reset the internal counters.
  202. *
  203. * @return boolean true
  204. **/
  205. public function resetCounts() {
  206. $this->_contentScore = 0;
  207. $this->_contentObjectCount = 0;
  208. return true;
  209. }
  210. /**
  211. * Calculate the section totals for a specific property.
  212. * Modify the results array and return via reference
  213. *
  214. * @param array $results Array of inprogress results
  215. * @param string $property Name of property being looked at
  216. * @access public
  217. * @return array
  218. **/
  219. protected function _calculateSectionTotals(&$results, $property) {
  220. $results['sectionTotals'][$property] = array(
  221. 'elementCount' => 0,
  222. 'score' => 0,
  223. 'average' => 0,
  224. );
  225. if ($this->_contentObjectCount != 0) {
  226. $results['sectionTotals'][$property] = array(
  227. 'elementCount' => $this->_contentObjectCount,
  228. 'score' => $this->_contentScore,
  229. 'average' => $this->_contentScore / $this->_contentObjectCount,
  230. );
  231. }
  232. $this->_totalElements += $this->_contentObjectCount;
  233. $this->_finalScore += $this->_contentScore;
  234. }
  235. /**
  236. * Score an element and generate results.
  237. *
  238. * @return array
  239. **/
  240. protected function _scoreElement($element) {
  241. $scores = $this->_runRules($element);
  242. $result = array(
  243. 'subject' => $element['name'],
  244. 'scores' => $scores,
  245. 'totalScore' => $scores['totalScore'],
  246. );
  247. unset($result['scores']['totalScore']);
  248. $this->_contentObjectCount++;
  249. $this->_contentScore += $result['totalScore'];
  250. return $result;
  251. }
  252. /**
  253. * _runRules against an element set
  254. *
  255. * @return array
  256. **/
  257. protected function _runRules($subject) {
  258. $results = array();
  259. $totalScore = 0;
  260. foreach ($this->rules as $name => $rule) {
  261. $rule->setSubject($subject);
  262. $score = $rule->score();
  263. if ($score < 1) {
  264. $results[] = array(
  265. 'rule' => $name,
  266. 'score' => $score,
  267. 'description' => $rule->description
  268. );
  269. }
  270. $totalScore += $score;
  271. }
  272. $results['totalScore'] = ($totalScore / count($this->rules));
  273. return $results;
  274. }
  275. }
  276. /**
  277. * Abstract Base Class for DocBlock Rules
  278. *
  279. * @package api_generator.vendors.doc_block_analyzer
  280. **/
  281. abstract class DocBlockRule {
  282. /**
  283. * Description of the rule
  284. *
  285. * @var string
  286. **/
  287. public $description = '';
  288. /**
  289. * setSubject - Set the array of doc block info to be looked at.
  290. *
  291. * @param array $docArray Array of parsed docblock info to evaluate.
  292. * @return void
  293. **/
  294. public function setSubject($docArray) {
  295. $this->_subject = $docArray;
  296. }
  297. /**
  298. * Score - Run the scoring system for this rule
  299. *
  300. * @access public
  301. * @return float Returns the float value (between 0 - 1) of the rule.
  302. **/
  303. abstract public function score();
  304. }
  305. /**
  306. * Check for missing @link tags
  307. *
  308. * @package default
  309. **/
  310. class MissingLinkDocBlockRule extends DocBlockRule {
  311. public $description = 'Check for a missing @link tag';
  312. /**
  313. * Check for a @link tag in the tags array.
  314. *
  315. * @return float
  316. **/
  317. public function score() {
  318. if (empty($this->_subject['comment']['tags']['link'])) {
  319. return 0;
  320. }
  321. return 1;
  322. }
  323. }
  324. /**
  325. * Check that the doc block has a description string.
  326. *
  327. **/
  328. class EmptyDocBlockRule extends DocBlockRule {
  329. /**
  330. * description
  331. *
  332. * @var string
  333. **/
  334. public $description = 'Check for empty doc string';
  335. /**
  336. * score method
  337. *
  338. * @return float
  339. **/
  340. public function score() {
  341. if (empty($this->_subject['comment']['description'])) {
  342. return 0;
  343. }
  344. if (strlen($this->_subject['comment']['description']) > (2 * $this->_subject['name'])) {
  345. return 1;
  346. }
  347. return 0.5;
  348. }
  349. }
  350. /**
  351. * Check that every argument has all the param tags filled out.
  352. *
  353. **/
  354. class MissingParamsDocBlockRule extends DocBlockRule {
  355. /**
  356. * description
  357. *
  358. * @var string
  359. **/
  360. public $description = 'Check for any empty @param tags';
  361. /**
  362. * score method
  363. *
  364. * @return float
  365. **/
  366. public function score() {
  367. if (empty($this->_subject['args'])) {
  368. return 1;
  369. }
  370. $good = 0;
  371. $totalArgs = count($this->_subject['args']);
  372. foreach ($this->_subject['args'] as $arg) {
  373. if (!empty($arg['comment'])) {
  374. $good += 0.5;
  375. }
  376. if (!empty($arg['type'])) {
  377. $good += 0.5;
  378. }
  379. }
  380. return $good / $totalArgs;
  381. }
  382. }
  383. /**
  384. * Check that tags requiring a value have them.
  385. *
  386. **/
  387. class IncompleteTagsDocBlockRule extends DocBlockRule {
  388. /**
  389. * description
  390. *
  391. * @var string
  392. **/
  393. public $description = 'Check for incomplete tag strings.';
  394. /**
  395. * tags that don't require text
  396. *
  397. * @var array
  398. **/
  399. protected $_singleTags = array(
  400. 'deprecated', 'abstract', 'ignore', 'final',
  401. );
  402. /**
  403. * score method
  404. *
  405. * @return float
  406. **/
  407. public function score() {
  408. if (empty($this->_subject['comment']['tags'])) {
  409. return 0;
  410. }
  411. $total = count($this->_subject['comment']['tags']);
  412. $good = 0;
  413. foreach ($this->_subject['comment']['tags'] as $tag => $value) {
  414. if (!empty($value) || !in_array($tag, $this->_singleTags)) {
  415. $good++;
  416. }
  417. }
  418. return $good / $total;
  419. }
  420. }