From 8f5d855193e6b67cd042ac61c8d9553359d6b8d5 Mon Sep 17 00:00:00 2001 From: James Inman Date: Wed, 18 Feb 2015 14:07:26 +0000 Subject: [PATCH] Adding Technical Debt plugin. --- PHPCI/Languages/lang.en.php | 1 + PHPCI/Plugin/TechnicalDebt.php | 183 ++++++++++++++++++ .../assets/js/build-plugins/technical_debt.js | 79 ++++++++ 3 files changed, 263 insertions(+) create mode 100755 PHPCI/Plugin/TechnicalDebt.php create mode 100755 public/assets/js/build-plugins/technical_debt.js diff --git a/PHPCI/Languages/lang.en.php b/PHPCI/Languages/lang.en.php index 9718e442..5f21d3dc 100644 --- a/PHPCI/Languages/lang.en.php +++ b/PHPCI/Languages/lang.en.php @@ -183,6 +183,7 @@ PHPCI', 'phpmd' => 'PHP Mess Detector', 'phpspec' => 'PHP Spec', 'phpunit' => 'PHP Unit', + 'technical_debt' => 'Technical Debt', 'file' => 'File', 'line' => 'Line', diff --git a/PHPCI/Plugin/TechnicalDebt.php b/PHPCI/Plugin/TechnicalDebt.php new file mode 100755 index 00000000..4d82dd25 --- /dev/null +++ b/PHPCI/Plugin/TechnicalDebt.php @@ -0,0 +1,183 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin +{ + /** + * @var \PHPCI\Builder + */ + protected $phpci; + + /** + * @var array + */ + protected $suffixes; + + /** + * @var string + */ + protected $directory; + + /** + * @var int + */ + protected $allowed_errors; + + /** + * @var int + */ + protected $allowed_warnings; + + /** + * @var string, based on the assumption the root may not hold the code to be + * tested, extends the base path + */ + protected $path; + + /** + * @var array - paths to ignore + */ + protected $ignore; + + /** + * @var array - terms to search for + */ + protected $searches; + + public static function canExecute($stage, Builder $builder, Build $build) + { + if ($stage == 'test') { + return true; + } + + return false; + } + + /** + * @param \PHPCI\Builder $phpci + * @param \PHPCI\Model\Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->suffixes = array('php'); + $this->directory = $phpci->buildPath; + $this->path = ''; + $this->ignore = $this->phpci->ignore; + $this->allowed_warnings = 0; + $this->allowed_errors = 0; + $this->searches = array('TODO', 'FIXME', 'TO DO', 'FIX ME'); + + if (isset($options['searches']) && is_array($options['searches'])) { + $this->searches = $options['searches']; + } + + if (isset($options['zero_config']) && $options['zero_config']) { + $this->allowed_warnings = -1; + $this->allowed_errors = -1; + } + } + + protected function setOptions($options) + { + foreach (array('directory', 'path', 'ignore', 'allowed_warnings', 'allowed_errors') as $key) { + if (array_key_exists($key, $options)) { + $this->{$key} = $options[$key]; + } + } + } + + /** + * Runs in a specified directory, to a specified standard. + */ + public function execute() + { + $this->phpci->logExecOutput(false); + + $ignores = $this->ignore; + $ignores[] = 'phpci.yml'; + + $dirIterator = new \RecursiveDirectoryIterator($this->directory); + $iterator = new \RecursiveIteratorIterator($dirIterator, \RecursiveIteratorIterator::SELF_FIRST); + $files = []; + + foreach ($iterator as $file) { + $filePath = $file->getRealPath(); + $skipFile = false; + foreach ($ignores as $ignore) { + if (stripos($filePath, $ignore) !== false) { + $skipFile = true; + break; + } + } + + // Ignore hidden files, else .git, .sass_cache, etc. all get looped over + if (stripos($filePath, '/.') !== false) { + $skipFile = true; + } + + if ($skipFile == false) { + $files[] = $file->getRealPath(); + } + } + + $files = array_filter(array_unique($files)); + $errorCount = 0; + $data = array(); + + foreach ($files as $file) { + foreach ($this->searches as $search) { + $fileContent = file_get_contents($file); + $allLines = explode(PHP_EOL, $fileContent); + $beforeString = strstr($fileContent, $search, true); + + if (false !== $beforeString) { + $lines = explode(PHP_EOL, $beforeString); + $lineNumber = count($lines); + $content = trim($allLines[$lineNumber - 1]); + + $errorCount++; + $this->phpci->log("Found $search on line $lineNumber of $file:\n$content"); + $data[] = array( + 'file' => str_replace($this->directory, '', $file), + 'line' => $lineNumber, + 'message' => $content + ); + } + } + } + + $this->phpci->log("Found $errorCount instances of " . implode(', ', $this->searches)); + + $this->build->storeMeta('technical_debt-warnings', $errorCount); + $this->build->storeMeta('technical_debt-data', $data); + + if ($this->allowed_errors != -1 && $errorCount > $this->allowed_errors) { + $success = false; + } + + return $success; + } +} + diff --git a/public/assets/js/build-plugins/technical_debt.js b/public/assets/js/build-plugins/technical_debt.js new file mode 100755 index 00000000..a187401d --- /dev/null +++ b/public/assets/js/build-plugins/technical_debt.js @@ -0,0 +1,79 @@ +var TechnicalDebtPlugin = ActiveBuild.UiPlugin.extend({ + id: 'build-technical_debt', + css: 'col-lg-6 col-md-12 col-sm-12 col-xs-12', + title: Lang.get('technical_debt'), + lastData: null, + box: true, + rendered: false, + + register: function() { + var self = this; + var query = ActiveBuild.registerQuery('technical_debt-data', -1, {key: 'technical_debt-data'}) + + $(window).on('technical_debt-data', function(data) { + self.onUpdate(data); + }); + + $(window).on('build-updated', function() { + if (!self.rendered) { + query(); + } + }); + }, + + render: function() { + return $('
' + + '' + + '' + + ' ' + + ' ' + + ' ' + + '' + + '
'+Lang.get('file')+''+Lang.get('line')+''+Lang.get('message')+'
'); + }, + + onUpdate: function(e) { + if (!e.queryData) { + $('#build-technical_debt').hide(); + return; + } + + this.rendered = true; + this.lastData = e.queryData; + + var errors = this.lastData[0].meta_value; + var tbody = $('#technical_debt-data tbody'); + tbody.empty(); + + if (errors.length == 0) { + $('#build-technical_debt').hide(); + return; + } + + for (var i in errors) { + var file = errors[i].file; + + if (ActiveBuild.fileLinkTemplate) { + var fileLink = ActiveBuild.fileLinkTemplate.replace('{FILE}', file); + fileLink = fileLink.replace('{LINE}', errors[i].line); + + file = '' + file + ''; + } + + var row = $('' + + ''+file+'' + + ''+errors[i].line+'' + + ''+errors[i].message+''); + + if (errors[i].type == 'ERROR') { + row.addClass('danger'); + } + + tbody.append(row); + } + + $('#build-technical_debt').show(); + } +}); + +ActiveBuild.registerPlugin(new TechnicalDebtPlugin());