#!/usr/bin/env php analyze( $paths, $includePhpUnit ? ROOT_PATH . '/phpunit/src' : null, $includePhpUnit ? ROOT_PATH . '/phpunit/code' : null, ); } catch (Throwable $error) { fwrite(STDERR, 'Coverage analysis failed: ' . $error->getMessage() . PHP_EOL); exit(1); } $rendered = match ($format) { 'summary' => $analyzer->renderSummary($report), 'markdown' => $analyzer->renderMarkdown($report), 'json' => json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL, }; if ($output === null) { echo $rendered; } else { $outputPath = isAbsolutePath($output) ? $output : ROOT_PATH . DIRECTORY_SEPARATOR . $output; $directory = dirname($outputPath); if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) { fwrite(STDERR, 'Unable to create output directory: ' . $directory . PHP_EOL); exit(1); } if (file_put_contents($outputPath, $rendered) === false) { fwrite(STDERR, 'Unable to write report: ' . $outputPath . PHP_EOL); exit(1); } echo 'Wrote ', $format, ' coverage report: ', relativePath(ROOT_PATH, $outputPath), PHP_EOL; } if ($strict && ($report['parse_errors'] !== [] || $report['unresolved_phpunit_fixtures'] !== [])) { exit(1); } function printUsage(string $script): void { echo << Write the report to a file --php-versions=8.4,8.5 Target PHP version columns --no-phpunit Do not scan PHPUnit compiler fixtures --strict Fail on parse issues or unresolved fixture links -h, --help Show this help Examples: php {$script} php {$script} --format=markdown --output=build/test-coverage.md php {$script} --format=json tests/compiler/type_decl tests/compiler/basic The tool reports separate, explicitly denominated AST-node, positive compile, runtime semantic and negative diagnostic coverage. It never emits a combined overall percentage. USAGE; } function isAbsolutePath(string $path): bool { return $path !== '' && ($path[0] === '/' || preg_match('/^[A-Za-z]:[\\\\\/]/', $path) === 1); } function relativePath(string $root, string $path): string { $prefix = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; return str_starts_with($path, $prefix) ? substr($path, strlen($prefix)) : $path; }