Skip to content

Commit 68a2e82

Browse files
committed
ADD implementation for TestTag on a class
1 parent 6c5c145 commit 68a2e82

13 files changed

+372
-5
lines changed

src/Rules/AbstractTestTagRule.php

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,40 @@ protected function getErrorOrNull(
4040
string $class,
4141
string $methodName,
4242
): ?RuleError {
43+
$callingClass = $scope->getClassReflection()?->getName();
44+
4345
$classReflection = $this->reflectionProvider->getClass($class);
4446
$className = $classReflection->getName();
4547
$nativeReflection = $classReflection->getNativeReflection();
4648

4749
$fullMethodName = "{$className}::{$methodName}";
4850

51+
if ($this->cache->hasEntry($className)) {
52+
$isTestTagOnClass = $this->cache->getEntry($className);
53+
} else {
54+
$isTestTagOnClass = count($nativeReflection->getAttributes(TestTag::class)) > 0;
55+
$this->cache->addEntry($className, $isTestTagOnClass);
56+
}
57+
4958
if ($this->cache->hasEntry($fullMethodName)) {
50-
$isTestTag = $this->cache->getEntry($fullMethodName);
59+
$isTestTagOnMethod = $this->cache->getEntry($fullMethodName);
5160
} else {
5261
if ($nativeReflection->hasMethod($methodName)) {
5362
$methodReflection = $nativeReflection->getMethod($methodName);
54-
$isTestTag = count($methodReflection->getAttributes(TestTag::class)) > 0;
63+
$isTestTagOnMethod = count($methodReflection->getAttributes(TestTag::class)) > 0;
5564
} else {
56-
$isTestTag = false;
65+
$isTestTagOnMethod = false;
5766
}
58-
$this->cache->addEntry($fullMethodName, $isTestTag);
67+
$this->cache->addEntry($fullMethodName, $isTestTagOnMethod);
68+
}
69+
70+
$hasTestTag = $isTestTagOnClass || $isTestTagOnMethod;
71+
72+
if (!$hasTestTag) {
73+
return null;
5974
}
6075

61-
if (!$isTestTag) {
76+
if ($isTestTagOnClass && ($className === $callingClass)) {
6277
return null;
6378
}
6479

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DaveLiddament\PhpstanPhpLanguageExtensions\Tests\Rules;
6+
7+
use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig;
8+
use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagNewCallRule;
9+
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;
10+
use DaveLiddament\PhpstanRuleTestHelper\ErrorMessageFormatter;
11+
use PHPStan\Rules\Rule;
12+
13+
/** @extends AbstractRuleTestCase<TestTagNewCallRule> */
14+
class TestTagClassOnConstructorIngoredOnTestClassTest extends AbstractRuleTestCase
15+
{
16+
protected function getRule(): Rule
17+
{
18+
return new TestTagNewCallRule(
19+
$this->createReflectionProvider(),
20+
new TestConfig(TestConfig::CLASS_NAME),
21+
);
22+
}
23+
24+
public function testMethodCall(): void
25+
{
26+
$this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnConstructorIgnoredInTestClass.php');
27+
}
28+
29+
protected function getErrorFormatter(): ErrorMessageFormatter|string
30+
{
31+
return 'TestTagClassOnConstructorIgnoredOnTestClass\Person::__construct is a test tag and can only be called from test code';
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DaveLiddament\PhpstanPhpLanguageExtensions\Tests\Rules;
6+
7+
use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig;
8+
use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagNewCallRule;
9+
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;
10+
use DaveLiddament\PhpstanRuleTestHelper\ErrorMessageFormatter;
11+
use PHPStan\Rules\Rule;
12+
13+
/** @extends AbstractRuleTestCase<TestTagNewCallRule> */
14+
class TestTagClassOnConstructorTest extends AbstractRuleTestCase
15+
{
16+
protected function getRule(): Rule
17+
{
18+
return new TestTagNewCallRule(
19+
$this->createReflectionProvider(),
20+
new TestConfig(TestConfig::CLASS_NAME),
21+
);
22+
}
23+
24+
public function testMethodCall(): void
25+
{
26+
$this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnConstructor.php');
27+
}
28+
29+
protected function getErrorFormatter(): ErrorMessageFormatter|string
30+
{
31+
return 'TestTagClassOnConstructor\Person::__construct is a test tag and can only be called from test code';
32+
}
33+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DaveLiddament\PhpstanPhpLanguageExtensions\Tests\Rules;
6+
7+
use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig;
8+
use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagMethodCallRule;
9+
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;
10+
use PHPStan\Rules\Rule;
11+
12+
/** @extends AbstractRuleTestCase<TestTagMethodCallRule> */
13+
class TestTagClassOnMethodIgnoredOnTestClassTest extends AbstractRuleTestCase
14+
{
15+
protected function getRule(): Rule
16+
{
17+
return new TestTagMethodCallRule(
18+
$this->createReflectionProvider(),
19+
new TestConfig(TestConfig::CLASS_NAME),
20+
);
21+
}
22+
23+
public function testMethodCall(): void
24+
{
25+
$this->assertIssuesReported(
26+
__DIR__.'/data/testTag/testTagClassOnMethodIgnoredInTestClass.php',
27+
);
28+
}
29+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DaveLiddament\PhpstanPhpLanguageExtensions\Tests\Rules;
6+
7+
use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig;
8+
use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagMethodCallRule;
9+
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;
10+
use DaveLiddament\PhpstanRuleTestHelper\ErrorMessageFormatter;
11+
use PHPStan\Rules\Rule;
12+
13+
/** @extends AbstractRuleTestCase<TestTagMethodCallRule> */
14+
class TestTagClassOnMethodTest extends AbstractRuleTestCase
15+
{
16+
protected function getRule(): Rule
17+
{
18+
return new TestTagMethodCallRule(
19+
$this->createReflectionProvider(),
20+
new TestConfig(TestConfig::CLASS_NAME),
21+
);
22+
}
23+
24+
public function testMethodCall(): void
25+
{
26+
$this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnMethod.php');
27+
}
28+
29+
protected function getErrorFormatter(): ErrorMessageFormatter|string
30+
{
31+
return 'TestTagClassOnMethod\Person::updateName is a test tag and can only be called from test code';
32+
}
33+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DaveLiddament\PhpstanPhpLanguageExtensions\Tests\Rules;
6+
7+
use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig;
8+
use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagStaticCallRule;
9+
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;
10+
use PHPStan\Rules\Rule;
11+
12+
/** @extends AbstractRuleTestCase<TestTagStaticCallRule> */
13+
class TestTagClassOnStaticIgnoredOnTestClassTest extends AbstractRuleTestCase
14+
{
15+
protected function getRule(): Rule
16+
{
17+
return new TestTagStaticCallRule(
18+
$this->createReflectionProvider(),
19+
new TestConfig(TestConfig::CLASS_NAME),
20+
);
21+
}
22+
23+
public function testMethodCall(): void
24+
{
25+
$this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnStaticMethodIgnoredInTestClass.php');
26+
}
27+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DaveLiddament\PhpstanPhpLanguageExtensions\Tests\Rules;
6+
7+
use DaveLiddament\PhpstanPhpLanguageExtensions\Config\TestConfig;
8+
use DaveLiddament\PhpstanPhpLanguageExtensions\Rules\TestTagStaticCallRule;
9+
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;
10+
use PHPStan\Rules\Rule;
11+
12+
/** @extends AbstractRuleTestCase<TestTagStaticCallRule> */
13+
final class TestTagClassOnStaticTest extends AbstractRuleTestCase
14+
{
15+
protected function getRule(): Rule
16+
{
17+
return new TestTagStaticCallRule(
18+
$this->createReflectionProvider(),
19+
new TestConfig(TestConfig::CLASS_NAME),
20+
);
21+
}
22+
23+
public function testMethodCall(): void
24+
{
25+
$this->assertIssuesReported(__DIR__.'/data/testTag/testTagClassOnStaticMethod.php');
26+
}
27+
28+
protected function getErrorFormatter(): string
29+
{
30+
return 'TestTagClassOnStaticMethod\Person::updateName is a test tag and can only be called from test code';
31+
}
32+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace TestTagClassOnConstructor;
4+
5+
use DaveLiddament\PhpLanguageExtensions\TestTag;
6+
7+
#[TestTag]
8+
class Person
9+
{
10+
public function __construct()
11+
{
12+
}
13+
14+
public static function create(): Person
15+
{
16+
return new Person(); // OK - whole class is marked with TestTag, so OK to call methods within it.
17+
}
18+
19+
public static function createSelf(): self
20+
{
21+
return new self(); // OK - whole class is marked with TestTag, so OK to call methods within it.
22+
}
23+
}
24+
25+
class AnotherClass
26+
{
27+
public function buildPerson(): Person
28+
{
29+
return new Person(); // ERROR
30+
}
31+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace TestTagClassOnConstructorIgnoredInTestClass;
4+
5+
use DaveLiddament\PhpLanguageExtensions\TestTag;
6+
7+
#[TestTag]
8+
class Person
9+
{
10+
public function __construct()
11+
{
12+
}
13+
}
14+
15+
class PersonTest
16+
{
17+
public function buildPerson(): Person
18+
{
19+
return new Person(); // OK TetTag called from a test class
20+
}
21+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TestTagClassOnMethod;
6+
7+
use DaveLiddament\PhpLanguageExtensions\TestTag;
8+
9+
#[TestTag]
10+
class Person
11+
{
12+
public function updateName(): void
13+
{
14+
}
15+
16+
public function update(): void
17+
{
18+
$this->updateName(); // OK - whole class is marked with TestTag, so OK to call methods within it.
19+
}
20+
}
21+
22+
class Updater
23+
{
24+
public function updater(Person $person): void
25+
{
26+
$person->updateName(); // ERROR
27+
}
28+
}
29+
30+
$person = new Person();
31+
$person->updateName(); // ERROR
32+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TestTagClassOnMethodIgnoredInTestClass;
6+
7+
use DaveLiddament\PhpLanguageExtensions\TestTag;
8+
9+
#[TestTag]
10+
class Person
11+
{
12+
public function updateName(): void
13+
{
14+
}
15+
}
16+
17+
class PersonTest
18+
{
19+
public function updater(Person $person): void
20+
{
21+
$person->updateName(); // OK - Called from Test class
22+
}
23+
}
24+
25+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace TestTagClassOnStaticMethod;
4+
5+
6+
use DaveLiddament\PhpLanguageExtensions\TestTag;
7+
8+
#[TestTag]
9+
class Person
10+
{
11+
public static function updateName(): void
12+
{
13+
}
14+
15+
public static function update(): void
16+
{
17+
Person::updateName(); // OK - whole class is marked with TestTag, so OK to call methods within it.
18+
}
19+
20+
public static function updateSelf(): void
21+
{
22+
self::updateName(); // OK - whole class is marked with TestTag, so OK to call methods within it.
23+
}
24+
}
25+
26+
class Updater
27+
{
28+
public function updater(): void
29+
{
30+
Person::updateName(); // ERROR
31+
}
32+
}
33+
34+
Person::updateName(); // ERROR

0 commit comments

Comments
 (0)