diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md similarity index 92% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md index 5ca75c07d..128d7d3f3 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md @@ -1,3 +1,8 @@ +--- +name: Generic issue +about: Use this template for all issues. +--- + | Q | A | --------------------------| --------------- | php-code-coverage version | x.y.z diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..3ba13e0ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f9c008d6..ffd3929fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,8 @@ on: name: CI env: - COMPOSER_ROOT_VERSION: 12.3.x-dev + COMPOSER_ROOT_VERSION: 12.4.x-dev + PHP_VERSION: 8.4 jobs: coding-guidelines: @@ -17,12 +18,23 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH - name: Install PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.3 + php-version: ${{ env.PHP_VERSION }} extensions: none, iconv, json, phar, tokenizer coverage: none tools: none @@ -37,17 +49,41 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH - name: Install PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.4 + php-version: ${{ env.PHP_VERSION }} extensions: none, ctype, curl, dom, iconv, mbstring, opcache, simplexml, tokenizer, xml, xmlwriter coverage: none tools: none - - name: Install dependencies with Composer + - name: Get Composer cache directory + id: composer-cache + shell: bash + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer cache directory + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Update dependencies with composer run: ./tools/composer update --no-interaction --no-ansi --no-progress - name: Run PHPStan @@ -84,7 +120,18 @@ jobs: run: git config --global core.autocrlf false - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH - name: Install PHP with extensions uses: shivammathur/setup-php@v2 @@ -95,11 +142,24 @@ jobs: ini-values: ${{ env.PHP_INI_VALUES }} tools: none + - name: Get Composer cache directory + id: composer-cache + shell: bash + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer cache directory + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install dependencies with Composer run: php ./tools/composer update --no-ansi --no-interaction --no-progress - name: Run tests with PHPUnit - run: ./vendor/bin/phpunit --log-junit test-results.xml --coverage-openclover=code-coverage.xml + run: vendor/bin/phpunit --log-junit test-results.xml --coverage-clover=code-coverage.xml - name: Upload test results to Codecov.io if: ${{ !cancelled() }} diff --git a/.phive/phars.xml b/.phive/phars.xml index dac7fc73f..07724ca0b 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,5 +1,5 @@ - - + + diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 89883a868..79e9f7edb 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -351,7 +351,7 @@ ], 'types_spaces' => true, 'unary_operator_spaces' => true, - 'visibility_required' => [ + 'modifier_keywords' => [ 'elements' => [ 'const', 'method', diff --git a/ChangeLog-12.3.md b/ChangeLog-12.3.md deleted file mode 100644 index f076381c5..000000000 --- a/ChangeLog-12.3.md +++ /dev/null @@ -1,31 +0,0 @@ -# ChangeLog - -All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. - -## [12.3.2] - 2025-07-29 - -### Changed - -* Add coverage and complexity columns to class and method complexity tables -* Add CRAP to graph tooltip - -### Fixed - -* [#1081](https://github.com/sebastianbergmann/php-code-coverage/issues/1081): Class complexity scatter chart tooltips show incorrect class - -## [12.3.1] - 2025-06-18 - -### Changed - -* Changed CSS for HTML report to not use common ligatures as this sometimes lead to hard-to-read code -* Updated Bootstrap to version 5.3.6 for HTML report - -## [12.3.0] - 2025-05-23 - -### Changed - -* [#1080](https://github.com/sebastianbergmann/php-code-coverage/pull/1080): Support for reporting code coverage information in OpenClover XML format; unlike the existing Clover XML reporter, which remains unchanged, the XML documents generated by this new reporter validate against the OpenClover project's XML schema definition, with one exception: we do not generate the `` element. This feature is experimental and the generated XML might change in order to improve compliance with the OpenClover project's XML schema definition further. Such changes will be made in bugfix and/or minor releases even if they break backward compatibility. - -[12.3.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/12.3.1...12.3.2 -[12.3.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/12.3.0...12.3.1 -[12.3.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/12.2.1...12.3.0 diff --git a/ChangeLog-12.4.md b/ChangeLog-12.4.md new file mode 100644 index 000000000..8ff3a6e1e --- /dev/null +++ b/ChangeLog-12.4.md @@ -0,0 +1,11 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [12.4.0] - 2025-09-24 + +### Added + +* [#1095](https://github.com/sebastianbergmann/php-code-coverage/pull/1095): Support for light/dark mode in HTML report + +[12.4.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/12.3.8...main diff --git a/composer.json b/composer.json index bfd990ca1..744507de0 100644 --- a/composer.json +++ b/composer.json @@ -33,17 +33,17 @@ "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.4.0", + "nikic/php-parser": "^5.6.1", "phpunit/php-file-iterator": "^6.0", "phpunit/php-text-template": "^5.0", "sebastian/complexity": "^5.0", - "sebastian/environment": "^8.0", + "sebastian/environment": "^8.0.3", "sebastian/lines-of-code": "^4.0", "sebastian/version": "^6.0", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^12.1" + "phpunit/phpunit": "^12.3.7" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -61,7 +61,7 @@ }, "extra": { "branch-alias": { - "dev-main": "12.3.x-dev" + "dev-main": "12.4.x-dev" } } } diff --git a/phpstan.neon b/phpstan.neon index 7d7f95975..4d1a69e1e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,10 +1,13 @@ parameters: level: 5 + paths: - src - tests/tests - tests/bootstrap.php + resultCachePath: %tmpDir%/php-code-coverage-12.3.php + checkTooWideReturnTypesInProtectedAndPublicMethods: true reportAlwaysTrueInLastCondition: true reportPossiblyNonexistentConstantArrayOffset: true diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index bedc11927..ab9936ce7 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -47,27 +47,28 @@ final class CodeCoverage private const string UNCOVERED_FILES = 'UNCOVERED_FILES'; private readonly Driver $driver; private readonly Filter $filter; + private ?FileAnalyser $analyser = null; private ?Mapper $targetMapper = null; + private ?string $cacheDirectory = null; private bool $checkForUnintentionallyCoveredCode = false; + private bool $collectBranchAndPathCoverage = false; private bool $includeUncoveredFiles = true; private bool $ignoreDeprecatedCode = false; - private ?string $currentId = null; - private ?TestSize $currentSize = null; - private ProcessedCodeCoverageData $data; - private bool $useAnnotationsForIgnoringCode = true; + private bool $useAnnotationsForIgnoringCode = true; /** - * @var array + * @var list */ - private array $tests = []; + private array $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = []; + private ?string $currentId = null; + private ?TestSize $currentSize = null; + private ProcessedCodeCoverageData $data; /** - * @var list + * @var array */ - private array $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = []; - private ?FileAnalyser $analyser = null; - private ?string $cacheDirectory = null; - private ?Directory $cachedReport = null; + private array $tests = []; + private ?Directory $cachedReport = null; public function __construct(Driver $driver, Filter $filter) { @@ -76,6 +77,26 @@ public function __construct(Driver $driver, Filter $filter) $this->data = new ProcessedCodeCoverageData; } + public function __serialize(): array + { + $prefix = "\x00" . self::class . "\x00"; + + return [ + // Configuration + $prefix . 'cacheDirectory' => $this->cacheDirectory, + $prefix . 'checkForUnintentionallyCoveredCode' => $this->checkForUnintentionallyCoveredCode, + $prefix . 'includeUncoveredFiles' => $this->includeUncoveredFiles, + $prefix . 'ignoreDeprecatedCode' => $this->ignoreDeprecatedCode, + $prefix . 'parentClassesExcludedFromUnintentionallyCoveredCodeCheck' => $this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck, + $prefix . 'useAnnotationsForIgnoringCode' => $this->useAnnotationsForIgnoringCode, + $prefix . 'filter' => $this->filter, + + // Data + $prefix . 'data' => $this->data, + $prefix . 'tests' => $this->tests, + ]; + } + /** * Returns the code coverage information as a graph of node objects. */ @@ -363,16 +384,20 @@ public function excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck( public function enableBranchAndPathCoverage(): void { $this->driver->enableBranchAndPathCoverage(); + + $this->collectBranchAndPathCoverage = true; } public function disableBranchAndPathCoverage(): void { $this->driver->disableBranchAndPathCoverage(); + + $this->collectBranchAndPathCoverage = false; } public function collectsBranchAndPathCoverage(): bool { - return $this->driver->collectsBranchAndPathCoverage(); + return $this->collectBranchAndPathCoverage; } public function validate(TargetCollection $targets): ValidationResult @@ -418,15 +443,15 @@ private function applyCoversAndUsesFilter(RawCodeCoverageData $rawData, array|fa private function applyFilter(RawCodeCoverageData $data): void { - if ($this->filter->isEmpty()) { - return; - } - - foreach (array_keys($data->lineCoverage()) as $filename) { - if ($this->filter->isExcluded($filename)) { - $data->removeCoverageDataForFile($filename); + if (!$this->filter->isEmpty()) { + foreach (array_keys($data->lineCoverage()) as $filename) { + if ($this->filter->isExcluded($filename)) { + $data->removeCoverageDataForFile($filename); + } } } + + $data->skipEmptyLines(); } private function applyExecutableLinesFilter(RawCodeCoverageData $data): void diff --git a/src/Data/ProcessedBranchCoverageData.php b/src/Data/ProcessedBranchCoverageData.php new file mode 100644 index 000000000..b988a2b2a --- /dev/null +++ b/src/Data/ProcessedBranchCoverageData.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +use function array_merge; +use function array_unique; +use NoDiscard; +use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @phpstan-import-type TestIdType from ProcessedCodeCoverageData + * @phpstan-import-type XdebugBranchCoverageType from XdebugDriver + */ +final class ProcessedBranchCoverageData +{ + public readonly int $op_start; + public readonly int $op_end; + public readonly int $line_start; + public readonly int $line_end; + + /** @var list */ + public array $hit; + + /** @var array */ + public readonly array $out; + + /** @var array */ + public readonly array $out_hit; + + /** + * @param XdebugBranchCoverageType $xdebugCoverageData + */ + public static function fromXdebugCoverage(array $xdebugCoverageData): self + { + return new self( + $xdebugCoverageData['op_start'], + $xdebugCoverageData['op_end'], + $xdebugCoverageData['line_start'], + $xdebugCoverageData['line_end'], + [], + $xdebugCoverageData['out'], + $xdebugCoverageData['out_hit'], + ); + } + + /** + * @param list $hit + * @param array $out + * @param array $out_hit + */ + public function __construct( + int $op_start, + int $op_end, + int $line_start, + int $line_end, + array $hit, + array $out, + array $out_hit, + ) { + $this->out_hit = $out_hit; + $this->out = $out; + $this->hit = $hit; + $this->line_end = $line_end; + $this->line_start = $line_start; + $this->op_end = $op_end; + $this->op_start = $op_start; + } + + #[NoDiscard] + public function merge(self $data): self + { + if ($data->hit === []) { + return $this; + } + + return new self( + $this->op_start, + $this->op_end, + $this->line_start, + $this->line_end, + array_unique(array_merge($this->hit, $data->hit)), + $this->out, + $this->out_hit, + ); + } + + /** + * @param TestIdType $testCaseId + */ + public function recordHit(string $testCaseId): void + { + $this->hit[] = $testCaseId; + } +} diff --git a/src/Data/ProcessedClassType.php b/src/Data/ProcessedClassType.php new file mode 100644 index 000000000..593e8de0f --- /dev/null +++ b/src/Data/ProcessedClassType.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ProcessedClassType +{ + public readonly string $className; + public readonly string $namespace; + + /** + * @var array + */ + public array $methods; + public readonly int $startLine; + public int $executableLines; + public int $executedLines; + public int $executableBranches; + public int $executedBranches; + public int $executablePaths; + public int $executedPaths; + public int $ccn; + public float|int $coverage; + public int|string $crap; + public readonly string $link; + + public function __construct( + string $className, + string $namespace, + /** + * @var array + */ + array $methods, + int $startLine, + int $executableLines, + int $executedLines, + int $executableBranches, + int $executedBranches, + int $executablePaths, + int $executedPaths, + int $ccn, + float|int $coverage, + int|string $crap, + string $link, + ) { + $this->className = $className; + $this->namespace = $namespace; + $this->methods = $methods; + $this->startLine = $startLine; + $this->executableLines = $executableLines; + $this->executedLines = $executedLines; + $this->executableBranches = $executableBranches; + $this->executedBranches = $executedBranches; + $this->executablePaths = $executablePaths; + $this->executedPaths = $executedPaths; + $this->ccn = $ccn; + $this->coverage = $coverage; + $this->crap = $crap; + $this->link = $link; + } +} diff --git a/src/Data/ProcessedCodeCoverageData.php b/src/Data/ProcessedCodeCoverageData.php index 57ccbb166..5745ccb08 100644 --- a/src/Data/ProcessedCodeCoverageData.php +++ b/src/Data/ProcessedCodeCoverageData.php @@ -25,23 +25,8 @@ * @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver * * @phpstan-type TestIdType string - * @phpstan-type FunctionCoverageDataType array{ - * branches: array, - * out: array, - * out_hit: array, - * }>, - * paths: array, - * hit: list, - * }>, - * hit: list - * } - * @phpstan-type FunctionCoverageType array> + * @phpstan-type FunctionCoverageType array> + * @phpstan-type LineCoverageType array>> */ final class ProcessedCodeCoverageData { @@ -49,7 +34,7 @@ final class ProcessedCodeCoverageData * Line coverage data. * An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids. * - * @var array>> + * @var LineCoverageType */ private array $lineCoverage = []; @@ -99,24 +84,30 @@ public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverage foreach ($functions as $functionName => $functionData) { foreach ($functionData['branches'] as $branchId => $branchData) { if ($branchData['hit'] === Driver::BRANCH_HIT) { - $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'][] = $testCaseId; + $this->functionCoverage[$file][$functionName]->recordBranchHit($branchId, $testCaseId); } } foreach ($functionData['paths'] as $pathId => $pathData) { if ($pathData['hit'] === Driver::BRANCH_HIT) { - $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'][] = $testCaseId; + $this->functionCoverage[$file][$functionName]->recordPathHit($pathId, $testCaseId); } } } } } + /** + * @param LineCoverageType $lineCoverage + */ public function setLineCoverage(array $lineCoverage): void { $this->lineCoverage = $lineCoverage; } + /** + * @return LineCoverageType + */ public function lineCoverage(): array { ksort($this->lineCoverage); @@ -124,11 +115,17 @@ public function lineCoverage(): array return $this->lineCoverage; } + /** + * @param FunctionCoverageType $functionCoverage + */ public function setFunctionCoverage(array $functionCoverage): void { $this->functionCoverage = $functionCoverage; } + /** + * @return FunctionCoverageType + */ public function functionCoverage(): array { ksort($this->functionCoverage); @@ -136,6 +133,9 @@ public function functionCoverage(): array return $this->functionCoverage; } + /** + * @return array + */ public function coveredFiles(): array { ksort($this->lineCoverage); @@ -198,14 +198,6 @@ public function merge(self $newData): void } else { $this->initPreviouslyUnseenFunction($file, $functionName, $functionData); } - - foreach ($functionData['branches'] as $branchId => $branchData) { - $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'], $branchData['hit'])); - } - - foreach ($functionData['paths'] as $pathId => $pathData) { - $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'], $pathData['hit'])); - } } } } @@ -242,19 +234,15 @@ private function priorityForLine(array $data, int $line): int /** * For a function we have never seen before, copy all data over and simply init the 'hit' array. * - * @param FunctionCoverageDataType|XdebugFunctionCoverageType $functionData + * @param ProcessedFunctionCoverageData|XdebugFunctionCoverageType $functionData */ - private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData): void + private function initPreviouslyUnseenFunction(string $file, string $functionName, array|ProcessedFunctionCoverageData $functionData): void { - $this->functionCoverage[$file][$functionName] = $functionData; - - foreach (array_keys($functionData['branches']) as $branchId) { - $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = []; + if (is_array($functionData)) { + $functionData = ProcessedFunctionCoverageData::fromXdebugCoverage($functionData); } - foreach (array_keys($functionData['paths']) as $pathId) { - $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = []; - } + $this->functionCoverage[$file][$functionName] = $functionData; } /** @@ -262,22 +250,16 @@ private function initPreviouslyUnseenFunction(string $file, string $functionName * Techniques such as mocking and where the contents of a file are different vary during tests (e.g. compiling * containers) mean that the functions inside a file cannot be relied upon to be static. * - * @param FunctionCoverageDataType|XdebugFunctionCoverageType $functionData + * @param ProcessedFunctionCoverageData|XdebugFunctionCoverageType $functionData */ - private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData): void + private function initPreviouslySeenFunction(string $file, string $functionName, array|ProcessedFunctionCoverageData $functionData): void { - foreach ($functionData['branches'] as $branchId => $branchData) { - if (!isset($this->functionCoverage[$file][$functionName]['branches'][$branchId])) { - $this->functionCoverage[$file][$functionName]['branches'][$branchId] = $branchData; - $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = []; - } + if (is_array($functionData)) { + $functionData = ProcessedFunctionCoverageData::fromXdebugCoverage($functionData); } - foreach ($functionData['paths'] as $pathId => $pathData) { - if (!isset($this->functionCoverage[$file][$functionName]['paths'][$pathId])) { - $this->functionCoverage[$file][$functionName]['paths'][$pathId] = $pathData; - $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = []; - } - } + $this->functionCoverage[$file][$functionName] = $this->functionCoverage[$file][$functionName]->merge( + $functionData, + ); } } diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php new file mode 100644 index 000000000..96baa262a --- /dev/null +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +use NoDiscard; +use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @phpstan-import-type TestIdType from ProcessedCodeCoverageData + * @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver + */ +final readonly class ProcessedFunctionCoverageData +{ + /** @var array */ + public array $branches; + + /** @var array */ + public array $paths; + + /** + * @param XdebugFunctionCoverageType $xdebugCoverageData + */ + public static function fromXdebugCoverage(array $xdebugCoverageData): self + { + $branches = []; + + foreach ($xdebugCoverageData['branches'] as $branchId => $branch) { + $branches[$branchId] = ProcessedBranchCoverageData::fromXdebugCoverage($branch); + } + $paths = []; + + foreach ($xdebugCoverageData['paths'] as $pathId => $path) { + $paths[$pathId] = ProcessedPathCoverageData::fromXdebugCoverage($path); + } + + return new self( + $branches, + $paths, + ); + } + + /** + * @param array $branches + * @param array $paths + */ + public function __construct( + array $branches, + array $paths, + ) { + $this->paths = $paths; + $this->branches = $branches; + } + + #[NoDiscard] + public function merge(self $data): self + { + $branches = null; + + if ($data->branches !== $this->branches) { + $branches = $this->branches; + + foreach ($data->branches as $branchId => $branch) { + if (!isset($branches[$branchId])) { + $branches[$branchId] = $branch; + } else { + $branches[$branchId] = $branches[$branchId]->merge($branch); + } + } + } + + $paths = null; + + if ($data->paths !== $this->paths) { + $paths = $this->paths; + + foreach ($data->paths as $pathId => $path) { + if (!isset($paths[$pathId])) { + $paths[$pathId] = $path; + } else { + $paths[$pathId] = $paths[$pathId]->merge($path); + } + } + } + + if ($branches === null && $paths === null) { + return $this; + } + + return new self( + $branches ?? $this->branches, + $paths ?? $this->paths, + ); + } + + /** + * @param TestIdType $testCaseId + */ + public function recordBranchHit(int $branchId, string $testCaseId): void + { + $this->branches[$branchId]->recordHit($testCaseId); + } + + /** + * @param TestIdType $testCaseId + */ + public function recordPathHit(int $pathId, string $testCaseId): void + { + $this->paths[$pathId]->recordHit($testCaseId); + } +} diff --git a/src/Data/ProcessedFunctionType.php b/src/Data/ProcessedFunctionType.php new file mode 100644 index 000000000..ff3a77f3c --- /dev/null +++ b/src/Data/ProcessedFunctionType.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ProcessedFunctionType +{ + public readonly string $functionName; + public readonly string $namespace; + public readonly string $signature; + public readonly int $startLine; + public readonly int $endLine; + public int $executableLines; + public int $executedLines; + public int $executableBranches; + public int $executedBranches; + public int $executablePaths; + public int $executedPaths; + public int $ccn; + public float|int $coverage; + public int|string $crap; + public readonly string $link; + + public function __construct( + string $functionName, + string $namespace, + string $signature, + int $startLine, + int $endLine, + int $executableLines, + int $executedLines, + int $executableBranches, + int $executedBranches, + int $executablePaths, + int $executedPaths, + int $ccn, + float|int $coverage, + int|string $crap, + string $link, + ) { + $this->link = $link; + $this->crap = $crap; + $this->coverage = $coverage; + $this->ccn = $ccn; + $this->executedPaths = $executedPaths; + $this->executablePaths = $executablePaths; + $this->executedBranches = $executedBranches; + $this->executableBranches = $executableBranches; + $this->executedLines = $executedLines; + $this->executableLines = $executableLines; + $this->endLine = $endLine; + $this->startLine = $startLine; + $this->signature = $signature; + $this->namespace = $namespace; + $this->functionName = $functionName; + } +} diff --git a/src/Data/ProcessedMethodType.php b/src/Data/ProcessedMethodType.php new file mode 100644 index 000000000..f1827633f --- /dev/null +++ b/src/Data/ProcessedMethodType.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ProcessedMethodType +{ + public readonly string $methodName; + public readonly string $visibility; + public readonly string $signature; + public readonly int $startLine; + public readonly int $endLine; + public int $executableLines; + public int $executedLines; + public int $executableBranches; + public int $executedBranches; + public int $executablePaths; + public int $executedPaths; + public int $ccn; + public float|int $coverage; + public int|string $crap; + public readonly string $link; + + public function __construct( + string $methodName, + string $visibility, + string $signature, + int $startLine, + int $endLine, + int $executableLines, + int $executedLines, + int $executableBranches, + int $executedBranches, + int $executablePaths, + int $executedPaths, + int $ccn, + float|int $coverage, + int|string $crap, + string $link, + ) { + $this->link = $link; + $this->crap = $crap; + $this->coverage = $coverage; + $this->ccn = $ccn; + $this->executedPaths = $executedPaths; + $this->executablePaths = $executablePaths; + $this->executedBranches = $executedBranches; + $this->executableBranches = $executableBranches; + $this->executedLines = $executedLines; + $this->executableLines = $executableLines; + $this->endLine = $endLine; + $this->startLine = $startLine; + $this->signature = $signature; + $this->visibility = $visibility; + $this->methodName = $methodName; + } +} diff --git a/src/Data/ProcessedPathCoverageData.php b/src/Data/ProcessedPathCoverageData.php new file mode 100644 index 000000000..e7283542d --- /dev/null +++ b/src/Data/ProcessedPathCoverageData.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +use function array_merge; +use function array_unique; +use NoDiscard; +use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @phpstan-import-type TestIdType from ProcessedCodeCoverageData + * @phpstan-import-type XdebugPathCoverageType from XdebugDriver + */ +final class ProcessedPathCoverageData +{ + /** @var array */ + public readonly array $path; + + /** @var list */ + public array $hit; + + /** + * @param XdebugPathCoverageType $xdebugCoverageData + */ + public static function fromXdebugCoverage(array $xdebugCoverageData): self + { + return new self( + $xdebugCoverageData['path'], + [], + ); + } + + /** + * @param array $path + * @param list $hit + */ + public function __construct( + array $path, + array $hit, + ) { + $this->hit = $hit; + $this->path = $path; + } + + #[NoDiscard] + public function merge(self $data): self + { + if ($data->hit === []) { + return $this; + } + + return new self( + $this->path, + array_unique(array_merge($this->hit, $data->hit)), + ); + } + + /** + * @param TestIdType $testCaseId + */ + public function recordHit(string $testCaseId): void + { + $this->hit[] = $testCaseId; + } +} diff --git a/src/Data/ProcessedTraitType.php b/src/Data/ProcessedTraitType.php new file mode 100644 index 000000000..86e7b4687 --- /dev/null +++ b/src/Data/ProcessedTraitType.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Data; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ProcessedTraitType +{ + public readonly string $traitName; + public readonly string $namespace; + + /** + * @var array + */ + public array $methods; + public readonly int $startLine; + public int $executableLines; + public int $executedLines; + public int $executableBranches; + public int $executedBranches; + public int $executablePaths; + public int $executedPaths; + public int $ccn; + public float|int $coverage; + public int|string $crap; + public readonly string $link; + + public function __construct( + string $traitName, + string $namespace, + /** + * @var array + */ + array $methods, + int $startLine, + int $executableLines, + int $executedLines, + int $executableBranches, + int $executedBranches, + int $executablePaths, + int $executedPaths, + int $ccn, + float|int $coverage, + int|string $crap, + string $link, + ) { + $this->link = $link; + $this->crap = $crap; + $this->coverage = $coverage; + $this->ccn = $ccn; + $this->executedPaths = $executedPaths; + $this->executablePaths = $executablePaths; + $this->executedBranches = $executedBranches; + $this->executableBranches = $executableBranches; + $this->executedLines = $executedLines; + $this->executableLines = $executableLines; + $this->startLine = $startLine; + $this->methods = $methods; + $this->namespace = $namespace; + $this->traitName = $traitName; + } +} diff --git a/src/Data/RawCodeCoverageData.php b/src/Data/RawCodeCoverageData.php index f6e847ae3..cbe7a9a3a 100644 --- a/src/Data/RawCodeCoverageData.php +++ b/src/Data/RawCodeCoverageData.php @@ -104,8 +104,6 @@ private function __construct(array $lineCoverage, array $functionCoverage) { $this->lineCoverage = $lineCoverage; $this->functionCoverage = $functionCoverage; - - $this->skipEmptyLines(); } public function clear(): void @@ -252,7 +250,7 @@ public function removeCoverageDataForLines(string $filename, array $lines): void * * @see https://github.com/sebastianbergmann/php-code-coverage/issues/799 */ - private function skipEmptyLines(): void + public function skipEmptyLines(): void { foreach ($this->lineCoverage as $filename => $coverage) { foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) { diff --git a/src/Node/AbstractNode.php b/src/Node/AbstractNode.php index 7e82a3daf..3b54d82b1 100644 --- a/src/Node/AbstractNode.php +++ b/src/Node/AbstractNode.php @@ -15,15 +15,14 @@ use function str_replace; use function substr; use Countable; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\StaticAnalysis\LinesOfCode; use SebastianBergmann\CodeCoverage\Util\Percentage; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - * - * @phpstan-import-type ProcessedFunctionType from File - * @phpstan-import-type ProcessedClassType from File - * @phpstan-import-type ProcessedTraitType from File */ abstract class AbstractNode implements Countable { @@ -186,11 +185,11 @@ public function cyclomaticComplexity(): int $ccn = 0; foreach ($this->classesAndTraits() as $classLike) { - $ccn += $classLike['ccn']; + $ccn += $classLike->ccn; } foreach ($this->functions() as $function) { - $ccn += $function['ccn']; + $ccn += $function->ccn; } return $ccn; diff --git a/src/Node/Builder.php b/src/Node/Builder.php index 19fc3a24d..9a2efe145 100644 --- a/src/Node/Builder.php +++ b/src/Node/Builder.php @@ -140,6 +140,9 @@ private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array { $result = []; + $lineCoverage = $data->lineCoverage(); + $functionCoverage = $data->functionCoverage(); + foreach ($data->coveredFiles() as $originalPath) { $path = explode(DIRECTORY_SEPARATOR, $originalPath); $pointer = &$result; @@ -156,8 +159,8 @@ private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array } $pointer = [ - 'lineCoverage' => $data->lineCoverage()[$originalPath] ?? [], - 'functionCoverage' => $data->functionCoverage()[$originalPath] ?? [], + 'lineCoverage' => $lineCoverage[$originalPath] ?? [], + 'functionCoverage' => $functionCoverage[$originalPath] ?? [], ]; } @@ -203,12 +206,14 @@ private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array */ private function reducePaths(ProcessedCodeCoverageData $coverage): string { - if ($coverage->coveredFiles() === []) { + $coveredFiles = $coverage->coveredFiles(); + + if ($coveredFiles === []) { return '.'; } $commonPath = ''; - $paths = $coverage->coveredFiles(); + $paths = $coveredFiles; if (count($paths) === 1) { $commonPath = dirname($paths[0]) . DIRECTORY_SEPARATOR; @@ -260,7 +265,7 @@ private function reducePaths(ProcessedCodeCoverageData $coverage): string } } - $original = $coverage->coveredFiles(); + $original = $coveredFiles; $max = count($original); for ($i = 0; $i < $max; $i++) { diff --git a/src/Node/Directory.php b/src/Node/Directory.php index 2802f93ab..818d665dd 100644 --- a/src/Node/Directory.php +++ b/src/Node/Directory.php @@ -14,15 +14,14 @@ use function count; use IteratorAggregate; use RecursiveIteratorIterator; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\StaticAnalysis\LinesOfCode; /** * @template-implements IteratorAggregate * - * @phpstan-import-type ProcessedFunctionType from File - * @phpstan-import-type ProcessedClassType from File - * @phpstan-import-type ProcessedTraitType from File - * * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Directory extends AbstractNode implements IteratorAggregate diff --git a/src/Node/File.php b/src/Node/File.php index 54ee70b4a..3c27fde30 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -13,6 +13,12 @@ use function count; use function range; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\StaticAnalysis\AnalysisResult; use SebastianBergmann\CodeCoverage\StaticAnalysis\Class_; use SebastianBergmann\CodeCoverage\StaticAnalysis\Function_; @@ -25,73 +31,6 @@ * * @phpstan-import-type TestType from CodeCoverage * @phpstan-import-type LinesType from AnalysisResult - * - * @phpstan-type ProcessedFunctionType array{ - * functionName: string, - * namespace: string, - * signature: string, - * startLine: int, - * endLine: int, - * executableLines: int, - * executedLines: int, - * executableBranches: int, - * executedBranches: int, - * executablePaths: int, - * executedPaths: int, - * ccn: int, - * coverage: int|float, - * crap: int|string, - * link: string - * } - * @phpstan-type ProcessedMethodType array{ - * methodName: string, - * visibility: string, - * signature: string, - * startLine: int, - * endLine: int, - * executableLines: int, - * executedLines: int, - * executableBranches: int, - * executedBranches: int, - * executablePaths: int, - * executedPaths: int, - * ccn: int, - * coverage: float|int, - * crap: int|string, - * link: string - * } - * @phpstan-type ProcessedClassType array{ - * className: string, - * namespace: string, - * methods: array, - * startLine: int, - * executableLines: int, - * executedLines: int, - * executableBranches: int, - * executedBranches: int, - * executablePaths: int, - * executedPaths: int, - * ccn: int, - * coverage: int|float, - * crap: int|string, - * link: string - * } - * @phpstan-type ProcessedTraitType array{ - * traitName: string, - * namespace: string, - * methods: array, - * startLine: int, - * executableLines: int, - * executedLines: int, - * executableBranches: int, - * executedBranches: int, - * executablePaths: int, - * executedPaths: int, - * ccn: int, - * coverage: float|int, - * crap: int|string, - * link: string - * } */ final class File extends AbstractNode { @@ -136,7 +75,7 @@ final class File extends AbstractNode private ?int $numTestedFunctions = null; /** - * @var array + * @var array */ private array $codeUnitsByLine = []; @@ -250,8 +189,8 @@ public function numberOfClasses(): int $this->numClasses = 0; foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0) { + foreach ($class->methods as $method) { + if ($method->executableLines > 0) { $this->numClasses++; continue 2; @@ -274,8 +213,8 @@ public function numberOfTraits(): int $this->numTraits = 0; foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0) { + foreach ($trait->methods as $method) { + if ($method->executableLines > 0) { $this->numTraits++; continue 2; @@ -298,16 +237,16 @@ public function numberOfMethods(): int $this->numMethods = 0; foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0) { + foreach ($class->methods as $method) { + if ($method->executableLines > 0) { $this->numMethods++; } } } foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0) { + foreach ($trait->methods as $method) { + if ($method->executableLines > 0) { $this->numMethods++; } } @@ -323,18 +262,18 @@ public function numberOfTestedMethods(): int $this->numTestedMethods = 0; foreach ($this->classes as $class) { - foreach ($class['methods'] as $method) { - if ($method['executableLines'] > 0 && - $method['coverage'] === 100) { + foreach ($class->methods as $method) { + if ($method->executableLines > 0 && + $method->coverage === 100) { $this->numTestedMethods++; } } } foreach ($this->traits as $trait) { - foreach ($trait['methods'] as $method) { - if ($method['executableLines'] > 0 && - $method['coverage'] === 100) { + foreach ($trait->methods as $method) { + if ($method->executableLines > 0 && + $method->coverage === 100) { $this->numTestedMethods++; } } @@ -355,8 +294,8 @@ public function numberOfTestedFunctions(): int $this->numTestedFunctions = 0; foreach ($this->functions as $function) { - if ($function['executableLines'] > 0 && - $function['coverage'] === 100) { + if ($function->executableLines > 0 && + $function->coverage === 100) { $this->numTestedFunctions++; } } @@ -383,7 +322,7 @@ private function calculateStatistics(array $classes, array $traits, array $funct foreach (range(1, $this->linesOfCode->linesOfCode()) as $lineNumber) { if (isset($this->lineCoverageData[$lineNumber])) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - $codeUnit['executableLines']++; + $codeUnit->executableLines++; } unset($codeUnit); @@ -392,7 +331,7 @@ private function calculateStatistics(array $classes, array $traits, array $funct if (count($this->lineCoverageData[$lineNumber]) > 0) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - $codeUnit['executedLines']++; + $codeUnit->executedLines++; } unset($codeUnit); @@ -403,27 +342,27 @@ private function calculateStatistics(array $classes, array $traits, array $funct } foreach ($this->traits as &$trait) { - foreach ($trait['methods'] as &$method) { - $methodLineCoverage = $method['executableLines'] > 0 ? ($method['executedLines'] / $method['executableLines']) * 100 : 100; - $methodBranchCoverage = $method['executableBranches'] > 0 ? ($method['executedBranches'] / $method['executableBranches']) * 100 : 0; - $methodPathCoverage = $method['executablePaths'] > 0 ? ($method['executedPaths'] / $method['executablePaths']) * 100 : 0; + foreach ($trait->methods as &$method) { + $methodLineCoverage = $method->executableLines > 0 ? ($method->executedLines / $method->executableLines) * 100 : 100; + $methodBranchCoverage = $method->executableBranches > 0 ? ($method->executedBranches / $method->executableBranches) * 100 : 0; + $methodPathCoverage = $method->executablePaths > 0 ? ($method->executedPaths / $method->executablePaths) * 100 : 0; - $method['coverage'] = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; - $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); + $method->coverage = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; + $method->crap = (new CrapIndex($method->ccn, $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); - $trait['ccn'] += $method['ccn']; + $trait->ccn += $method->ccn; } unset($method); - $traitLineCoverage = $trait['executableLines'] > 0 ? ($trait['executedLines'] / $trait['executableLines']) * 100 : 100; - $traitBranchCoverage = $trait['executableBranches'] > 0 ? ($trait['executedBranches'] / $trait['executableBranches']) * 100 : 0; - $traitPathCoverage = $trait['executablePaths'] > 0 ? ($trait['executedPaths'] / $trait['executablePaths']) * 100 : 0; + $traitBranchCoverage = $trait->executableBranches > 0 ? ($trait->executedBranches / $trait->executableBranches) * 100 : 0; + $traitLineCoverage = $trait->executableLines > 0 ? ($trait->executedLines / $trait->executableLines) * 100 : 100; + $traitPathCoverage = $trait->executablePaths > 0 ? ($trait->executedPaths / $trait->executablePaths) * 100 : 0; - $trait['coverage'] = $traitBranchCoverage > 0 ? $traitBranchCoverage : $traitLineCoverage; - $trait['crap'] = (new CrapIndex($trait['ccn'], $traitPathCoverage > 0 ? $traitPathCoverage : $traitLineCoverage))->asString(); + $trait->coverage = $traitBranchCoverage > 0 ? $traitBranchCoverage : $traitLineCoverage; + $trait->crap = (new CrapIndex($trait->ccn, $traitPathCoverage > 0 ? $traitPathCoverage : $traitLineCoverage))->asString(); - if ($trait['executableLines'] > 0 && $trait['coverage'] === 100) { + if ($trait->executableLines > 0 && $trait->coverage === 100) { $this->numTestedClasses++; } } @@ -431,27 +370,27 @@ private function calculateStatistics(array $classes, array $traits, array $funct unset($trait); foreach ($this->classes as &$class) { - foreach ($class['methods'] as &$method) { - $methodLineCoverage = $method['executableLines'] > 0 ? ($method['executedLines'] / $method['executableLines']) * 100 : 100; - $methodBranchCoverage = $method['executableBranches'] > 0 ? ($method['executedBranches'] / $method['executableBranches']) * 100 : 0; - $methodPathCoverage = $method['executablePaths'] > 0 ? ($method['executedPaths'] / $method['executablePaths']) * 100 : 0; + foreach ($class->methods as &$method) { + $methodLineCoverage = $method->executableLines > 0 ? ($method->executedLines / $method->executableLines) * 100 : 100; + $methodBranchCoverage = $method->executableBranches > 0 ? ($method->executedBranches / $method->executableBranches) * 100 : 0; + $methodPathCoverage = $method->executablePaths > 0 ? ($method->executedPaths / $method->executablePaths) * 100 : 0; - $method['coverage'] = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; - $method['crap'] = (new CrapIndex($method['ccn'], $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); + $method->coverage = $methodBranchCoverage > 0 ? $methodBranchCoverage : $methodLineCoverage; + $method->crap = (new CrapIndex($method->ccn, $methodPathCoverage > 0 ? $methodPathCoverage : $methodLineCoverage))->asString(); - $class['ccn'] += $method['ccn']; + $class->ccn += $method->ccn; } unset($method); - $classLineCoverage = $class['executableLines'] > 0 ? ($class['executedLines'] / $class['executableLines']) * 100 : 100; - $classBranchCoverage = $class['executableBranches'] > 0 ? ($class['executedBranches'] / $class['executableBranches']) * 100 : 0; - $classPathCoverage = $class['executablePaths'] > 0 ? ($class['executedPaths'] / $class['executablePaths']) * 100 : 0; + $classLineCoverage = $class->executableLines > 0 ? ($class->executedLines / $class->executableLines) * 100 : 100; + $classBranchCoverage = $class->executableBranches > 0 ? ($class->executedBranches / $class->executableBranches) * 100 : 0; + $classPathCoverage = $class->executablePaths > 0 ? ($class->executedPaths / $class->executablePaths) * 100 : 0; - $class['coverage'] = $classBranchCoverage > 0 ? $classBranchCoverage : $classLineCoverage; - $class['crap'] = (new CrapIndex($class['ccn'], $classPathCoverage > 0 ? $classPathCoverage : $classLineCoverage))->asString(); + $class->coverage = $classBranchCoverage > 0 ? $classBranchCoverage : $classLineCoverage; + $class->crap = (new CrapIndex($class->ccn, $classPathCoverage > 0 ? $classPathCoverage : $classLineCoverage))->asString(); - if ($class['executableLines'] > 0 && $class['coverage'] === 100) { + if ($class->executableLines > 0 && $class->coverage === 100) { $this->numTestedClasses++; } } @@ -459,14 +398,14 @@ private function calculateStatistics(array $classes, array $traits, array $funct unset($class); foreach ($this->functions as &$function) { - $functionLineCoverage = $function['executableLines'] > 0 ? ($function['executedLines'] / $function['executableLines']) * 100 : 100; - $functionBranchCoverage = $function['executableBranches'] > 0 ? ($function['executedBranches'] / $function['executableBranches']) * 100 : 0; - $functionPathCoverage = $function['executablePaths'] > 0 ? ($function['executedPaths'] / $function['executablePaths']) * 100 : 0; + $functionLineCoverage = $function->executableLines > 0 ? ($function->executedLines / $function->executableLines) * 100 : 100; + $functionBranchCoverage = $function->executableBranches > 0 ? ($function->executedBranches / $function->executableBranches) * 100 : 0; + $functionPathCoverage = $function->executablePaths > 0 ? ($function->executedPaths / $function->executablePaths) * 100 : 0; - $function['coverage'] = $functionBranchCoverage > 0 ? $functionBranchCoverage : $functionLineCoverage; - $function['crap'] = (new CrapIndex($function['ccn'], $functionPathCoverage > 0 ? $functionPathCoverage : $functionLineCoverage))->asString(); + $function->coverage = $functionBranchCoverage > 0 ? $functionBranchCoverage : $functionLineCoverage; + $function->crap = (new CrapIndex($function->ccn, $functionPathCoverage > 0 ? $functionPathCoverage : $functionLineCoverage))->asString(); - if ($function['coverage'] === 100) { + if ($function->coverage === 100) { $this->numTestedFunctions++; } } @@ -480,41 +419,41 @@ private function processClasses(array $classes): void $link = $this->id() . '.html#'; foreach ($classes as $className => $class) { - $this->classes[$className] = [ - 'className' => $className, - 'namespace' => $class->namespace(), - 'methods' => [], - 'startLine' => $class->startLine(), - 'executableLines' => 0, - 'executedLines' => 0, - 'executableBranches' => 0, - 'executedBranches' => 0, - 'executablePaths' => 0, - 'executedPaths' => 0, - 'ccn' => 0, - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $class->startLine(), - ]; + $this->classes[$className] = new ProcessedClassType( + $className, + $class->namespace(), + [], + $class->startLine(), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + $link . $class->startLine(), + ); foreach ($class->methods() as $methodName => $method) { - $methodData = $this->newMethod($className, $method, $link); - $this->classes[$className]['methods'][$methodName] = $methodData; + $methodData = $this->newMethod($className, $method, $link); + $this->classes[$className]->methods[$methodName] = $methodData; - $this->classes[$className]['executableBranches'] += $methodData['executableBranches']; - $this->classes[$className]['executedBranches'] += $methodData['executedBranches']; - $this->classes[$className]['executablePaths'] += $methodData['executablePaths']; - $this->classes[$className]['executedPaths'] += $methodData['executedPaths']; + $this->classes[$className]->executableBranches += $methodData->executableBranches; + $this->classes[$className]->executedBranches += $methodData->executedBranches; + $this->classes[$className]->executablePaths += $methodData->executablePaths; + $this->classes[$className]->executedPaths += $methodData->executedPaths; - $this->numExecutableBranches += $methodData['executableBranches']; - $this->numExecutedBranches += $methodData['executedBranches']; - $this->numExecutablePaths += $methodData['executablePaths']; - $this->numExecutedPaths += $methodData['executedPaths']; + $this->numExecutableBranches += $methodData->executableBranches; + $this->numExecutedBranches += $methodData->executedBranches; + $this->numExecutablePaths += $methodData->executablePaths; + $this->numExecutedPaths += $methodData->executedPaths; foreach (range($method->startLine(), $method->endLine()) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [ &$this->classes[$className], - &$this->classes[$className]['methods'][$methodName], + &$this->classes[$className]->methods[$methodName], ]; } } @@ -529,41 +468,41 @@ private function processTraits(array $traits): void $link = $this->id() . '.html#'; foreach ($traits as $traitName => $trait) { - $this->traits[$traitName] = [ - 'traitName' => $traitName, - 'namespace' => $trait->namespace(), - 'methods' => [], - 'startLine' => $trait->startLine(), - 'executableLines' => 0, - 'executedLines' => 0, - 'executableBranches' => 0, - 'executedBranches' => 0, - 'executablePaths' => 0, - 'executedPaths' => 0, - 'ccn' => 0, - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $trait->startLine(), - ]; + $this->traits[$traitName] = new ProcessedTraitType( + $traitName, + $trait->namespace(), + [], + $trait->startLine(), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + $link . $trait->startLine(), + ); foreach ($trait->methods() as $methodName => $method) { - $methodData = $this->newMethod($traitName, $method, $link); - $this->traits[$traitName]['methods'][$methodName] = $methodData; + $methodData = $this->newMethod($traitName, $method, $link); + $this->traits[$traitName]->methods[$methodName] = $methodData; - $this->traits[$traitName]['executableBranches'] += $methodData['executableBranches']; - $this->traits[$traitName]['executedBranches'] += $methodData['executedBranches']; - $this->traits[$traitName]['executablePaths'] += $methodData['executablePaths']; - $this->traits[$traitName]['executedPaths'] += $methodData['executedPaths']; + $this->traits[$traitName]->executableBranches += $methodData->executableBranches; + $this->traits[$traitName]->executedBranches += $methodData->executedBranches; + $this->traits[$traitName]->executablePaths += $methodData->executablePaths; + $this->traits[$traitName]->executedPaths += $methodData->executedPaths; - $this->numExecutableBranches += $methodData['executableBranches']; - $this->numExecutedBranches += $methodData['executedBranches']; - $this->numExecutablePaths += $methodData['executablePaths']; - $this->numExecutedPaths += $methodData['executedPaths']; + $this->numExecutableBranches += $methodData->executableBranches; + $this->numExecutedBranches += $methodData->executedBranches; + $this->numExecutablePaths += $methodData->executablePaths; + $this->numExecutedPaths += $methodData->executedPaths; foreach (range($method->startLine(), $method->endLine()) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [ &$this->traits[$traitName], - &$this->traits[$traitName]['methods'][$methodName], + &$this->traits[$traitName]->methods[$methodName], ]; } } @@ -578,124 +517,125 @@ private function processFunctions(array $functions): void $link = $this->id() . '.html#'; foreach ($functions as $functionName => $function) { - $this->functions[$functionName] = [ - 'functionName' => $functionName, - 'namespace' => $function->namespace(), - 'signature' => $function->signature(), - 'startLine' => $function->startLine(), - 'endLine' => $function->endLine(), - 'executableLines' => 0, - 'executedLines' => 0, - 'executableBranches' => 0, - 'executedBranches' => 0, - 'executablePaths' => 0, - 'executedPaths' => 0, - 'ccn' => $function->cyclomaticComplexity(), - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $function->startLine(), - ]; + $this->functions[$functionName] = new ProcessedFunctionType( + $functionName, + $function->namespace(), + $function->signature(), + $function->startLine(), + $function->endLine(), + 0, + 0, + 0, + 0, + 0, + 0, + $function->cyclomaticComplexity(), + 0, + 0, + $link . $function->startLine(), + ); foreach (range($function->startLine(), $function->endLine()) as $lineNumber) { $this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]]; } - if (isset($this->functionCoverageData[$functionName]['branches'])) { - $this->functions[$functionName]['executableBranches'] = count( - $this->functionCoverageData[$functionName]['branches'], + if (isset($this->functionCoverageData[$functionName])) { + $this->functions[$functionName]->executableBranches = count( + $this->functionCoverageData[$functionName]->branches, ); - $this->functions[$functionName]['executedBranches'] = count( + $this->functions[$functionName]->executedBranches = count( array_filter( - $this->functionCoverageData[$functionName]['branches'], - static function (array $branch) + $this->functionCoverageData[$functionName]->branches, + static function (ProcessedBranchCoverageData $branch) { - return (bool) $branch['hit']; + return (bool) $branch->hit; }, ), ); } - if (isset($this->functionCoverageData[$functionName]['paths'])) { - $this->functions[$functionName]['executablePaths'] = count( - $this->functionCoverageData[$functionName]['paths'], + if (isset($this->functionCoverageData[$functionName])) { + $this->functions[$functionName]->executablePaths = count( + $this->functionCoverageData[$functionName]->paths, ); - $this->functions[$functionName]['executedPaths'] = count( + $this->functions[$functionName]->executedPaths = count( array_filter( - $this->functionCoverageData[$functionName]['paths'], - static function (array $path) + $this->functionCoverageData[$functionName]->paths, + static function (ProcessedPathCoverageData $path) { - return (bool) $path['hit']; + return (bool) $path->hit; }, ), ); } - $this->numExecutableBranches += $this->functions[$functionName]['executableBranches']; - $this->numExecutedBranches += $this->functions[$functionName]['executedBranches']; - $this->numExecutablePaths += $this->functions[$functionName]['executablePaths']; - $this->numExecutedPaths += $this->functions[$functionName]['executedPaths']; + $this->numExecutableBranches += $this->functions[$functionName]->executableBranches; + $this->numExecutedBranches += $this->functions[$functionName]->executedBranches; + $this->numExecutablePaths += $this->functions[$functionName]->executablePaths; + $this->numExecutedPaths += $this->functions[$functionName]->executedPaths; } } - /** - * @return ProcessedMethodType - */ - private function newMethod(string $className, Method $method, string $link): array + private function newMethod(string $className, Method $method, string $link): ProcessedMethodType { - $methodData = [ - 'methodName' => $method->name(), - 'visibility' => $method->visibility()->value, - 'signature' => $method->signature(), - 'startLine' => $method->startLine(), - 'endLine' => $method->endLine(), - 'executableLines' => 0, - 'executedLines' => 0, - 'executableBranches' => 0, - 'executedBranches' => 0, - 'executablePaths' => 0, - 'executedPaths' => 0, - 'ccn' => $method->cyclomaticComplexity(), - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $method->startLine(), - ]; - $key = $className . '->' . $method->name(); - if (isset($this->functionCoverageData[$key]['branches'])) { - $methodData['executableBranches'] = count( - $this->functionCoverageData[$key]['branches'], + $executableBranches = 0; + $executedBranches = 0; + + if (isset($this->functionCoverageData[$key])) { + $executableBranches = count( + $this->functionCoverageData[$key]->branches, ); - $methodData['executedBranches'] = count( + $executedBranches = count( array_filter( - $this->functionCoverageData[$key]['branches'], - static function (array $branch) + $this->functionCoverageData[$key]->branches, + static function (ProcessedBranchCoverageData $branch) { - return (bool) $branch['hit']; + return (bool) $branch->hit; }, ), ); } - if (isset($this->functionCoverageData[$key]['paths'])) { - $methodData['executablePaths'] = count( - $this->functionCoverageData[$key]['paths'], + $executablePaths = 0; + $executedPaths = 0; + + if (isset($this->functionCoverageData[$key])) { + $executablePaths = count( + $this->functionCoverageData[$key]->paths, ); - $methodData['executedPaths'] = count( + $executedPaths = count( array_filter( - $this->functionCoverageData[$key]['paths'], - static function (array $path) + $this->functionCoverageData[$key]->paths, + static function (ProcessedPathCoverageData $path) { - return (bool) $path['hit']; + return (bool) $path->hit; }, ), ); } - return $methodData; + return new ProcessedMethodType( + $method->name(), + $method->visibility()->value, + $method->signature(), + $method->startLine(), + $method->endLine(), + 0, + 0, + $executableBranches, + $executedBranches, + $executablePaths, + $executedPaths, + $method->cyclomaticComplexity(), + 0, + 0, + $link . $method->startLine(), + ); } } diff --git a/src/Report/Clover.php b/src/Report/Clover.php index a5f1c09e6..8e59d4c65 100644 --- a/src/Report/Clover.php +++ b/src/Report/Clover.php @@ -10,31 +10,31 @@ namespace SebastianBergmann\CodeCoverage\Report; use function count; -use function dirname; -use function file_put_contents; use function is_string; use function ksort; use function max; use function range; -use function str_contains; use function time; use DOMDocument; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Node\File; use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Util\Xml; use SebastianBergmann\CodeCoverage\WriteOperationFailedException; final class Clover { /** + * @param null|non-empty-string $target + * @param null|non-empty-string $name + * * @throws WriteOperationFailedException */ public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string { $time = (string) time(); - $xmlDocument = new DOMDocument('1.0', 'UTF-8'); - $xmlDocument->formatOutput = true; + $xmlDocument = new DOMDocument('1.0', 'UTF-8'); $xmlCoverage = $xmlDocument->createElement('coverage'); $xmlCoverage->setAttribute('generated', $time); @@ -74,39 +74,39 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $classMethods = 0; // Assumption: one namespace per file - if ($class['namespace'] !== '') { - $namespace = $class['namespace']; + if ($class->namespace !== '') { + $namespace = $class->namespace; } - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { /** @phpstan-ignore equal.notAllowed */ - if ($method['executableLines'] == 0) { + if ($method->executableLines == 0) { continue; } $classMethods++; - $classStatements += $method['executableLines']; - $coveredClassStatements += $method['executedLines']; + $classStatements += $method->executableLines; + $coveredClassStatements += $method->executedLines; /** @phpstan-ignore equal.notAllowed */ - if ($method['coverage'] == 100) { + if ($method->coverage == 100) { $coveredMethods++; } $methodCount = 0; - foreach (range($method['startLine'], $method['endLine']) as $line) { + foreach (range($method->startLine, $method->endLine) as $line) { if (isset($coverageData[$line])) { $methodCount = max($methodCount, count($coverageData[$line])); } } - $lines[$method['startLine']] = [ - 'ccn' => $method['ccn'], + $lines[$method->startLine] = [ + 'ccn' => $method->ccn, 'count' => $methodCount, - 'crap' => $method['crap'], + 'crap' => $method->crap, 'type' => 'method', - 'visibility' => $method['visibility'], + 'visibility' => $method->visibility, 'name' => $methodName, ]; } @@ -118,15 +118,15 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $xmlFile->appendChild($xmlClass); $xmlMetrics = $xmlDocument->createElement('metrics'); - $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); + $xmlMetrics->setAttribute('complexity', (string) $class->ccn); $xmlMetrics->setAttribute('methods', (string) $classMethods); $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods); - $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']); - $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']); + $xmlMetrics->setAttribute('conditionals', (string) $class->executableBranches); + $xmlMetrics->setAttribute('coveredconditionals', (string) $class->executedBranches); $xmlMetrics->setAttribute('statements', (string) $classStatements); $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); - $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches'])); - $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches'])); + $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class->executableBranches)); + $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class->executedBranches)); $xmlClass->appendChild($xmlMetrics); } @@ -216,16 +216,10 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $xmlMetrics->setAttribute('coveredelements', (string) ($report->numberOfTestedMethods() + $report->numberOfExecutedLines() + $report->numberOfExecutedBranches())); $xmlProject->appendChild($xmlMetrics); - $buffer = $xmlDocument->saveXML(); + $buffer = Xml::asString($xmlDocument); if ($target !== null) { - if (!str_contains($target, '://')) { - Filesystem::createDirectory(dirname($target)); - } - - if (@file_put_contents($target, $buffer) === false) { - throw new WriteOperationFailedException($target); - } + Filesystem::write($target, $buffer); } return $buffer; diff --git a/src/Report/Cobertura.php b/src/Report/Cobertura.php index 51786e5df..38f0e79ee 100644 --- a/src/Report/Cobertura.php +++ b/src/Report/Cobertura.php @@ -12,22 +12,22 @@ use const DIRECTORY_SEPARATOR; use function basename; use function count; -use function dirname; -use function file_put_contents; use function preg_match; use function range; -use function str_contains; use function str_replace; use function time; use DOMImplementation; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Node\File; use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Util\Xml; use SebastianBergmann\CodeCoverage\WriteOperationFailedException; final class Cobertura { /** + * @param null|non-empty-string $target + * * @throws WriteOperationFailedException */ public function process(CodeCoverage $coverage, ?string $target = null): string @@ -44,10 +44,9 @@ public function process(CodeCoverage $coverage, ?string $target = null): string 'http://cobertura.sourceforge.net/xml/coverage-04.dtd', ); - $document = $implementation->createDocument('', '', $documentType); - $document->xmlVersion = '1.0'; - $document->encoding = 'UTF-8'; - $document->formatOutput = true; + $document = $implementation->createDocument('', '', $documentType); + $document->xmlVersion = '1.0'; + $document->encoding = 'UTF-8'; $coverageElement = $document->createElement('coverage'); @@ -115,15 +114,15 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $coverageData = $item->lineCoverageData(); foreach ($classes as $className => $class) { - $complexity += $class['ccn']; - $packageComplexity += $class['ccn']; + $complexity += $class->ccn; + $packageComplexity += $class->ccn; - $linesValid = $class['executableLines']; - $linesCovered = $class['executedLines']; + $linesValid = $class->executableLines; + $linesCovered = $class->executedLines; $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid); - $branchesValid = $class['executableBranches']; - $branchesCovered = $class['executedBranches']; + $branchesValid = $class->executableBranches; + $branchesCovered = $class->executedBranches; $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); $classElement = $document->createElement('class'); @@ -132,7 +131,7 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $classElement->setAttribute('filename', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString())); $classElement->setAttribute('line-rate', (string) $lineRate); $classElement->setAttribute('branch-rate', (string) $branchRate); - $classElement->setAttribute('complexity', (string) $class['ccn']); + $classElement->setAttribute('complexity', (string) $class->ccn); $classesElement->appendChild($classElement); @@ -144,19 +143,19 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $classElement->appendChild($classLinesElement); - foreach ($class['methods'] as $methodName => $method) { - if ($method['executableLines'] === 0) { + foreach ($class->methods as $methodName => $method) { + if ($method->executableLines === 0) { continue; } - preg_match("/\((.*?)\)/", $method['signature'], $signature); + preg_match("/\((.*?)\)/", $method->signature, $signature); - $linesValid = $method['executableLines']; - $linesCovered = $method['executedLines']; + $linesValid = $method->executableLines; + $linesCovered = $method->executedLines; $lineRate = $linesCovered / $linesValid; - $branchesValid = $method['executableBranches']; - $branchesCovered = $method['executedBranches']; + $branchesValid = $method->executableBranches; + $branchesCovered = $method->executedBranches; $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); $methodElement = $document->createElement('method'); @@ -165,13 +164,13 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $methodElement->setAttribute('signature', $signature[1]); $methodElement->setAttribute('line-rate', (string) $lineRate); $methodElement->setAttribute('branch-rate', (string) $branchRate); - $methodElement->setAttribute('complexity', (string) $method['ccn']); + $methodElement->setAttribute('complexity', (string) $method->ccn); $methodLinesElement = $document->createElement('lines'); $methodElement->appendChild($methodLinesElement); - foreach (range($method['startLine'], $method['endLine']) as $line) { + foreach (range($method->startLine, $method->endLine) as $line) { if (!isset($coverageData[$line])) { continue; } @@ -218,23 +217,23 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $functions = $item->functions(); foreach ($functions as $functionName => $function) { - if ($function['executableLines'] === 0) { + if ($function->executableLines === 0) { continue; } - $complexity += $function['ccn']; - $packageComplexity += $function['ccn']; - $functionsComplexity += $function['ccn']; + $complexity += $function->ccn; + $packageComplexity += $function->ccn; + $functionsComplexity += $function->ccn; - $linesValid = $function['executableLines']; - $linesCovered = $function['executedLines']; + $linesValid = $function->executableLines; + $linesCovered = $function->executedLines; $lineRate = $linesCovered / $linesValid; $functionsLinesValid += $linesValid; $functionsLinesCovered += $linesCovered; - $branchesValid = $function['executableBranches']; - $branchesCovered = $function['executedBranches']; + $branchesValid = $function->executableBranches; + $branchesCovered = $function->executedBranches; $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); $functionsBranchesValid += $branchesValid; @@ -243,16 +242,16 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $methodElement = $document->createElement('method'); $methodElement->setAttribute('name', $functionName); - $methodElement->setAttribute('signature', $function['signature']); + $methodElement->setAttribute('signature', $function->signature); $methodElement->setAttribute('line-rate', (string) $lineRate); $methodElement->setAttribute('branch-rate', (string) $branchRate); - $methodElement->setAttribute('complexity', (string) $function['ccn']); + $methodElement->setAttribute('complexity', (string) $function->ccn); $methodLinesElement = $document->createElement('lines'); $methodElement->appendChild($methodLinesElement); - foreach (range($function['startLine'], $function['endLine']) as $line) { + foreach (range($function->startLine, $function->endLine) as $line) { if (!isset($coverageData[$line])) { continue; } @@ -289,16 +288,10 @@ public function process(CodeCoverage $coverage, ?string $target = null): string $coverageElement->setAttribute('complexity', (string) $complexity); - $buffer = $document->saveXML(); + $buffer = Xml::asString($document); if ($target !== null) { - if (!str_contains($target, '://')) { - Filesystem::createDirectory(dirname($target)); - } - - if (@file_put_contents($target, $buffer) === false) { - throw new WriteOperationFailedException($target); - } + Filesystem::write($target, $buffer); } return $buffer; diff --git a/src/Report/Crap4j.php b/src/Report/Crap4j.php index 57fabfce5..b015908b4 100644 --- a/src/Report/Crap4j.php +++ b/src/Report/Crap4j.php @@ -10,16 +10,14 @@ namespace SebastianBergmann\CodeCoverage\Report; use function date; -use function dirname; -use function file_put_contents; use function htmlspecialchars; use function is_string; use function round; -use function str_contains; use DOMDocument; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Node\File; use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Util\Xml; use SebastianBergmann\CodeCoverage\WriteOperationFailedException; final readonly class Crap4j @@ -32,12 +30,14 @@ public function __construct(int $threshold = 30) } /** + * @param null|non-empty-string $target + * @param null|non-empty-string $name + * * @throws WriteOperationFailedException */ public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string { - $document = new DOMDocument('1.0', 'UTF-8'); - $document->formatOutput = true; + $document = new DOMDocument('1.0', 'UTF-8'); $root = $document->createElement('crap_result'); $document->appendChild($root); @@ -70,31 +70,31 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $classes = $item->classesAndTraits(); foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { - $crapLoad = $this->crapLoad((float) $method['crap'], $method['ccn'], $method['coverage']); + foreach ($class->methods as $methodName => $method) { + $crapLoad = $this->crapLoad((float) $method->crap, $method->ccn, $method->coverage); - $fullCrap += $method['crap']; + $fullCrap += $method->crap; $fullCrapLoad += $crapLoad; $fullMethodCount++; - if ($method['crap'] >= $this->threshold) { + if ($method->crap >= $this->threshold) { $fullCrapMethodCount++; } $methodNode = $document->createElement('method'); - if ($class['namespace'] !== '') { - $namespace = $class['namespace']; + if ($class->namespace !== '') { + $namespace = $class->namespace; } $methodNode->appendChild($document->createElement('package', $namespace)); $methodNode->appendChild($document->createElement('className', $className)); $methodNode->appendChild($document->createElement('methodName', $methodName)); - $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature']))); - $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature']))); - $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue((float) $method['crap']))); - $methodNode->appendChild($document->createElement('complexity', (string) $method['ccn'])); - $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method['coverage']))); + $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method->signature))); + $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method->signature))); + $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue((float) $method->crap))); + $methodNode->appendChild($document->createElement('complexity', (string) $method->ccn)); + $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method->coverage))); $methodNode->appendChild($document->createElement('crapLoad', (string) round($crapLoad))); $methodsNode->appendChild($methodNode); @@ -119,16 +119,10 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $root->appendChild($stats); $root->appendChild($methodsNode); - $buffer = $document->saveXML(); + $buffer = Xml::asString($document); if ($target !== null) { - if (!str_contains($target, '://')) { - Filesystem::createDirectory(dirname($target)); - } - - if (@file_put_contents($target, $buffer) === false) { - throw new WriteOperationFailedException($target); - } + Filesystem::write($target, $buffer); } return $buffer; diff --git a/src/Report/Html/Facade.php b/src/Report/Html/Facade.php index 0e8b230aa..44c63c923 100644 --- a/src/Report/Html/Facade.php +++ b/src/Report/Html/Facade.php @@ -41,16 +41,17 @@ public function __construct(string $generator = '', ?Colors $colors = null, ?Thr public function process(CodeCoverage $coverage, string $target): void { - $target = $this->directory($target); - $report = $coverage->getReport(); - $date = date('D M j G:i:s T Y'); + $target = $this->directory($target); + $report = $coverage->getReport(); + $date = date('D M j G:i:s T Y'); + $hasBranchCoverage = $coverage->getData(true)->functionCoverage() !== []; $dashboard = new Dashboard( $this->templatePath, $this->generator, $date, $this->thresholds, - $coverage->collectsBranchAndPathCoverage(), + $hasBranchCoverage, ); $directory = new Directory( @@ -58,7 +59,7 @@ public function process(CodeCoverage $coverage, string $target): void $this->generator, $date, $this->thresholds, - $coverage->collectsBranchAndPathCoverage(), + $hasBranchCoverage, ); $file = new File( @@ -66,7 +67,7 @@ public function process(CodeCoverage $coverage, string $target): void $this->generator, $date, $this->thresholds, - $coverage->collectsBranchAndPathCoverage(), + $hasBranchCoverage, ); $directory->render($report, $target . 'index.html'); diff --git a/src/Report/Html/Renderer/Dashboard.php b/src/Report/Html/Renderer/Dashboard.php index 305c7fa10..9df28c3f4 100644 --- a/src/Report/Html/Renderer/Dashboard.php +++ b/src/Report/Html/Renderer/Dashboard.php @@ -20,17 +20,16 @@ use function str_replace; use function uasort; use function usort; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; +use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; -use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\Template\Exception; use SebastianBergmann\Template\Template; /** - * @phpstan-import-type ProcessedClassType from FileNode - * @phpstan-import-type ProcessedTraitType from FileNode - * * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Dashboard extends Renderer @@ -96,26 +95,26 @@ private function complexity(array $classes, string $baseLink): array $result = ['class' => [], 'method' => []]; foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { if ($className !== '*') { $methodName = $className . '::' . $methodName; } $result['method'][] = [ - $method['coverage'], - $method['ccn'], - str_replace($baseLink, '', $method['link']), + $method->coverage, + $method->ccn, + str_replace($baseLink, '', $method->link), $methodName, - $method['crap'], + $method->crap, ]; } $result['class'][] = [ - $class['coverage'], - $class['ccn'], - str_replace($baseLink, '', $class['link']), + $class->coverage, + $class->ccn, + str_replace($baseLink, '', $class->link), $className, - $class['crap'], + $class->crap, ]; } @@ -172,24 +171,24 @@ private function coverageDistribution(array $classes): array ]; foreach ($classes as $class) { - foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] === 0) { + foreach ($class->methods as $method) { + if ($method->coverage === 0) { $result['method']['0%']++; - } elseif ($method['coverage'] === 100) { + } elseif ($method->coverage === 100) { $result['method']['100%']++; } else { - $key = floor($method['coverage'] / 10) * 10; + $key = floor($method->coverage / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['method'][$key]++; } } - if ($class['coverage'] === 0) { + if ($class->coverage === 0) { $result['class']['0%']++; - } elseif ($class['coverage'] === 100) { + } elseif ($class->coverage === 100) { $result['class']['100%']++; } else { - $key = floor($class['coverage'] / 10) * 10; + $key = floor($class->coverage / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['class'][$key]++; } @@ -218,20 +217,20 @@ private function insufficientCoverage(array $classes, string $baseLink): array $result = ['class' => '', 'method' => '']; foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] < $this->thresholds->highLowerBound()) { + foreach ($class->methods as $methodName => $method) { + if ($method->coverage < $this->thresholds->highLowerBound()) { $key = $methodName; if ($className !== '*') { $key = $className . '::' . $methodName; } - $leastTestedMethods[$key] = $method['coverage']; + $leastTestedMethods[$key] = $method->coverage; } } - if ($class['coverage'] < $this->thresholds->highLowerBound()) { - $leastTestedClasses[$className] = $class['coverage']; + if ($class->coverage < $this->thresholds->highLowerBound()) { + $leastTestedClasses[$className] = $class->coverage; } } @@ -241,7 +240,7 @@ private function insufficientCoverage(array $classes, string $baseLink): array foreach ($leastTestedClasses as $className => $coverage) { $result['class'] .= sprintf( ' %s%d%%' . "\n", - str_replace($baseLink, '', $classes[$className]['link']), + str_replace($baseLink, '', $classes[$className]->link), $className, $coverage, ); @@ -252,7 +251,7 @@ private function insufficientCoverage(array $classes, string $baseLink): array $result['method'] .= sprintf( ' %s%d%%' . "\n", - str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), + str_replace($baseLink, '', $classes[$class]->methods[$method]->link), $methodName, $method, $coverage, @@ -274,8 +273,8 @@ private function projectRisks(array $classes, string $baseLink): array $result = ['class' => '', 'method' => '']; foreach ($classes as $className => $class) { - foreach ($class['methods'] as $methodName => $method) { - if ($method['coverage'] < $this->thresholds->highLowerBound() && $method['ccn'] > 1) { + foreach ($class->methods as $methodName => $method) { + if ($method->coverage < $this->thresholds->highLowerBound() && $method->ccn > 1) { $key = $methodName; if ($className !== '*') { @@ -286,29 +285,29 @@ private function projectRisks(array $classes, string $baseLink): array } } - if ($class['coverage'] < $this->thresholds->highLowerBound() && - $class['ccn'] > count($class['methods'])) { + if ($class->coverage < $this->thresholds->highLowerBound() && + $class->ccn > count($class->methods)) { $classRisks[$className] = $class; } } - uasort($classRisks, static function (array $a, array $b) + uasort($classRisks, static function (ProcessedClassType|ProcessedTraitType $a, ProcessedClassType|ProcessedTraitType $b) { - return ((int) ($a['crap']) <=> (int) ($b['crap'])) * -1; + return ((int) ($a->crap) <=> (int) ($b->crap)) * -1; }); - uasort($methodRisks, static function (array $a, array $b) + uasort($methodRisks, static function (ProcessedMethodType $a, ProcessedMethodType $b) { - return ((int) ($a['crap']) <=> (int) ($b['crap'])) * -1; + return ((int) ($a->crap) <=> (int) ($b->crap)) * -1; }); foreach ($classRisks as $className => $class) { $result['class'] .= sprintf( ' %s%.1f%%%d%d' . "\n", - str_replace($baseLink, '', $classes[$className]['link']), + str_replace($baseLink, '', $classes[$className]->link), $className, - $class['coverage'], - $class['ccn'], - $class['crap'], + $class->coverage, + $class->ccn, + $class->crap, ); } @@ -317,12 +316,12 @@ private function projectRisks(array $classes, string $baseLink): array $result['method'] .= sprintf( ' %s%.1f%%%d%d' . "\n", - str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), + str_replace($baseLink, '', $classes[$class]->methods[$method]->link), $methodName, $method, - $methodVals['coverage'], - $methodVals['ccn'], - $methodVals['crap'], + $methodVals->coverage, + $methodVals->ccn, + $methodVals->crap, ); } diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index 09dbe31fe..b2808fbd0 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -102,6 +102,13 @@ use function str_replace; use function token_get_all; use function trim; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\Util\Percentage; @@ -109,11 +116,6 @@ use SebastianBergmann\Template\Template; /** - * @phpstan-import-type ProcessedClassType from FileNode - * @phpstan-import-type ProcessedTraitType from FileNode - * @phpstan-import-type ProcessedMethodType from FileNode - * @phpstan-import-type ProcessedFunctionType from FileNode - * * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class File extends Renderer @@ -340,30 +342,30 @@ private function renderTraitOrClassItems(array $items, Template $template, Templ $numMethods = 0; $numTestedMethods = 0; - foreach ($item['methods'] as $method) { - if ($method['executableLines'] > 0) { + foreach ($item->methods as $method) { + if ($method->executableLines > 0) { $numMethods++; - if ($method['executedLines'] === $method['executableLines']) { + if ($method->executedLines === $method->executableLines) { $numTestedMethods++; } } } - if ($item['executableLines'] > 0) { + if ($item->executableLines > 0) { $numClasses = 1; $numTestedClasses = $numTestedMethods === $numMethods ? 1 : 0; $linesExecutedPercentAsString = Percentage::fromFractionAndTotal( - $item['executedLines'], - $item['executableLines'], + $item->executedLines, + $item->executableLines, )->asString(); $branchesExecutedPercentAsString = Percentage::fromFractionAndTotal( - $item['executedBranches'], - $item['executableBranches'], + $item->executedBranches, + $item->executableBranches, )->asString(); $pathsExecutedPercentAsString = Percentage::fromFractionAndTotal( - $item['executedPaths'], - $item['executablePaths'], + $item->executedPaths, + $item->executablePaths, )->asString(); } else { $numClasses = 0; @@ -392,35 +394,35 @@ private function renderTraitOrClassItems(array $items, Template $template, Templ 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Percentage::fromFractionAndTotal( - $item['executedLines'], - $item['executableLines'], + $item->executedLines, + $item->executableLines, )->asFloat(), 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, - 'numExecutedLines' => $item['executedLines'], - 'numExecutableLines' => $item['executableLines'], + 'numExecutedLines' => $item->executedLines, + 'numExecutableLines' => $item->executableLines, 'branchesExecutedPercent' => Percentage::fromFractionAndTotal( - $item['executedBranches'], - $item['executableBranches'], + $item->executedBranches, + $item->executableBranches, )->asFloat(), 'branchesExecutedPercentAsString' => $branchesExecutedPercentAsString, - 'numExecutedBranches' => $item['executedBranches'], - 'numExecutableBranches' => $item['executableBranches'], + 'numExecutedBranches' => $item->executedBranches, + 'numExecutableBranches' => $item->executableBranches, 'pathsExecutedPercent' => Percentage::fromFractionAndTotal( - $item['executedPaths'], - $item['executablePaths'], + $item->executedPaths, + $item->executablePaths, )->asFloat(), 'pathsExecutedPercentAsString' => $pathsExecutedPercentAsString, - 'numExecutedPaths' => $item['executedPaths'], - 'numExecutablePaths' => $item['executablePaths'], + 'numExecutedPaths' => $item->executedPaths, + 'numExecutablePaths' => $item->executablePaths, 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), 'testedClassesPercent' => $testedClassesPercentage->asFloat(), 'testedClassesPercentAsString' => $testedClassesPercentage->asString(), - 'crap' => $item['crap'], + 'crap' => $item->crap, ], ); - foreach ($item['methods'] as $method) { + foreach ($item->methods as $method) { $buffer .= $this->renderFunctionOrMethodItem( $methodItemTemplate, $method, @@ -453,35 +455,32 @@ private function renderFunctionItems(array $functions, Template $template): stri return $buffer; } - /** - * @param ProcessedFunctionType|ProcessedMethodType $item - */ - private function renderFunctionOrMethodItem(Template $template, array $item, string $indent = ''): string + private function renderFunctionOrMethodItem(Template $template, ProcessedFunctionType|ProcessedMethodType $item, string $indent = ''): string { $numMethods = 0; $numTestedMethods = 0; - if ($item['executableLines'] > 0) { + if ($item->executableLines > 0) { $numMethods = 1; - if ($item['executedLines'] === $item['executableLines']) { + if ($item->executedLines === $item->executableLines) { $numTestedMethods = 1; } } $executedLinesPercentage = Percentage::fromFractionAndTotal( - $item['executedLines'], - $item['executableLines'], + $item->executedLines, + $item->executableLines, ); $executedBranchesPercentage = Percentage::fromFractionAndTotal( - $item['executedBranches'], - $item['executableBranches'], + $item->executedBranches, + $item->executableBranches, ); $executedPathsPercentage = Percentage::fromFractionAndTotal( - $item['executedPaths'], - $item['executablePaths'], + $item->executedPaths, + $item->executablePaths, ); $testedMethodsPercentage = Percentage::fromFractionAndTotal( @@ -495,27 +494,27 @@ private function renderFunctionOrMethodItem(Template $template, array $item, str 'name' => sprintf( '%s%s', $indent, - $item['startLine'], - htmlspecialchars($item['signature'], self::HTML_SPECIAL_CHARS_FLAGS), - $item['functionName'] ?? $item['methodName'], + $item->startLine, + htmlspecialchars($item->signature, self::HTML_SPECIAL_CHARS_FLAGS), + $item instanceof ProcessedFunctionType ? $item->functionName : $item->methodName, ), 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => $executedLinesPercentage->asFloat(), 'linesExecutedPercentAsString' => $executedLinesPercentage->asString(), - 'numExecutedLines' => $item['executedLines'], - 'numExecutableLines' => $item['executableLines'], + 'numExecutedLines' => $item->executedLines, + 'numExecutableLines' => $item->executableLines, 'branchesExecutedPercent' => $executedBranchesPercentage->asFloat(), 'branchesExecutedPercentAsString' => $executedBranchesPercentage->asString(), - 'numExecutedBranches' => $item['executedBranches'], - 'numExecutableBranches' => $item['executableBranches'], + 'numExecutedBranches' => $item->executedBranches, + 'numExecutableBranches' => $item->executableBranches, 'pathsExecutedPercent' => $executedPathsPercentage->asFloat(), 'pathsExecutedPercentAsString' => $executedPathsPercentage->asString(), - 'numExecutedPaths' => $item['executedPaths'], - 'numExecutablePaths' => $item['executablePaths'], + 'numExecutedPaths' => $item->executedPaths, + 'numExecutablePaths' => $item->executablePaths, 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), - 'crap' => $item['crap'], + 'crap' => $item->crap, ], ); } @@ -607,18 +606,20 @@ private function renderSourceWithBranchCoverage(FileNode $node): string ]; } + /** @var ProcessedFunctionCoverageData $method */ foreach ($functionCoverageData as $method) { - foreach ($method['branches'] as $branch) { - foreach (range($branch['line_start'], $branch['line_end']) as $line) { + /** @var ProcessedBranchCoverageData $branch */ + foreach ($method->branches as $branch) { + foreach (range($branch->line_start, $branch->line_end) as $line) { if (!isset($lineData[$line])) { // blank line at end of file is sometimes included here continue; } $lineData[$line]['includedInBranches']++; - if ($branch['hit']) { + if ($branch->hit !== []) { $lineData[$line]['includedInHitBranches']++; - $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $branch['hit'])); + $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $branch->hit)); } } } @@ -693,18 +694,20 @@ private function renderSourceWithPathCoverage(FileNode $node): string ]; } + /** @var ProcessedFunctionCoverageData $method */ foreach ($functionCoverageData as $method) { - foreach ($method['paths'] as $pathId => $path) { - foreach ($path['path'] as $branchTaken) { - foreach (range($method['branches'][$branchTaken]['line_start'], $method['branches'][$branchTaken]['line_end']) as $line) { + /** @var ProcessedPathCoverageData $path */ + foreach ($method->paths as $pathId => $path) { + foreach ($path->path as $branchTaken) { + foreach (range($method->branches[$branchTaken]->line_start, $method->branches[$branchTaken]->line_end) as $line) { if (!isset($lineData[$line])) { continue; } $lineData[$line]['includedInPaths'][] = $pathId; - if ($path['hit']) { + if ($path->hit !== []) { $lineData[$line]['includedInHitPaths'][] = $pathId; - $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $path['hit'])); + $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $path->hit)); } } } @@ -774,14 +777,12 @@ private function renderBranchStructure(FileNode $node): string ksort($coverageData); + /** @var ProcessedFunctionCoverageData $methodData */ foreach ($coverageData as $methodName => $methodData) { - if (!$methodData['branches']) { - continue; - } - $branchStructure = ''; - foreach ($methodData['branches'] as $branch) { + /** @var ProcessedBranchCoverageData $branch */ + foreach ($methodData->branches as $branch) { $branchStructure .= $this->renderBranchLines($branch, $codeLines, $testData); } @@ -799,14 +800,14 @@ private function renderBranchStructure(FileNode $node): string /** * @param list $codeLines */ - private function renderBranchLines(array $branch, array $codeLines, array $testData): string + private function renderBranchLines(ProcessedBranchCoverageData $branch, array $codeLines, array $testData): string { $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); $lines = ''; - $branchLines = range($branch['line_start'], $branch['line_end']); + $branchLines = range($branch->line_start, $branch->line_end); sort($branchLines); // sometimes end_line < start_line /** @var int $line */ @@ -818,7 +819,7 @@ private function renderBranchLines(array $branch, array $codeLines, array $testD $popoverContent = ''; $popoverTitle = ''; - $numTests = count($branch['hit']); + $numTests = count($branch->hit); if ($numTests === 0) { $trClass = 'danger'; @@ -832,7 +833,7 @@ private function renderBranchLines(array $branch, array $codeLines, array $testD $popoverTitle = '1 test covers this branch'; } - foreach ($branch['hit'] as $test) { + foreach ($branch->hit as $test) { if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] === 'small') { @@ -877,21 +878,18 @@ private function renderPathStructure(FileNode $node): string ksort($coverageData); + /** @var ProcessedFunctionCoverageData $methodData */ foreach ($coverageData as $methodName => $methodData) { - if (!$methodData['paths']) { - continue; - } - $pathStructure = ''; - if (count($methodData['paths']) > 100) { - $pathStructure .= '

' . count($methodData['paths']) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.

'; + if (count($methodData->paths) > 100) { + $pathStructure .= '

' . count($methodData->paths) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.

'; continue; } - foreach ($methodData['paths'] as $path) { - $pathStructure .= $this->renderPathLines($path, $methodData['branches'], $codeLines, $testData); + foreach ($methodData->paths as $path) { + $pathStructure .= $this->renderPathLines($path, $methodData->branches, $codeLines, $testData); } if ($pathStructure !== '') { @@ -906,9 +904,10 @@ private function renderPathStructure(FileNode $node): string } /** - * @param list $codeLines + * @param array $branches + * @param list $codeLines */ - private function renderPathLines(array $path, array $branches, array $codeLines, array $testData): string + private function renderPathLines(ProcessedPathCoverageData $path, array $branches, array $codeLines, array $testData): string { $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); @@ -916,14 +915,14 @@ private function renderPathLines(array $path, array $branches, array $codeLines, $lines = ''; $first = true; - foreach ($path['path'] as $branchId) { + foreach ($path->path as $branchId) { if ($first) { $first = false; } else { $lines .= '  ' . "\n"; } - $branchLines = range($branches[$branchId]['line_start'], $branches[$branchId]['line_end']); + $branchLines = range($branches[$branchId]->line_start, $branches[$branchId]->line_end); sort($branchLines); // sometimes end_line < start_line /** @var int $line */ @@ -935,7 +934,7 @@ private function renderPathLines(array $path, array $branches, array $codeLines, $popoverContent = ''; $popoverTitle = ''; - $numTests = count($path['hit']); + $numTests = count($path->hit); if ($numTests === 0) { $trClass = 'danger'; @@ -949,7 +948,7 @@ private function renderPathLines(array $path, array $branches, array $codeLines, $popoverTitle = '1 test covers this path'; } - foreach ($path['hit'] as $test) { + foreach ($path->hit as $test) { if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] === 'small') { diff --git a/src/Report/Html/Renderer/Template/css/style.css b/src/Report/Html/Renderer/Template/css/style.css index c0a045889..4303bf844 100644 --- a/src/Report/Html/Renderer/Template/css/style.css +++ b/src/Report/Html/Renderer/Template/css/style.css @@ -1,13 +1,86 @@ + :root { - --phpunit-breadcrumbs: var(--bs-gray-200); - --phpunit-success-bar: #28a745; - --phpunit-success-high: {{success-high}}; - --phpunit-success-medium: {{success-medium}}; - --phpunit-success-low: {{success-low}}; - --phpunit-warning: {{warning}}; - --phpunit-warning-bar: #ffc107; - --phpunit-danger: {{danger}}; - --phpunit-danger-bar: #dc3545; + /* Implementing an auto-selection of dark/light theme via: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark */ + color-scheme: light dark; + + /* PHPUnit light/dark colors */ + --phpunit-breadcrumbs: light-dark(var(--bs-gray-200), var(--bs-gray-800)); + --phpunit-success-bar: light-dark(#28a745 ,#1f8135); + --phpunit-success-high: light-dark(#99cb84, #3d5c4e); + --phpunit-success-medium: light-dark(#c3e3b5,#3c6051); + --phpunit-success-low: light-dark(#dff0d8, #2d4431); + --phpunit-warning: light-dark(#fcf8e3, #3e3408); + --phpunit-warning-bar: light-dark(#ffc107 ,#c19406); + --phpunit-danger: light-dark(#f2dede, #42221e); + --phpunit-danger-bar: light-dark(#dc3545, #a62633); + + /* Bootstrap v5.3 default colors (light, dark) */ + --bs-body-bg-rgb: 255, 255, 255; + --bs-body-bg: light-dark(#fff, #212529); + --bs-body-color-rgb: light-dark(33, 37, 41, 222, 226, 230); + --bs-body-color: light-dark(#212529, #dee2e6); + --bs-border-color-translucent: light-dark(rgba(0, 0, 0, 0.175), rgba(255, 255, 255, 0.15)); + --bs-border-color: light-dark(#dee2e6, #495057); + --bs-code-color: light-dark(#d63384, #e685b5); + --bs-danger-bg-subtle: light-dark(#f8d7da, #2c0b0e); + --bs-danger-border-subtle: light-dark(#f1aeb5, #842029); + --bs-danger-text-emphasis: light-dark(#58151c, #ea868f); + --bs-dark-bg-subtle: light-dark(#ced4da, #1a1d20); + --bs-dark-border-subtle: light-dark(#adb5bd, #343a40); + --bs-dark-text-emphasis: light-dark(#495057, #dee2e6); + --bs-emphasis-color-rgb: 0, 0, 0; + --bs-emphasis-color: light-dark(#000, #fff); + --bs-form-invalid-border-color: light-dark(#dc3545, #ea868f); + --bs-form-invalid-color: light-dark(#dc3545, #ea868f); + --bs-form-valid-border-color: light-dark(#198754, #75b798); + --bs-form-valid-color: light-dark(#198754, #75b798); + --bs-highlight-bg: light-dark(#fff3cd, #664d03); + --bs-highlight-color: light-dark(#212529, #dee2e6); + --bs-info-bg-subtle: light-dark(#cff4fc, #032830); + --bs-info-border-subtle: light-dark(#9eeaf9, #087990); + --bs-info-text-emphasis: light-dark(#055160, #6edff6); + --bs-light-bg-subtle: light-dark(#fcfcfd, #343a40); + --bs-light-border-subtle: light-dark(#e9ecef, #495057); + --bs-light-text-emphasis: light-dark(#495057, #f8f9fa); + --bs-link-color-rgb: 13, 110, 253; + --bs-link-color: light-dark(#0d6efd, #6ea8fe); + --bs-link-hover-color-rgb: 10, 88, 202; + --bs-link-hover-color: light-dark(#0a58ca, #8bb9fe); + --bs-primary-bg-subtle: light-dark(#cfe2ff, #031633); + --bs-primary-border-subtle: light-dark(#9ec5fe, #084298); + --bs-primary-text-emphasis: light-dark(#052c65, #6ea8fe); + --bs-secondary-bg-rgb: 233, 236, 239; + --bs-secondary-bg-subtle: light-dark(#e2e3e5, #161719); + --bs-secondary-bg: light-dark(#e9ecef, #343a40); + --bs-secondary-border-subtle: light-dark(#c4c8cb, #41464b); + --bs-secondary-color-rgb: 33, 37, 41; + --bs-secondary-color: light-dark(rgba(33, 37, 41, 0.75), rgba(222, 226, 230, 0.75)); + --bs-secondary-text-emphasis: light-dark(#2b2f32, #a7acb1); + --bs-success-bg-subtle: light-dark(#d1e7dd, #051b11); + --bs-success-border-subtle: light-dark(#a3cfbb, #0f5132); + --bs-success-text-emphasis: light-dark(#0a3622, #75b798); + --bs-tertiary-bg-rgb: light-dark(248, 249, 250, 43, 48, 53); + --bs-tertiary-bg: light-dark(#f8f9fa, #2b3035); + --bs-tertiary-color-rgb: 33, 37, 41; + --bs-tertiary-color: light-dark(rgba(33, 37, 41, 0.5), rgba(222, 226, 230, 0.5)); + --bs-warning-bg-subtle: light-dark(#fff3cd, #332701); + --bs-warning-border-subtle: light-dark(#ffe69c, #997404); + --bs-warning-text-emphasis: light-dark(#664d03, #ffda6a); +} + +@media (prefers-color-scheme: dark) { + :root { + --bs-body-bg-rgb: 33, 37, 41; + --bs-emphasis-color-rgb: 255, 255, 255; + --bs-link-color-rgb: 110, 168, 254; + --bs-link-hover-color-rgb: 139, 185, 254; + --bs-secondary-bg-rgb: 52, 58, 64; + --bs-secondary-color-rgb: 222, 226, 230; + --bs-tertiary-color-rgb: 222, 226, 230; + } + + /* Invert icon's colors on dark mode to improve readability */ + img.octicon { filter: invert(1); } } body { @@ -198,4 +271,4 @@ table#code td:first-of-type a { .progress-bar.bg-danger { background-color: var(--phpunit-danger-bar) !important; -} \ No newline at end of file +} diff --git a/src/Report/OpenClover.php b/src/Report/OpenClover.php index 042132b33..3c9a7c3d1 100644 --- a/src/Report/OpenClover.php +++ b/src/Report/OpenClover.php @@ -12,13 +12,10 @@ use function assert; use function basename; use function count; -use function dirname; -use function file_put_contents; use function is_string; use function ksort; use function max; use function range; -use function str_contains; use function str_replace; use function time; use DOMDocument; @@ -26,6 +23,7 @@ use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Node\File; use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Util\Xml; use SebastianBergmann\CodeCoverage\Version; use SebastianBergmann\CodeCoverage\WriteOperationFailedException; @@ -80,53 +78,53 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $classMethods = 0; // Assumption: one namespace per file - if ($class['namespace'] !== '') { - $namespace = $class['namespace']; + if ($class->namespace !== '') { + $namespace = $class->namespace; } - foreach ($class['methods'] as $methodName => $method) { + foreach ($class->methods as $methodName => $method) { /** @phpstan-ignore equal.notAllowed */ - if ($method['executableLines'] == 0) { + if ($method->executableLines == 0) { continue; } $classMethods++; - $classStatements += $method['executableLines']; - $coveredClassStatements += $method['executedLines']; + $classStatements += $method->executableLines; + $coveredClassStatements += $method->executedLines; /** @phpstan-ignore equal.notAllowed */ - if ($method['coverage'] == 100) { + if ($method->coverage == 100) { $coveredMethods++; } $methodCount = 0; - foreach (range($method['startLine'], $method['endLine']) as $line) { + foreach (range($method->startLine, $method->endLine) as $line) { if (isset($coverageData[$line])) { $methodCount = max($methodCount, count($coverageData[$line])); } } - $lines[$method['startLine']] = [ - 'ccn' => $method['ccn'], + $lines[$method->startLine] = [ + 'ccn' => $method->ccn, 'count' => $methodCount, 'type' => 'method', - 'signature' => $method['signature'], - 'visibility' => $method['visibility'], + 'signature' => $method->signature, + 'visibility' => $method->visibility, ]; } $xmlClass = $xmlDocument->createElement('class'); - $xmlClass->setAttribute('name', str_replace($class['namespace'] . '\\', '', $className)); + $xmlClass->setAttribute('name', str_replace($class->namespace . '\\', '', $className)); $xmlFile->appendChild($xmlClass); $xmlMetrics = $xmlDocument->createElement('metrics'); - $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); - $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches'])); - $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches'])); - $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']); - $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']); + $xmlMetrics->setAttribute('complexity', (string) $class->ccn); + $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class->executableBranches)); + $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class->executedBranches)); + $xmlMetrics->setAttribute('conditionals', (string) $class->executableBranches); + $xmlMetrics->setAttribute('coveredconditionals', (string) $class->executedBranches); $xmlMetrics->setAttribute('statements', (string) $classStatements); $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); $xmlMetrics->setAttribute('methods', (string) $classMethods); @@ -240,16 +238,10 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string $xmlMetrics->setAttribute('coveredmethods', (string) $report->numberOfTestedMethods()); $xmlProject->insertBefore($xmlMetrics, $xmlProject->firstChild); - $buffer = $xmlDocument->saveXML(); + $buffer = Xml::asString($xmlDocument); if ($target !== null) { - if (!str_contains($target, '://')) { - Filesystem::createDirectory(dirname($target)); - } - - if (@file_put_contents($target, $buffer) === false) { - throw new WriteOperationFailedException($target); - } + Filesystem::write($target, $buffer); } return $buffer; diff --git a/src/Report/PHP.php b/src/Report/PHP.php index 051f9154e..aa941dc8a 100644 --- a/src/Report/PHP.php +++ b/src/Report/PHP.php @@ -10,16 +10,18 @@ namespace SebastianBergmann\CodeCoverage\Report; use const PHP_EOL; -use function dirname; -use function file_put_contents; use function serialize; -use function str_contains; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Util\Filesystem; use SebastianBergmann\CodeCoverage\WriteOperationFailedException; final class PHP { + /** + * @param null|non-empty-string $target + * + * @throws WriteOperationFailedException + */ public function process(CodeCoverage $coverage, ?string $target = null): string { $coverage->clearCache(); @@ -28,13 +30,7 @@ public function process(CodeCoverage $coverage, ?string $target = null): string return \unserialize(<<<'END_OF_COVERAGE_SERIALIZATION'" . PHP_EOL . serialize($coverage) . PHP_EOL . 'END_OF_COVERAGE_SERIALIZATION' . PHP_EOL . ');'; if ($target !== null) { - if (!str_contains($target, '://')) { - Filesystem::createDirectory(dirname($target)); - } - - if (@file_put_contents($target, $buffer) === false) { - throw new WriteOperationFailedException($target); - } + Filesystem::write($target, $buffer); } return $buffer; diff --git a/src/Report/Text.php b/src/Report/Text.php index f18820b70..4c8d70986 100644 --- a/src/Report/Text.php +++ b/src/Report/Text.php @@ -190,28 +190,28 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $coveredMethods = 0; $classMethods = 0; - foreach ($class['methods'] as $method) { + foreach ($class->methods as $method) { /** @phpstan-ignore equal.notAllowed */ - if ($method['executableLines'] == 0) { + if ($method->executableLines == 0) { continue; } $classMethods++; - $classExecutableLines += $method['executableLines']; - $classExecutedLines += $method['executedLines']; - $classExecutableBranches += $method['executableBranches']; - $classExecutedBranches += $method['executedBranches']; - $classExecutablePaths += $method['executablePaths']; - $classExecutedPaths += $method['executedPaths']; + $classExecutableLines += $method->executableLines; + $classExecutedLines += $method->executedLines; + $classExecutableBranches += $method->executableBranches; + $classExecutedBranches += $method->executedBranches; + $classExecutablePaths += $method->executablePaths; + $classExecutedPaths += $method->executedPaths; /** @phpstan-ignore equal.notAllowed */ - if ($method['coverage'] == 100) { + if ($method->coverage == 100) { $coveredMethods++; } } $classCoverage[$className] = [ - 'namespace' => $class['namespace'], + 'namespace' => $class->namespace, 'className' => $className, 'methodsCovered' => $coveredMethods, 'methodCount' => $classMethods, diff --git a/src/Report/Xml/BuildInformation.php b/src/Report/Xml/BuildInformation.php index dba230123..654eecb31 100644 --- a/src/Report/Xml/BuildInformation.php +++ b/src/Report/Xml/BuildInformation.php @@ -22,13 +22,15 @@ { private DOMElement $contextNode; - public function __construct(DOMElement $contextNode) - { + public function __construct( + DOMElement $contextNode, + Runtime $runtime, + DateTimeImmutable $buildDate, + string $phpUnitVersion, + string $coverageVersion + ) { $this->contextNode = $contextNode; - } - public function setRuntimeInformation(Runtime $runtime): void - { $runtimeNode = $this->nodeByName('runtime'); $runtimeNode->setAttribute('name', $runtime->getName()); @@ -46,34 +48,21 @@ public function setRuntimeInformation(Runtime $runtime): void $driverNode->setAttribute('name', 'pcov'); $driverNode->setAttribute('version', phpversion('pcov')); } - } - public function setBuildTime(DateTimeImmutable $date): void - { - $this->contextNode->setAttribute('time', $date->format('D M j G:i:s T Y')); - } + $this->contextNode->setAttribute('time', $buildDate->format('D M j G:i:s T Y')); - public function setGeneratorVersions(string $phpUnitVersion, string $coverageVersion): void - { $this->contextNode->setAttribute('phpunit', $phpUnitVersion); $this->contextNode->setAttribute('coverage', $coverageVersion); } private function nodeByName(string $name): DOMElement { - $node = $this->contextNode->getElementsByTagNameNS( - 'https://schema.phpunit.de/coverage/1.0', - $name, - )->item(0); - - if ($node === null) { - $node = $this->contextNode->appendChild( - $this->contextNode->ownerDocument->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', - $name, - ), - ); - } + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS( + Facade::XML_NAMESPACE, + $name, + ), + ); assert($node instanceof DOMElement); diff --git a/src/Report/Xml/Coverage.php b/src/Report/Xml/Coverage.php index afb70a069..9462780be 100644 --- a/src/Report/Xml/Coverage.php +++ b/src/Report/Xml/Coverage.php @@ -10,7 +10,6 @@ namespace SebastianBergmann\CodeCoverage\Report\Xml; use DOMElement; -use SebastianBergmann\CodeCoverage\ReportAlreadyFinalizedException; use XMLWriter; /** @@ -18,48 +17,35 @@ */ final class Coverage { - private readonly XMLWriter $writer; private readonly DOMElement $contextNode; - private bool $finalized = false; + private readonly string $line; public function __construct(DOMElement $context, string $line) { $this->contextNode = $context; - - $this->writer = new XMLWriter; - $this->writer->openMemory(); - $this->writer->startElementNs(null, $context->nodeName, 'https://schema.phpunit.de/coverage/1.0'); - $this->writer->writeAttribute('nr', $line); + $this->line = $line; } - /** - * @throws ReportAlreadyFinalizedException - */ - public function addTest(string $test): void + public function finalize(array $tests): void { - if ($this->finalized) { - // @codeCoverageIgnoreStart - throw new ReportAlreadyFinalizedException; - // @codeCoverageIgnoreEnd + $writer = new XMLWriter; + $writer->openMemory(); + $writer->startElementNs(null, $this->contextNode->nodeName, Facade::XML_NAMESPACE); + $writer->writeAttribute('nr', $this->line); + + foreach ($tests as $test) { + $writer->startElement('covered'); + $writer->writeAttribute('by', $test); + $writer->endElement(); } - - $this->writer->startElement('covered'); - $this->writer->writeAttribute('by', $test); - $this->writer->endElement(); - } - - public function finalize(): void - { - $this->writer->endElement(); + $writer->endElement(); $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); - $fragment->appendXML($this->writer->outputMemory()); + $fragment->appendXML($writer->outputMemory()); $this->contextNode->parentNode->replaceChild( $fragment, $this->contextNode, ); - - $this->finalized = true; } } diff --git a/src/Report/Xml/Facade.php b/src/Report/Xml/Facade.php index ee2e8aa01..c3767088e 100644 --- a/src/Report/Xml/Facade.php +++ b/src/Report/Xml/Facade.php @@ -10,43 +10,39 @@ namespace SebastianBergmann\CodeCoverage\Report\Xml; use const DIRECTORY_SEPARATOR; -use const PHP_EOL; use function count; use function dirname; use function file_get_contents; -use function file_put_contents; use function is_array; use function is_dir; use function is_file; use function is_writable; -use function libxml_clear_errors; -use function libxml_get_errors; -use function libxml_use_internal_errors; use function sprintf; use function strlen; use function substr; use DateTimeImmutable; use DOMDocument; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; +use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType; use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; -use SebastianBergmann\CodeCoverage\Node\File; use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\PathExistsButIsNotDirectoryException; -use SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil; +use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Util\Xml; use SebastianBergmann\CodeCoverage\Version; use SebastianBergmann\CodeCoverage\WriteOperationFailedException; use SebastianBergmann\CodeCoverage\XmlException; use SebastianBergmann\Environment\Runtime; /** - * @phpstan-import-type ProcessedClassType from File - * @phpstan-import-type ProcessedTraitType from File - * @phpstan-import-type ProcessedFunctionType from File * @phpstan-import-type TestType from CodeCoverage */ final class Facade { + public const string XML_NAMESPACE = 'https://schema.phpunit.de/coverage/1.0'; private string $target; private Project $project; private readonly string $phpUnitVersion; @@ -83,10 +79,12 @@ public function process(CodeCoverage $coverage, string $target): void private function setBuildInformation(): void { - $buildNode = $this->project->buildInformation(); - $buildNode->setRuntimeInformation(new Runtime); - $buildNode->setBuildTime(new DateTimeImmutable); - $buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id()); + $this->project->buildInformation( + new Runtime, + new DateTimeImmutable, + $this->phpUnitVersion, + Version::id(), + ); } /** @@ -107,7 +105,7 @@ private function initTargetDirectory(string $directory): void // @codeCoverageIgnoreEnd } - DirectoryUtil::createDirectory($directory); + Filesystem::createDirectory($directory); } /** @@ -169,12 +167,7 @@ private function processFile(FileNode $file, Directory $context): void } $coverage = $fileReport->lineCoverage((string) $line); - - foreach ($tests as $test) { - $coverage->addTest($test); - } - - $coverage->finalize(); + $coverage->finalize($tests); } $fileReport->source()->setSourceCode( @@ -184,50 +177,54 @@ private function processFile(FileNode $file, Directory $context): void $this->saveDocument($fileReport->asDom(), $file->id()); } - /** - * @param ProcessedClassType|ProcessedTraitType $unit - */ - private function processUnit(array $unit, Report $report): void + private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report $report): void { - if (isset($unit['className'])) { - $unitObject = $report->classObject($unit['className']); + if ($unit instanceof ProcessedClassType) { + $unitObject = $report->classObject( + $unit->className, + $unit->namespace, + $unit->startLine, + $unit->executableLines, + $unit->executedLines, + (float) $unit->crap, + ); } else { - $unitObject = $report->traitObject($unit['traitName']); + $unitObject = $report->traitObject( + $unit->traitName, + $unit->namespace, + $unit->startLine, + $unit->executableLines, + $unit->executedLines, + (float) $unit->crap, + ); } - $unitObject->setLines( - $unit['startLine'], - $unit['executableLines'], - $unit['executedLines'], - ); - - $unitObject->setCrap((float) $unit['crap']); - $unitObject->setNamespace($unit['namespace']); - - foreach ($unit['methods'] as $method) { - $methodObject = $unitObject->addMethod($method['methodName']); - $methodObject->setSignature($method['signature']); - $methodObject->setLines((string) $method['startLine'], (string) $method['endLine']); - $methodObject->setCrap($method['crap']); - $methodObject->setTotals( - (string) $method['executableLines'], - (string) $method['executedLines'], - (string) $method['coverage'], + foreach ($unit->methods as $method) { + $unitObject->addMethod( + $method->methodName, + $method->signature, + (string) $method->startLine, + (string) $method->endLine, + (string) $method->executableLines, + (string) $method->executedLines, + (string) $method->coverage, + $method->crap, ); } } - /** - * @param ProcessedFunctionType $function - */ - private function processFunction(array $function, Report $report): void + private function processFunction(ProcessedFunctionType $function, Report $report): void { - $functionObject = $report->functionObject($function['functionName']); - - $functionObject->setSignature($function['signature']); - $functionObject->setLines((string) $function['startLine']); - $functionObject->setCrap($function['crap']); - $functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']); + $report->functionObject( + $function->functionName, + $function->signature, + (string) $function->startLine, + null, + (string) $function->executableLines, + (string) $function->executedLines, + (string) $function->coverage, + $function->crap, + ); } /** @@ -280,45 +277,20 @@ private function targetDirectory(): string return $this->target; } - /** - * @throws XmlException - */ - private function saveDocument(DOMDocument $document, string $name): void + private function targetFilePath(string $name): string { $filename = sprintf('%s/%s.xml', $this->targetDirectory(), $name); - $document->formatOutput = true; - $document->preserveWhiteSpace = false; $this->initTargetDirectory(dirname($filename)); - file_put_contents($filename, $this->documentAsString($document)); + return $filename; } /** * @throws XmlException - * - * @see https://bugs.php.net/bug.php?id=79191 */ - private function documentAsString(DOMDocument $document): string + private function saveDocument(DOMDocument $document, string $name): void { - $xmlErrorHandling = libxml_use_internal_errors(true); - $xml = $document->saveXML(); - - if ($xml === false) { - // @codeCoverageIgnoreStart - $message = 'Unable to generate the XML'; - - foreach (libxml_get_errors() as $error) { - $message .= PHP_EOL . $error->message; - } - - throw new XmlException($message); - // @codeCoverageIgnoreEnd - } - - libxml_clear_errors(); - libxml_use_internal_errors($xmlErrorHandling); - - return $xml; + Filesystem::write($this->targetFilePath($name), Xml::asString($document)); } } diff --git a/src/Report/Xml/File.php b/src/Report/Xml/File.php index 4a3fea008..e6dd5c4ba 100644 --- a/src/Report/Xml/File.php +++ b/src/Report/Xml/File.php @@ -12,14 +12,16 @@ use function assert; use DOMDocument; use DOMElement; +use DOMNode; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ class File { - private readonly DOMDocument $dom; + protected readonly DOMDocument $dom; private readonly DOMElement $contextNode; + private ?DOMNode $lineCoverage = null; public function __construct(DOMElement $context) { @@ -29,16 +31,12 @@ public function __construct(DOMElement $context) public function totals(): Totals { - $totalsContainer = $this->contextNode->firstChild; - - if ($totalsContainer === null) { - $totalsContainer = $this->contextNode->appendChild( - $this->dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', - 'totals', - ), - ); - } + $totalsContainer = $this->contextNode->appendChild( + $this->dom->createElementNS( + Facade::XML_NAMESPACE, + 'totals', + ), + ); assert($totalsContainer instanceof DOMElement); @@ -47,23 +45,19 @@ public function totals(): Totals public function lineCoverage(string $line): Coverage { - $coverage = $this->contextNode->getElementsByTagNameNS( - 'https://schema.phpunit.de/coverage/1.0', - 'coverage', - )->item(0); - - if ($coverage === null) { - $coverage = $this->contextNode->appendChild( + if ($this->lineCoverage === null) { + $this->lineCoverage = $this->contextNode->appendChild( $this->dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'coverage', ), ); } + assert($this->lineCoverage instanceof DOMElement); - $lineNode = $coverage->appendChild( + $lineNode = $this->lineCoverage->appendChild( $this->dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'line', ), ); @@ -77,9 +71,4 @@ protected function contextNode(): DOMElement { return $this->contextNode; } - - protected function dom(): DOMDocument - { - return $this->dom; - } } diff --git a/src/Report/Xml/Method.php b/src/Report/Xml/Method.php index 1994d0f79..1b5bdb28f 100644 --- a/src/Report/Xml/Method.php +++ b/src/Report/Xml/Method.php @@ -18,41 +18,32 @@ { private DOMElement $contextNode; - public function __construct(DOMElement $context, string $name) - { + public function __construct( + DOMElement $context, + string $name, + string $signature, + string $start, + ?string $end, + string $executable, + string $executed, + string $coverage, + string $crap + ) { $this->contextNode = $context; - $this->setName($name); - } - - public function setSignature(string $signature): void - { + $this->contextNode->setAttribute('name', $name); $this->contextNode->setAttribute('signature', $signature); - } - public function setLines(string $start, ?string $end = null): void - { $this->contextNode->setAttribute('start', $start); if ($end !== null) { $this->contextNode->setAttribute('end', $end); } - } - public function setTotals(string $executable, string $executed, string $coverage): void - { + $this->contextNode->setAttribute('crap', $crap); + $this->contextNode->setAttribute('executable', $executable); $this->contextNode->setAttribute('executed', $executed); $this->contextNode->setAttribute('coverage', $coverage); } - - public function setCrap(string $crap): void - { - $this->contextNode->setAttribute('crap', $crap); - } - - private function setName(string $name): void - { - $this->contextNode->setAttribute('name', $name); - } } diff --git a/src/Report/Xml/Node.php b/src/Report/Xml/Node.php index e41197a08..86fe70df4 100644 --- a/src/Report/Xml/Node.php +++ b/src/Report/Xml/Node.php @@ -18,17 +18,13 @@ */ abstract class Node { - private DOMDocument $dom; - private DOMElement $contextNode; + protected readonly DOMDocument $dom; + private readonly DOMElement $contextNode; public function __construct(DOMElement $context) { - $this->setContextNode($context); - } - - public function dom(): DOMDocument - { - return $this->dom; + $this->dom = $context->ownerDocument; + $this->contextNode = $context; } public function totals(): Totals @@ -38,7 +34,7 @@ public function totals(): Totals if ($totalsContainer === null) { $totalsContainer = $this->contextNode()->appendChild( $this->dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'totals', ), ); @@ -51,8 +47,8 @@ public function totals(): Totals public function addDirectory(string $name): Directory { - $dirNode = $this->dom()->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + $dirNode = $this->dom->createElementNS( + Facade::XML_NAMESPACE, 'directory', ); @@ -64,8 +60,8 @@ public function addDirectory(string $name): Directory public function addFile(string $name, string $href): File { - $fileNode = $this->dom()->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + $fileNode = $this->dom->createElementNS( + Facade::XML_NAMESPACE, 'file', ); @@ -76,12 +72,6 @@ public function addFile(string $name, string $href): File return new File($fileNode); } - protected function setContextNode(DOMElement $context): void - { - $this->dom = $context->ownerDocument; - $this->contextNode = $context; - } - protected function contextNode(): DOMElement { return $this->contextNode; diff --git a/src/Report/Xml/Project.php b/src/Report/Xml/Project.php index 21b5a2ce1..c81a6a933 100644 --- a/src/Report/Xml/Project.php +++ b/src/Report/Xml/Project.php @@ -10,64 +10,68 @@ namespace SebastianBergmann\CodeCoverage\Report\Xml; use function assert; +use DateTimeImmutable; use DOMDocument; use DOMElement; +use SebastianBergmann\Environment\Runtime; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Project extends Node { - /** - * @phpstan-ignore constructor.missingParentCall - */ + private readonly string $directory; + public function __construct(string $directory) { - $this->init(); - $this->setProjectSourceDirectory($directory); + $dom = new DOMDocument; + $dom->loadXML(''); + + parent::__construct( + $dom->getElementsByTagNameNS( + Facade::XML_NAMESPACE, + 'project', + )->item(0), + ); + + $this->directory = $directory; } public function projectSourceDirectory(): string { - return $this->contextNode()->getAttribute('source'); + return $this->directory; } - public function buildInformation(): BuildInformation - { - $buildNode = $this->dom()->getElementsByTagNameNS( - 'https://schema.phpunit.de/coverage/1.0', + public function buildInformation( + Runtime $runtime, + DateTimeImmutable $buildDate, + string $phpUnitVersion, + string $coverageVersion + ): void { + $buildNode = $this->dom->getElementsByTagNameNS( + Facade::XML_NAMESPACE, 'build', )->item(0); - if ($buildNode === null) { - $buildNode = $this->dom()->documentElement->appendChild( - $this->dom()->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', - 'build', - ), - ); - } - assert($buildNode instanceof DOMElement); - return new BuildInformation($buildNode); + new BuildInformation( + $buildNode, + $runtime, + $buildDate, + $phpUnitVersion, + $coverageVersion, + ); } public function tests(): Tests { - $testsNode = $this->contextNode()->getElementsByTagNameNS( - 'https://schema.phpunit.de/coverage/1.0', - 'tests', - )->item(0); - - if ($testsNode === null) { - $testsNode = $this->contextNode()->appendChild( - $this->dom()->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', - 'tests', - ), - ); - } + $testsNode = $this->contextNode()->appendChild( + $this->dom->createElementNS( + Facade::XML_NAMESPACE, + 'tests', + ), + ); assert($testsNode instanceof DOMElement); @@ -76,24 +80,8 @@ public function tests(): Tests public function asDom(): DOMDocument { - return $this->dom(); - } + $this->contextNode()->setAttribute('source', $this->directory); - private function init(): void - { - $dom = new DOMDocument; - $dom->loadXML(''); - - $this->setContextNode( - $dom->getElementsByTagNameNS( - 'https://schema.phpunit.de/coverage/1.0', - 'project', - )->item(0), - ); - } - - private function setProjectSourceDirectory(string $name): void - { - $this->contextNode()->setAttribute('source', $name); + return $this->dom; } } diff --git a/src/Report/Xml/Report.php b/src/Report/Xml/Report.php index f39ab860c..ee9b401de 100644 --- a/src/Report/Xml/Report.php +++ b/src/Report/Xml/Report.php @@ -20,88 +20,114 @@ */ final class Report extends File { + private readonly string $name; + public function __construct(string $name) { $dom = new DOMDocument; $dom->loadXML(''); $contextNode = $dom->getElementsByTagNameNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'file', )->item(0); parent::__construct($contextNode); - $this->setName($name); + $this->name = $name; } public function asDom(): DOMDocument { - return $this->dom(); + $this->contextNode()->setAttribute('name', basename($this->name)); + $this->contextNode()->setAttribute('path', dirname($this->name)); + + return $this->dom; } - public function functionObject(string $name): Method - { + public function functionObject( + string $name, + string $signature, + string $start, + ?string $end, + string $executable, + string $executed, + string $coverage, + string $crap + ): void { $node = $this->contextNode()->appendChild( - $this->dom()->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + $this->dom->createElementNS( + Facade::XML_NAMESPACE, 'function', ), ); assert($node instanceof DOMElement); - return new Method($node, $name); - } - - public function classObject(string $name): Unit - { - return $this->unitObject('class', $name); + new Method( + $node, + $name, + $signature, + $start, + $end, + $executable, + $executed, + $coverage, + $crap, + ); } - public function traitObject(string $name): Unit - { - return $this->unitObject('trait', $name); - } + public function classObject( + string $name, + string $namespace, + int $start, + int $executable, + int $executed, + float $crap + ): Unit { + $node = $this->contextNode()->appendChild( + $this->dom->createElementNS( + Facade::XML_NAMESPACE, + 'class', + ), + ); - public function source(): Source - { - $source = $this->contextNode()->getElementsByTagNameNS( - 'https://schema.phpunit.de/coverage/1.0', - 'source', - )->item(0); + assert($node instanceof DOMElement); - if ($source === null) { - $source = $this->contextNode()->appendChild( - $this->dom()->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', - 'source', - ), - ); - } + return new Unit($node, $name, $namespace, $start, $executable, $executed, $crap); + } - assert($source instanceof DOMElement); + public function traitObject( + string $name, + string $namespace, + int $start, + int $executable, + int $executed, + float $crap + ): Unit { + $node = $this->contextNode()->appendChild( + $this->dom->createElementNS( + Facade::XML_NAMESPACE, + 'trait', + ), + ); - return new Source($source); - } + assert($node instanceof DOMElement); - private function setName(string $name): void - { - $this->contextNode()->setAttribute('name', basename($name)); - $this->contextNode()->setAttribute('path', dirname($name)); + return new Unit($node, $name, $namespace, $start, $executable, $executed, $crap); } - private function unitObject(string $tagName, string $name): Unit + public function source(): Source { - $node = $this->contextNode()->appendChild( - $this->dom()->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', - $tagName, + $source = $this->contextNode()->appendChild( + $this->dom->createElementNS( + Facade::XML_NAMESPACE, + 'source', ), ); - assert($node instanceof DOMElement); + assert($source instanceof DOMElement); - return new Unit($node, $name); + return new Source($source); } } diff --git a/src/Report/Xml/Source.php b/src/Report/Xml/Source.php index 448fe72d6..698a71b6d 100644 --- a/src/Report/Xml/Source.php +++ b/src/Report/Xml/Source.php @@ -31,7 +31,7 @@ public function setSourceCode(string $source): void $context = $this->context; $tokens = (new Tokenizer)->parse($source); - $srcDom = (new XMLSerializer(new NamespaceUri($context->namespaceURI)))->toDom($tokens); + $srcDom = (new XMLSerializer(new NamespaceUri(Facade::XML_NAMESPACE)))->toDom($tokens); $context->parentNode->replaceChild( $context->ownerDocument->importNode($srcDom->documentElement, true), diff --git a/src/Report/Xml/Tests.php b/src/Report/Xml/Tests.php index c9e9c48ef..1760fdfa5 100644 --- a/src/Report/Xml/Tests.php +++ b/src/Report/Xml/Tests.php @@ -34,7 +34,7 @@ public function addTest(string $test, array $result): void { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'test', ), ); diff --git a/src/Report/Xml/Totals.php b/src/Report/Xml/Totals.php index 8e285a78e..28612f7aa 100644 --- a/src/Report/Xml/Totals.php +++ b/src/Report/Xml/Totals.php @@ -29,27 +29,27 @@ public function __construct(DOMElement $container) $dom = $container->ownerDocument; $this->linesNode = $dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'lines', ); $this->methodsNode = $dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'methods', ); $this->functionsNode = $dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'functions', ); $this->classesNode = $dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'classes', ); $this->traitsNode = $dom->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'traits', ); diff --git a/src/Report/Xml/Unit.php b/src/Report/Xml/Unit.php index a00f85d39..fa97909c2 100644 --- a/src/Report/Xml/Unit.php +++ b/src/Report/Xml/Unit.php @@ -19,62 +19,63 @@ { private DOMElement $contextNode; - public function __construct(DOMElement $context, string $name) - { + public function __construct( + DOMElement $context, + string $name, + string $namespace, + int $start, + int $executable, + int $executed, + float $crap + ) { $this->contextNode = $context; - $this->setName($name); - } - - public function setLines(int $start, int $executable, int $executed): void - { + $this->contextNode->setAttribute('name', $name); $this->contextNode->setAttribute('start', (string) $start); $this->contextNode->setAttribute('executable', (string) $executable); $this->contextNode->setAttribute('executed', (string) $executed); - } - - public function setCrap(float $crap): void - { $this->contextNode->setAttribute('crap', (string) $crap); - } - - public function setNamespace(string $namespace): void - { - $node = $this->contextNode->getElementsByTagNameNS( - 'https://schema.phpunit.de/coverage/1.0', - 'namespace', - )->item(0); - - if ($node === null) { - $node = $this->contextNode->appendChild( - $this->contextNode->ownerDocument->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', - 'namespace', - ), - ); - } + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS( + Facade::XML_NAMESPACE, + 'namespace', + ), + ); assert($node instanceof DOMElement); $node->setAttribute('name', $namespace); } - public function addMethod(string $name): Method - { + public function addMethod( + string $name, + string $signature, + string $start, + ?string $end, + string $executable, + string $executed, + string $coverage, + string $crap + ): void { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( - 'https://schema.phpunit.de/coverage/1.0', + Facade::XML_NAMESPACE, 'method', ), ); assert($node instanceof DOMElement); - return new Method($node, $name); - } - - private function setName(string $name): void - { - $this->contextNode->setAttribute('name', $name); + new Method( + $node, + $name, + $signature, + $start, + $end, + $executable, + $executed, + $coverage, + $crap, + ); } } diff --git a/src/Target/MapBuilder.php b/src/Target/MapBuilder.php index be859e2eb..8e8d93fbb 100644 --- a/src/Target/MapBuilder.php +++ b/src/Target/MapBuilder.php @@ -11,7 +11,6 @@ use function array_keys; use function array_merge; -use function array_merge_recursive; use function array_slice; use function array_unique; use function count; @@ -72,20 +71,7 @@ public function build(Filter $filter, FileAnalyser $analyser): array continue; } - $file = array_keys($traits[$traitName])[0]; - - if (!isset($traits[$trait->namespacedName()][$file])) { - $traits[$trait->namespacedName()][$file] = $traits[$traitName][$file]; - - continue; - } - - $traits[$trait->namespacedName()][$file] = array_unique( - array_merge( - $traits[$trait->namespacedName()][$file], - $traits[$traitName][$file], - ), - ); + $this->mergeLines($trait->namespacedName(), $traits[$traitName], $traits); } } } @@ -109,20 +95,7 @@ public function build(Filter $filter, FileAnalyser $analyser): array continue; } - foreach ($traits[$traitName] as $traitFile => $lines) { - if (!isset($classes[$class->namespacedName()][$traitFile])) { - $classes[$class->namespacedName()][$traitFile] = $lines; - - continue; - } - - $classes[$class->namespacedName()][$traitFile] = array_unique( - array_merge( - $classes[$class->namespacedName()][$traitFile], - $lines, - ), - ); - } + $this->mergeLines($class->namespacedName(), $traits[$traitName], $classes); } $this->processMethods($class, $file, $methods, $reverseLookup); @@ -144,8 +117,8 @@ public function build(Filter $filter, FileAnalyser $analyser): array } } - foreach (array_keys($namespaces) as $namespace) { - foreach (array_keys($namespaces[$namespace]) as $file) { + foreach ($namespaces as $namespace => $files) { + foreach (array_keys($files) as $file) { $namespaces[$namespace][$file] = array_unique($namespaces[$namespace][$file]); } } @@ -160,10 +133,7 @@ public function build(Filter $filter, FileAnalyser $analyser): array } foreach ($this->parentClasses($classDetails, $class) as $parentClass) { - $classes[$class->namespacedName()] = array_merge_recursive( - $classes[$class->namespacedName()], - $classes[$parentClass->namespacedName()], - ); + $this->mergeLines($class->namespacedName(), $classes[$parentClass->namespacedName()], $classes); if (isset($classesThatExtendClass[$parentClass->namespacedName()])) { $this->process($classesThatExtendClass, $parentClass->namespacedName(), $class->file(), $class->startLine(), $class->endLine()); @@ -187,15 +157,6 @@ public function build(Filter $filter, FileAnalyser $analyser): array unset($classesThatExtendClass[$className]); } - /** - * @todo Avoid duplication and remove this loop - */ - foreach (array_keys($classes) as $className) { - foreach (array_keys($classes[$className]) as $file) { - $classes[$className][$file] = array_unique($classes[$className][$file]); - } - } - return [ 'namespaces' => $namespaces, 'traits' => $traits, @@ -208,6 +169,32 @@ public function build(Filter $filter, FileAnalyser $analyser): array ]; } + private function mergeLines(string $targetClass, array $sourceData, array &$data): void + { + /** + * In large inheritance trees we might handle a lot of data. + * This loop needs to prevent unnecessary work whenever possible. + */ + foreach ($sourceData as $file => $lines) { + if (!isset($data[$targetClass][$file])) { + $data[$targetClass][$file] = $lines; + + continue; + } + + if ($data[$targetClass][$file] === $lines) { + continue; + } + + $data[$targetClass][$file] = array_unique( + array_merge( + $data[$targetClass][$file], + $lines, + ), + ); + } + } + private function processMethods(Class_|Trait_ $classOrTrait, string $file, array &$methods, array &$reverseLookup): void { foreach ($classOrTrait->methods() as $method) { diff --git a/src/Util/Filesystem.php b/src/Util/Filesystem.php index 0e99b1593..f73388ae2 100644 --- a/src/Util/Filesystem.php +++ b/src/Util/Filesystem.php @@ -9,9 +9,13 @@ */ namespace SebastianBergmann\CodeCoverage\Util; +use function dirname; +use function file_put_contents; use function is_dir; use function mkdir; use function sprintf; +use function str_contains; +use SebastianBergmann\CodeCoverage\WriteOperationFailedException; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage @@ -34,4 +38,20 @@ public static function createDirectory(string $directory): void ); } } + + /** + * @param non-empty-string $target + * + * @throws WriteOperationFailedException + */ + public static function write(string $target, string $buffer): void + { + if (!str_contains($target, '://')) { + self::createDirectory(dirname($target)); + } + + if (@file_put_contents($target, $buffer) === false) { + throw new WriteOperationFailedException($target); + } + } } diff --git a/src/Util/Xml.php b/src/Util/Xml.php new file mode 100644 index 000000000..de958a4b2 --- /dev/null +++ b/src/Util/Xml.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Util; + +use const PHP_EOL; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use DOMDocument; +use SebastianBergmann\CodeCoverage\XmlException; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class Xml +{ + /** + * @throws XmlException + * + * @see https://bugs.php.net/bug.php?id=79191 + */ + public static function asString(DOMDocument $document): string + { + $xmlErrorHandling = libxml_use_internal_errors(true); + + $document->formatOutput = true; + $document->preserveWhiteSpace = false; + + $buffer = $document->saveXML(); + + if ($buffer === false) { + $message = 'Unable to generate the XML'; + + foreach (libxml_get_errors() as $error) { + $message .= PHP_EOL . $error->message; + } + + throw new XmlException($message); + } + + libxml_clear_errors(); + libxml_use_internal_errors($xmlErrorHandling); + + return $buffer; + } +} diff --git a/src/Version.php b/src/Version.php index fe7c20d6c..a52b872a3 100644 --- a/src/Version.php +++ b/src/Version.php @@ -19,7 +19,7 @@ final class Version public static function id(): string { if (self::$version === '') { - self::$version = (new VersionId('12.3.2', dirname(__DIR__)))->asString(); + self::$version = (new VersionId('12.4.0', dirname(__DIR__)))->asString(); } return self::$version; diff --git a/tests/src/TestCase.php b/tests/src/TestCase.php index aff94901c..f8273d17c 100644 --- a/tests/src/TestCase.php +++ b/tests/src/TestCase.php @@ -14,6 +14,9 @@ use BankAccount; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; use SebastianBergmann\CodeCoverage\Driver\Driver; use SebastianBergmann\CodeCoverage\Test\Target\Target; @@ -1083,8 +1086,6 @@ protected function getPathCoverageForBankAccount(): CodeCoverage $stub = $this->createStub(Driver::class); - $stub->method('collectsBranchAndPathCoverage')->willReturn(true); - $stub->method('stop') ->willReturn(...$data); @@ -1093,6 +1094,8 @@ protected function getPathCoverageForBankAccount(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); + $coverage->enableBranchAndPathCoverage(); + $coverage->start( 'BankAccountTest::testBalanceIsInitiallyZero', null, @@ -1162,8 +1165,6 @@ protected function getPathCoverageForSourceWithoutNamespace(): CodeCoverage $stub = $this->createStub(Driver::class); - $stub->method('collectsBranchAndPathCoverage')->willReturn(true); - $stub->method('stop') ->willReturn(...$data); @@ -1172,6 +1173,8 @@ protected function getPathCoverageForSourceWithoutNamespace(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); + $coverage->enableBranchAndPathCoverage(); + $coverage->start( 'faketest', null, @@ -1472,6 +1475,8 @@ protected function getPathCoverageForBankAccountForFirstTwoTests(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); + $coverage->enableBranchAndPathCoverage(); + $coverage->start( 'BankAccountTest::testBalanceIsInitiallyZero', null, @@ -1519,6 +1524,8 @@ protected function getPathCoverageForBankAccountForLastTwoTests(): CodeCoverage $coverage = new CodeCoverage($stub, $filter); + $coverage->enableBranchAndPathCoverage(); + $coverage->start( 'BankAccountTest::testBalanceCannotBecomeNegative2', ); @@ -1556,201 +1563,199 @@ protected function getExpectedPathCoverageDataArrayForBankAccount(): array { return [ TEST_FILES_PATH . 'BankAccount.php' => [ - 'BankAccount->depositMoney' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 14, - 'line_start' => 20, - 'line_end' => 25, - 'hit' => [ + 'BankAccount->depositMoney' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 14, + 20, + 25, + [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - 'out' => [ - ], - 'out_hit' => [ - ], - ], + [], + [], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [ + [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - ], + ), ], - ], - 'BankAccount->getBalance' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 5, - 'line_start' => 6, - 'line_end' => 9, - 'hit' => [ + ), + 'BankAccount->getBalance' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 5, + 6, + 9, + [ 0 => 'BankAccountTest::testBalanceIsInitiallyZero', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - 'out' => [ + [ ], - 'out_hit' => [ + [ ], - ], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [ + [ 0 => 'BankAccountTest::testBalanceIsInitiallyZero', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - ], + ), ], - ], - 'BankAccount->withdrawMoney' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 14, - 'line_start' => 27, - 'line_end' => 32, - 'hit' => [ + ), + 'BankAccount->withdrawMoney' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 14, + 27, + 32, + [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - 'out' => [ + [ ], - 'out_hit' => [ + [ ], - ], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [ + [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - ], + ), ], - ], - '{main}' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 1, - 'line_start' => 34, - 'line_end' => 34, - 'hit' => [ - ], - 'out' => [ + ), + '{main}' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 1, + 34, + 34, + [ + ], + [ 0 => 2147483645, ], - 'out_hit' => [ + [ 0 => 0, ], - ], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [ + [ ], - ], + ), ], - ], - 'BankAccount->setBalance' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 4, - 'line_start' => 11, - 'line_end' => 13, - 'hit' => [ - ], - 'out' => [ + ), + 'BankAccount->setBalance' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 4, + 11, + 13, + [ + ], + [ 0 => 5, 1 => 9, ], - 'out_hit' => [ + [ 0 => 0, 1 => 0, ], - ], - 5 => [ - 'op_start' => 5, - 'op_end' => 8, - 'line_start' => 14, - 'line_end' => 14, - 'hit' => [ - ], - 'out' => [ + ), + 5 => new ProcessedBranchCoverageData( + 5, + 8, + 14, + 14, + [ + ], + [ 0 => 13, ], - 'out_hit' => [ + [ 0 => 0, ], - ], - 9 => [ - 'op_start' => 9, - 'op_end' => 12, - 'line_start' => 16, - 'line_end' => 16, - 'hit' => [ - ], - 'out' => [ + ), + 9 => new ProcessedBranchCoverageData( + 9, + 12, + 16, + 16, + [ + ], + [ 0 => 2147483645, ], - 'out_hit' => [ + [ 0 => 0, ], - ], - 13 => [ - 'op_start' => 13, - 'op_end' => 14, - 'line_start' => 18, - 'line_end' => 18, - 'hit' => [ - ], - 'out' => [ + ), + 13 => new ProcessedBranchCoverageData( + 13, + 14, + 18, + 18, + [ + ], + [ 0 => 2147483645, ], - 'out_hit' => [ + [ 0 => 0, ], - ], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, 1 => 5, 2 => 13, ], - 'hit' => [ + [ ], - ], - 1 => [ - 'path' => [ + ), + 1 => new ProcessedPathCoverageData( + [ 0 => 0, 1 => 9, ], - 'hit' => [ + [ ], - ], + ), ], - ], + ), ], ]; } diff --git a/tests/tests/Data/ProcessedCodeCoverageDataTest.php b/tests/tests/Data/ProcessedCodeCoverageDataTest.php index 46e144eec..e903636b8 100644 --- a/tests/tests/Data/ProcessedCodeCoverageDataTest.php +++ b/tests/tests/Data/ProcessedCodeCoverageDataTest.php @@ -75,29 +75,27 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $coverage->setFunctionCoverage( [ '/some/path/SomeClass.php' => [ - 'SomeClass->firstFunction' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 14, - 'line_start' => 20, - 'line_end' => 25, - 'hit' => [], - 'out' => [ - ], - 'out_hit' => [ - ], - ], + 'SomeClass->firstFunction' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 14, + 20, + 25, + [], + [], + [], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [], - ], + [], + ), ], - ], + ), ], ], ); @@ -106,75 +104,70 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $newCoverage->setFunctionCoverage( [ '/some/path/SomeClass.php' => [ - 'SomeClass->firstFunction' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 14, - 'line_start' => 20, - 'line_end' => 25, - 'hit' => [], - 'out' => [ - ], - 'out_hit' => [ - ], - ], - 1 => [ - 'op_start' => 15, - 'op_end' => 16, - 'line_start' => 26, - 'line_end' => 27, - 'hit' => [], - 'out' => [ - ], - 'out_hit' => [ - ], - ], + 'SomeClass->firstFunction' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 14, + 20, + 25, + [], + [], + [], + ), + 1 => new ProcessedBranchCoverageData( + 15, + 16, + 26, + 27, + [], + [], + [], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [], - ], - 1 => [ - 'path' => [ + [], + ), + 1 => new ProcessedPathCoverageData( + [ 0 => 1, ], - 'hit' => [], - ], + [], + ), ], - ], - 'SomeClass->secondFunction' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 24, - 'line_start' => 30, - 'line_end' => 35, - 'hit' => [], - 'out' => [ - ], - 'out_hit' => [ - ], - ], + ), + 'SomeClass->secondFunction' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 24, + 30, + 35, + [], + [], + [], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [], - ], + [], + ), ], - ], + ), ], ], ); $coverage->merge($newCoverage); + $this->assertIsArray($newCoverage->functionCoverage()['/some/path/SomeClass.php']); $this->assertArrayHasKey('SomeClass->secondFunction', $newCoverage->functionCoverage()['/some/path/SomeClass.php']); } } diff --git a/tests/tests/Data/RawCodeCoverageDataTest.php b/tests/tests/Data/RawCodeCoverageDataTest.php index 98c4f45cb..7ec012acc 100644 --- a/tests/tests/Data/RawCodeCoverageDataTest.php +++ b/tests/tests/Data/RawCodeCoverageDataTest.php @@ -327,6 +327,8 @@ public function testCoverageForFileWithInlineAnnotations(): void ], ); + $coverage->skipEmptyLines(); + $this->assertEquals( [ 13 => -1, diff --git a/tests/tests/Report/PhpTest.php b/tests/tests/Report/PhpTest.php index 358e588a7..ad67df576 100644 --- a/tests/tests/Report/PhpTest.php +++ b/tests/tests/Report/PhpTest.php @@ -10,14 +10,13 @@ namespace SebastianBergmann\CodeCoverage\Report; use ReflectionProperty; +use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\TestCase; final class PhpTest extends TestCase { protected function tearDown(): void { - parent::tearDown(); - $this->removeTemporaryFiles(); } @@ -30,7 +29,9 @@ public function testPHPSerialisationProducesValidCode(): void $unserialized = require TEST_FILES_PATH . 'tmp/serialized.php'; - $this->assertEquals($coverage, $unserialized); + $this->assertInstanceOf(CodeCoverage::class, $unserialized); + $this->assertEquals($coverage->getData(), $unserialized->getData()); + $this->assertEquals($coverage->getTests(), $unserialized->getTests()); } public function testPHPSerialisationProducesValidCodeWhenOutputIncludesSingleQuote(): void @@ -42,7 +43,9 @@ public function testPHPSerialisationProducesValidCodeWhenOutputIncludesSingleQuo $unserialized = require TEST_FILES_PATH . 'tmp/serialized.php'; - $this->assertEquals($coverage, $unserialized); + $this->assertInstanceOf(CodeCoverage::class, $unserialized); + $this->assertEquals($coverage->getData(), $unserialized->getData()); + $this->assertEquals($coverage->getTests(), $unserialized->getTests()); } public function testCacheDataNeverGetSaved(): void diff --git a/tests/tests/Target/MapBuilderTest.php b/tests/tests/Target/MapBuilderTest.php index 0c03682b4..49c1c0ed6 100644 --- a/tests/tests/Target/MapBuilderTest.php +++ b/tests/tests/Target/MapBuilderTest.php @@ -418,6 +418,10 @@ public static function provider(): array ]; } + /** + * @param TargetMap $expected, + * @param non-empty-list $files + */ #[DataProvider('provider')] public function testBuildsMap(array $expected, array $files): void { diff --git a/tools/.phpstan/composer.json b/tools/.phpstan/composer.json index 37e97680a..9851b7c08 100644 --- a/tools/.phpstan/composer.json +++ b/tools/.phpstan/composer.json @@ -1,10 +1,10 @@ { "require-dev": { - "phpstan/phpstan": "^2.1.21", + "phpstan/phpstan": "^2.1.32", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan-strict-rules": "^2.0.6", + "phpstan/phpstan-strict-rules": "^2.0.7", "tomasvotruba/type-coverage": "^2.0.2", - "ergebnis/phpstan-rules": "^2.10.5" + "ergebnis/phpstan-rules": "^2.12.0" }, "config": { "allow-plugins": { diff --git a/tools/.phpstan/composer.lock b/tools/.phpstan/composer.lock index bac49621d..e823a15ad 100644 --- a/tools/.phpstan/composer.lock +++ b/tools/.phpstan/composer.lock @@ -4,26 +4,26 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "21a2ca88ab1be049930ddca65ab02f99", + "content-hash": "2c8ea294a7a4e09e4793951d07859bf8", "packages": [], "packages-dev": [ { "name": "ergebnis/phpstan-rules", - "version": "2.10.5", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/ergebnis/phpstan-rules.git", - "reference": "719a53e793e2417da46d497f21fb8d2f007ecb78" + "reference": "c4e0121a937b3b551f800a86e7d78794da2783ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/719a53e793e2417da46d497f21fb8d2f007ecb78", - "reference": "719a53e793e2417da46d497f21fb8d2f007ecb78", + "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/c4e0121a937b3b551f800a86e7d78794da2783ea", + "reference": "c4e0121a937b3b551f800a86e7d78794da2783ea", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "phpstan/phpstan": "^2.1.8" }, "require-dev": { @@ -31,14 +31,13 @@ "doctrine/orm": "^2.20.0 || ^3.3.0", "ergebnis/composer-normalize": "^2.47.0", "ergebnis/license": "^2.6.0", - "ergebnis/php-cs-fixer-config": "^6.46.0", - "ergebnis/phpunit-slow-test-detector": "^2.19.1", + "ergebnis/php-cs-fixer-config": "^6.54.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", "fakerphp/faker": "^1.24.1", - "nette/di": "^3.1.10", "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpstan/phpstan-phpunit": "^2.0.6", - "phpstan/phpstan-strict-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", "phpunit/phpunit": "^9.6.21", "psr/container": "^2.0.2", "symfony/finder": "^5.4.45", @@ -79,33 +78,33 @@ "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/phpstan-rules" }, - "time": "2025-06-23T17:35:58+00:00" + "time": "2025-09-07T13:31:33+00:00" }, { "name": "nette/utils", - "version": "v4.0.7", + "version": "v4.0.8", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", "shasum": "" }, "require": { - "php": "8.0 - 8.4" + "php": "8.0 - 8.5" }, "conflict": { "nette/finder": "<3", "nette/schema": "<1.2.2" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.5", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -123,6 +122,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -163,9 +165,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.7" + "source": "https://github.com/nette/utils/tree/v4.0.8" }, - "time": "2025-06-03T04:55:08+00:00" + "time": "2025-08-06T21:43:34+00:00" }, { "name": "phpstan/extension-installer", @@ -217,16 +219,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.21", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6" - }, + "version": "2.1.32", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6", - "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", + "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", "shasum": "" }, "require": { @@ -271,25 +268,25 @@ "type": "github" } ], - "time": "2025-07-28T19:35:08+00:00" + "time": "2025-11-11T15:18:17+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.6", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094" + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f9f77efa9de31992a832ff77ea52eb42d675b094", - "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.4" + "phpstan/phpstan": "^2.1.29" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -317,9 +314,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.6" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" }, - "time": "2025-07-21T12:19:29+00:00" + "time": "2025-09-26T11:19:08+00:00" }, { "name": "tomasvotruba/type-coverage", @@ -386,5 +383,5 @@ "prefer-lowest": false, "platform": {}, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/tools/.phpstan/vendor/composer/autoload_psr4.php b/tools/.phpstan/vendor/composer/autoload_psr4.php index 1dc5d9e4c..6b557fe59 100644 --- a/tools/.phpstan/vendor/composer/autoload_psr4.php +++ b/tools/.phpstan/vendor/composer/autoload_psr4.php @@ -9,5 +9,6 @@ 'TomasVotruba\\TypeCoverage\\' => array($vendorDir . '/tomasvotruba/type-coverage/src'), 'PHPStan\\ExtensionInstaller\\' => array($vendorDir . '/phpstan/extension-installer/src'), 'PHPStan\\' => array($vendorDir . '/phpstan/phpstan-strict-rules/src'), + 'Nette\\' => array($vendorDir . '/nette/utils/src'), 'Ergebnis\\PHPStan\\Rules\\' => array($vendorDir . '/ergebnis/phpstan-rules/src'), ); diff --git a/tools/.phpstan/vendor/composer/autoload_static.php b/tools/.phpstan/vendor/composer/autoload_static.php index 17adb2b10..b66b4992e 100644 --- a/tools/.phpstan/vendor/composer/autoload_static.php +++ b/tools/.phpstan/vendor/composer/autoload_static.php @@ -11,35 +11,43 @@ class ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7 ); public static $prefixLengthsPsr4 = array ( - 'T' => + 'T' => array ( 'TomasVotruba\\TypeCoverage\\' => 26, ), - 'P' => + 'P' => array ( 'PHPStan\\ExtensionInstaller\\' => 27, 'PHPStan\\' => 8, ), - 'E' => + 'N' => + array ( + 'Nette\\' => 6, + ), + 'E' => array ( 'Ergebnis\\PHPStan\\Rules\\' => 23, ), ); public static $prefixDirsPsr4 = array ( - 'TomasVotruba\\TypeCoverage\\' => + 'TomasVotruba\\TypeCoverage\\' => array ( 0 => __DIR__ . '/..' . '/tomasvotruba/type-coverage/src', ), - 'PHPStan\\ExtensionInstaller\\' => + 'PHPStan\\ExtensionInstaller\\' => array ( 0 => __DIR__ . '/..' . '/phpstan/extension-installer/src', ), - 'PHPStan\\' => + 'PHPStan\\' => array ( 0 => __DIR__ . '/..' . '/phpstan/phpstan-strict-rules/src', ), - 'Ergebnis\\PHPStan\\Rules\\' => + 'Nette\\' => + array ( + 0 => __DIR__ . '/..' . '/nette/utils/src', + ), + 'Ergebnis\\PHPStan\\Rules\\' => array ( 0 => __DIR__ . '/..' . '/ergebnis/phpstan-rules/src', ), diff --git a/tools/.phpstan/vendor/composer/installed.json b/tools/.phpstan/vendor/composer/installed.json index c13206e30..76385ec26 100644 --- a/tools/.phpstan/vendor/composer/installed.json +++ b/tools/.phpstan/vendor/composer/installed.json @@ -2,22 +2,22 @@ "packages": [ { "name": "ergebnis/phpstan-rules", - "version": "2.10.5", - "version_normalized": "2.10.5.0", + "version": "2.12.0", + "version_normalized": "2.12.0.0", "source": { "type": "git", "url": "https://github.com/ergebnis/phpstan-rules.git", - "reference": "719a53e793e2417da46d497f21fb8d2f007ecb78" + "reference": "c4e0121a937b3b551f800a86e7d78794da2783ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/719a53e793e2417da46d497f21fb8d2f007ecb78", - "reference": "719a53e793e2417da46d497f21fb8d2f007ecb78", + "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/c4e0121a937b3b551f800a86e7d78794da2783ea", + "reference": "c4e0121a937b3b551f800a86e7d78794da2783ea", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "phpstan/phpstan": "^2.1.8" }, "require-dev": { @@ -25,20 +25,19 @@ "doctrine/orm": "^2.20.0 || ^3.3.0", "ergebnis/composer-normalize": "^2.47.0", "ergebnis/license": "^2.6.0", - "ergebnis/php-cs-fixer-config": "^6.46.0", - "ergebnis/phpunit-slow-test-detector": "^2.19.1", + "ergebnis/php-cs-fixer-config": "^6.54.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", "fakerphp/faker": "^1.24.1", - "nette/di": "^3.1.10", "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpstan/phpstan-phpunit": "^2.0.6", - "phpstan/phpstan-strict-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", "phpunit/phpunit": "^9.6.21", "psr/container": "^2.0.2", "symfony/finder": "^5.4.45", "symfony/process": "^5.4.47" }, - "time": "2025-06-23T17:35:58+00:00", + "time": "2025-09-07T13:31:33+00:00", "type": "phpstan-extension", "extra": { "phpstan": { @@ -79,30 +78,30 @@ }, { "name": "nette/utils", - "version": "v4.0.7", - "version_normalized": "4.0.7.0", + "version": "v4.0.8", + "version_normalized": "4.0.8.0", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", "shasum": "" }, "require": { - "php": "8.0 - 8.4" + "php": "8.0 - 8.5" }, "conflict": { "nette/finder": "<3", "nette/schema": "<1.2.2" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.5", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -113,7 +112,7 @@ "ext-mbstring": "to use Strings::lower() etc...", "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, - "time": "2025-06-03T04:55:08+00:00", + "time": "2025-08-06T21:43:34+00:00", "type": "library", "extra": { "branch-alias": { @@ -122,6 +121,9 @@ }, "installation-source": "dist", "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -162,7 +164,7 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.7" + "source": "https://github.com/nette/utils/tree/v4.0.8" }, "install-path": "../nette/utils" }, @@ -219,17 +221,12 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.21", - "version_normalized": "2.1.21.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6" - }, + "version": "2.1.32", + "version_normalized": "2.1.32.0", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6", - "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", + "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", "shasum": "" }, "require": { @@ -238,7 +235,7 @@ "conflict": { "phpstan/phpstan-shim": "*" }, - "time": "2025-07-28T19:35:08+00:00", + "time": "2025-11-11T15:18:17+00:00", "bin": [ "phpstan", "phpstan.phar" @@ -280,22 +277,22 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.6", - "version_normalized": "2.0.6.0", + "version": "2.0.7", + "version_normalized": "2.0.7.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094" + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f9f77efa9de31992a832ff77ea52eb42d675b094", - "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.4" + "phpstan/phpstan": "^2.1.29" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -303,7 +300,7 @@ "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.6" }, - "time": "2025-07-21T12:19:29+00:00", + "time": "2025-09-26T11:19:08+00:00", "type": "phpstan-extension", "extra": { "phpstan": { @@ -325,7 +322,7 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.6" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" }, "install-path": "../phpstan/phpstan-strict-rules" }, diff --git a/tools/.phpstan/vendor/composer/installed.php b/tools/.phpstan/vendor/composer/installed.php index 25f75aa88..58d90cfef 100644 --- a/tools/.phpstan/vendor/composer/installed.php +++ b/tools/.phpstan/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '530bb5df4bf62ecfdc3ad304fa6b9e8afe4acd66', + 'reference' => '37047b5f0e98b31dccb9b7e7241e4f66ebfcd358', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,25 +13,25 @@ '__root__' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '530bb5df4bf62ecfdc3ad304fa6b9e8afe4acd66', + 'reference' => '37047b5f0e98b31dccb9b7e7241e4f66ebfcd358', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), 'ergebnis/phpstan-rules' => array( - 'pretty_version' => '2.10.5', - 'version' => '2.10.5.0', - 'reference' => '719a53e793e2417da46d497f21fb8d2f007ecb78', + 'pretty_version' => '2.12.0', + 'version' => '2.12.0.0', + 'reference' => 'c4e0121a937b3b551f800a86e7d78794da2783ea', 'type' => 'phpstan-extension', 'install_path' => __DIR__ . '/../ergebnis/phpstan-rules', 'aliases' => array(), 'dev_requirement' => true, ), 'nette/utils' => array( - 'pretty_version' => 'v4.0.7', - 'version' => '4.0.7.0', - 'reference' => 'e67c4061eb40b9c113b218214e42cb5a0dda28f2', + 'pretty_version' => 'v4.0.8', + 'version' => '4.0.8.0', + 'reference' => 'c930ca4e3cf4f17dcfb03037703679d2396d2ede', 'type' => 'library', 'install_path' => __DIR__ . '/../nette/utils', 'aliases' => array(), @@ -47,18 +47,18 @@ 'dev_requirement' => true, ), 'phpstan/phpstan' => array( - 'pretty_version' => '2.1.21', - 'version' => '2.1.21.0', - 'reference' => '1ccf445757458c06a04eb3f803603cb118fe5fa6', + 'pretty_version' => '2.1.32', + 'version' => '2.1.32.0', + 'reference' => 'e126cad1e30a99b137b8ed75a85a676450ebb227', 'type' => 'library', 'install_path' => __DIR__ . '/../phpstan/phpstan', 'aliases' => array(), 'dev_requirement' => true, ), 'phpstan/phpstan-strict-rules' => array( - 'pretty_version' => '2.0.6', - 'version' => '2.0.6.0', - 'reference' => 'f9f77efa9de31992a832ff77ea52eb42d675b094', + 'pretty_version' => '2.0.7', + 'version' => '2.0.7.0', + 'reference' => 'd6211c46213d4181054b3d77b10a5c5cb0d59538', 'type' => 'phpstan-extension', 'install_path' => __DIR__ . '/../phpstan/phpstan-strict-rules', 'aliases' => array(), diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md index 38ee8788b..7729147af 100644 --- a/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md @@ -6,7 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased -For a full diff see [`2.10.5...main`][2.10.5...main]. +For a full diff see [`2.12.0...main`][2.12.0...main]. + +## [`2.12.0`][2.12.0] + +For a full diff see [`2.11.0...2.12.0`][2.11.0...2.12.0]. + +### Added + +- Added support for PHP 8.5 ([#977]), by [@localheinz] + +## [`2.11.0`][2.11.0] + +For a full diff see [`2.10.5...2.11.0`][2.10.5...2.11.0]. + +### Changed + +- Allowed installation on PHP 8.5 ([#972]), by [@localheinz] ## [`2.10.5`][2.10.5] @@ -570,6 +586,8 @@ For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0]. [2.10.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.3 [2.10.4]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.4 [2.10.5]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.5 +[2.11.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.11.0 +[2.12.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.12.0 [362c7ea...0.1.0]: https://github.com/ergebnis/phpstan-rules/compare/362c7ea...0.1.0 [0.1.0...0.2.0]: https://github.com/ergebnis/phpstan-rules/compare/0.1.0...0.2.0 @@ -618,7 +636,9 @@ For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0]. [2.10.2...2.10.3]: https://github.com/ergebnis/phpstan-rules/compare/2.10.2...2.10.3 [2.10.3...2.10.4]: https://github.com/ergebnis/phpstan-rules/compare/2.10.3...2.10.4 [2.10.4...2.10.5]: https://github.com/ergebnis/phpstan-rules/compare/2.10.4...2.10.5 -[2.10.5...main]: https://github.com/ergebnis/phpstan-rules/compare/2.10.5...main +[2.10.5...2.11.0]: https://github.com/ergebnis/phpstan-rules/compare/2.10.5...2.11.0 +[2.11.0...2.12.0]: https://github.com/ergebnis/phpstan-rules/compare/2.11.0...2.12.0 +[2.12.0...main]: https://github.com/ergebnis/phpstan-rules/compare/2.12.0...main [#1]: https://github.com/ergebnis/phpstan-rules/pull/1 [#4]: https://github.com/ergebnis/phpstan-rules/pull/4 @@ -714,6 +734,8 @@ For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0]. [#951]: https://github.com/ergebnis/phpstan-rules/pull/951 [#957]: https://github.com/ergebnis/phpstan-rules/pull/957 [#958]: https://github.com/ergebnis/phpstan-rules/pull/958 +[#972]: https://github.com/ergebnis/phpstan-rules/pull/972 +[#977]: https://github.com/ergebnis/phpstan-rules/pull/977 [@cosmastech]: https://github.com/cosmastech [@enumag]: https://github.com/enumag diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md index 7f80534b7..690d4ad04 100644 --- a/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md @@ -735,7 +735,7 @@ The maintainers of this project ask contributors to follow the [code of conduct] The maintainers of this project provide limited support. -You can support the maintenance of this project by [sponsoring @localheinz](https://github.com/sponsors/localheinz) or [requesting an invoice for services related to this project](mailto:am@localheinz.com?subject=ergebnis/phpstan-rules:%20Requesting%20invoice%20for%20services). +You can support the maintenance of this project by [sponsoring @ergebnis](https://github.com/sponsors/ergebnis). ## PHP Version Support Policy diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json b/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json index 10429599c..5060dde2d 100644 --- a/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json @@ -21,7 +21,7 @@ "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md" }, "require": { - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "ext-mbstring": "*", "phpstan/phpstan": "^2.1.8" }, @@ -30,14 +30,13 @@ "doctrine/orm": "^2.20.0 || ^3.3.0", "ergebnis/composer-normalize": "^2.47.0", "ergebnis/license": "^2.6.0", - "ergebnis/php-cs-fixer-config": "^6.46.0", - "ergebnis/phpunit-slow-test-detector": "^2.19.1", + "ergebnis/php-cs-fixer-config": "^6.54.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", "fakerphp/faker": "^1.24.1", - "nette/di": "^3.1.10", "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpstan/phpstan-phpunit": "^2.0.6", - "phpstan/phpstan-strict-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", "phpunit/phpunit": "^9.6.21", "psr/container": "^2.0.2", "symfony/finder": "^5.4.45", diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php index 937bf0ec2..05a9f3ca6 100644 --- a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php @@ -93,11 +93,11 @@ public function processNode( return []; } - if ($this->hasWhitelistedAnnotation($node)) { + if (self::hasWhitelistedAnnotation($node)) { return []; } - if ($this->hasWhitelistedAttribute($node)) { + if (self::hasWhitelistedAttribute($node)) { return []; } @@ -124,7 +124,7 @@ public function processNode( * @see https://github.com/SpacePossum * @see https://github.com/Slamdunk */ - private function hasWhitelistedAnnotation(Node\Stmt\Class_ $node): bool + private static function hasWhitelistedAnnotation(Node\Stmt\Class_ $node): bool { $docComment = $node->getDocComment(); @@ -147,7 +147,7 @@ private function hasWhitelistedAnnotation(Node\Stmt\Class_ $node): bool return false; } - private function hasWhitelistedAttribute(Node\Stmt\Class_ $node): bool + private static function hasWhitelistedAttribute(Node\Stmt\Class_ $node): bool { foreach ($node->attrGroups as $attributeGroup) { foreach ($attributeGroup->attrs as $attribute) { diff --git a/tools/.phpstan/vendor/nette/utils/composer.json b/tools/.phpstan/vendor/nette/utils/composer.json index 74bd6183c..b17ea832b 100644 --- a/tools/.phpstan/vendor/nette/utils/composer.json +++ b/tools/.phpstan/vendor/nette/utils/composer.json @@ -15,13 +15,13 @@ } ], "require": { - "php": "8.0 - 8.4" + "php": "8.0 - 8.5" }, "require-dev": { "nette/tester": "^2.5", "tracy/tracy": "^2.9", - "phpstan/phpstan": "^1.0", - "jetbrains/phpstorm-attributes": "dev-master" + "phpstan/phpstan-nette": "^2.0@stable", + "jetbrains/phpstorm-attributes": "^1.2" }, "conflict": { "nette/finder": "<3", @@ -36,7 +36,10 @@ "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "autoload": { - "classmap": ["src/"] + "classmap": ["src/"], + "psr-4": { + "Nette\\": "src" + } }, "minimum-stability": "dev", "scripts": { diff --git a/tools/.phpstan/vendor/nette/utils/readme.md b/tools/.phpstan/vendor/nette/utils/readme.md index 0038f534f..46e255170 100644 --- a/tools/.phpstan/vendor/nette/utils/readme.md +++ b/tools/.phpstan/vendor/nette/utils/readme.md @@ -41,7 +41,7 @@ The recommended way to install is via Composer: composer require nette/utils ``` -Nette Utils 4.0 is compatible with PHP 8.0 to 8.4. +Nette Utils 4.0 is compatible with PHP 8.0 to 8.5.   diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayHash.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayHash.php index 349ffe0ef..6e6516bd6 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayHash.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayHash.php @@ -10,6 +10,7 @@ namespace Nette\Utils; use Nette; +use function count, is_array, is_scalar, sprintf; /** @@ -59,7 +60,7 @@ public function count(): int /** - * Replaces or appends a item. + * Replaces or appends an item. * @param array-key $key * @param T $value */ @@ -74,7 +75,7 @@ public function offsetSet($key, $value): void /** - * Returns a item. + * Returns an item. * @param array-key $key * @return T */ @@ -86,7 +87,7 @@ public function offsetGet($key) /** - * Determines whether a item exists. + * Determines whether an item exists. * @param array-key $key */ public function offsetExists($key): bool diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php index a402f9bf6..c9fe5386f 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php @@ -10,6 +10,7 @@ namespace Nette\Utils; use Nette; +use function array_slice, array_splice, count, is_int; /** @@ -63,7 +64,7 @@ public function count(): int /** - * Replaces or appends a item. + * Replaces or appends an item. * @param int|null $index * @param T $value * @throws Nette\OutOfRangeException @@ -83,7 +84,7 @@ public function offsetSet($index, $value): void /** - * Returns a item. + * Returns an item. * @param int $index * @return T * @throws Nette\OutOfRangeException @@ -99,7 +100,7 @@ public function offsetGet($index): mixed /** - * Determines whether a item exists. + * Determines whether an item exists. * @param int $index */ public function offsetExists($index): bool @@ -124,7 +125,7 @@ public function offsetUnset($index): void /** - * Prepends a item. + * Prepends an item. * @param T $value */ public function prepend(mixed $value): void diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php index 02522ca21..8985a7078 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php @@ -11,7 +11,8 @@ use JetBrains\PhpStorm\Language; use Nette; -use function is_array, is_int, is_object, count; +use function array_combine, array_intersect_key, array_is_list, array_key_exists, array_key_first, array_key_last, array_keys, array_reverse, array_search, array_slice, array_walk_recursive, count, func_num_args, in_array, is_array, is_int, is_object, key, preg_split, range; +use const PHP_VERSION_ID, PREG_GREP_INVERT, PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php index 1777428fd..7d384f25e 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php @@ -10,7 +10,7 @@ namespace Nette\Utils; use Nette; -use function is_array, is_object, is_string; +use function explode, func_get_args, ini_get, is_array, is_callable, is_object, is_string, preg_replace, restore_error_handler, set_error_handler, sprintf, str_contains, str_ends_with; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php b/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php index 811f68dbe..cb59682fd 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php @@ -10,6 +10,7 @@ namespace Nette\Utils; use Nette; +use function array_merge, checkdate, implode, is_numeric, is_string, preg_replace_callback, sprintf, time, trim; /** @@ -45,7 +46,7 @@ class DateTime extends \DateTime implements \JsonSerializable public static function from(string|int|\DateTimeInterface|null $time): static { if ($time instanceof \DateTimeInterface) { - return new static($time->format('Y-m-d H:i:s.u'), $time->getTimezone()); + return static::createFromInterface($time); } elseif (is_numeric($time)) { if ($time <= self::YEAR) { @@ -96,10 +97,7 @@ public static function createFromFormat( string|\DateTimeZone|null $timezone = null, ): static|false { - if ($timezone === null) { - $timezone = new \DateTimeZone(date_default_timezone_get()); - - } elseif (is_string($timezone)) { + if (is_string($timezone)) { $timezone = new \DateTimeZone($timezone); } @@ -149,7 +147,7 @@ public function setTime(int $hour, int $minute, int $second = 0, int $microsecon */ public static function relativeToSeconds(string $relativeTime): int { - return (new \DateTimeImmutable('1970-01-01 ' . $relativeTime, new \DateTimeZone('UTC'))) + return (new self('@0 ' . $relativeTime)) ->getTimestamp(); } @@ -158,7 +156,7 @@ private function apply(string $datetime, $timezone = null, bool $ctr = false): v { $relPart = ''; $absPart = preg_replace_callback( - '/[+-]?\s*\d+\s+((microsecond|millisecond|[mµu]sec)s?|[mµ]s|sec(ond)?s?|min(ute)?s?|hours?)\b/iu', + '/[+-]?\s*\d+\s+((microsecond|millisecond|[mµu]sec)s?|[mµ]s|sec(ond)?s?|min(ute)?s?|hours?)(\s+ago)?\b/iu', function ($m) use (&$relPart) { $relPart .= $m[0] . ' '; return ''; diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php b/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php index fb92d1191..a102dad10 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php @@ -10,6 +10,7 @@ namespace Nette\Utils; use Nette; +use const DIRECTORY_SEPARATOR; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php b/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php index 6328fb856..a95a7f741 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php @@ -10,6 +10,8 @@ namespace Nette\Utils; use Nette; +use function array_pop, chmod, decoct, dirname, end, fclose, file_exists, file_get_contents, file_put_contents, fopen, implode, is_dir, is_file, is_link, mkdir, preg_match, preg_split, realpath, rename, rmdir, rtrim, sprintf, str_replace, stream_copy_to_stream, stream_is_local, strtr; +use const DIRECTORY_SEPARATOR; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php index 587b35972..0027e7743 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php @@ -10,6 +10,8 @@ namespace Nette\Utils; use Nette; +use function array_merge, count, func_get_args, func_num_args, glob, implode, is_array, is_dir, iterator_to_array, preg_match, preg_quote, preg_replace, preg_split, rtrim, spl_object_id, sprintf, str_ends_with, str_starts_with, strnatcmp, strpbrk, strrpos, strtolower, strtr, substr, usort; +use const GLOB_NOESCAPE, GLOB_NOSORT, GLOB_ONLYDIR; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php index cc2781d71..ed78a5503 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php @@ -10,6 +10,7 @@ namespace Nette\Utils; use Nette; +use function abs, is_finite, is_nan, max, round; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php index 21efb2ac7..31c9439a3 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php @@ -10,6 +10,8 @@ namespace Nette\Utils; use Nette; +use function array_unique, ini_get, levenshtein, max, min, ob_end_clean, ob_get_clean, ob_start, preg_replace, strlen; +use const PHP_OS_FAMILY; class Helpers @@ -22,7 +24,7 @@ class Helpers */ public static function capture(callable $func): string { - ob_start(function () {}); + ob_start(fn() => ''); try { $func(); return ob_get_clean(); diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php index fc0e3ef2a..cad0baddb 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php @@ -11,7 +11,8 @@ use Nette; use Nette\HtmlStringable; -use function is_array, is_float, is_object, is_string; +use function array_merge, array_splice, count, explode, func_num_args, html_entity_decode, htmlspecialchars, http_build_query, implode, is_array, is_bool, is_float, is_object, is_string, json_encode, max, number_format, rtrim, str_contains, str_repeat, str_replace, strip_tags, strncmp, strpbrk, substr; +use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php index 19ce7b4f4..a557a1881 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php @@ -10,6 +10,8 @@ namespace Nette\Utils; use Nette; +use function is_array, is_int, is_string; +use const IMG_BMP, IMG_FLIP_BOTH, IMG_FLIP_HORIZONTAL, IMG_FLIP_VERTICAL, IMG_GIF, IMG_JPG, IMG_PNG, IMG_WEBP, PATHINFO_EXTENSION; /** @@ -771,7 +773,7 @@ public function __call(string $name, array $args): mixed public function __clone() { - ob_start(function () {}); + ob_start(fn() => ''); imagepng($this->image, null, 0); $this->setImageResource(imagecreatefromstring(ob_get_clean())); } @@ -794,7 +796,7 @@ private static function isPercent(int|string &$num): bool /** * Prevents serialization. */ - public function __sleep(): array + public function __serialize(): array { throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.'); } diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php index 013adbd6e..66966620d 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php @@ -10,6 +10,7 @@ namespace Nette\Utils; use Nette; +use function hexdec, ltrim, max, min, round, strlen; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php index 3092c8f02..080ca8a09 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php @@ -9,6 +9,8 @@ namespace Nette\Utils; +use const IMAGETYPE_BMP, IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_WEBP; + /** * Type of image file. diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Iterables.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Iterables.php index c7a7b8755..8eb6a9117 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Iterables.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Iterables.php @@ -10,6 +10,7 @@ namespace Nette\Utils; use Nette; +use function is_array; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php index b87917b2a..4e4cf238e 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php @@ -10,6 +10,8 @@ namespace Nette\Utils; use Nette; +use function defined, is_int, json_decode, json_encode, json_last_error, json_last_error_msg; +use const JSON_BIGINT_AS_STRING, JSON_FORCE_OBJECT, JSON_HEX_AMP, JSON_HEX_APOS, JSON_HEX_QUOT, JSON_HEX_TAG, JSON_OBJECT_AS_ARRAY, JSON_PRESERVE_ZERO_FRACTION, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ObjectHelpers.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ObjectHelpers.php index f4bd55f2b..5d7d9e291 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/ObjectHelpers.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ObjectHelpers.php @@ -11,6 +11,8 @@ use Nette; use Nette\MemberAccessException; +use function array_filter, array_merge, array_pop, array_unique, get_class_methods, get_parent_class, implode, is_a, levenshtein, method_exists, preg_match_all, preg_replace, strlen, ucfirst; +use const PREG_SET_ORDER, SORT_REGULAR; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php index b14fbd55e..e636dd072 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php @@ -11,6 +11,8 @@ use Nette; use Random\Randomizer; +use function strlen; +use const PHP_VERSION_ID; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php index 215c56860..e68476228 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php @@ -10,6 +10,8 @@ namespace Nette\Utils; use Nette; +use function constant, current, defined, end, explode, file_get_contents, implode, ltrim, next, ord, strrchr, strtolower, substr; +use const PHP_VERSION_ID, T_AS, T_CLASS, T_COMMENT, T_CURLY_OPEN, T_DOC_COMMENT, T_DOLLAR_OPEN_CURLY_BRACES, T_ENUM, T_INTERFACE, T_NAME_FULLY_QUALIFIED, T_NAME_QUALIFIED, T_NAMESPACE, T_NS_SEPARATOR, T_STRING, T_TRAIT, T_USE, T_WHITESPACE, TOKEN_PARSE; /** @@ -33,7 +35,6 @@ public static function isClassKeyword(string $name): bool } - /** @deprecated use native ReflectionParameter::getDefaultValue() */ public static function getParameterDefaultValue(\ReflectionParameter $param): mixed { if ($param->isDefaultValueConstant()) { diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php index b003fcbd1..2a8a55c62 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php @@ -9,6 +9,8 @@ namespace Nette\Utils; +use function explode, is_string, str_contains; + /** * ReflectionMethod preserving the original class name. diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php index 79fa46bb0..19d20f805 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php @@ -11,7 +11,8 @@ use JetBrains\PhpStorm\Language; use Nette; -use function is_array, is_object, strlen; +use function array_keys, array_map, array_shift, array_values, bin2hex, class_exists, defined, extension_loaded, function_exists, htmlspecialchars, htmlspecialchars_decode, iconv, iconv_strlen, iconv_substr, implode, in_array, is_array, is_callable, is_int, is_object, is_string, key, max, mb_convert_case, mb_strlen, mb_strtolower, mb_strtoupper, mb_substr, pack, preg_last_error, preg_last_error_msg, preg_quote, preg_replace, str_contains, str_ends_with, str_repeat, str_replace, str_starts_with, strlen, strpos, strrev, strrpos, strtolower, strtoupper, strtr, substr, trim, unpack, utf8_decode; +use const ENT_IGNORE, ENT_NOQUOTES, ICONV_IMPL, MB_CASE_TITLE, PHP_EOL, PREG_OFFSET_CAPTURE, PREG_PATTERN_ORDER, PREG_SET_ORDER, PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY, PREG_SPLIT_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL; /** @@ -21,7 +22,7 @@ class Strings { use Nette\StaticClass; - public const TrimCharacters = " \t\n\r\0\x0B\u{A0}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{200B}"; + public const TrimCharacters = " \t\n\r\0\x0B\u{A0}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{200B}\u{2028}\u{3000}"; /** @deprecated use Strings::TrimCharacters */ public const TRIM_CHARACTERS = self::TrimCharacters; diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php index 3444a8f17..f1a8fa1c9 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php @@ -10,6 +10,8 @@ namespace Nette\Utils; use Nette; +use function array_map, array_search, array_splice, count, explode, implode, is_a, is_string, strcasecmp, strtolower, substr, trim; +use const PHP_VERSION_ID; /** diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php index 61ccf091a..940c3eb43 100644 --- a/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php @@ -10,6 +10,7 @@ namespace Nette\Utils; use Nette; +use function array_key_exists, class_exists, explode, gettype, interface_exists, is_callable, is_float, is_int, is_iterable, is_numeric, is_object, is_string, preg_match, str_ends_with, str_replace, str_starts_with, strlen, strtolower, substr, trait_exists, var_export; /** diff --git a/tools/.phpstan/vendor/phpstan/extension-installer/src/GeneratedConfig.php b/tools/.phpstan/vendor/phpstan/extension-installer/src/GeneratedConfig.php index c48636d65..c708b87ff 100644 --- a/tools/.phpstan/vendor/phpstan/extension-installer/src/GeneratedConfig.php +++ b/tools/.phpstan/vendor/phpstan/extension-installer/src/GeneratedConfig.php @@ -12,7 +12,7 @@ final class GeneratedConfig public const EXTENSIONS = array ( 'ergebnis/phpstan-rules' => array ( - 'install_path' => '/usr/local/src/php-code-coverage/tools/.phpstan/vendor/ergebnis/phpstan-rules', + 'install_path' => '/Users/sb/Work/OpenSource/php-code-coverage/tools/.phpstan/vendor/ergebnis/phpstan-rules', 'relative_install_path' => '../../../ergebnis/phpstan-rules', 'extra' => array ( @@ -21,12 +21,12 @@ final class GeneratedConfig 0 => 'rules.neon', ), ), - 'version' => '2.10.5', + 'version' => '2.12.0', 'phpstanVersionConstraint' => '>=2.1.8.0-dev, <3.0.0.0-dev', ), 'phpstan/phpstan-strict-rules' => array ( - 'install_path' => '/usr/local/src/php-code-coverage/tools/.phpstan/vendor/phpstan/phpstan-strict-rules', + 'install_path' => '/Users/sb/Work/OpenSource/php-code-coverage/tools/.phpstan/vendor/phpstan/phpstan-strict-rules', 'relative_install_path' => '../../phpstan-strict-rules', 'extra' => array ( @@ -35,12 +35,12 @@ final class GeneratedConfig 0 => 'rules.neon', ), ), - 'version' => '2.0.6', - 'phpstanVersionConstraint' => '>=2.0.4.0-dev, <3.0.0.0-dev', + 'version' => '2.0.7', + 'phpstanVersionConstraint' => '>=2.1.29.0-dev, <3.0.0.0-dev', ), 'tomasvotruba/type-coverage' => array ( - 'install_path' => '/usr/local/src/php-code-coverage/tools/.phpstan/vendor/tomasvotruba/type-coverage', + 'install_path' => '/Users/sb/Work/OpenSource/php-code-coverage/tools/.phpstan/vendor/tomasvotruba/type-coverage', 'relative_install_path' => '../../../tomasvotruba/type-coverage', 'extra' => array ( @@ -58,7 +58,7 @@ final class GeneratedConfig ); /** @var string|null */ - public const PHPSTAN_VERSION_CONSTRAINT = '>=2.1.8.0-dev, <3.0.0.0-dev'; + public const PHPSTAN_VERSION_CONSTRAINT = '>=2.1.29.0-dev, <3.0.0.0-dev'; private function __construct() { diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md index e56aa34d7..3a60ec8e1 100644 --- a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md @@ -13,14 +13,12 @@ | `numericOperandsInArithmeticOperators` | Require numeric operand in `+$var`, `-$var`, `$var++`, `$var--`, `++$var` and `--$var`. | | `numericOperandsInArithmeticOperators` | Require numeric operand in `$var++`, `$var--`, `++$var`and `--$var`. | | `strictFunctionCalls` | These functions contain a `$strict` parameter for better type safety, it must be set to `true`:
* `in_array` (3rd parameter)
* `array_search` (3rd parameter)
* `array_keys` (3rd parameter; only if the 2nd parameter `$search_value` is provided)
* `base64_decode` (2nd parameter). | -| `overwriteVariablesWithLoop` | Variables assigned in `while` loop condition and `for` loop initial assignment cannot be used after the loop. | -| `overwriteVariablesWithLoop` | Variables set in foreach that's always looped thanks to non-empty arrays cannot be used after the loop. | +| `overwriteVariablesWithLoop` | * Disallow overwriting variables with `foreach` key and value variables.
* Disallow overwriting variables with `for` loop initial assignment. | | `switchConditionsMatchingType` | Types in `switch` condition and `case` value must match. PHP compares them loosely by default and that can lead to unexpected results. | | `dynamicCallOnStaticMethod` | Check that statically declared methods are called statically. | | `disallowedEmpty` | Disallow `empty()` - it's a very loose comparison (see [manual](https://php.net/empty)), it's recommended to use more strict one. | | `disallowedShortTernary` | Disallow short ternary operator (`?:`) - implies weak comparison, it's recommended to use null coalesce operator (`??`) or ternary operator with strict condition. | | `noVariableVariables` | Disallow variable variables (`$$foo`, `$this->$method()` etc.). | -| `overwriteVariablesWithLoop` | Disallow overwriting variables with foreach key and value variables. | | `checkAlwaysTrueInstanceof`, `checkAlwaysTrueCheckTypeFunctionCall`, `checkAlwaysTrueStrictComparison` | Always true `instanceof`, type-checking `is_*` functions and strict comparisons `===`/`!==`. These checks can be turned off by setting `checkAlwaysTrueInstanceof`, `checkAlwaysTrueCheckTypeFunctionCall` and `checkAlwaysTrueStrictComparison` to false. | | | Correct case for referenced and called function names. | | `matchingInheritedMethodNames` | Correct case for inherited and implemented method names. | diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json index 2bbc44d69..bc72c5811 100644 --- a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.4" + "phpstan/phpstan": "^2.1.29" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon index 7a63c4ec3..0def6d878 100644 --- a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon @@ -11,6 +11,7 @@ parameters: reportStaticMethodSignatures: true reportMaybesInPropertyPhpDocTypes: true reportWrongPhpDocTypeInVarTag: true + checkStrictPrintfPlaceholderTypes: true strictRules: allRules: true disallowedLooseComparison: %strictRules.allRules% diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php index 77595810a..38c5e0339 100644 --- a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php @@ -104,26 +104,27 @@ private function callsParentConstruct(Node $parserNode): bool */ private function getParentConstructorClass($classReflection) { - while ($classReflection->getParentClass() !== false) { - $constructor = $classReflection->getParentClass()->hasMethod('__construct') ? $classReflection->getParentClass()->getMethod('__construct') : null; - $constructorWithClassName = $classReflection->getParentClass()->hasMethod($classReflection->getParentClass()->getName()) ? $classReflection->getParentClass()->getMethod($classReflection->getParentClass()->getName()) : null; + $parentClass = $classReflection->getParentClass(); + while ($parentClass !== false) { + $constructor = $parentClass->hasMethod('__construct') ? $parentClass->getMethod('__construct') : null; + $constructorWithClassName = $parentClass->hasMethod($parentClass->getName()) ? $parentClass->getMethod($parentClass->getName()) : null; if ( ( $constructor !== null - && $constructor->getDeclaringClass()->getName() === $classReflection->getParentClass()->getName() + && $constructor->getDeclaringClass()->getName() === $parentClass->getName() && !$constructor->isAbstract() && !$constructor->isPrivate() && !$constructor->isDeprecated() ) || ( $constructorWithClassName !== null - && $constructorWithClassName->getDeclaringClass()->getName() === $classReflection->getParentClass()->getName() + && $constructorWithClassName->getDeclaringClass()->getName() === $parentClass->getName() && !$constructorWithClassName->isAbstract() ) ) { - return $classReflection->getParentClass(); + return $parentClass; } - $classReflection = $classReflection->getParentClass(); + $parentClass = $parentClass->getParentClass(); } return false; diff --git a/tools/.phpstan/vendor/phpstan/phpstan/README.md b/tools/.phpstan/vendor/phpstan/phpstan/README.md index f4af4753a..49bed4fd7 100644 --- a/tools/.phpstan/vendor/phpstan/phpstan/README.md +++ b/tools/.phpstan/vendor/phpstan/phpstan/README.md @@ -60,7 +60,7 @@ Want your logo here? [Learn more »](https://phpstan.org/sponsor)
RightCapital     -ContentKing +Shoptet
ZOL     @@ -78,8 +78,6 @@ Want your logo here? [Learn more »](https://phpstan.org/sponsor)     Inviqa
-Shoptet -    diff --git a/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php b/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php index a5d341bfd..889755e60 100644 --- a/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php +++ b/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php @@ -92,6 +92,36 @@ final public static function loadClass(string $class): void { require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php81/Php81.php'; require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php81/bootstrap.php'; } + + if ( + PHP_VERSION_ID < 80300 + && empty ($GLOBALS['__composer_autoload_files']['662a729f963d39afe703c9d9b7ab4a8c']) + && !class_exists(\Symfony\Polyfill\Php83\Php83::class, false) + ) { + $GLOBALS['__composer_autoload_files']['662a729f963d39afe703c9d9b7ab4a8c'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php83/Php83.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php83/bootstrap.php'; + } + + if ( + PHP_VERSION_ID < 80400 + && empty ($GLOBALS['__composer_autoload_files']['9d2b9fc6db0f153a0a149fefb182415e']) + && !class_exists(\Symfony\Polyfill\Php83\Php84::class, false) + ) { + $GLOBALS['__composer_autoload_files']['9d2b9fc6db0f153a0a149fefb182415e'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php84/Php84.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php84/bootstrap.php'; + } + + if ( + PHP_VERSION_ID < 80500 + && empty ($GLOBALS['__composer_autoload_files']['606a39d89246991a373564698c2d8383']) + && !class_exists(\Symfony\Polyfill\Php83\Php85::class, false) + ) { + $GLOBALS['__composer_autoload_files']['606a39d89246991a373564698c2d8383'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php85/Php85.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php85/bootstrap.php'; + } } $filename = str_replace('\\', DIRECTORY_SEPARATOR, $class); diff --git a/tools/.phpstan/vendor/phpstan/phpstan/composer.json b/tools/.phpstan/vendor/phpstan/phpstan/composer.json index dc62c19ce..8b51d4b71 100644 --- a/tools/.phpstan/vendor/phpstan/phpstan/composer.json +++ b/tools/.phpstan/vendor/phpstan/phpstan/composer.json @@ -16,6 +16,11 @@ "autoload": { "files": ["bootstrap.php"] }, + "source": { + "type": "", + "url": "", + "reference": "" + }, "support": { "issues": "https://github.com/phpstan/phpstan/issues", "forum": "https://github.com/phpstan/phpstan/discussions", diff --git a/tools/.phpstan/vendor/phpstan/phpstan/phpstan.phar b/tools/.phpstan/vendor/phpstan/phpstan/phpstan.phar index 81475a5c8..8c83a085d 100755 Binary files a/tools/.phpstan/vendor/phpstan/phpstan/phpstan.phar and b/tools/.phpstan/vendor/phpstan/phpstan/phpstan.phar differ diff --git a/tools/.phpstan/vendor/phpstan/phpstan/phpstan.phar.asc b/tools/.phpstan/vendor/phpstan/phpstan/phpstan.phar.asc index 76eb1c8f9..62f935e89 100644 --- a/tools/.phpstan/vendor/phpstan/phpstan/phpstan.phar.asc +++ b/tools/.phpstan/vendor/phpstan/phpstan/phpstan.phar.asc @@ -1,16 +1,16 @@ -----BEGIN PGP SIGNATURE----- -iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmiH0MMACgkQUcZzBf/C -5cB4GRAAladOYQWUE2XHwwyhl/baoJEkPyPVbRmwZakva0Hg9kksH62fDwM944cE -NZCFWnuKn/ujVFLnd4vCVctibsKjM9EPTdugAFrmEna52JGfFFrb+0oj5VblRDc9 -CaSK6WJwcIDZI8xkmeO5m1fKCWosq99FzrATJkpXnB8vn/ODTT9OLVvi/3mU8h6B -TY5/RX3KKlc8iKE6tyQ+OljX4iMicyxC3CuISRuzP04hK7Mx+U4xAtaJa5Uv7aDH -LKdWpkiRMnDK8GklrSyrpdAl80J3KFt5aKX9m/mwb77tSujDTOkoOU5GfaamSNKX -CvyfuvoDn8tO5a8YeqbBsaje5GBWI9nuMrHG2zNEjNepYCk0N0dDclah1tu9sjs5 -mQNnEuI2Gj3b1O+rNbPRgwgOgUk7OjyRX/rvUotw013PQKclSWhEhY2mcpyla5bw -vq1Xfjf5eXbu610cxQnSnFG13DlIleMTbqXFXD4ICpiKYeOOCLSQf8+X7+WVlUjW -90/TrhV+VpsWILdjVBvceemgqcR5SyWRZe4Xx+FqsSFnJmmnRkK6d/sILnXrYug+ -FZVJutuUJN2HKa/vlm23BNOca8evEngUfqua/ykbanugJwBxy0HynfyzVRXa33Ud -WQ2MXepBMBC4ogwjCnyAw5wfc2uAnWSXf4yHTcWfvAlzMmpDHfQ= -=nV2o +iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmkTU5cACgkQUcZzBf/C +5cC1bg/+MAMAfCH1CDfI78Inqo/Mqa5q40QOn4RmVNex+xb8lKZ1AApc76nwNfn/ +BLI+War1G/5C2WWNgiqI9X7j/jo46oi+oO/1qywssqEn+7S9Ob7C56N2ERoikS3c +MsrseR5XEVMFRG/enk1Gsmde9PesuIiN10FnsFyhZiMDNUXhoiLHPrERzTlUocAv +K7/zm4gF152HM8tUir38i+h9BxadfNvah0zWCfzn7v+5OxSAHjz/DriWWdZkIsmB +VuyZUg1QX05JzRkVBAdEy3h1N9c57ktWVf92PDrNha3hhQ1lw0OvH9+1+B5+Me5w +BTUrLz+S6PGpFKkLY8M5ucQAvqHB41zgYp1QIN/Da1ZxvsQyAFq6yvD+d+yYG/zn +k1NVNDrQMAE6GV7YjCK1bDSKExaFyYXmTb93fRN90cxYi3pfZpIMaaiKNGLYFPsk +YHe1N4/et3741Y8VNSb2S1iu3KVTOzSnEsQL/olD5Trt6faKtscCSY7deeINoMkq +IGLQmZjbj4TUO0js2Lw8zTp/d8X3bd67ktdVmijjaKurNYXJsecYuAZXz3WOTek9 +aXuT8kC1aZz/h7CxvjaN2G3KAi5yZ2aN57CSUYhDpmeN2VhvDGaGgHBPFSHcE5fx +dr4BzRNTOU1ACBONChMGSvuDKCqC7kzt+qvWY29TlZxgwO+iIq8= +=0Qu+ -----END PGP SIGNATURE----- diff --git a/tools/composer b/tools/composer index 4f53d5aa0..572223953 100755 Binary files a/tools/composer and b/tools/composer differ diff --git a/tools/php-cs-fixer b/tools/php-cs-fixer index c1ebe8cd5..997a14782 100755 Binary files a/tools/php-cs-fixer and b/tools/php-cs-fixer differ diff --git a/tools/phpstan~11.0 b/tools/phpstan~11.0 new file mode 100755 index 000000000..c542df205 Binary files /dev/null and b/tools/phpstan~11.0 differ