diff --git a/.gitignore b/.gitignore index 6730f31e..fcc2609d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /vendor/ /build/ phpstan-phpqa.neon +.php_cs +.php_cs.cache diff --git a/.phpqa.yml b/.phpqa.yml index eee9c927..217432c0 100644 --- a/.phpqa.yml +++ b/.phpqa.yml @@ -12,6 +12,15 @@ phpcs: # you can include custom reports (https://github.com/wikidi/codesniffer/blob/master/reports/wikidi/Summary.php#L39) # ./vendor/owner/package/src/MySummaryReport.php: phpcs-summary.html +php-cs-fixer: + # http://cs.sensiolabs.org/#usage + rules: '@PSR2' + allowRiskyRules: false + # by default the tool is runned in dry-run mode (no fixers are applied) + isDryRun: true + # alternatively you can define path to your .phpcs_file (rules/allowRiskyRules config is ignored) + config: null + phpmd: standard: app/phpmd.xml @@ -31,3 +40,4 @@ report: phpcs: app/report/phpcs.xsl pdepend: app/report/pdepend.xsl phpmd: app/report/phpmd.xsl + php-cs-fixer: app/report/php-cs-fixer.xsl diff --git a/README.md b/README.md index 203d882c..315e0a1d 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Experimental tool is executed only if the tool is specified in `--tools`. Tool | PHP | Supported since | Description | Status | ---- | --- | --------------- | ----------- | ------ | +[php-cs-fixer](http://cs.sensiolabs.org/) | [`>= 5.3`](https://github.com/EdgedesignCZ/phpqa/pull/66#discussion_r115206573) | `1.12` | Automatically detect and fix PHP coding standards issues | stable | [parallel-lint](https://github.com/JakubOnderka/PHP-Parallel-Lint) | `>= 5.4` | `1.9` | Check syntax of PHP files | stable | [phpstan](https://github.com/phpstan/phpstan) | `>= 7.0` | `1.9` | Discover bugs in your code without running it | _experimental_ ([`v0.7`](https://github.com/EdgedesignCZ/phpqa/pull/43)) | @@ -157,6 +158,7 @@ phpcs | [checkstyle.xml](https://edgedesigncz.github.io/phpqa/report/checkstyle. pdepend | [pdepend-jdepend.xml](https://edgedesigncz.github.io/phpqa/report/pdepend-jdepend.xml), [pdepend-summary.xml](https://edgedesigncz.github.io/phpqa/report/pdepend-summary.xml), [pdepend-dependencies.xml](https://edgedesigncz.github.io/phpqa/report/pdepend-dependencies.xml), [pdepend-jdepend.svg](https://edgedesigncz.github.io/phpqa/report/pdepend-jdepend.svg), [pdepend-pyramid.svg](https://edgedesigncz.github.io/phpqa/report/pdepend-pyramid.svg) | ✗ | phpmd | [phpmd.xml](https://edgedesigncz.github.io/phpqa/report/phpmd.xml) | [✓](https://github.com/phpmd/phpmd/blob/master/src/main/php/PHPMD/Renderer/TextRenderer.php#L47) | phpmetrics | [phpmetrics.html (v1)](https://edgedesigncz.github.io/phpqa/report/phpmetrics.html), [phpmetrics/index.html (v2)](https://edgedesigncz.github.io/phpqa/report/phpmetrics/), [phpmetrics.xml](https://edgedesigncz.github.io/phpqa/report/phpmetrics.xml) | [✓](https://github.com/phpmetrics/PhpMetrics#usage) | +php-cs-fixer | [php-cs-fixer.html](https://edgedesigncz.github.io/phpqa/report/php-cs-fixer.html) | [✓](http://cs.sensiolabs.org/#usage "txt output format") | parallel-lint | [parallel-lint.html](https://edgedesigncz.github.io/phpqa/report/parallel-lint.html) | [✓](https://github.com/JakubOnderka/PHP-Parallel-Lint#example-output) | phpstan | [phpstan.html](https://edgedesigncz.github.io/phpqa/report/phpstan.html), [phpstan-phpqa.neon](https://edgedesigncz.github.io/phpqa/report/phpstan-phpqa.neon) | [✓](https://edgedesigncz.github.io/phpqa/report/phpstan.html), [phpstan-phpqa.neon](https://edgedesigncz.github.io/phpqa/report/phpstan-phpqa.neon "Generated configuration is saved in current working directory") | @@ -199,6 +201,10 @@ Tool | Settings | Default Value | Your value [phpcs.standard](https://pear.php.net/manual/en/package.php.php-codesniffer.usage.php#package.php.php-codesniffer.usage.coding-standard) | Coding standard | PSR2 | Name of existing standard (`PEAR`, `PHPCS`, `PSR1`, `PSR2`, `Squiz`, `Zend`), or path to your coding standard [phpcs.ignoreWarnings](https://github.com/EdgedesignCZ/phpqa/issues/53) | If number of allowed errors is compared with warnings+errors, or just errors from `checkstyle.xml` | `false` | Boolean value [phpcs.reports](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Reporting) | Report types | [`full`](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Reporting#printing-full-and-summary-reports) report in [cli mode](#output-modes), [`checkstyle`](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Reporting#printing-a-checkstyle-report) in [file mode](#output-modes) | Predefined [report types](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Reporting) or [custom reports](https://github.com/wikidi/codesniffer#examples) +[php-cs-fixer.rules](http://cs.sensiolabs.org/#usage) | Coding standard rules | `@PSR2` | String value +[php-cs-fixer.allowRiskyRules](http://cs.sensiolabs.org/#usage) | Whether risky rules may run | `false` | Boolean value +[php-cs-fixer.config](http://cs.sensiolabs.org/#usage) | Load configuration from [file](https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/.php_cs.dist) | `null` | Path to `.phpcs` file +[php-cs-fixer.isDryRun](http://cs.sensiolabs.org/#usage) | If code is just analyzed or fixers are applied | `true` | Boolean value [phpmd](http://phpmd.org/documentation/creating-a-ruleset.html) | Ruleset | [Edgedesign's standard](/app/phpmd.xml) | Path to ruleset [phpcpd](https://github.com/sebastianbergmann/phpcpd/blob/de9056615da6c1230f3294384055fa7d722c38fa/src/CLI/Command.php#L136) | Minimum number of lines/tokens for copy-paste detection | 5 lines, 70 tokens | [phpstan](https://github.com/phpstan/phpstan#configuration) | Level, config file | Level 0, `%currentWorkingDirectory%/phpstan.neon` | Take a look at [phpqa config in tests/.travis](/tests/.travis/) | diff --git a/app/report/php-cs-fixer.xsl b/app/report/php-cs-fixer.xsl new file mode 100644 index 00000000..b8d21c1c --- /dev/null +++ b/app/report/php-cs-fixer.xsl @@ -0,0 +1,126 @@ + + + + + + + + + PHP CS Fixer report + + + + + + +
+ +

