diff --git a/README.md b/README.md index 882dc41a..75ee5b62 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,17 @@ Rule::allClasses() ->should(new HaveNameMatching('*Controller')) ->because('it\'s a symfony naming convention'); ``` + +Since selecting classes by namespace is very common, there's a convenient shortcut: + +```php +Rule::namespace('App\Controller') + ->should(new HaveNameMatching('*Controller')) + ->because('it\'s a symfony naming convention'); +``` + +You can also specify multiple namespaces: `Rule::namespace('App\Controller', 'App\Service')`. + # Installation ## Using Composer diff --git a/src/Rules/Rule.php b/src/Rules/Rule.php index 6562da7e..506db60e 100644 --- a/src/Rules/Rule.php +++ b/src/Rules/Rule.php @@ -3,10 +3,18 @@ namespace Arkitect\Rules; +use Arkitect\Expression\ForClasses\ResideInOneOfTheseNamespaces; +use Arkitect\Rules\DSL\AndThatShouldParser; + class Rule { public static function allClasses(): AllClasses { return new AllClasses(); } + + public static function namespace(string ...$namespaces): AndThatShouldParser + { + return self::allClasses()->that(new ResideInOneOfTheseNamespaces(...$namespaces)); + } } diff --git a/tests/Integration/RuleNamespaceShortcutTest.php b/tests/Integration/RuleNamespaceShortcutTest.php new file mode 100644 index 00000000..44779925 --- /dev/null +++ b/tests/Integration/RuleNamespaceShortcutTest.php @@ -0,0 +1,148 @@ +createDummyProject())->url(); + + $runner = TestRunner::create('8.4'); + + $rule = Rule::namespace('App\HappyIsland') + ->should(new HaveNameMatching('Happy*')) + ->because("that's what she said"); + + $runner->run($dir, $rule); + + self::assertCount(0, $runner->getViolations()); + self::assertCount(0, $runner->getParsingErrors()); + } + + public function test_namespace_shortcut_detects_violations(): void + { + $dir = vfsStream::setup('root', null, $this->createDummyProject())->url(); + + $runner = TestRunner::create('8.4'); + + $rule = Rule::namespace('App\HappyIsland') + ->should(new HaveNameMatching('Sad*')) + ->because('we want sad names'); + + $runner->run($dir, $rule); + + self::assertCount(1, $runner->getViolations()); + } + + public function test_namespace_shortcut_supports_multiple_namespaces(): void + { + $dir = vfsStream::setup('root', null, $this->createDummyProject())->url(); + + $runner = TestRunner::create('8.4'); + + $rule = Rule::namespace('App\HappyIsland', 'App\BadCode') + ->should(new HaveNameMatching('*Code')) + ->because('we want Code suffix'); + + $runner->run($dir, $rule); + + // HappyClass doesn't match *Code pattern, so we should have 1 violation + self::assertCount(1, $runner->getViolations()); + } + + public function test_namespace_shortcut_is_equivalent_to_full_syntax(): void + { + $dir = vfsStream::setup('root', null, $this->createDummyProject())->url(); + + $runner = TestRunner::create('8.4'); + + // Using shortcut + $shortcutRule = Rule::namespace('App\HappyIsland') + ->should(new HaveNameMatching('Happy*')) + ->because('test'); + + $runner->run($dir, $shortcutRule); + $shortcutViolations = $runner->getViolations(); + + // Using full syntax + $fullRule = Rule::allClasses() + ->that(new ResideInOneOfTheseNamespaces('App\HappyIsland')) + ->should(new HaveNameMatching('Happy*')) + ->because('test'); + + $runner->run($dir, $fullRule); + $fullViolations = $runner->getViolations(); + + self::assertCount(\count($shortcutViolations), $fullViolations); + } + + public function createDummyProject(): array + { + return [ + 'BadCode' => [ + 'BadCode.php' => <<<'EOF' + happy = $happy; + } + } + EOF, + ], + 'OtherBadCode' => [ + 'OtherBadCode.php' => <<<'EOF' + happy = $happy; + } + } + EOF, + ], + + 'HappyIsland' => [ + 'HappyClass.php' => <<<'EOF' + bad = $bad; + } + } + EOF, + ], + ]; + } +}