Skip to content

Commit 40c1f08

Browse files
authored
Merge pull request #15 from 123inkt/Allow-per-directory-coverage-override
Allow per directory coverage override
2 parents 73ea42e + a6daf86 commit 40c1f08

35 files changed

+571
-175
lines changed

README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ File: `phpfci.xml`
3232
min-coverage="100"
3333
>
3434
<custom-coverage>
35-
<file path="src/FileA.php" min="80"/>
36-
<file path="src/FileB.php" min="60"/>
35+
<!-- directory based coverage rule -->
36+
<directory path="src/Lib/" min="90"/>
37+
<!-- subdirectories will superceed a parent directory rule -->
38+
<directory path="src/Lib/Config/" min="100"/>
39+
<!-- file rule will always superceed a directory rule -->
40+
<file path="src/Lib/Config/File.php" min="80"/>
3741
</custom-coverage>
3842
</phpfci>
3943
```
@@ -66,14 +70,15 @@ php vendor/bin/phpfci inspect coverage.xml reports/gitlab.errors.json --report=g
6670

6771
## Command line arguments
6872

69-
| Option | Values | Description |
70-
|---------------------------|------------------------|-------------------------------------------------------|
71-
| `argument 1` | `inspect`, `baseline` | the command to execute. |
72-
| `argument 2` | `coverage.xml` | the phpunit clover coverage input file. |
73-
| `argument 3` | `phpfci.xml` | the output file to write to. |
74-
| `--report=<report-style>` | `gitlab`, `checkstyle` | the output format. If absent will default to console. |
75-
| `--config=<path-to-file>` | `phpfci.xml` | the path to the config file. |
76-
| `--exit-code-on-failure` | - | Set exit code to `1` when there are failures. |
73+
| Option | Values | Description |
74+
|---------------------------|------------------------------------------|-------------------------------------------------------------------------|
75+
| `argument 1` | `inspect`, `baseline` | the command to execute. |
76+
| `argument 2` | `coverage.xml` | the phpunit clover coverage input file. |
77+
| `argument 3` | `phpfci.xml` | the output file to write to. |
78+
| `--report=<report-style>` | `gitlab`, `checkstyle` | the output format. If absent will default to console. |
79+
| `--config=<path-to-file>` | `phpfci.xml` | the path to the config file. |
80+
| `--baseDir=<path>` | defaults to directory of the output file | The root directory of the project, will be used to make paths relative. |
81+
| `--exit-code-on-failure` | - | Set exit code to `1` when there are failures. |
7782

7883
## About us
7984

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"symfony/polyfill-php80": "^1.24"
3434
},
3535
"require-dev": {
36+
"digitalrevolution/accessorpair-constraint": ">= 2.1.7",
3637
"roave/security-advisories": "dev-latest",
3738
"squizlabs/php_codesniffer": "^3.6",
3839
"phpmd/phpmd": "@stable",
@@ -41,7 +42,6 @@
4142
"phpstan/phpstan-phpunit": "^1.0",
4243
"phpstan/phpstan-strict-rules": "^1.1",
4344
"phpstan/extension-installer": "^1.1",
44-
"digitalrevolution/accessorpair-constraint": "^2.1",
4545
"mikey179/vfsstream": "^1.6.7"
4646
},
4747
"scripts": {

resources/phpfci.xsd

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,42 @@
1010

1111
<xs:element name="custom-coverage">
1212
<xs:complexType>
13-
<xs:sequence>
14-
<xs:element ref="file" maxOccurs="unbounded"/>
15-
</xs:sequence>
13+
<xs:group ref="pathGroup"/>
1614
</xs:complexType>
1715
</xs:element>
1816

19-
<xs:element name="file">
20-
<xs:complexType>
21-
<xs:attribute name="path" type="xs:string"/>
22-
<xs:attribute name="min" type="percentage"/>
23-
</xs:complexType>
24-
</xs:element>
17+
<xs:group name="pathGroup">
18+
<xs:sequence>
19+
<xs:choice maxOccurs="unbounded">
20+
<xs:element name="directory" type="directory"/>
21+
<xs:element name="file" type="file"/>
22+
</xs:choice>
23+
</xs:sequence>
24+
</xs:group>
25+
26+
<xs:complexType name="directory">
27+
<xs:simpleContent>
28+
<xs:extension base="xs:string">
29+
<xs:attribute name="path" type="xs:string"/>
30+
<xs:attribute name="min" type="percentage"/>
31+
</xs:extension>
32+
</xs:simpleContent>
33+
</xs:complexType>
34+
35+
<xs:complexType name="file">
36+
<xs:simpleContent>
37+
<xs:extension base="xs:string">
38+
<xs:attribute name="path" type="xs:string"/>
39+
<xs:attribute name="min" type="percentage"/>
40+
</xs:extension>
41+
</xs:simpleContent>
42+
</xs:complexType>
2543

2644
<!-- definition of root element -->
2745
<xs:element name="phpfci">
2846
<xs:complexType>
2947
<xs:sequence>
30-
<xs:element ref="custom-coverage" maxOccurs="1" minOccurs="0"/>
48+
<xs:element ref="custom-coverage" minOccurs="0"/>
3149
</xs:sequence>
3250
<xs:attribute name="min-coverage" type="percentage"/>
3351
<xs:attribute name="allow-uncovered-methods" type="xs:boolean" default="false"/>

src/Command/BaselineCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
4242
}
4343

4444
$outputPath = new SplFileInfo((string)$configArgument);
45-
$baseDir = $input->getOption('baseDir') ?? $outputPath->getPath() . '/';
45+
$baseDir = $input->getOption('baseDir') ?? $outputPath->getPath();
4646
$threshold = $input->getOption('threshold');
4747
$coverageFilePath = FileUtil::getExistingFile($input->getArgument('coverage'));
4848

src/Command/InspectCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ protected function configure(): void
4242
protected function execute(InputInterface $input, OutputInterface $output): int
4343
{
4444
$configPath = FileUtil::getExistingFile($input->getOption('config') ?? FileUtil::findFilePath((string)getcwd(), self::CONFIG_FILES));
45-
$baseDir = $input->getOption('baseDir') ?? $configPath->getPath() . '/';
45+
$baseDir = $input->getOption('baseDir') ?? $configPath->getPath();
4646
$coverageFilePath = FileUtil::getExistingFile($input->getArgument('coverage'));
4747
$outputFilePath = FileUtil::getFile($input->getArgument('output'));
4848
$schema = dirname(__DIR__, 2) . '/resources/phpfci.xsd';

src/Lib/IO/InspectionConfigFactory.php

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
namespace DigitalRevolution\CodeCoverageInspection\Lib\IO;
55

66
use DigitalRevolution\CodeCoverageInspection\Lib\Utility\XMLUtil;
7-
use DigitalRevolution\CodeCoverageInspection\Model\Config\FileInspectionConfig;
87
use DigitalRevolution\CodeCoverageInspection\Model\Config\InspectionConfig;
98
use DOMDocument;
109
use DOMXPath;
@@ -14,44 +13,30 @@ class InspectionConfigFactory
1413
{
1514
public static function fromDOMDocument(string $basePath, DOMDocument $doc): InspectionConfig
1615
{
17-
$xpath = new DOMXpath($doc);
18-
[$minCoverage, $isUncoveredAllowed] = self::getConfiguration($xpath);
19-
20-
// find all custom coverage files
21-
$files = [];
22-
$fileNodes = $xpath->query("/phpfci/custom-coverage/file");
23-
if ($fileNodes !== false) {
24-
foreach ($fileNodes as $item) {
25-
$path = (string)XMLUtil::getAttribute($item, 'path');
26-
$minimumCoverage = (int)XMLUtil::getAttribute($item, 'min');
27-
$files[$path] = new FileInspectionConfig($path, $minimumCoverage);
28-
}
16+
$xpath = new DOMXpath($doc);
17+
$inspectionConfig = self::getInspectionConfig($basePath, $xpath);
18+
19+
// find all custom coverage node
20+
$nodes = array_merge(XMLUtil::query($xpath, "/phpfci/custom-coverage/directory"), XMLUtil::query($xpath, "/phpfci/custom-coverage/file"));
21+
foreach ($nodes as $node) {
22+
$inspectionConfig->addPathInspection(PathInspectionConfigFactory::createFromNode($node));
2923
}
3024

31-
return new InspectionConfig($basePath, $minCoverage, $isUncoveredAllowed, $files);
25+
return $inspectionConfig;
3226
}
3327

34-
/**
35-
* @return array{int, bool}
36-
*/
37-
private static function getConfiguration(DOMXpath $xpath): array
28+
private static function getInspectionConfig(string $basePath, DOMXpath $xpath): InspectionConfig
3829
{
3930
// find global coverage settings
40-
$nodes = $xpath->query("/phpfci");
41-
if ($nodes === false || $nodes->count() === 0) {
31+
$nodes = XMLUtil::query($xpath, "/phpfci");
32+
if (count($nodes) === 0) {
4233
throw new RuntimeException('Missing `phpfci` in configuration file');
4334
}
4435

45-
$node = $nodes->item(0);
46-
if ($node === null) {
47-
// @codeCoverageIgnoreStart
48-
throw new RuntimeException('Missing attributes on `phpfci`');
49-
// @codeCoverageIgnoreEnd
50-
}
51-
36+
$node = reset($nodes);
5237
$minCoverage = (int)XMLUtil::getAttribute($node, 'min-coverage');
5338
$isUncoveredAllowed = XMLUtil::getAttribute($node, 'allow-uncovered-methods') === "true";
5439

55-
return [$minCoverage, $isUncoveredAllowed];
40+
return new InspectionConfig($basePath, $minCoverage, $isUncoveredAllowed);
5641
}
5742
}

src/Lib/IO/MetricsFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static function getFileMetrics(DOMDocument $document): array
3535
foreach ($domMetrics as $domMetric) {
3636
/** @var DOMNode $parentNode */
3737
$parentNode = $domMetric->parentNode;
38-
$filename = (string)XMLUtil::getAttribute($parentNode, 'name');
38+
$filename = str_replace('\\', '/', (string)XMLUtil::getAttribute($parentNode, 'name'));
3939

4040
// calculate coverage
4141
$statements = (int)XMLUtil::getAttribute($domMetric, 'statements');
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DigitalRevolution\CodeCoverageInspection\Lib\IO;
5+
6+
use DigitalRevolution\CodeCoverageInspection\Lib\Utility\XMLUtil;
7+
use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig;
8+
use DOMNode;
9+
use RuntimeException;
10+
11+
class PathInspectionConfigFactory
12+
{
13+
public static function createFromNode(DOMNode $item): PathInspectionConfig
14+
{
15+
if (in_array($item->nodeName, [PathInspectionConfig::TYPE_FILE, PathInspectionConfig::TYPE_DIR], true) === false) {
16+
throw new RuntimeException('Invalid node type: ' . $item->nodeName);
17+
}
18+
19+
$type = $item->nodeName;
20+
$path = (string)XMLUtil::getAttribute($item, 'path');
21+
$minimumCoverage = (int)XMLUtil::getAttribute($item, 'min');
22+
23+
return new PathInspectionConfig($type, $path, $minimumCoverage);
24+
}
25+
}

src/Lib/Metrics/Inspection/AbstractInspection.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection;
55

6-
use DigitalRevolution\CodeCoverageInspection\Model\Config\FileInspectionConfig;
6+
use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig;
77
use DigitalRevolution\CodeCoverageInspection\Model\Config\InspectionConfig;
88
use DigitalRevolution\CodeCoverageInspection\Model\Metric\Failure;
99
use DigitalRevolution\CodeCoverageInspection\Model\Metric\FileMetric;
@@ -17,5 +17,5 @@ public function __construct(InspectionConfig $config)
1717
$this->config = $config;
1818
}
1919

20-
abstract public function inspect(?FileInspectionConfig $fileConfig, FileMetric $metric): ?Failure;
20+
abstract public function inspect(?PathInspectionConfig $fileConfig, FileMetric $metric): ?Failure;
2121
}

src/Lib/Metrics/Inspection/BelowCustomCoverageInspection.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection;
55

6-
use DigitalRevolution\CodeCoverageInspection\Model\Config\FileInspectionConfig;
6+
use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig;
77
use DigitalRevolution\CodeCoverageInspection\Model\Metric\Failure;
88
use DigitalRevolution\CodeCoverageInspection\Model\Metric\FileMetric;
99

@@ -12,7 +12,7 @@
1212
*/
1313
class BelowCustomCoverageInspection extends AbstractInspection
1414
{
15-
public function inspect(?FileInspectionConfig $fileConfig, FileMetric $metric): ?Failure
15+
public function inspect(?PathInspectionConfig $fileConfig, FileMetric $metric): ?Failure
1616
{
1717
if ($fileConfig !== null && $metric->getCoverage() < $fileConfig->getMinimumCoverage()) {
1818
return new Failure($metric, $fileConfig->getMinimumCoverage(), Failure::CUSTOM_COVERAGE_TOO_LOW);

src/Lib/Metrics/Inspection/BelowGlobalCoverageInspection.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection;
55

6-
use DigitalRevolution\CodeCoverageInspection\Model\Config\FileInspectionConfig;
6+
use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig;
77
use DigitalRevolution\CodeCoverageInspection\Model\Metric\Failure;
88
use DigitalRevolution\CodeCoverageInspection\Model\Metric\FileMetric;
99

@@ -12,7 +12,7 @@
1212
*/
1313
class BelowGlobalCoverageInspection extends AbstractInspection
1414
{
15-
public function inspect(?FileInspectionConfig $fileConfig, FileMetric $metric): ?Failure
15+
public function inspect(?PathInspectionConfig $fileConfig, FileMetric $metric): ?Failure
1616
{
1717
if ($fileConfig === null && $metric->getCoverage() < $this->config->getMinimumCoverage()) {
1818
return new Failure($metric, $this->config->getMinimumCoverage(), Failure::GLOBAL_COVERAGE_TOO_LOW);

src/Lib/Metrics/Inspection/CustomCoverageAboveGlobalInspection.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection;
55

66
use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\FileMetricAnalyzer;
7-
use DigitalRevolution\CodeCoverageInspection\Model\Config\FileInspectionConfig;
7+
use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig;
88
use DigitalRevolution\CodeCoverageInspection\Model\Metric\Failure;
99
use DigitalRevolution\CodeCoverageInspection\Model\Metric\FileMetric;
1010

@@ -13,12 +13,18 @@
1313
*/
1414
class CustomCoverageAboveGlobalInspection extends AbstractInspection
1515
{
16-
public function inspect(?FileInspectionConfig $fileConfig, FileMetric $metric): ?Failure
16+
public function inspect(?PathInspectionConfig $fileConfig, FileMetric $metric): ?Failure
1717
{
1818
$uncoveredMethod = FileMetricAnalyzer::getUncoveredMethodMetric($metric);
19+
if ($fileConfig === null || $uncoveredMethod !== null) {
20+
return null;
21+
}
22+
23+
$globalCoverage = $this->config->getMinimumCoverage();
24+
$customCoverage = $fileConfig->getMinimumCoverage();
1925

20-
// custom coverage, but file is already above global coverage
21-
if ($fileConfig !== null && $uncoveredMethod === null && $metric->getCoverage() >= $this->config->getMinimumCoverage()) {
26+
// custom coverage is lower than global coverage, and file is above global coverage
27+
if ($customCoverage < $globalCoverage && $metric->getCoverage() >= $globalCoverage) {
2228
return new Failure($metric, $fileConfig->getMinimumCoverage(), Failure::UNNECESSARY_CUSTOM_COVERAGE);
2329
}
2430

src/Lib/Metrics/Inspection/UncoveredMethodsInspection.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace DigitalRevolution\CodeCoverageInspection\Lib\Metrics\Inspection;
55

66
use DigitalRevolution\CodeCoverageInspection\Lib\Metrics\FileMetricAnalyzer;
7-
use DigitalRevolution\CodeCoverageInspection\Model\Config\FileInspectionConfig;
7+
use DigitalRevolution\CodeCoverageInspection\Model\Config\PathInspectionConfig;
88
use DigitalRevolution\CodeCoverageInspection\Model\Metric\Failure;
99
use DigitalRevolution\CodeCoverageInspection\Model\Metric\FileMetric;
1010

@@ -13,7 +13,7 @@
1313
*/
1414
class UncoveredMethodsInspection extends AbstractInspection
1515
{
16-
public function inspect(?FileInspectionConfig $fileConfig, FileMetric $metric): ?Failure
16+
public function inspect(?PathInspectionConfig $fileConfig, FileMetric $metric): ?Failure
1717
{
1818
$uncoveredMethod = FileMetricAnalyzer::getUncoveredMethodMetric($metric);
1919

src/Lib/Metrics/MetricsAnalyzer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function analyze(): array
4646
$failures = [];
4747

4848
foreach ($this->metrics as $metric) {
49-
$fileConfig = $this->config->getFileInspection($metric->getFilepath());
49+
$fileConfig = $this->config->getPathInspection($metric->getFilepath());
5050

5151
foreach ($this->inspections as $inspection) {
5252
$failure = $inspection->inspect($fileConfig, $metric);

src/Lib/Utility/XMLUtil.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@
44
namespace DigitalRevolution\CodeCoverageInspection\Lib\Utility;
55

66
use DOMNode;
7+
use DOMXPath;
78

89
class XMLUtil
910
{
11+
/**
12+
* @return DOMNode[]
13+
*/
14+
public static function query(DOMXpath $xpath, string $query): array
15+
{
16+
$nodes = $xpath->query($query);
17+
18+
return $nodes === false ? [] : iterator_to_array($nodes, false);
19+
}
20+
1021
public static function getAttribute(DOMNode $node, string $attribute): ?string
1122
{
1223
if ($node->attributes === null) {

src/Model/Config/FileInspectionConfig.php

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)