Skip to content

Implementation Reference

Specs: S001, S002, S003, S004, S005, S006, S007, S008, S010, S011

This page maps the public CLI behavior to the implementation surface in app/. It is intended as the complete hand-off reference for maintainers, plugin authors, and deployment owners.

Commands

init

Implementation: app/Commands/InitCommand.php

parity init creates parity.yaml in the current working directory. It does not overwrite an existing file. The generated template includes:

  • settings.namespace_roots
  • settings.source_extension
  • settings.test_suffix
  • settings.test_extension
  • settings.namespace_separator
  • coverage_xml: [.parity/per-test, parity-coverage.json, coverage-xml, clover.xml, cobertura.xml]
  • min_coverage
  • optional test block for per-test report generation
  • one example structure block with paths.source, paths.test, and rules

Exit behavior:

ConditionExit
New config written0
Config already exists0
Current directory cannot be resolved1
Config file cannot be written1

check

Implementation: app/Commands/CheckCommand.php

parity check reads a config file and an existing coverage report. It does not run tests.

Options:

OptionDefaultBehavior
--config=path/to/parity.yaml./parity.yaml discovered from current/project rootSets the config path and makes relative paths resolve from that config's directory.
--format=tabletableRenders human-readable structure tables and a summary.
--format=jsontableEmits a single JSON document with no warnings, headings, ANSI tags, or extra prose.
--show-testsfalseDisplays individual test names when the selected coverage source includes attribution data. Has no effect for Clover or Cobertura.

Runtime sequence:

  1. Resolve the project root from --config or the current directory.
  2. Parse YAML with clean error handling for invalid YAML.
  3. Build Settings from top-level config.
  4. Load project, global, and Composer plugins into the rule registry.
  5. Resolve the first existing coverage candidate.
  6. Read coverage through ParityPerTestCoverageReader for directories with index.json, PhpUnitXmlCoverageReader for directories with index.xml, ParityJsonCoverageReader for JSON files, and CoverageReader for aggregate XML files.
  7. Resolve each structure's rules, auto-prepending test-exists and auto-adding attribution rules when the selected coverage source exposes per-test line attribution.
  8. Map every source file to its expected test file.
  9. Evaluate rules and render table or JSON output.
  10. Return failure if any enforced rule fails or global coverage is below threshold.

Configuration

Implementation: app/Settings/Settings.php

Top-level keys:

KeyTypeDefaultPurpose
settingsmap{}File naming and namespace/path behavior.
coverage_xmlstring or list[.parity/per-test, parity-coverage.json, coverage-xml, clover.xml, cobertura.xml]Coverage candidates, checked in order. Prefer attribution-capable sources first.
min_coveragenumber80Default per-file minimum for legacy/generated rules.
min_coverage_globalnumber or omittednullOptional project-level coverage threshold.
min_matched_coveragenumber or omittednullOptional default for matching-test-only coverage.
structurelist[]Source/test directory mappings and rules.

settings keys:

KeyTypeDefaultPurpose
namespace_rootsmap{ app: App, tests: Tests }Maps path roots to identifier roots.
source_extensionstring.phpSource file extension discovered in source paths.
test_suffixstringTestSuffix appended to source basename.
test_extensionstringsame as source_extensionTest file extension.
namespace_separatorstring\\Identifier separator for generated FQCN-like names.

Structure formats:

yaml
structure:
  - name: "Unit Services"
    paths:
      source: "app/Services"
      test: "tests/Unit/Services"
    rules:
      - enforce-coverage-link
      - minimum-coverage:
          min: 80
    file_map:
      "Auth/LoginService.php": "Auth/LoginServiceTest.php"

Legacy structure keys remain supported:

yaml
structure:
  - name: "Legacy Actions"
    source_path: "app/Actions"
    test_path: "tests/Unit/Actions"
    enforce_attribute: "PHPUnit\\Framework\\Attributes\\CoversClass"
    min_coverage: 90

