diff --git a/src/Rules/AbstractTestTagRule.php b/src/Rules/AbstractTestTagRule.php index 9ef69c2..51b516b 100644 --- a/src/Rules/AbstractTestTagRule.php +++ b/src/Rules/AbstractTestTagRule.php @@ -40,25 +40,40 @@ protected function getErrorOrNull( string $class, string $methodName, ): ?RuleError { + $callingClass = $scope->getClassReflection()?->getName(); + $classReflection = $this->reflectionProvider->getClass($class); $className = $classReflection->getName(); $nativeReflection = $classReflection->getNativeReflection(); $fullMethodName = "{$className}::{$methodName}"; + if ($this->cache->hasEntry($className)) { + $isTestTagOnClass = $this->cache->getEntry($className); + } else { + $isTestTagOnClass = count($nativeReflection->getAttributes(TestTag::class)) > 0; + $this->cache->addEntry($className, $isTestTagOnClass); + } + if ($this->cache->hasEntry($fullMethodName)) { - $isTestTag = $this->cache->getEntry($fullMethodName); + $isTestTagOnMethod = $this->cache->getEntry($fullMethodName); } else { if ($nativeReflection->hasMethod($methodName)) { $methodReflection = $nativeReflection->getMethod($methodName); - $isTestTag = count($methodReflection->getAttributes(TestTag::class)) > 0; + $isTestTagOnMethod = count($methodReflection->getAttributes(TestTag::class)) > 0; } else { - $isTestTag = false; + $isTestTagOnMethod = false; } - $this->cache->addEntry($fullMethodName, $isTestTag); + $this->cache->addEntry($fullMethodName, $isTestTagOnMethod); + } + + $hasTestTag = $isTestTagOnClass || $isTestTagOnMethod; + + if (!$hasTestTag) { + return null; } - if (!$isTestTag) { + if ($isTestTagOnClass && ($className === $callingClass)) { return null; } diff --git a/tests/Rules/TestTagClassOnConstructorIngoredOnTestClassTest.php b/tests/Rules/TestTagClassOnConstructorIngoredOnTestClassTest.php new file mode 100644 index 0000000..8211568 --- /dev/null +++ b/tests/Rules/TestTagClassOnConstructorIngoredOnTestClassTest.php @@ -0,0 +1,33 @@ + */ +class TestTagClassOnConstructorIngoredOnTestClassTest extends AbstractRuleTestCase +{ + protected function getRule(): Rule + { + return new TestTagNewCallRule( + $this->createReflectionProvider(), + new TestConfig(TestConfig::CLASS_NAME), + ); + } + + public function testMethodCall(): void + { + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnConstructorIgnoredInTestClass.php'); + } + + protected function getErrorFormatter(): ErrorMessageFormatter|string + { + return 'TestTagClassOnConstructorIgnoredOnTestClass\Person::__construct is a test tag and can only be called from test code'; + } +} diff --git a/tests/Rules/TestTagClassOnConstructorTest.php b/tests/Rules/TestTagClassOnConstructorTest.php new file mode 100644 index 0000000..e80446d --- /dev/null +++ b/tests/Rules/TestTagClassOnConstructorTest.php @@ -0,0 +1,33 @@ + */ +class TestTagClassOnConstructorTest extends AbstractRuleTestCase +{ + protected function getRule(): Rule + { + return new TestTagNewCallRule( + $this->createReflectionProvider(), + new TestConfig(TestConfig::CLASS_NAME), + ); + } + + public function testMethodCall(): void + { + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnConstructor.php'); + } + + protected function getErrorFormatter(): ErrorMessageFormatter|string + { + return 'TestTagClassOnConstructor\Person::__construct is a test tag and can only be called from test code'; + } +} diff --git a/tests/Rules/TestTagClassOnMethodIgnoredOnTestClassTest.php b/tests/Rules/TestTagClassOnMethodIgnoredOnTestClassTest.php new file mode 100644 index 0000000..0c0f9ee --- /dev/null +++ b/tests/Rules/TestTagClassOnMethodIgnoredOnTestClassTest.php @@ -0,0 +1,29 @@ + */ +class TestTagClassOnMethodIgnoredOnTestClassTest extends AbstractRuleTestCase +{ + protected function getRule(): Rule + { + return new TestTagMethodCallRule( + $this->createReflectionProvider(), + new TestConfig(TestConfig::CLASS_NAME), + ); + } + + public function testMethodCall(): void + { + $this->assertIssuesReported( + __DIR__.'/data/testTag/testTagClassOnMethodIgnoredInTestClass.php', + ); + } +} diff --git a/tests/Rules/TestTagClassOnMethodTest.php b/tests/Rules/TestTagClassOnMethodTest.php new file mode 100644 index 0000000..9b15c24 --- /dev/null +++ b/tests/Rules/TestTagClassOnMethodTest.php @@ -0,0 +1,33 @@ + */ +class TestTagClassOnMethodTest extends AbstractRuleTestCase +{ + protected function getRule(): Rule + { + return new TestTagMethodCallRule( + $this->createReflectionProvider(), + new TestConfig(TestConfig::CLASS_NAME), + ); + } + + public function testMethodCall(): void + { + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnMethod.php'); + } + + protected function getErrorFormatter(): ErrorMessageFormatter|string + { + return 'TestTagClassOnMethod\Person::updateName is a test tag and can only be called from test code'; + } +} diff --git a/tests/Rules/TestTagClassOnStaticIgnoredOnTestClassTest.php b/tests/Rules/TestTagClassOnStaticIgnoredOnTestClassTest.php new file mode 100644 index 0000000..b3880c2 --- /dev/null +++ b/tests/Rules/TestTagClassOnStaticIgnoredOnTestClassTest.php @@ -0,0 +1,27 @@ + */ +class TestTagClassOnStaticIgnoredOnTestClassTest extends AbstractRuleTestCase +{ + protected function getRule(): Rule + { + return new TestTagStaticCallRule( + $this->createReflectionProvider(), + new TestConfig(TestConfig::CLASS_NAME), + ); + } + + public function testMethodCall(): void + { + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnStaticMethodIgnoredInTestClass.php'); + } +} diff --git a/tests/Rules/TestTagClassOnStaticTest.php b/tests/Rules/TestTagClassOnStaticTest.php new file mode 100644 index 0000000..206939c --- /dev/null +++ b/tests/Rules/TestTagClassOnStaticTest.php @@ -0,0 +1,32 @@ + */ +final class TestTagClassOnStaticTest extends AbstractRuleTestCase +{ + protected function getRule(): Rule + { + return new TestTagStaticCallRule( + $this->createReflectionProvider(), + new TestConfig(TestConfig::CLASS_NAME), + ); + } + + public function testMethodCall(): void + { + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnStaticMethod.php'); + } + + protected function getErrorFormatter(): string + { + return 'TestTagClassOnStaticMethod\Person::updateName is a test tag and can only be called from test code'; + } +} diff --git a/tests/Rules/data/testTag/testTagClassOnConstructor.php b/tests/Rules/data/testTag/testTagClassOnConstructor.php new file mode 100644 index 0000000..fe76ec3 --- /dev/null +++ b/tests/Rules/data/testTag/testTagClassOnConstructor.php @@ -0,0 +1,31 @@ +updateName(); // OK - whole class is marked with TestTag, so OK to call methods within it. + } +} + +class Updater +{ + public function updater(Person $person): void + { + $person->updateName(); // ERROR + } +} + +$person = new Person(); +$person->updateName(); // ERROR + diff --git a/tests/Rules/data/testTag/testTagClassOnMethodIgnoredInTestClass.php b/tests/Rules/data/testTag/testTagClassOnMethodIgnoredInTestClass.php new file mode 100644 index 0000000..9e067f8 --- /dev/null +++ b/tests/Rules/data/testTag/testTagClassOnMethodIgnoredInTestClass.php @@ -0,0 +1,25 @@ +updateName(); // OK - Called from Test class + } +} + + diff --git a/tests/Rules/data/testTag/testTagClassOnStaticMethod.php b/tests/Rules/data/testTag/testTagClassOnStaticMethod.php new file mode 100644 index 0000000..6d8d397 --- /dev/null +++ b/tests/Rules/data/testTag/testTagClassOnStaticMethod.php @@ -0,0 +1,34 @@ +