From 580a02b78eaa4382c6ca4ddadbcd074700d07ea7 Mon Sep 17 00:00:00 2001 From: Herberto Graca Date: Wed, 26 Jul 2023 21:58:12 +0200 Subject: [PATCH 1/2] Create a And expression This will allow for complex nested expressions, like ANDs inside ORs. --- src/Expression/Boolean/Andx.php | 56 +++++++++++ tests/Unit/Expressions/Boolean/AndxTest.php | 105 ++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 src/Expression/Boolean/Andx.php create mode 100644 tests/Unit/Expressions/Boolean/AndxTest.php diff --git a/src/Expression/Boolean/Andx.php b/src/Expression/Boolean/Andx.php new file mode 100644 index 00000000..a4a8c329 --- /dev/null +++ b/src/Expression/Boolean/Andx.php @@ -0,0 +1,56 @@ +expressions = $expressions; + } + + public function describe(ClassDescription $theClass, string $because): Description + { + $expressionsDescriptions = []; + foreach ($this->expressions as $expression) { + $expressionsDescriptions[] = $expression->describe($theClass, '')->toString(); + } + + return new Description( + 'all expressions must be true ('.implode(', ', $expressionsDescriptions).')', + $because + ); + } + + public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void + { + foreach ($this->expressions as $expression) { + $newViolations = new Violations(); + $expression->evaluate($theClass, $newViolations, $because); + if (0 !== $newViolations->count()) { + $violations->add(Violation::create( + $theClass->getFQCN(), + ViolationMessage::withDescription( + $this->describe($theClass, $because), + "The class '".$theClass->getFQCN()."' violated the expression " + .$expression->describe($theClass, '')->toString() + ) + )); + + return; + } + } + } +} diff --git a/tests/Unit/Expressions/Boolean/AndxTest.php b/tests/Unit/Expressions/Boolean/AndxTest.php new file mode 100644 index 00000000..1af18c05 --- /dev/null +++ b/tests/Unit/Expressions/Boolean/AndxTest.php @@ -0,0 +1,105 @@ +evaluate($classDescription, $violations, $because); + + self::assertEquals(0, $violations->count()); + } + + public function test_it_should_pass_the_rule_when_and_is_empty(): void + { + $interface = 'interface'; + $class = 'SomeClass'; + $classDescription = new ClassDescription( + FullyQualifiedClassName::fromString('HappyIsland'), + [], + [FullyQualifiedClassName::fromString($interface)], + FullyQualifiedClassName::fromString($class), + false, + false, + false, + false, + false + ); + $andConstraint = new Andx(); + + $because = 'reasons'; + $violations = new Violations(); + $andConstraint->evaluate($classDescription, $violations, $because); + + self::assertEquals(0, $violations->count()); + } + + public function test_it_should_not_pass_the_rule(): void + { + $interface = 'SomeInterface'; + $class = 'SomeClass'; + + $classDescription = new ClassDescription( + FullyQualifiedClassName::fromString('HappyIsland'), + [], + [FullyQualifiedClassName::fromString($interface)], + null, + false, + false, + false, + false, + false + ); + + $implementConstraint = new Implement($interface); + $extendsConstraint = new Extend($class); + $andConstraint = new Andx($implementConstraint, $extendsConstraint); + + $because = 'reasons'; + $violationError = $andConstraint->describe($classDescription, $because)->toString(); + + $violations = new Violations(); + $andConstraint->evaluate($classDescription, $violations, $because); + self::assertNotEquals(0, $violations->count()); + + $this->assertEquals( + 'all expressions must be true (should implement SomeInterface, should extend SomeClass) because reasons', + $violationError + ); + $this->assertEquals( + "The class 'HappyIsland' violated the expression should extend SomeClass, but " + .'all expressions must be true (should implement SomeInterface, should extend SomeClass) because reasons', + $violations->get(0)->getError() + ); + } +} From a0c29bfc9dfe5d6947576195bc0430e7ee03047f Mon Sep 17 00:00:00 2001 From: Herberto Graca Date: Sun, 10 Sep 2023 13:06:55 +0200 Subject: [PATCH 2/2] fixup! --- src/Expression/Boolean/Andx.php | 12 ++++----- tests/Unit/Expressions/Boolean/AndxTest.php | 28 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Expression/Boolean/Andx.php b/src/Expression/Boolean/Andx.php index a4a8c329..5f4cb222 100644 --- a/src/Expression/Boolean/Andx.php +++ b/src/Expression/Boolean/Andx.php @@ -25,13 +25,13 @@ public function describe(ClassDescription $theClass, string $because): Descripti { $expressionsDescriptions = []; foreach ($this->expressions as $expression) { - $expressionsDescriptions[] = $expression->describe($theClass, '')->toString(); + $expressionsDescriptions[] = $expression->describe($theClass, $because)->toString(); } + $expressionsDescriptionsString = "(\n" + .implode("\nAND\n", array_unique(array_map('trim', $expressionsDescriptions))) + ."\n)"; - return new Description( - 'all expressions must be true ('.implode(', ', $expressionsDescriptions).')', - $because - ); + return new Description($expressionsDescriptionsString, $because); } public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void @@ -44,7 +44,7 @@ public function evaluate(ClassDescription $theClass, Violations $violations, str $theClass->getFQCN(), ViolationMessage::withDescription( $this->describe($theClass, $because), - "The class '".$theClass->getFQCN()."' violated the expression " + "The class '".$theClass->getFQCN()."' violated the expression\n" .$expression->describe($theClass, '')->toString() ) )); diff --git a/tests/Unit/Expressions/Boolean/AndxTest.php b/tests/Unit/Expressions/Boolean/AndxTest.php index 1af18c05..31ace709 100644 --- a/tests/Unit/Expressions/Boolean/AndxTest.php +++ b/tests/Unit/Expressions/Boolean/AndxTest.php @@ -5,6 +5,7 @@ namespace Arkitect\Tests\Unit\Expressions\Boolean; use Arkitect\Analyzer\ClassDescription; +use Arkitect\Analyzer\ClassDescriptionBuilder; use Arkitect\Analyzer\FullyQualifiedClassName; use Arkitect\Expression\Boolean\Andx; use Arkitect\Expression\ForClasses\Extend; @@ -14,6 +15,21 @@ class AndxTest extends TestCase { + public function test_it_should_return_no_violation_when_empty(): void + { + $and = new Andx(); + + $classDescription = (new ClassDescriptionBuilder()) + ->setClassName('My\Class') + ->setExtends('My\BaseClass', 10) + ->build(); + + $violations = new Violations(); + $and->evaluate($classDescription, $violations, 'because'); + + self::assertEquals(0, $violations->count()); + } + public function test_it_should_pass_the_rule(): void { $interface = 'interface'; @@ -93,12 +109,18 @@ public function test_it_should_not_pass_the_rule(): void self::assertNotEquals(0, $violations->count()); $this->assertEquals( - 'all expressions must be true (should implement SomeInterface, should extend SomeClass) because reasons', + "(\nshould implement SomeInterface because reasons\nAND\nshould extend SomeClass because reasons\n) because reasons", $violationError ); $this->assertEquals( - "The class 'HappyIsland' violated the expression should extend SomeClass, but " - .'all expressions must be true (should implement SomeInterface, should extend SomeClass) because reasons', + "The class 'HappyIsland' violated the expression\n" + ."should extend SomeClass\n" + ."from the rule\n" + ."(\n" + ."should implement SomeInterface because reasons\n" + ."AND\n" + ."should extend SomeClass because reasons\n" + .') because reasons', $violations->get(0)->getError() ); }