Skip to content

S004 Edge Cases

IDScenarioExpected Behavior
S004-EC-001File content contains class as a string literal inside a Pest test (e.g. $x = 'class Foo extends Bar {';) but no actual class declarationPestCoversLinker::supports() MUST return true only if the regex \bclass\s+\w+\s*(extends|implements|\{) does not match. A string literal containing class Foo extends will cause a false negative (linker rejects the file). This is a known limitation of regex-based detection.
S004-EC-002covers() argument uses a leading backslash: \App\Services\FooService::classresolveClassReference() MUST strip the leading backslash and return App\Services\FooService.
S004-EC-003Use statement with alias: use App\Services\FooService as Foo; and coverage declaration ->covers(Foo::class)resolveClassReference() MUST look up Foo in the $useMap (which maps Foo => App\Services\FooService) and return App\Services\FooService.
S004-EC-004Reference contains a sub-namespace: Services\FooService::class with $useMap['Services'] = 'App\Services'resolveClassReference() MUST resolve the first segment Services via $useMap to App\Services, then append FooService, returning App\Services\FooService.
S004-EC-005Bare class name without use map or namespace: FooService::class with $useMap = [] and $namespace = nullresolveClassReference() MUST return the bare name FooService (no resolution possible, returned as-is).
S004-EC-006Empty or whitespace-only test file contentsupports() MUST return false for both linkers. extractCoveredClasses() MUST return [] if called.
S004-EC-007covers() argument is a string literal instead of ::class: ->covers('App\Services\FooService')resolveClassReference() MUST parse the string literal and return App\Services\FooService (unescape double backslashes).
S004-EC-008#[CoversClass()] attribute appears inside the class body (on a method)PhpAttributeLinker MUST ignore it because extraction only scans the portion before the first class keyword (per S004-FR-012).
S004-EC-009fromConfig() receives an array with an unknown linker name: ['pest-covers', 'unknown-linker']The unknown name MUST be silently skipped. The registry MUST contain only PestCoversLinker. No exception thrown.
S004-EC-010File has both a class declaration and Pest-style it() calls (hybrid file)PestCoversLinker::supports() MUST return false (class declaration takes precedence). PhpAttributeLinker::supports() MUST return true. The registry will select PhpAttributeLinker.
S004-EC-011Same class referenced multiple times: ->covers(FooService::class, FooService::class)PestCoversLinker::extractCoveredClasses() MUST deduplicate and return exactly one entry for FooService.
S004-EC-012Test file has non-CoversClass attributes: #[Group('unit')], #[DataProvider('data')]PhpAttributeLinker MUST ignore all attributes whose name does not match the configured attribute short name. Extracted classes MUST be empty if no CoversClass attribute is present.
S004-EC-013resolveClassReference() receives an argument that is not a ::class reference, not a string literal (e.g. 42, $variable, SomeClass::METHOD)MUST return null. No exception thrown.
S004-EC-014Unbalanced brackets in attribute: #[CoversClass(Foo::class (missing closing bracket or paren)PhpAttributeLinker MUST handle gracefully without throwing an exception. The malformed attribute MUST be skipped. The extractBalancedParens() method returns null for unbalanced input.