├── .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 |
--------------------------------------------------------------------------------