Path and Namespace Mapping

Implementation: app/Services/NamespaceHelper.php

NamespaceHelper normalizes path separators, strips configured source extensions, applies namespace roots, and converts source paths to expected test paths.

Examples:

SettingsSourceExpected Test
PHP defaultsapp/Services/Invoice.phptests/Unit/Services/InvoiceTest.php
TypeScript samplesrc/formatCurrency.tstests/formatCurrency.test.ts
Rust samplesrc/lib.rstests/lib_test.rs

file_map overrides the automatic mapping for a specific source-relative path.

Coverage Readers

Single-file XML reader

Implementation: app/Services/CoverageReader.php

Supported file formats:

FormatDetectionPer-file coverageGlobal coveragePer-test attribution
Parity per-test directoryExisting configured directory with index.jsonmerged from covered line setsmerged from all source filesYes
Parity JSONExisting configured .json filefrom coveragePercent or computed from covered linesfrom globalPercentYes
Clover XMLAny existing configured file with Clover <file><metrics> nodescoveredstatements / statements * 100project <metrics files="...">No
Cobertura XMLAny existing configured file with Cobertura <class filename line-rate> nodesline-rate * 100, or line-hit fallbackroot <coverage line-rate>No

Malformed XML, missing files, unreadable files, missing metrics, and empty paths return empty/null results without throwing.

Attribution-capable readers

Implementation: app/Services/ParityPerTestCoverageReader.php, app/Services/ParityJsonCoverageReader.php, app/Services/PhpUnitXmlCoverageReader.php

If a configured coverage candidate is a directory containing index.json, Parity treats it as its native per-test report format. If the candidate is a .json file, Parity treats it as parity-coverage.json. If the candidate is a directory containing index.xml, Parity treats it as PHPUnit XML coverage.

All three attribution-capable formats provide:

  • per-file line coverage percentages
  • project/global coverage
  • per-line executable counts
  • test names that covered each line
  • testsByFile data for attribution and incidental coverage reporting

test

Implementation: app/Commands/TestCommand.php, app/Services/ParityTestWorkspace.php

parity test runs each expected test file individually, normalizes the resulting single-test coverage artifact into Parity's native per-test report format, writes a report directory, and then runs parity check against that directory by default.

Config surface:

yaml
test:
  command: "./vendor/bin/pest {test_abs} --coverage-clover={coverage}"
  coverage: ".parity/tmp/{slug}.xml"
  reports: ".parity/per-test"
  timeout: 300

Supported placeholders are {test}, {test_abs}, {coverage}, {slug}, and {project_root}. --output overrides test.reports, --timeout overrides the default 300-second process timeout, and --no-check stops after report generation.

ParityTestWorkspace validates artifact ownership, every existing path component, and report/coverage separation; removes runner artifacts after every attempt; writes into a sibling staging directory; atomically publishes only complete report sets; and preserves the previous set on failure. The reader rejects unsupported schema versions, mismatched test IDs, and report or source paths that escape the declared report root.

Rules

Built-in rules are registered in app/Providers/AppServiceProvider.php.

RuleImplementationEnforcedParametersOutput Column
test-existsTestExistsRuleYesnone
enforce-coverage-linkEnforceCoverageLinkRuleYeslinkers, attributeLink
minimum-coverageMinimumCoverageRuleYesrequired min number 0..100Cov
matched-coverageMatchedCoverageRuleYesoptional min number 0..100Match
coverage-attributionCoverageAttributionRuleNooptional show_names#, Other

Rule config formats:

yaml
rules:
  - test-exists
  - minimum-coverage:
      min: 80
  - name: minimum-coverage
    min: 90

Unknown rule names or invalid parameters fail cleanly before evaluation.

Coverage Linkers

Implementation: app/Services/CoverageLinkers/

