diff --git a/composer.json b/composer.json index ed04904..adfed34 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "require": { "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", "phpstan/phpstan": "^1.11", - "dave-liddament/php-language-extensions": "^0.7.0" + "dave-liddament/php-language-extensions": "^0.8.0" }, "require-dev": { "phpunit/phpunit": "^9.6.12", diff --git a/composer.lock b/composer.lock index 2ae6f03..ec7eb2c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "65b372eb17b17e5642eb1d52e725ac94", + "content-hash": "f1810665e88e79a13c1219525f4f04e9", "packages": [ { "name": "dave-liddament/php-language-extensions", - "version": "0.7.0", + "version": "0.8.0", "source": { "type": "git", "url": "https://github.com/DaveLiddament/php-language-extensions.git", - "reference": "99e3149643a1df05aa94ab9e21826043f29e6445" + "reference": "b64b35e49149f82e2e27bb0395aa71825d573f39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DaveLiddament/php-language-extensions/zipball/99e3149643a1df05aa94ab9e21826043f29e6445", - "reference": "99e3149643a1df05aa94ab9e21826043f29e6445", + "url": "https://api.github.com/repos/DaveLiddament/php-language-extensions/zipball/b64b35e49149f82e2e27bb0395aa71825d573f39", + "reference": "b64b35e49149f82e2e27bb0395aa71825d573f39", "shasum": "" }, "require": { @@ -54,9 +54,9 @@ ], "support": { "issues": "https://github.com/DaveLiddament/php-language-extensions/issues", - "source": "https://github.com/DaveLiddament/php-language-extensions/tree/0.7.0" + "source": "https://github.com/DaveLiddament/php-language-extensions/tree/0.8.0" }, - "time": "2024-08-08T10:43:28+00:00" + "time": "2024-08-12T16:51:09+00:00" }, { "name": "phpstan/phpstan", 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/AbstractTestTagRuleTest.php b/tests/Rules/AbstractTestTagRuleTest.php deleted file mode 100644 index f251e00..0000000 --- a/tests/Rules/AbstractTestTagRuleTest.php +++ /dev/null @@ -1,35 +0,0 @@ - - */ -abstract class AbstractTestTagRuleTest extends RuleTestCase -{ - /** @param array $errors */ - final protected function assertErrorsReported(string $file, array $errors): void - { - $expectedIssues = []; - foreach ($errors as list($lineNumber, $targetMethodName)) { - $expectedIssues[] = [ - "$targetMethodName is a test tag and can only be called from test code", - $lineNumber, - ]; - } - - $this->analyse([$file], $expectedIssues); - } - - final protected function assertNoErrorsReported(string $file): void - { - $this->analyse([$file], []); - } -} 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/TestTagOnConstructorIgnoredInTestNamespaceTest.php b/tests/Rules/TestTagOnConstructorIgnoredInTestNamespaceTest.php index 27f94eb..e156705 100644 --- a/tests/Rules/TestTagOnConstructorIgnoredInTestNamespaceTest.php +++ b/tests/Rules/TestTagOnConstructorIgnoredInTestNamespaceTest.php @@ -6,10 +6,11 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagNewCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnConstructorIgnoredInTestNamespaceTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +class TestTagOnConstructorIgnoredInTestNamespaceTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -24,7 +25,7 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertNoErrorsReported( + $this->assertIssuesReported( __DIR__.'/data/testTag/testTagOnConstructorIgnoredInTestNamespace.php', ); } diff --git a/tests/Rules/TestTagOnConstructorIgnoredOnTestClassTest.php b/tests/Rules/TestTagOnConstructorIgnoredOnTestClassTest.php index 1add46e..2d0dea0 100644 --- a/tests/Rules/TestTagOnConstructorIgnoredOnTestClassTest.php +++ b/tests/Rules/TestTagOnConstructorIgnoredOnTestClassTest.php @@ -6,10 +6,11 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagNewCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnConstructorIgnoredOnTestClassTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +class TestTagOnConstructorIgnoredOnTestClassTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -21,7 +22,7 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertNoErrorsReported( + $this->assertIssuesReported( __DIR__.'/data/testTag/testTagOnConstructorIgnoredInTestClass.php', ); } diff --git a/tests/Rules/TestTagOnConstructorTest.php b/tests/Rules/TestTagOnConstructorTest.php index 7a19041..cdfb6f4 100644 --- a/tests/Rules/TestTagOnConstructorTest.php +++ b/tests/Rules/TestTagOnConstructorTest.php @@ -6,10 +6,12 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagNewCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; +use DaveLiddament\PhpstanRuleTestHelper\ErrorMessageFormatter; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnConstructorTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +class TestTagOnConstructorTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -21,22 +23,11 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertErrorsReported( - __DIR__.'/data/testTag/testTagOnConstructor.php', - [ - [ - 16, - 'TestTagOnConstructor\Person::__construct', - ], - [ - 21, - 'TestTagOnConstructor\Person::__construct', - ], - [ - 29, - 'TestTagOnConstructor\Person::__construct', - ], - ], - ); + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagOnConstructor.php'); + } + + protected function getErrorFormatter(): ErrorMessageFormatter|string + { + return 'TestTagOnConstructor\Person::__construct is a test tag and can only be called from test code'; } } diff --git a/tests/Rules/TestTagOnMethodIgnoredInTestNamespaceTest.php b/tests/Rules/TestTagOnMethodIgnoredInTestNamespaceTest.php index 32a5bad..197ad9b 100644 --- a/tests/Rules/TestTagOnMethodIgnoredInTestNamespaceTest.php +++ b/tests/Rules/TestTagOnMethodIgnoredInTestNamespaceTest.php @@ -6,10 +6,11 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagMethodCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnMethodIgnoredInTestNamespaceTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +class TestTagOnMethodIgnoredInTestNamespaceTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -24,7 +25,7 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertNoErrorsReported( + $this->assertIssuesReported( __DIR__.'/data/testTag/testTagOnMethodIgnoredInTestNamespace.php', ); } diff --git a/tests/Rules/TestTagOnMethodIgnoredOnTestClassTest.php b/tests/Rules/TestTagOnMethodIgnoredOnTestClassTest.php index 77a269c..c5533f3 100644 --- a/tests/Rules/TestTagOnMethodIgnoredOnTestClassTest.php +++ b/tests/Rules/TestTagOnMethodIgnoredOnTestClassTest.php @@ -6,10 +6,11 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagMethodCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnMethodIgnoredOnTestClassTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +class TestTagOnMethodIgnoredOnTestClassTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -21,7 +22,7 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertNoErrorsReported( + $this->assertIssuesReported( __DIR__.'/data/testTag/testTagOnMethodIgnoredInTestClass.php', ); } diff --git a/tests/Rules/TestTagOnMethodTest.php b/tests/Rules/TestTagOnMethodTest.php index b8bfc05..d9289cc 100644 --- a/tests/Rules/TestTagOnMethodTest.php +++ b/tests/Rules/TestTagOnMethodTest.php @@ -6,10 +6,12 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagMethodCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; +use DaveLiddament\PhpstanRuleTestHelper\ErrorMessageFormatter; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnMethodTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +class TestTagOnMethodTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -21,22 +23,11 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertErrorsReported( - __DIR__.'/data/testTag/testTagOnMethod.php', - [ - [ - 18, - 'TestTagOnMethod\Person::updateName', - ], - [ - 26, - 'TestTagOnMethod\Person::updateName', - ], - [ - 31, - 'TestTagOnMethod\Person::updateName', - ], - ], - ); + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagOnMethod.php'); + } + + protected function getErrorFormatter(): ErrorMessageFormatter|string + { + return 'TestTagOnMethod\Person::updateName is a test tag and can only be called from test code'; } } diff --git a/tests/Rules/TestTagOnStaticIgnoreIOnTestNamespaceTest.php b/tests/Rules/TestTagOnStaticIgnoreIOnTestNamespaceTest.php index 72bd2b0..143aa83 100644 --- a/tests/Rules/TestTagOnStaticIgnoreIOnTestNamespaceTest.php +++ b/tests/Rules/TestTagOnStaticIgnoreIOnTestNamespaceTest.php @@ -6,10 +6,11 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagStaticCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnStaticIgnoreIOnTestNamespaceTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +class TestTagOnStaticIgnoreIOnTestNamespaceTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -24,7 +25,7 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertNoErrorsReported( + $this->assertIssuesReported( __DIR__.'/data/testTag/testTagOnStaticMethodIgnoredInTestNamespace.php', ); } diff --git a/tests/Rules/TestTagOnStaticIgnoredOnTestClassTest.php b/tests/Rules/TestTagOnStaticIgnoredOnTestClassTest.php index d49ddd5..0435423 100644 --- a/tests/Rules/TestTagOnStaticIgnoredOnTestClassTest.php +++ b/tests/Rules/TestTagOnStaticIgnoredOnTestClassTest.php @@ -6,10 +6,11 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagStaticCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnStaticIgnoredOnTestClassTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +class TestTagOnStaticIgnoredOnTestClassTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -21,8 +22,6 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertNoErrorsReported( - __DIR__.'/data/testTag/testTagOnStaticMethodIgnoredInTestClass.php', - ); + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagOnStaticMethodIgnoredInTestClass.php'); } } diff --git a/tests/Rules/TestTagOnStaticTest.php b/tests/Rules/TestTagOnStaticTest.php index 1385217..45a90a4 100644 --- a/tests/Rules/TestTagOnStaticTest.php +++ b/tests/Rules/TestTagOnStaticTest.php @@ -6,10 +6,11 @@ use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig; use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagStaticCallRule; +use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase; use PHPStan\Rules\Rule; -/** @extends AbstractTestTagRuleTest */ -class TestTagOnStaticTest extends AbstractTestTagRuleTest +/** @extends AbstractRuleTestCase */ +final class TestTagOnStaticTest extends AbstractRuleTestCase { protected function getRule(): Rule { @@ -21,26 +22,11 @@ protected function getRule(): Rule public function testMethodCall(): void { - $this->assertErrorsReported( - __DIR__.'/data/testTag/testTagOnStaticMethod.php', - [ - [ - 17, - 'TestTagOnStaticMethod\Person::updateName', - ], - [ - 22, - 'TestTagOnStaticMethod\Person::updateName', - ], - [ - 30, - 'TestTagOnStaticMethod\Person::updateName', - ], - [ - 34, - 'TestTagOnStaticMethod\Person::updateName', - ], - ], - ); + $this->assertIssuesReported(__DIR__.'/data/testTag/testTagOnStaticMethod.php'); + } + + protected function getErrorFormatter(): string + { + return 'TestTagOnStaticMethod\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 @@ +