Skip to content

Commit c45b25f

Browse files
authored
Merge pull request #573 from sukei/feature/is-not-a
Add a IsNotA expression as a symmetry to the existing IsA expression.
2 parents 75ccf07 + c18d341 commit c45b25f

File tree

4 files changed

+168
-12
lines changed

4 files changed

+168
-12
lines changed

src/Expression/ForClasses/IsA.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function __construct(string $allowedFqcn)
2626

2727
public function describe(ClassDescription $theClass, string $because = ''): Description
2828
{
29-
return new Description("should inherit from: $this->allowedFqcn", $because);
29+
return new Description("{$theClass->getName()} should be a $this->allowedFqcn", $because);
3030
}
3131

3232
public function evaluate(ClassDescription $theClass, Violations $violations, string $because = ''): void
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Expression\ForClasses;
6+
7+
use Arkitect\Analyzer\ClassDescription;
8+
use Arkitect\Expression\Description;
9+
use Arkitect\Expression\Expression;
10+
use Arkitect\Rules\Violation;
11+
use Arkitect\Rules\ViolationMessage;
12+
use Arkitect\Rules\Violations;
13+
14+
final class IsNotA implements Expression
15+
{
16+
/** @var class-string */
17+
private string $disallowedFqcn;
18+
19+
/**
20+
* @param class-string $disallowedFqcn
21+
*/
22+
public function __construct(string $disallowedFqcn)
23+
{
24+
$this->disallowedFqcn = $disallowedFqcn;
25+
}
26+
27+
public function describe(ClassDescription $theClass, string $because = ''): Description
28+
{
29+
return new Description("{$theClass->getName()} should not be a $this->disallowedFqcn", $because);
30+
}
31+
32+
public function evaluate(ClassDescription $theClass, Violations $violations, string $because = ''): void
33+
{
34+
if (is_a($theClass->getFQCN(), $this->disallowedFqcn, true)) {
35+
$violation = Violation::create(
36+
$theClass->getFQCN(),
37+
ViolationMessage::selfExplanatory($this->describe($theClass, $because)),
38+
$theClass->getFilePath()
39+
);
40+
41+
$violations->add($violation);
42+
}
43+
}
44+
}

tests/Unit/Expressions/ForClasses/IsATest.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
use Arkitect\Analyzer\ClassDescriptionBuilder;
88
use Arkitect\Expression\ForClasses\IsA;
99
use Arkitect\Rules\Violations;
10-
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Animal\Dog;
11-
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit\Banana;
12-
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit\CavendishBanana;
13-
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit\DwarfCavendishBanana;
14-
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit\FruitInterface;
10+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsATest\Animal\Dog;
11+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsATest\Fruit\Banana;
12+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsATest\Fruit\CavendishBanana;
13+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsATest\Fruit\DwarfCavendishBanana;
14+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsATest\Fruit\FruitInterface;
1515
use PHPUnit\Framework\TestCase;
1616

1717
final class IsATest extends TestCase
@@ -48,7 +48,7 @@ public function test_it_should_have_no_violation_when_it_extends(): void
4848
self::assertEquals(0, $violations->count());
4949
}
5050

51-
public function test_it_should_have_violation_if_it_doesnt_extend(): void
51+
public function test_it_should_have_violation_when_it_doesnt_extend(): void
5252
{
5353
$interface = FruitInterface::class;
5454
$isA = new IsA($interface);
@@ -62,12 +62,12 @@ public function test_it_should_have_violation_if_it_doesnt_extend(): void
6262

6363
self::assertEquals(1, $violations->count());
6464
self::assertEquals(
65-
"should inherit from: $interface",
65+
"Dog should be a $interface",
6666
$isA->describe($classDescription, '')->toString()
6767
);
6868
}
6969

70-
public function test_it_should_have_violation_if_it_doesnt_implement(): void
70+
public function test_it_should_have_violation_when_it_doesnt_implement(): void
7171
{
7272
$class = Banana::class;
7373
$isA = new IsA($class);
@@ -81,19 +81,19 @@ public function test_it_should_have_violation_if_it_doesnt_implement(): void
8181

8282
self::assertEquals(1, $violations->count());
8383
self::assertEquals(
84-
"should inherit from: $class",
84+
"Dog should be a $class",
8585
$isA->describe($classDescription, '')->toString()
8686
);
8787
}
8888
}
8989

