├── .gitignore ├── Composer └── ScriptHandler.php ├── Config ├── Phpcs │ ├── laravel.xml │ └── ruleset.xml ├── Phpmd │ ├── laravel.xml │ └── ruleset.xml └── tools.yml ├── File └── FileLocator.php ├── LICENSE ├── README.md ├── Tool ├── Phpcpd.php ├── Phpcs.php ├── Phpdcd.php ├── Phpmd.php └── Tool.php └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /Composer/ScriptHandler.php: -------------------------------------------------------------------------------- 1 | 'Config/tools.yml', 17 | 'file' => [] 18 | ]; 19 | 20 | /** 21 | * @param $event CommandEvent A instance 22 | */ 23 | public static function checkThresholds(CommandEvent $event) 24 | { 25 | $options = static::loadConfiguration($event); 26 | $tools = static::getTools($options); 27 | 28 | foreach ($tools as $toolName => $toolConfiguration) { 29 | /** @var \piotrpasich\CodeQualityThreshold\Tool\Tool $tool */ 30 | $tool = new $toolConfiguration['class']($toolConfiguration); 31 | 32 | if (! $tool instanceof Tool) { 33 | throw new \RuntimeException('The tool should implement Tool abstract class'); 34 | } 35 | 36 | $process = static::runCommand($event, $tool, $tool->composeCommand()); 37 | 38 | if (!$process->isSuccessful()) { 39 | throw new \RuntimeException(sprintf("[%s] %s: %s", $toolName, $process->getErrorOutput(), $process->getOutput())); 40 | } 41 | 42 | if ((int)$process->getOutput() > $tool->getThreshold()) { 43 | static::runCommand($event, $tool, $tool->composeReportCommand()); 44 | 45 | throw new \RuntimeException(sprintf("[%s] %s: %s", $toolName, $tool->getErrorMessage(), $process->getOutput())); 46 | } 47 | 48 | $event->getIO()->write("[{$toolName}] {$tool->getSuccessMessage()}"); 49 | } 50 | } 51 | 52 | protected static function loadConfiguration(CommandEvent $event) 53 | { 54 | $composerConfiguration = $event->getComposer()->getPackage()->getExtra(); 55 | 56 | return static::getOptions(isset($composerConfiguration['cqt-parameters']) ? $composerConfiguration['cqt-parameters'] : []); 57 | } 58 | 59 | protected static function runCommand(CommandEvent $event, Tool $tool, $command) 60 | { 61 | if ($event->getIO()->isVerbose()) { 62 | $event->getIO()->write($command); 63 | } 64 | 65 | $process = new Process($command, getcwd(), null, null, $tool->getTimeout()); 66 | $process->run(function ($type, $buffer) use ($event) { 67 | if ($event->getIO()->isVerbose()) { 68 | $event->getIO()->write($buffer, false); 69 | } 70 | }); 71 | 72 | return $process; 73 | } 74 | 75 | protected static function getOptions(array $options) 76 | { 77 | return array_merge(static::$defaultOptions, $options); 78 | } 79 | 80 | protected static function getTools(array $options) 81 | { 82 | $tools = static::loadFile($options['default-file']); 83 | 84 | if (isset($options['file']) && is_array($options['file'])) { 85 | foreach ($options['file'] as $file) { 86 | $tools = array_merge($tools, static::loadFile($file)); 87 | } 88 | } else if (isset($options['file']) && is_string($options['file'])) { 89 | $tools = array_merge($tools, static::loadFile($options['file'])); 90 | } 91 | 92 | return $tools; 93 | } 94 | 95 | protected static function loadFile($filePath) 96 | { 97 | $filePath = (new FileLocator())->locateFile($filePath); 98 | 99 | return (new Parser())->parse(file_get_contents($filePath)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Config/Phpcs/laravel.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom Coding Standards 4 | 5 | 6 | 7 | 0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | error 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | */.git/* 74 | -------------------------------------------------------------------------------- /Config/Phpcs/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | The Symfony2 coding standard. 4 | 5 | 6 | */Resources/* 7 | 8 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 0 66 | 67 | 68 | 69 | 70 | 0 71 | 72 | 73 | 74 | 75 | 0 76 | 77 | 78 | 0 79 | 80 | -------------------------------------------------------------------------------- /Config/Phpmd/laravel.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | PHPMD ruleset 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Config/Phpmd/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | Piotr Pasich PHP MD Code Quality Threshold Ruleset 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Config/tools.yml: -------------------------------------------------------------------------------- 1 | phpmd: 2 | class: piotrpasich\CodeQualityThreshold\Tool\Phpmd 3 | 4 | phpcs: 5 | class: piotrpasich\CodeQualityThreshold\Tool\Phpcs 6 | 7 | phpcpd: 8 | class: piotrpasich\CodeQualityThreshold\Tool\Phpcpd 9 | 10 | phpdcd: 11 | class: piotrpasich\CodeQualityThreshold\Tool\Phpdcd 12 | -------------------------------------------------------------------------------- /File/FileLocator.php: -------------------------------------------------------------------------------- 1 | locate($filePath); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Piotr Pasich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Quality Threshold 2 | 3 | This extension is purposed to run every necessary tool to check the code against violation from composer 4 | (ex. tools - PHP Mess Detector, PHP Code Sniffer, PHP Dead Code Detector, PHP Copy Paste). 5 | 6 | ## Installation 7 | 8 | composer require piotrpasich/code-quality-threshold:dev-master --dev 9 | 10 | ## Configuration 11 | 12 | To your composer.json file you might add 13 | 14 | "scripts": { 15 | "quality-test": [ 16 | "piotrpasich\\CodeQualityThreshold\\Composer\\ScriptHandler::checkThresholds" 17 | ] 18 | }, 19 | 20 | And execute the command 21 | 22 | composer quality-test 23 | 24 | You can also add this script to already existing scripts like `post-update-cmd`. 25 | 26 | ## Reports 27 | 28 | If the test throws an exception you can always ask for the report: 29 | 30 | composer quality-test -v 31 | 32 | ## Advanced configuration 33 | 34 | This plugin checks the app folder by default, but you can always change this behavior by creating yml file. This might be 35 | placed wherever you want, ex. app/Config/cqt.yml 36 | 37 | Then you need to add to your composer.json file the extra option: 38 | 39 | "extra": { 40 | "cqt-parameters": { 41 | "file": "app/Config/cqt.yml" 42 | } 43 | } 44 | 45 | The example yml file might look like: 46 | 47 | phpmd: 48 | class: piotrpasich\CodeQualityThreshold\Tool\Phpmd 49 | options: 50 | threshold: 42 51 | directory: src 52 | rules: Config/Phpmd/ruleset.xml 53 | 54 | phpcs: 55 | class: piotrpasich\CodeQualityThreshold\Tool\Phpcs 56 | options: 57 | threshold: 42 58 | rules: Config/Phpcs/ruleset.xml 59 | directory: src 60 | 61 | phpcpd: 62 | class: piotrpasich\CodeQualityThreshold\Tool\Phpcpd 63 | options: 64 | threshold: 42 65 | directory: src 66 | 67 | Phpdcd: 68 | class: piotrpasich\CodeQualityThreshold\Tool\Phpdcd 69 | options: 70 | directory: src 71 | threshold: 21 72 | 73 | ## Adding new tools 74 | 75 | To add new tool you need to specify new record in yml file (look at the Advanced configuration chapter) and create a class 76 | extending Tool abstract class from the plugin. 77 | -------------------------------------------------------------------------------- /Tool/Phpcpd.php: -------------------------------------------------------------------------------- 1 | 'app', 13 | 'command' => 'php vendor/sebastian/phpcpd/composer/bin/phpcpd', 14 | 'threshold' => 0, 15 | 'timeout' => 1200 16 | ]; 17 | 18 | public function composeCommand() 19 | { 20 | return "{$this->composeReportCommand()} | egrep 'Found [0-9]+ exact clones' -o | egrep [0-9]+ -o || echo 0"; 21 | } 22 | 23 | public function composeReportCommand() 24 | { 25 | return "{$this->configuration['command']} {$this->configuration['directory']}"; 26 | } 27 | 28 | public function getThreshold() 29 | { 30 | return isset($this->configuration['threshold']) ? $this->configuration['threshold'] : 0; 31 | } 32 | 33 | public function getErrorMessage() 34 | { 35 | return 'The PHP Copy Paste Detector threshold is exceeded'; 36 | } 37 | 38 | public function getSuccessMessage() 39 | { 40 | return 'The PHP Copy Paste Detector threshold passed'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tool/Phpcs.php: -------------------------------------------------------------------------------- 1 | 'app', 14 | 'command' => 'php vendor/squizlabs/php_codesniffer/scripts/phpcs', 15 | 'rules' => 'Config/Phpcs/ruleset.xml', 16 | 'threshold' => 0, 17 | 'timeout' => 1200 18 | ]; 19 | 20 | public function composeCommand() 21 | { 22 | 23 | return "{$this->composeReportCommand()} --report=csv | tail -n +2 | wc -l"; 24 | } 25 | 26 | public function composeReportCommand() 27 | { 28 | $rules = (new FileLocator())->locateFile($this->configuration['rules']); 29 | 30 | return "{$this->configuration['command']} {$this->configuration['directory']} --standard={$rules}"; 31 | } 32 | 33 | public function getThreshold() 34 | { 35 | return isset($this->configuration['threshold']) ? $this->configuration['threshold'] : 0; 36 | } 37 | 38 | public function getErrorMessage() 39 | { 40 | return 'The PHP Code Sniffer threshold is exceeded'; 41 | } 42 | 43 | public function getSuccessMessage() 44 | { 45 | return 'The PHP Code Sniffer threshold passed'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tool/Phpdcd.php: -------------------------------------------------------------------------------- 1 | 'app', 13 | 'command' => 'php vendor/sebastian/phpdcd/phpdcd', 14 | 'threshold' => 0, 15 | 'timeout' => 1200 16 | ]; 17 | 18 | public function composeCommand() 19 | { 20 | return "{$this->composeReportCommand()} | grep LOC | wc -l"; 21 | } 22 | 23 | public function composeReportCommand() 24 | { 25 | return "{$this->configuration['command']} {$this->configuration['directory']}"; 26 | } 27 | 28 | public function getThreshold() 29 | { 30 | return isset($this->configuration['threshold']) ? $this->configuration['threshold'] : 0; 31 | } 32 | 33 | public function getErrorMessage() 34 | { 35 | return 'The PHP Dead Code Detector threshold is exceeded'; 36 | } 37 | 38 | public function getSuccessMessage() 39 | { 40 | return 'The PHP Dead Code Detector threshold passed'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tool/Phpmd.php: -------------------------------------------------------------------------------- 1 | 'app', 14 | 'rules' => 'cleancode,codesize,unusedcode', 15 | 'command' => 'php vendor/phpmd/phpmd/src/bin/phpmd', 16 | 'threshold' => 0, 17 | 'timeout' => 1200 18 | ]; 19 | 20 | public function composeCommand() 21 | { 22 | return "{$this->composeReportCommand()} | wc -l"; 23 | } 24 | 25 | public function composeReportCommand() 26 | { 27 | $rules = $this->configuration['rules']; 28 | if ('xml' == pathinfo($rules, PATHINFO_EXTENSION)) { 29 | $rules = (new FileLocator())->locateFile($rules); 30 | } 31 | 32 | return "{$this->configuration['command']} {$this->configuration['directory']} text {$rules}"; 33 | } 34 | 35 | public function getThreshold() 36 | { 37 | return isset($this->configuration['threshold']) ? $this->configuration['threshold'] : 0; 38 | } 39 | 40 | public function getErrorMessage() 41 | { 42 | return 'The PHP MD threshold is exceeded'; 43 | } 44 | 45 | public function getSuccessMessage() 46 | { 47 | return 'The PHP MD threshold passed'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tool/Tool.php: -------------------------------------------------------------------------------- 1 | configuration = $this->resolveConfigurationOptions(isset($configuration['options']) ? $configuration['options'] : []); 20 | } 21 | 22 | /** 23 | * Returns a string with bash command 24 | * 25 | * @return string 26 | */ 27 | abstract public function composeCommand(); 28 | 29 | /** 30 | * Creates a command to return full report 31 | * 32 | * @return string 33 | */ 34 | abstract public function composeReportCommand(); 35 | 36 | /** 37 | * Returns an integer with the threshold 38 | * 39 | * @return Integer 40 | */ 41 | abstract public function getThreshold(); 42 | 43 | /** 44 | * Returns error message if exception occurs 45 | * 46 | * @return string 47 | */ 48 | abstract public function getErrorMessage(); 49 | 50 | /** 51 | * Returns a message when succeed 52 | * 53 | * @return string 54 | */ 55 | abstract public function getSuccessMessage(); 56 | 57 | /** 58 | * Return timeout for the command 59 | * 60 | * @return int 61 | */ 62 | public function getTimeout() 63 | { 64 | return $this->configuration['timeout']; 65 | } 66 | 67 | /** 68 | * Resolves options 69 | * 70 | * @param array $options 71 | * @return array 72 | */ 73 | protected function resolveConfigurationOptions(array $configurationOptions) 74 | { 75 | $optionsResolver = new OptionsResolver(); 76 | $optionsResolver->setDefaults($this->defaultOptions); 77 | $optionsResolver->setRequired(array_keys($this->defaultOptions)); 78 | 79 | return $optionsResolver->resolve($configurationOptions); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "piotrpasich/code-quality-threshold", 3 | "license": "MIT", 4 | "authors": [ 5 | { 6 | "name": "Piotr Pasich", 7 | "email": "piotr.pasich@x-team.com" 8 | } 9 | ], 10 | "require": { 11 | "symfony/process": "2.*", 12 | "symfony/yaml": "2.*", 13 | "symfony/options-resolver": "2.*", 14 | "phpmd/phpmd": "@stable", 15 | "squizlabs/php_codesniffer": "2.*", 16 | "sebastian/phpcpd": "*", 17 | "sebastian/phpdcd": "*" 18 | }, 19 | "minimum-stability": "dev", 20 | "extra": { 21 | "cqt-parameters": { 22 | "file": "appa/config/parameters.yml" 23 | } 24 | }, 25 | "scripts": { 26 | "test": [ 27 | "piotrpasich\\CodeQualityThreshold\\Composer\\ScriptHandler::checkThresholds" 28 | ] 29 | }, 30 | "target-dir": "piotrpasich/CodeQualityThreshold", 31 | "autoload": { 32 | "psr-0": { "piotrpasich\\CodeQualityThreshold": "" } 33 | } 34 | } 35 | --------------------------------------------------------------------------------