registerNullFilter(); $this->silenceStderr(); /** @var array $argv */ $argv = $_SERVER['argv']; $argv = $this->ensureErrorFormatJson($argv); $argv = $this->ensureNoProgress($argv); $_SERVER['argv'] = $argv; $this->silenceStdout(); } /** * @return array|null */ public function parse(): ?array { $captured = trim(CaptureFilter::output()); CaptureFilter::reset(); if ($captured === '') { return null; } $start = strpos($captured, '{'); if ($start !== false && $start > 0) { $captured = substr($captured, $start); } /** @var array|null $data */ $data = json_decode($captured, associative: true); if (! is_array($data) || ! isset($data['totals'])) { return null; } /** @var array> $errorDetails */ $errorDetails = []; $totalFileErrors = 0; /** @var array}> $files */ $files = is_array($data['files'] ?? null) ? $data['files'] : []; foreach ($files as $file => $fileData) { foreach ($fileData['messages'] as $message) { $totalFileErrors++; $detail = [ 'line' => $message['line'], 'message' => $message['message'], 'identifier' => $message['identifier'] ?? 'unknown', ]; if (isset($message['ignorable']) && $message['ignorable'] === false) { $detail['ignorable'] = false; } if (isset($message['tip']) && $message['tip'] !== '') { $detail['tip'] = $message['tip']; } $errorDetails[$file][] = $detail; } } /** @var list $errors */ $errors = is_array($data['errors'] ?? null) ? $data['errors'] : []; /** @var list $generalErrors */ $generalErrors = array_values(array_filter($errors, static fn (string $error): bool => $error !== '')); $totalErrors = $totalFileErrors + count($generalErrors); /** @var array $result */ $result = [ 'result' => $totalErrors > 0 ? 'failed' : 'passed', 'errors' => $totalErrors, ]; if ($errorDetails !== []) { $verbose = $this->isVerbose(); $limit = 30; if (! $verbose && $totalFileErrors > $limit) { $result['error_details'] = $this->truncateGrouped($errorDetails, $limit); $result['truncated'] = true; $result['hint'] = 'Pass -v to see all errors.'; } else { $result['error_details'] = $errorDetails; } } if ($generalErrors !== []) { $result['general_errors'] = $generalErrors; } return $result; } /** * @param array> $grouped * @return array> */ private function truncateGrouped(array $grouped, int $limit): array { $result = []; $count = 0; foreach ($grouped as $file => $errors) { foreach ($errors as $error) { if ($count >= $limit) { break 2; } $result[$file][] = $error; $count++; } } return $result; } private function isVerbose(): bool { /** @var array $argv */ $argv = $_SERVER['argv'] ?? []; foreach ($argv as $arg) { if (in_array($arg, ['-v', '-vv', '-vvv', '--verbose'], true)) { return true; } } return false; } /** * @param array $argv * @return array */ private function ensureErrorFormatJson(array $argv): array { $filtered = []; $skipNext = false; foreach ($argv as $arg) { if ($skipNext) { $skipNext = false; continue; } if (str_starts_with($arg, '--error-format=')) { continue; } if ($arg === '--error-format') { $skipNext = true; continue; } $filtered[] = $arg; } $filtered[] = '--error-format=json'; return $filtered; } /** * @param array $argv * @return array */ private function ensureNoProgress(array $argv): array { if (! in_array('--no-progress', $argv, true)) { $argv[] = '--no-progress'; } return $argv; } }