90-
namespace Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Animal;
90+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\IsATest\Animal;
9191

9292
final class Dog
9393
{
9494
}
9595

96-
namespace Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit;
96+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\IsATest\Fruit;
9797

9898
interface FruitInterface
9999
{
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses;
6+
7+
use Arkitect\Analyzer\ClassDescriptionBuilder;
8+
use Arkitect\Expression\ForClasses\IsNotA;
9+
use Arkitect\Rules\Violations;
10+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsNotA\Animal\Dog;
11+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsNotA\Fruit\Banana;
12+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsNotA\Fruit\CavendishBanana;
13+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsNotA\Fruit\DwarfCavendishBanana;
14+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsNotA\Fruit\FruitInterface;
15+
use PHPUnit\Framework\TestCase;
16+
17+
final class IsNotATest extends TestCase
18+
{
19+
public function test_it_should_have_no_violation_when_it_doesnt_extend(): void
20+
{
21+
$interface = FruitInterface::class;
22+
$isNotA = new IsNotA($interface);
23+
$classDescription = (new ClassDescriptionBuilder())
24+
->setFilePath('src/Foo.php')
25+
->setClassName(Dog::class)
26+
->build();
27+
28+
$violations = new Violations();
29+
$isNotA->evaluate($classDescription, $violations, '');
30+
31+
self::assertEquals(0, $violations->count());
32+
}
33+
34+
public function test_it_should_have_no_violation_when_it_doesnt_implement(): void
35+
{
36+
$class = Banana::class;
37+
$isNotA = new IsNotA($class);
38+
$classDescription = (new ClassDescriptionBuilder())
39+
->setFilePath('src/Foo.php')
40+
->setClassName(Dog::class)
41+
->build();
42+
43+
$violations = new Violations();
44+
$isNotA->evaluate($classDescription, $violations, '');
45+
46+
self::assertEquals(0, $violations->count());
47+
}
48+
49+
public function test_it_should_have_violation_when_it_implements(): void
50+
{
51+
$interface = FruitInterface::class;
52+
$isNotA = new IsNotA($interface);
53+
$classDescription = (new ClassDescriptionBuilder())
54+
->setFilePath('src/Foo.php')
55+
->setClassName(CavendishBanana::class)
56+
->addInterface($interface, 10)
57+
->build();
58+
59+
$violations = new Violations();
60+
$isNotA->evaluate($classDescription, $violations, '');
61+
62+
self::assertEquals(1, $violations->count());
63+
self::assertEquals(
64+
"CavendishBanana should not be a $interface",
65+
$isNotA->describe($classDescription, '')->toString()
66+
);
67+
}
68+
69+
public function test_it_should_have_violation_when_it_extends(): void
70+
{
71+
$class = Banana::class;
72+
$isNotA = new IsNotA($class);
73+
$classDescription = (new ClassDescriptionBuilder())
74+
->setFilePath('src/Foo.php')
75+
->setClassName(DwarfCavendishBanana::class)
76+
->addExtends($class, 10)
77+
->build();
78+
79+
$violations = new Violations();
80+
$isNotA->evaluate($classDescription, $violations, '');
81+
82+
self::assertEquals(1, $violations->count());
83+
self::assertEquals(
84+
"DwarfCavendishBanana should not be a $class",
85+
$isNotA->describe($classDescription, '')->toString()
86+
);
87+
}
88+
}
89+
90+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\IsNotA\Animal;
91+
92+
final class Dog
93+
{
94+
}
95+
96+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\IsNotA\Fruit;
97+
98+
interface FruitInterface
99+
{
100+
}
101+
102+
class Banana implements FruitInterface
103+
{
104+
}
105+
106+
class CavendishBanana extends Banana
107+
{
108+
}
109+
110+
final class DwarfCavendishBanana extends CavendishBanana
111+
{
112+
}

0 commit comments

Comments
 (0)