PHPCPDTask.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <?php
  2. /**
  3. * $Id: PHPCPDTask.php 905 2010-10-05 16:28:03Z mrook $
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information please see
  19. * <http://phing.info>.
  20. */
  21. require_once 'phing/Task.php';
  22. /**
  23. * Runs PHP Copy & Paste Detector. Checking PHP files for duplicated code.
  24. * Refactored original PhpCpdTask provided by
  25. * Timo Haberkern <timo.haberkern@fantastic-bits.de>
  26. *
  27. * @package phing.tasks.ext.phpcpd
  28. * @author Benjamin Schultz <bschultz@proqrent.de>
  29. * @version $Id: PHPCPDTask.php 905 2010-10-05 16:28:03Z mrook $
  30. */
  31. class PHPCPDTask extends Task
  32. {
  33. /**
  34. * A php source code filename or directory
  35. *
  36. * @var PhingFile
  37. */
  38. protected $_file = null;
  39. /**
  40. * All fileset objects assigned to this task
  41. *
  42. * @var array<FileSet>
  43. */
  44. protected $_filesets = array();
  45. /**
  46. * Minimum number of identical lines.
  47. *
  48. * @var integer
  49. */
  50. protected $_minLines = 5;
  51. /**
  52. * Minimum number of identical tokens.
  53. *
  54. * @var integer
  55. */
  56. protected $_minTokens = 70;
  57. /**
  58. * List of valid file extensions for analyzed files.
  59. *
  60. * @var array
  61. */
  62. protected $_allowedFileExtensions = array('php');
  63. /**
  64. * List of exclude directory patterns.
  65. *
  66. * @var array
  67. */
  68. protected $_ignorePatterns = array('.git', '.svn', 'CVS', '.bzr', '.hg');
  69. /**
  70. * The format for the report
  71. *
  72. * @var string
  73. */
  74. protected $_format = 'default';
  75. /**
  76. * Formatter elements.
  77. *
  78. * @var array<PHPCPDFormatterElement>
  79. */
  80. protected $_formatters = array();
  81. /**
  82. * Load the necessary environment for running PHPCPD.
  83. *
  84. * @throws BuildException - if the phpcpd classes can't be loaded.
  85. */
  86. public function init()
  87. {
  88. /**
  89. * Determine PHPCPD installation
  90. */
  91. @include_once 'PHPCPD/TextUI/Command.php';
  92. if (! class_exists('PHPCPD_TextUI_Command')) {
  93. throw new BuildException(
  94. 'PHPCPDTask depends on PHPCPD being installed '
  95. . 'and on include_path.',
  96. $this->getLocation()
  97. );
  98. }
  99. // Other dependencies that should only be loaded
  100. // when class is actually used
  101. require_once 'phing/tasks/ext/phpcpd/PHPCPDFormatterElement.php';
  102. require_once 'PHPCPD/Detector.php';
  103. }
  104. /**
  105. * Set the input source file or directory.
  106. *
  107. * @param PhingFile $file The input source file or directory.
  108. *
  109. * @return void
  110. */
  111. public function setFile(PhingFile $file)
  112. {
  113. $this->_file = $file;
  114. }
  115. /**
  116. * Nested creator, adds a set of files (nested fileset attribute).
  117. *
  118. * @return FileSet The created fileset object
  119. */
  120. public function createFileSet()
  121. {
  122. $num = array_push($this->_filesets, new FileSet());
  123. return $this->_filesets[$num-1];
  124. }
  125. /**
  126. * Sets the minimum rule priority.
  127. *
  128. * @param integer $minimumPriority Minimum rule priority.
  129. *
  130. * @return void
  131. */
  132. public function setMinimumPriority($minimumPriority)
  133. {
  134. $this->_minimumPriority = $minimumPriority;
  135. }
  136. /**
  137. * Sets the minimum number of identical lines (default: 5).
  138. *
  139. * @param integer $minLines Minimum number of identical lines
  140. *
  141. * @return void
  142. */
  143. public function setMinLines($minLines)
  144. {
  145. $this->_minLines = $minLines;
  146. }
  147. /**
  148. * Sets the minimum number of identical tokens (default: 70).
  149. *
  150. * @param integer $minTokens Minimum number of identical tokens
  151. */
  152. public function setMinTokens($minTokens)
  153. {
  154. $this->_minTokens = $minTokens;
  155. }
  156. /**
  157. * Sets a list of filename extensions for valid php source code files.
  158. *
  159. * @param string $fileExtensions List of valid file extensions.
  160. *
  161. * @return void
  162. */
  163. public function setAllowedFileExtensions($fileExtensions)
  164. {
  165. $this->_allowedFileExtensions = array();
  166. $token = ' ,;';
  167. $ext = strtok($fileExtensions, $token);
  168. while ($ext !== false) {
  169. $this->_allowedFileExtensions[] = $ext;
  170. $ext = strtok($token);
  171. }
  172. }
  173. /**
  174. * Sets a list of ignore patterns that is used to exclude directories from
  175. * the source analysis.
  176. *
  177. * @param string $ignorePatterns List of ignore patterns.
  178. *
  179. * @return void
  180. */
  181. public function setIgnorePatterns($ignorePatterns)
  182. {
  183. $this->_ignorePatterns = array();
  184. $token = ' ,;';
  185. $pattern = strtok($ignorePatterns, $token);
  186. while ($pattern !== false) {
  187. $this->_ignorePatterns[] = $pattern;
  188. $pattern = strtok($token);
  189. }
  190. }
  191. /**
  192. * Sets the output format
  193. *
  194. * @param string $format Format of the report
  195. */
  196. public function setFormat($format)
  197. {
  198. $this->_format = $format;
  199. }
  200. /**
  201. * Create object for nested formatter element.
  202. *
  203. * @return PHPCPDFormatterElement
  204. */
  205. public function createFormatter()
  206. {
  207. $num = array_push(
  208. $this->_formatters,
  209. new PHPCPDFormatterElement($this)
  210. );
  211. return $this->_formatters[$num-1];
  212. }
  213. /**
  214. * Executes PHPCPD against PhingFile or a FileSet
  215. *
  216. * @return void
  217. */
  218. public function main()
  219. {
  220. if (!isset($this->_file) and count($this->_filesets) == 0) {
  221. throw new BuildException(
  222. "Missing either a nested fileset or attribute 'file' set"
  223. );
  224. }
  225. if (count($this->_formatters) == 0) {
  226. // turn legacy format attribute into formatter
  227. $fmt = new PHPCPDFormatterElement($this);
  228. $fmt->setType($this->format);
  229. $fmt->setUseFile(false);
  230. $this->_formatters[] = $fmt;
  231. }
  232. $this->validateFormatters();
  233. $filesToParse = array();
  234. if ($this->_file instanceof PhingFile) {
  235. $filesToParse[] = $this->_file->getPath();
  236. } else {
  237. // append any files in filesets
  238. foreach ($this->_filesets as $fs) {
  239. $files = $fs->getDirectoryScanner($this->project)
  240. ->getIncludedFiles();
  241. foreach ($files as $filename) {
  242. $f = new PhingFile($fs->getDir($this->project), $filename);
  243. $filesToParse[] = $f->getAbsolutePath();
  244. }
  245. }
  246. }
  247. $this->log('Processing files...');
  248. $detector = new PHPCPD_Detector();
  249. $clones = $detector->copyPasteDetection(
  250. $filesToParse,
  251. $this->_minLines,
  252. $this->_minTokens
  253. );
  254. $this->log('Finished copy/paste detection');
  255. foreach ($this->_formatters as $fe) {
  256. $formatter = $fe->getFormatter();
  257. $formatter->processClones(
  258. $clones,
  259. $fe->getOutfile(),
  260. $this->project
  261. );
  262. }
  263. }
  264. /**
  265. * Validates the available formatters
  266. *
  267. * @throws BuildException
  268. * @return void
  269. */
  270. protected function validateFormatters()
  271. {
  272. foreach ($this->_formatters as $fe) {
  273. if ($fe->getType() == '') {
  274. throw new BuildException(
  275. "Formatter missing required 'type' attribute."
  276. );
  277. }
  278. if ($fe->getUsefile() && $fe->getOutfile() === null) {
  279. throw new BuildException(
  280. "Formatter requires 'outfile' attribute "
  281. . "when 'useFile' is true."
  282. );
  283. }
  284. }
  285. }
  286. }