PHP CS Fixer report

+ + + +
+ +
+ + + + + + + + + + + + + +
FilesErrorsDuration
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + + +
FileErrors (fixers)
+
+
+
+ + + + + + + +
+
\ No newline at end of file diff --git a/app/report/phpqa.html.twig b/app/report/phpqa.html.twig index 01886efa..93f3ea3c 100644 --- a/app/report/phpqa.html.twig +++ b/app/report/phpqa.html.twig @@ -24,6 +24,10 @@ set tabs = { 'overview': 'Overview', 'errors': 'Errors', }, + 'php-cs-fixer': { + 'overview': 'Overview', + 'errors': 'Errors', + }, 'phpmd': { 'overview': 'Overview', 'errors': 'Errors', diff --git a/bin/ci.sh b/bin/ci.sh index 7f81469c..20a36df9 100755 --- a/bin/ci.sh +++ b/bin/ci.sh @@ -1,3 +1,3 @@ #!/bin/sh -./phpqa --verbose --report --config tests/.travis --tools phpcs:0,phpmd:0,phpcpd:0,parallel-lint:0,phpstan,phpmetrics,phploc,pdepend +./phpqa --verbose --report --config tests/.travis --tools phpcs:0,php-cs-fixer:0,phpmd:0,phpcpd:0,parallel-lint:0,phpstan,phpmetrics,phploc,pdepend diff --git a/bin/suggested-tools.sh b/bin/suggested-tools.sh index 4f35585e..9a40c65c 100755 --- a/bin/suggested-tools.sh +++ b/bin/suggested-tools.sh @@ -9,8 +9,8 @@ mode="$1" if [ $mode = "install" ] then echo "Installing suggested tools" - composer require jakub-onderka/php-parallel-lint jakub-onderka/php-console-highlighter phpstan/phpstan + composer require jakub-onderka/php-parallel-lint jakub-onderka/php-console-highlighter phpstan/phpstan friendsofphp/php-cs-fixer:~2.2 else echo "Removing suggested tools" - composer remove jakub-onderka/php-parallel-lint jakub-onderka/php-console-highlighter phpstan/phpstan + composer remove jakub-onderka/php-parallel-lint jakub-onderka/php-console-highlighter phpstan/phpstan friendsofphp/php-cs-fixer fi diff --git a/src/CodeAnalysisTasks.php b/src/CodeAnalysisTasks.php index 13fe5d46..8b87e57b 100644 --- a/src/CodeAnalysisTasks.php +++ b/src/CodeAnalysisTasks.php @@ -41,6 +41,14 @@ trait CodeAnalysisTasks 'composer' => 'squizlabs/php_codesniffer', 'binary' => 'phpcs', ), + 'php-cs-fixer' => array( + 'optionSeparator' => ' ', + 'internalClass' => 'PhpCsFixer\Config', + 'outputMode' => OutputMode::XML_CONSOLE_OUTPUT, + 'composer' => 'friendsofphp/php-cs-fixer', + 'xml' => ['php-cs-fixer.xml'], + 'errorsXPath' => '//testsuites/testsuite/testcase/failure', + ), 'phpmd' => array( 'optionSeparator' => ' ', 'xml' => ['phpmd.xml'], @@ -61,13 +69,13 @@ trait CodeAnalysisTasks 'parallel-lint' => array( 'optionSeparator' => ' ', 'internalClass' => 'JakubOnderka\PhpParallelLint\ParallelLint', - 'hasOnlyConsoleOutput' => true, + 'outputMode' => OutputMode::RAW_CONSOLE_OUTPUT, 'composer' => 'jakub-onderka/php-parallel-lint', ), 'phpstan' => array( 'optionSeparator' => ' ', 'internalClass' => 'PHPStan\Analyser\Analyser', - 'hasOnlyConsoleOutput' => true, + 'outputMode' => OutputMode::RAW_CONSOLE_OUTPUT, 'composer' => 'phpstan/phpstan', ), ); @@ -384,13 +392,53 @@ function ($relativeDir) { ); } + private function phpcsfixer() + { + $configFile = $this->config->value('php-cs-fixer.config'); + if ($configFile) { + $analyzedDir = $this->options->getAnalyzedDirs(' '); + } else { + $analyzedDirs = $this->options->getAnalyzedDirs(); + $analyzedDir = reset($analyzedDirs); + if (count($analyzedDirs) > 1) { + $this->say("php-cs-fixer analyzes only first directory {$analyzedDir}"); + $this->say( + "- multiple dirs are supported if you specify " . + "php-cs-fixer.config in .phpqa.yml" + ); + } + } + $args = [ + 'fix', + $analyzedDir, + 'verbose' => '', + 'format' => $this->options->isSavedToFiles ? 'junit' : 'txt', + ]; + if ($configFile) { + $args['config'] = $configFile; + } else { + $args += [ + 'rules' => $this->config->value('php-cs-fixer.rules'), + 'allow-risky' => $this->config->value('php-cs-fixer.allowRiskyRules') ? 'yes' : 'no', + ]; + } + if ($this->config->value('php-cs-fixer.isDryRun')) { + $args['dry-run'] = ''; + } + return $args; + } + private function buildHtmlReport() { foreach ($this->usedTools as $tool) { if (!$tool->htmlReport) { $tool->htmlReport = $this->options->rawFile("{$tool->binary}.html"); } - if ($tool->hasOnlyConsoleOutput) { + if ($tool->hasOutput(OutputMode::XML_CONSOLE_OUTPUT)) { + file_put_contents($this->options->rawFile("{$tool}.xml"), $tool->process->getOutput()); + } + + if ($tool->hasOutput(OutputMode::RAW_CONSOLE_OUTPUT)) { twigToHtml( 'cli.html.twig', array( diff --git a/src/OutputMode.php b/src/OutputMode.php new file mode 100644 index 00000000..d6a9e2db --- /dev/null +++ b/src/OutputMode.php @@ -0,0 +1,10 @@ + [], 'errorsXPath' => '', 'allowedErrorsCount' => null, - 'hasOnlyConsoleOutput' => false, + 'outputMode' => OutputMode::HANDLED_BY_TOOL, 'internalClass' => null, ]; $this->tool = $tool; @@ -39,7 +39,7 @@ public function __construct($tool, array $toolConfig) $this->errorsXPath = is_array($config['errorsXPath']) ? $config['errorsXPath'] : [$this->errorsXPath => $config['errorsXPath']]; $this->allowedErrorsCount = $config['allowedErrorsCount']; - $this->hasOnlyConsoleOutput = $config['hasOnlyConsoleOutput']; + $this->outputMode = $config['outputMode']; } public function isInstalled() @@ -47,6 +47,11 @@ public function isInstalled() return !$this->internalClass || class_exists($this->internalClass); } + public function hasOutput($outputMode) + { + return $this->outputMode == $outputMode; + } + public function buildOption($arg, $value) { if ($value || $value === 0) { @@ -65,7 +70,7 @@ public function analyzeResult($hasNoOutput = false) { $xpath = $this->errorsXPath[$this->errorsType]; - if ($hasNoOutput || $this->hasOnlyConsoleOutput) { + if ($hasNoOutput || $this->hasOutput(OutputMode::RAW_CONSOLE_OUTPUT)) { return $this->evaluteErrorsCount($this->process->getExitCode() ? 1 : 0); } elseif (!$xpath) { return [true, '']; diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index de3148a3..64a0c5a9 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -13,6 +13,10 @@ public function testLoadDefaultConfig() assertThat($config->value('phpcs.ignoreWarnings'), identicalTo(false)); assertThat($config->value('phpcs.reports.cli'), is(nonEmptyArray())); assertThat($config->value('phpcs.reports.file'), is(nonEmptyArray())); + assertThat($config->value('php-cs-fixer.rules'), is(nonEmptyString())); + assertThat($config->value('php-cs-fixer.isDryRun'), identicalTo(true)); + assertThat($config->value('php-cs-fixer.allowRiskyRules'), identicalTo(false)); + assertThat($config->path('php-cs-fixer.config'), is(nullValue())); assertThat($config->path('phpmd.standard'), is(nonEmptyString())); assertThat($config->value('phpstan.level'), identicalTo(0)); }