Repository navigation
feat: expose structured JSON check reports #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d5f93fe
feat: add explicit structured JSON checking
coisa 0fd4695
fix: use canonical changelog fragment metadata
coisa 26a52d9
Merge main after the PHPDoc skill integration
coisa 3ef8076
style: align multiline conditions with the PHP style contract
coisa fd13a7a
fix: preserve check exit codes across output formats
coisa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| category: added | ||
| type: minor | ||
| --- | ||
|
|
||
| Add explicit JSON check reports with native ECS findings, syntax results, incomplete-batch accounting and bounded nonstreaming capture; preserve default text and explicit fix behavior. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| Structured check output | ||
| ======================= | ||
|
|
||
| ``check --format=json`` selects one nonstreaming JSON report on stdout. The default | ||
| ``--format=text`` retains human output and existing exit behavior. ``fix`` does not | ||
| accept this option. Formatting and semantic refactoring remain separate operations. | ||
|
|
||
| .. code-block:: console | ||
|
|
||
| vendor/bin/code-standards check --all --format=json | ||
| vendor/bin/code-standards check --base=BASE_SHA --head=HEAD_SHA --format=json | ||
|
|
||
| Use existing complete immutable commits for ``BASE_SHA`` and ``HEAD_SHA``. Selection, | ||
| consumer ignore rules, explicit ECS configuration and PHP runtime requirements are | ||
| the same as text checking. JSON checking never requests ``--fix``. Executable | ||
| configuration is not sandboxed; use the same unprivileged environment as text mode. | ||
|
|
||
| Report contract | ||
| --------------- | ||
|
|
||
| The report includes ``schema_version`` (1), ``command`` (``check``), ``status``, ``exit_code``, ``complete``, | ||
| ``selection``, ``files``, ``errors`` and ``diagnostics.stderr``. ``files`` is an object | ||
| keyed by exact absolute paths. Native ``errors`` records retain line, file path, | ||
| message and checker identity; native ``diffs`` retain diff text and applied checkers. | ||
| No line numbers are guessed from a diff or a human PHP diagnostic. | ||
|
|
||
| ``selection.files`` counts selected files; ``syntax_checked`` counts completed | ||
| native PHP syntax invocations; ``style_submitted`` counts files submitted in | ||
| completed, consistently reported ECS batches. The corresponding ``syntax_remaining`` | ||
| and ``style_remaining`` counts identify unfinished work. ECS may reuse its cache or | ||
| honor consumer skips, so submitted is not a claim of fresh parsing of every file. | ||
|
|
||
| ``complete`` means all selected syntax invocations and ECS batches completed with | ||
| consistent reports. Checking stops at the first nonzero syntax result, findings | ||
| batch or operational error, matching the bounded text-mode policy. A findings | ||
| report can be incomplete when later batches remain. An empty selection is complete | ||
| and clean without starting a checker. | ||
|
|
||
| .. list-table:: Result states | ||
| :header-rows: 1 | ||
|
|
||
| * - Status | ||
| - Exit | ||
| - Meaning | ||
| * - ``clean`` | ||
| - 0 | ||
| - All selected work completed consistently without findings, or selection is empty. | ||
| * - ``findings`` | ||
| - 2 | ||
| - Native, consistently reported style violations or diffs; check ``complete``. | ||
| * - ``syntax-failed`` | ||
| - Native nonzero status | ||
| - ``php -n -l`` did not validate a selected file; exact diagnostics are data, not parsed classifications. | ||
| * - ``operational-error`` | ||
| - 1, or 2 for controlled selection failures | ||
| - Selection, execution, report validation or encoding could not establish a trustworthy result. | ||
|
|
||
| Controlled selection failures retain text-mode status 2, including invalid input, | ||
| missing configuration, unavailable Git history, a mismatched HEAD or a dirty tracked | ||
| checkout. Unexpected failures and checker/report errors use status 1. | ||
|
|
||
| Native stderr remains separate diagnostic data and is also written to stderr; | ||
| warnings alone do not imply failure. Operation errors additionally produce a fixed | ||
| stderr diagnostic. Invalid format names fail before execution with status 2 and | ||
| stderr output. Symfony binding/help errors and global quiet/silent flags retain | ||
| their native Console behavior; they are not JSON-report requests to an executed | ||
| check. | ||
|
|
||
| Validation and limits | ||
| --------------------- | ||
|
|
||
| The isolated worker uses the tool's own autoloader and native ECS JSON formatter. | ||
| Custom process overrides must implement the optional ``LimitedResultProcessRunner`` | ||
| capability for JSON checking. Existing ``ProcessRunner`` implementations retain | ||
| their text-mode contract; an unsupported JSON transport fails before a checker starts. | ||
| The parser checks the whole JSON document, native status, record shapes, selected | ||
| paths and counts. It does not strip banners or search for a later JSON fragment. | ||
| ECS totals can include system errors absent from its file inventory; mismatches | ||
| are operational errors, never clean results or ordinary style findings. | ||
|
|
||
| Each ECS batch contains at most 50 paths, with a 300-second execution timeout, | ||
| 2 MiB retained stdout and 64 KiB retained stderr limits. Syntax invocations use | ||
| 300 seconds and 64 KiB per channel. Combined retained diagnostics are limited to | ||
| 64 KiB. Output is limited during native process callbacks; overflow fails instead | ||
| of returning a truncated report. Symfony's transient pipe reads and the child's | ||
| memory are outside this retained-output bound. Termination covers the owned | ||
| process; no sandbox or descendant-process isolation is promised. | ||
|
|
||
| Report strings must be representable as JSON without UTF-8 replacement. Invalid | ||
| native JSON, unsupported bytes or an oversized report fail objectively. A complete | ||
| machine report can be larger than human output because ECS JSON contains diffs; | ||
| ``--no-diffs`` is not used as a false size guarantee. Native caches and explicit | ||
| consumer overrides remain active, as described in the configuration contract. | ||
|
|
||
| This format covers checking, not automatic fixes, Rector reports, type analysis or | ||
| agent detection. Select it explicitly; environment detection never grants write | ||
| authority or overrides the requested format. | ||
|
|
||
| The broader `structured-output contract (#10) <https://github.com/php-fast-forward/code-standards/issues/10>`__ | ||
| also covers shared pretty/color controls and CWD/cache metadata. Those fields and | ||
| cross-command policies are outside this check schema. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace FastForward\CodeStandards\Console; | ||
|
|
||
| use FastForward\CodeStandards\Console\Input\ScanInput; | ||
| use FastForward\CodeStandards\Filesystem\PhpFiles; | ||
| use FastForward\CodeStandards\Git\FileSelection; | ||
| use FastForward\CodeStandards\Project\ProjectScopeResolver; | ||
| use FastForward\CodeStandards\Standards\CheckResult; | ||
| use FastForward\CodeStandards\Standards\StructuredCheckRunner; | ||
| use InvalidArgumentException; | ||
| use JsonException; | ||
| use RuntimeException; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
| use Symfony\Component\Console\Style\SymfonyStyle; | ||
| use Throwable; | ||
|
|
||
| /** Presents one explicitly selected JSON check without human stdout or implicit source writes. */ | ||
| final readonly class StructuredCheckOperation | ||
| { | ||
| /** Injects the same selection policy as text mode and a nonstreaming check runner. */ | ||
| public function __construct( | ||
| private ProjectScopeResolver $projects, | ||
| private FileSelection $selection, | ||
| private PhpFiles $phpFiles, | ||
| private StructuredCheckRunner $standards, | ||
| ) {} | ||
|
|
||
| /** Resolves one selection and writes exactly one JSON document after result classification. */ | ||
| public function run(ScanInput $settings, SymfonyStyle $io): int | ||
| { | ||
| try { | ||
| $scope = $this->projects->resolve($settings->all, $settings->base, $settings->head, $settings->directory, $settings->config); | ||
| $paths = $this->selection->select($scope->directory, $scope->base, $scope->head, $scope->config); | ||
| $files = $this->phpFiles->filter($scope->directory, $paths); | ||
| $result = $this->standards->run($scope->directory, $files, $scope->config); | ||
| } catch (Throwable $exception) { | ||
| $exitCode = $exception instanceof InvalidArgumentException || $exception instanceof RuntimeException ? 2 : 1; | ||
| $result = new CheckResult('operational-error', $exitCode, false, 0, 0, 0, errors: [ | ||
| ['phase' => 'selection', 'message' => $exception->getMessage()], | ||
| ]); | ||
| } | ||
|
|
||
| return $this->present($result, $io); | ||
| } | ||
|
|
||
| /** Rejects unrepresentable byte strings without silently replacing paths or truncating a report. */ | ||
| private function present(CheckResult $result, SymfonyStyle $io): int | ||
| { | ||
| try { | ||
| $json = json_encode($result->toArray(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES); | ||
| } catch (JsonException) { | ||
| $result = new CheckResult('operational-error', 1, false, $result->selected, $result->syntaxChecked, $result->styleSubmitted, errors: [ | ||
| ['phase' => 'encoding', 'message' => 'Check output contains bytes that cannot be represented as JSON.'], | ||
| ]); | ||
| $json = json_encode($result->toArray(), JSON_THROW_ON_ERROR); | ||
| } | ||
| if ([] !== $result->errors) { | ||
| $io->getErrorStyle()->writeln('Code Standards check did not complete successfully.', OutputInterface::OUTPUT_RAW); | ||
| } | ||
| if ('' !== $result->stderr) { | ||
| $io->getErrorStyle()->write($result->stderr, false, OutputInterface::OUTPUT_RAW); | ||
| } | ||
| $io->writeln($json, OutputInterface::OUTPUT_RAW); | ||
|
|
||
| return $result->exitCode; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace FastForward\CodeStandards\Process; | ||
|
|
||
| /** Adds capture with explicit retained-output limits. */ | ||
| interface LimitedResultProcessRunner extends ResultProcessRunner | ||
| { | ||
| /** | ||
| * Captures a completed result within caller-selected byte limits. | ||
| * | ||
| * Channels MUST remain separate and unmodified without streaming. Exceeding either | ||
| * positive limit MUST raise an exception rather than return truncated output. | ||
| * | ||
| * @param list<string> $arguments Executable followed by literal arguments. | ||
| * @param string $directory Child working directory. | ||
| * @param positive-int $stdoutLimit Maximum retained stdout bytes. | ||
| * @param positive-int $stderrLimit Maximum retained stderr bytes. | ||
| * @param float $timeout Positive, finite execution limit in seconds. | ||
| * @return ProcessResult Completed status and exact output within the limits. | ||
| * @throws \Throwable When execution cannot complete within the supplied bounds. | ||
| */ | ||
| public function captureLimitedResult(array $arguments, string $directory, int $stdoutLimit, int $stderrLimit, float $timeout = 300): ProcessResult; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.