Skip to content

S004: Coverage Linkers

FieldValue
SpecS004
FeatureCoverage Linkers
Date2026-04-23
StatusDraft
Authorspec-writer-agent

Overview

Coverage Linkers are the static analysis components responsible for determining whether a PHP test file declares what source code it covers. They work entirely through regex-based analysis of file content -- no PHP execution, no AST parsing, no autoloading required. This is a core differentiator of Parity's approach: coverage declaration validation at static analysis speed.

The module provides two concrete linkers -- PestCoversLinker for Pest's covers() / ->covers() syntax and PhpAttributeLinker for PHPUnit's #[CoversClass()] PHP 8 attribute -- unified behind a common CoverageLinkerInterface. A CoverageLinkerRegistry orchestrates linker selection, supporting explicit configuration (pick one or both) and an auto mode that tries all registered linkers in order.

The enforce-coverage-link rule (specified in S002) is the primary consumer. It delegates to the registry to verify that a test file's coverage declarations match the expected source class FQCN. This spec covers the linker interface contract, both concrete implementations, the registry's selection strategy, class reference resolution (use maps, namespaces, aliased imports, FQCN), and the configuration surface.

User Scenarios

S004-US-001 [P1] As a developer using Pest, I want Parity to detect my covers() and ->covers() declarations so that I can validate coverage links without running tests.

S004-US-002 [P1] As a developer using PHPUnit, I want Parity to detect my #[CoversClass()] attributes so that I can enforce coverage declarations statically.

S004-US-003 [P1] As a developer on a mixed project (Pest + PHPUnit), I want Parity to auto-detect the correct linker per test file so that I do not need separate configuration for each testing framework.

S004-US-004 [P2] As a developer, I want to configure which linkers apply to each structure block so that I can limit detection to only the syntax my project uses.

S004-US-005 [P2] As a developer using a custom attribute (not the default PHPUnit CoversClass), I want to configure the attribute FQCN so that Parity detects my project-specific attribute.

Requirements Summary

IDTypePriorityTitleStatus
S004-FR-001FunctionalP1CoverageLinkerInterface contractDraft
S004-FR-002FunctionalP1PestCoversLinker supports() detectionDraft
S004-FR-003FunctionalP1PestCoversLinker extraction patternsDraft
S004-FR-004FunctionalP1PhpAttributeLinker supports() detectionDraft
S004-FR-005FunctionalP1PhpAttributeLinker extraction patternsDraft
S004-FR-006FunctionalP1Class reference resolutionDraft
S004-FR-007FunctionalP1CoverageLinkerRegistry auto-detectionDraft
S004-FR-008FunctionalP1Registry fromConfig() factoryDraft
S004-FR-009FunctionalP2Custom attribute FQCN configurationDraft
S004-FR-010FunctionalP1Static analysis only (no execution)Draft
S004-FR-011FunctionalP1Deduplication of extracted classesDraft
S004-FR-012FunctionalP1PhpAttributeLinker only reads pre-class attributesDraft
S004-IF-001InterfaceP1CoverageLinkerInterface method signaturesDraft
S004-IF-002InterfaceP1CoverageLinkerRegistry public APIDraft
S004-IF-003InterfaceP1resolveClassReference static APIDraft
S004-IF-004InterfaceP2Registry fromConfig() config mappingDraft
S004-AS-001AcceptanceP1Pest chained covers() detectedDraft
S004-AS-002AcceptanceP1Pest file-level covers() detectedDraft
S004-AS-003AcceptanceP1PHPUnit CoversClass attribute detectedDraft
S004-AS-004AcceptanceP1Auto mode selects correct linkerDraft
S004-AS-005AcceptanceP1Multiple classes extractedDraft
S004-AS-006AcceptanceP2Custom attribute FQCN worksDraft
S004-AS-007AcceptanceP1Validation rejects wrong FQCNDraft
S004-AS-008AcceptanceP1No linker matches returns emptyDraft
S004-EC-001Edge CaseP1Class keyword in string literalDraft
S004-EC-002Edge CaseP1Leading backslash FQCNDraft
S004-EC-003Edge CaseP1Aliased import (use ... as ...)Draft
S004-EC-004Edge CaseP1Sub-namespace in class referenceDraft
S004-EC-005Edge CaseP1No use map and no namespaceDraft
S004-EC-006Edge CaseP2Empty test fileDraft
S004-EC-007Edge CaseP2covers() with string literal argumentDraft
S004-EC-008Edge CaseP1Attribute inside class body ignoredDraft
S004-EC-009Edge CaseP2Unknown linker name in configDraft
S004-EC-010Edge CaseP1File with both class declaration and Pest callsDraft
S004-EC-011Edge CaseP2Duplicate covers of same classDraft
S004-EC-012Edge CaseP1Non-CoversClass attributes ignoredDraft
S004-EC-013Edge CaseP2Unresolvable argument formatDraft
S004-EC-014Edge CaseP2Unbalanced brackets in attributeDraft
S004-SC-001SuccessP1All Pest covers() variants detectedDraft
S004-SC-002SuccessP1All PHPUnit CoversClass variants detectedDraft
S004-SC-003SuccessP1Auto-detection selects correct linkerDraft
S004-SC-004SuccessP1Zero false positives on non-coverage syntaxDraft
S004-SC-005SuccessP1Class resolution handles all import stylesDraft

Cross-Spec Dependencies

  • Depends on: None (standalone module)
  • Required by: S002 (Rules System -- enforce-coverage-link rule consumes the linker registry), S006 (Configuration -- linkers parameter per structure block)