LinkerConfig NameSupportsDeclarations
PestCoversLinkerpest-coversPest-style files without class declarations that use it(), test(), describe(), beforeEach(), or beforeAll()->covers(Foo::class), ->coversClass(Foo::class), covers(Foo::class)
PhpAttributeLinkerphp-attributePHP files with class declarations#[CoversClass(Foo::class)] before the test class

CoverageLinkerRegistry::fromConfig() accepts:

yaml
rules:
  - enforce-coverage-link:
      linkers: [auto]
      attribute: PHPUnit\Framework\Attributes\CoversClass

Valid linkers values are auto, pest-covers, and php-attribute. auto uses both built-in linkers. attribute customizes the PHP attribute class name for the PHP attribute linker.

Class reference resolution supports:

  • imported class aliases from use Foo\Bar as Baz;
  • imported short names from use Foo\Bar;
  • relative names in the current namespace
  • leading-backslash absolute names
  • string literal FQCNs

Plugin System

Implementation: app/Services/PluginLoader.php

Load order:

  1. project-local .parity/plugins/*.php
  2. user-global ~/.parity/plugins/*.php
  3. Composer packages declaring extra.parity.rules

Project/global plugin files must return either a RuleInterface instance or an array of RuleInterface instances.

Composer plugin declaration:

json
{
  "extra": {
    "parity": {
      "rules": [
        "Acme\\Parity\\Rules\\NamingRule"
      ]
    }
  }
}

Plugin warnings are printed in table mode with a Plugin: prefix. They are suppressed in JSON mode so stdout remains parseable.

Plugins are trusted executable PHP. Do not load unreviewed plugins in public CI.

Output Contracts

Table mode

Table mode renders:

  1. optional global coverage message
  2. one section per structure
  3. source/test paths for each structure
  4. a dynamic table based on resolved rules
  5. a final summary table
  6. optional unmatched-test warnings when attribution data is available

The first two columns are always Source and Test; the final column is always OK. Rule columns are added from RuleInterface::columnHeader().

Summary rows use Per rule for structure-specific thresholds and derive OK or FAIL from the evaluated rules, so they cannot contradict file rows with overrides. Files with zero executable lines are excluded from minimum and average values when the coverage format exposes executable-line metadata.

JSON mode

JSON mode emits only this top-level shape:

json
{
  "passed": true,
  "global_coverage": null,
  "min_coverage_global": null,
  "structures": []
}

Each structure contains name, source_path, test_path, and files. Each file contains source, test, passed, and rules. Each rule result contains passed, value, and error.

Release and Runtime Providers

config/app.php reads APP_VERSION or VERSION, then falls back to unreleased.

config/commands.php is the Laravel Zero command registration file. Public commands are registered through this runtime configuration and backed by app/Commands/InitCommand.php, app/Commands/CheckCommand.php, and app/Commands/TestCommand.php.

app/Providers/GitVersionFallbackProvider.php binds git.version to the same VERSION fallback behavior for packaged/runtime contexts.

dev/release-version.sh requires curated ## [Unreleased] notes, handles dry-run and real version bumps, promotes those notes into the dated release section, verifies version metadata, creates an annotated tag, and can atomically push the release commit and tag. It also rejects dirty worktrees, duplicate tags, and releases outside main.

Samples

The sample matrix validates representative layouts and coverage formats:

SampleLanguage/FrameworkCoverage Fixture
samples/phpPlain PHPClover
samples/laravelLaravel-style PHPClover
samples/viteVite/TypeScriptCobertura
samples/adonisjsAdonisJS-style TypeScriptClover
samples/rustRust-style layoutClover
samples/phpunitRunnable PHPUnit appClover
samples/pestRunnable Pest appClover plus Pest ->covers() link validation
samples/jestRunnable Jest appClover
samples/mochaRunnable Mocha appClover
samples/vitestRunnable Vitest appClover
samples/cargoRunnable Cargo/Rust crateCobertura