diff --git a/src/Target/Class_.php b/src/Target/Class_.php new file mode 100644 index 000000000..089887f64 --- /dev/null +++ b/src/Target/Class_.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Class_ extends Target +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + protected function __construct(string $className) + { + $this->className = $className; + } + + public function isClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Target/ClassesThatExtendClass.php b/src/Target/ClassesThatExtendClass.php new file mode 100644 index 000000000..5c4b0d6bd --- /dev/null +++ b/src/Target/ClassesThatExtendClass.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ClassesThatExtendClass extends Target +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + protected function __construct(string $className) + { + $this->className = $className; + } + + public function isClassesThatExtendClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Target/ClassesThatImplementInterface.php b/src/Target/ClassesThatImplementInterface.php new file mode 100644 index 000000000..911fb4ca8 --- /dev/null +++ b/src/Target/ClassesThatImplementInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ClassesThatImplementInterface extends Target +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param class-string $interfaceName + */ + protected function __construct(string $interfaceName) + { + $this->interfaceName = $interfaceName; + } + + public function isClassesThatImplementInterface(): true + { + return true; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/src/Target/Function_.php b/src/Target/Function_.php new file mode 100644 index 000000000..917c3456d --- /dev/null +++ b/src/Target/Function_.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Function_ extends Target +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param non-empty-string $functionName + */ + protected function __construct(string $functionName) + { + $this->functionName = $functionName; + } + + public function isFunction(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/src/Target/Method.php b/src/Target/Method.php new file mode 100644 index 000000000..388fc3071 --- /dev/null +++ b/src/Target/Method.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Method extends Target +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + public function isMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Target/Namespace_.php b/src/Target/Namespace_.php new file mode 100644 index 000000000..51cd5ccf9 --- /dev/null +++ b/src/Target/Namespace_.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Namespace_ extends Target +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param non-empty-string $namespace + */ + protected function __construct(string $namespace) + { + $this->namespace = $namespace; + } + + public function isNamespace(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/src/Target/Target.php b/src/Target/Target.php new file mode 100644 index 000000000..cd4469766 --- /dev/null +++ b/src/Target/Target.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract class Target +{ + /** + * @param non-empty-string $namespace + */ + public static function forNamespace(string $namespace): Namespace_ + { + return new Namespace_($namespace); + } + + /** + * @param class-string $className + */ + public static function forClass(string $className): Class_ + { + return new Class_($className); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function forMethod(string $className, string $methodName): Method + { + return new Method($className, $methodName); + } + + /** + * @param class-string $interfaceName + */ + public static function forClassesThatImplementInterface(string $interfaceName): ClassesThatImplementInterface + { + return new ClassesThatImplementInterface($interfaceName); + } + + /** + * @param class-string $className + */ + public static function forClassesThatExtendClass(string $className): ClassesThatExtendClass + { + return new ClassesThatExtendClass($className); + } + + /** + * @param non-empty-string $functionName + */ + public static function forFunction(string $functionName): Function_ + { + return new Function_($functionName); + } + + public function isNamespace(): bool + { + return false; + } + + public function isClass(): bool + { + return false; + } + + public function isMethod(): bool + { + return false; + } + + public function isClassesThatImplementInterface(): bool + { + return false; + } + + public function isClassesThatExtendClass(): bool + { + return false; + } + + public function isFunction(): bool + { + return false; + } +} diff --git a/src/Target/TargetCollection.php b/src/Target/TargetCollection.php new file mode 100644 index 000000000..ef6e32ac8 --- /dev/null +++ b/src/Target/TargetCollection.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class TargetCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $targets; + + /** + * @param list $targets + */ + public static function fromArray(array $targets): self + { + return new self(...$targets); + } + + private function __construct(Target ...$targets) + { + $this->targets = $targets; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->targets; + } + + public function count(): int + { + return count($this->targets); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } + + public function isNotEmpty(): bool + { + return $this->count() > 0; + } + + public function getIterator(): TargetCollectionIterator + { + return new TargetCollectionIterator($this); + } +} diff --git a/src/Target/TargetCollectionIterator.php b/src/Target/TargetCollectionIterator.php new file mode 100644 index 000000000..9a5ca06d9 --- /dev/null +++ b/src/Target/TargetCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class TargetCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $targets; + private int $position = 0; + + public function __construct(TargetCollection $metadata) + { + $this->targets = $metadata->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->targets); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Target + { + return $this->targets[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/tests/tests/Target/TargetCollectionTest.php b/tests/tests/Target/TargetCollectionTest.php new file mode 100644 index 000000000..b953c5286 --- /dev/null +++ b/tests/tests/Target/TargetCollectionTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(TargetCollection::class)] +#[CoversClass(TargetCollectionIterator::class)] +#[UsesClass(Class_::class)] +#[Small] +final class TargetCollectionTest extends TestCase +{ + public function testCanBeEmpty(): void + { + $collection = TargetCollection::fromArray([]); + + $this->assertCount(0, $collection); + $this->assertTrue($collection->isEmpty()); + $this->assertFalse($collection->isNotEmpty()); + } + + public function testCanBeCreatedFromArray(): void + { + $target = Target::forClass('className'); + $collection = TargetCollection::fromArray([$target]); + + $this->assertContains($target, $collection); + } + + public function testIsCountable(): void + { + $target = Target::forClass('className'); + $collection = TargetCollection::fromArray([$target]); + + $this->assertCount(1, $collection); + $this->assertFalse($collection->isEmpty()); + $this->assertTrue($collection->isNotEmpty()); + } + + public function testIsIterable(): void + { + $target = Target::forClass('className'); + $collection = TargetCollection::fromArray([$target]); + + foreach ($collection as $key => $value) { + $this->assertSame(0, $key); + $this->assertSame($target, $value); + } + } +} diff --git a/tests/tests/Target/TargetTest.php b/tests/tests/Target/TargetTest.php new file mode 100644 index 000000000..7dd4306be --- /dev/null +++ b/tests/tests/Target/TargetTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Target::class)] +#[CoversClass(Class_::class)] +#[CoversClass(ClassesThatExtendClass::class)] +#[CoversClass(ClassesThatImplementInterface::class)] +#[CoversClass(Function_::class)] +#[CoversClass(Method::class)] +#[CoversClass(Namespace_::class)] +#[Small] +final class TargetTest extends TestCase +{ + public function testCanBeClass(): void + { + $className = 'className'; + + $target = Target::forClass($className); + + $this->assertTrue($target->isClass()); + $this->assertFalse($target->isClassesThatExtendClass()); + $this->assertFalse($target->isClassesThatImplementInterface()); + $this->assertFalse($target->isFunction()); + $this->assertFalse($target->isMethod()); + $this->assertFalse($target->isNamespace()); + + $this->assertSame($className, $target->className()); + } + + public function testCanBeClassesThatExtendClass(): void + { + $className = 'className'; + + $target = Target::forClassesThatExtendClass($className); + + $this->assertFalse($target->isClass()); + $this->assertTrue($target->isClassesThatExtendClass()); + $this->assertFalse($target->isClassesThatImplementInterface()); + $this->assertFalse($target->isFunction()); + $this->assertFalse($target->isMethod()); + $this->assertFalse($target->isNamespace()); + + $this->assertSame($className, $target->className()); + } + + public function testCanBeClassesThatImplementInterface(): void + { + $interfaceName = 'interfaceName'; + + $target = Target::forClassesThatImplementInterface($interfaceName); + + $this->assertFalse($target->isClass()); + $this->assertFalse($target->isClassesThatExtendClass()); + $this->assertTrue($target->isClassesThatImplementInterface()); + $this->assertFalse($target->isFunction()); + $this->assertFalse($target->isMethod()); + $this->assertFalse($target->isNamespace()); + + $this->assertSame($interfaceName, $target->interfaceName()); + } + + public function testCanBeFunction(): void + { + $functionName = 'function'; + + $target = Target::forFunction($functionName); + + $this->assertFalse($target->isClass()); + $this->assertFalse($target->isClassesThatExtendClass()); + $this->assertFalse($target->isClassesThatImplementInterface()); + $this->assertTrue($target->isFunction()); + $this->assertFalse($target->isMethod()); + $this->assertFalse($target->isNamespace()); + + $this->assertSame($functionName, $target->functionName()); + } + + public function testCanBeMethod(): void + { + $className = 'className'; + $methodName = 'methodName'; + + $target = Target::forMethod($className, $methodName); + + $this->assertFalse($target->isClass()); + $this->assertFalse($target->isClassesThatExtendClass()); + $this->assertFalse($target->isClassesThatImplementInterface()); + $this->assertFalse($target->isFunction()); + $this->assertTrue($target->isMethod()); + $this->assertFalse($target->isNamespace()); + + $this->assertSame($className, $target->className()); + $this->assertSame($methodName, $target->methodName()); + } + + public function testCanBeNamespace(): void + { + $namespace = 'namespace'; + + $target = Target::forNamespace($namespace); + + $this->assertFalse($target->isClass()); + $this->assertFalse($target->isClassesThatExtendClass()); + $this->assertFalse($target->isClassesThatImplementInterface()); + $this->assertFalse($target->isFunction()); + $this->assertFalse($target->isMethod()); + $this->assertTrue($target->isNamespace()); + + $this->assertSame($namespace, $target->namespace()); + } +}