Skip to content

Commit

Permalink
Add ShouldHaveAttribute assertion
Browse files Browse the repository at this point in the history
  • Loading branch information
gertjuhh committed Nov 6, 2023
1 parent 80004c0 commit f9b2712
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 2 deletions.
3 changes: 3 additions & 0 deletions docs/documentation/assertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ It asserts that the selected classes **do not depend** on the target classes.
## shouldNotConstruct()
It asserts that the selected classes **do not use the constructor** of the target classes.

## shouldHaveAttribute()
It asserts that the selected classes **apply** the target attributes.

## canOnlyDependOn()
It asserts that the selected classes **do not depend** on anything else than the target classes.

Expand Down
6 changes: 6 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ services:
tags:
- phpstan.rules.rule

# ShouldHaveAttribute rules
-
class: PHPat\Rule\Assertion\Relation\ShouldHaveAttribute\ClassAttributeRule
tags:
- phpstan.rules.rule

parametersSchema:
phpat: structure([
ignore_doc_comments: bool(),
Expand Down
5 changes: 3 additions & 2 deletions src/Rule/Assertion/Relation/RelationAssertion.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPat\Configuration;
use PHPat\Rule\Assertion\Assertion;
use PHPat\Rule\Assertion\Relation\ShouldExtend\ShouldExtend;
use PHPat\Rule\Assertion\Relation\ShouldHaveAttribute\ShouldHaveAttribute;
use PHPat\Rule\Assertion\Relation\ShouldImplement\ShouldImplement;
use PHPat\Selector\Classname;
use PHPat\Selector\SelectorInterface;
Expand Down Expand Up @@ -97,8 +98,8 @@ protected function ruleApplies(Scope $scope, array $nodes): bool
return false;
}

// Can not skip if the rule is a ShouldExtend or ShouldImplement rule
if (is_a($this, ShouldExtend::class) || is_a($this, ShouldImplement::class)) {
// Can not skip if the rule is a ShouldExtend, ShouldImplement or ShouldHaveAttribute rule
if (is_a($this, ShouldExtend::class) || is_a($this, ShouldImplement::class) || is_a($this, ShouldHaveAttribute::class)) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);

namespace PHPat\Rule\Assertion\Relation\ShouldHaveAttribute;

use PHPat\Rule\Extractor\Relation\ClassAttributeExtractor;
use PHPStan\Node\InClassNode;
use PHPStan\Rules\Rule;

/**
* @implements Rule<InClassNode>
*/
final class ClassAttributeRule extends ShouldHaveAttribute implements Rule
{
use ClassAttributeExtractor;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php declare(strict_types=1);

namespace PHPat\Rule\Assertion\Relation\ShouldHaveAttribute;

use PHPat\Configuration;
use PHPat\Rule\Assertion\Relation\RelationAssertion;
use PHPat\Rule\Assertion\Relation\ValidationTrait;
use PHPat\Statement\Builder\StatementBuilderFactory;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\FileTypeMapper;

abstract class ShouldHaveAttribute extends RelationAssertion
{
use ValidationTrait;

public function __construct(
StatementBuilderFactory $statementBuilderFactory,
Configuration $configuration,
ReflectionProvider $reflectionProvider,
FileTypeMapper $fileTypeMapper
) {
parent::__construct(
__CLASS__,
$statementBuilderFactory,
$configuration,
$reflectionProvider,
$fileTypeMapper
);
}

protected function applyValidation(
string $ruleName,
ClassReflection $subject,
array $targets,
array $targetExcludes,
array $nodes,
array $tips
): array {
return $this->applyShould($ruleName, $subject, $targets, $targetExcludes, $nodes, $tips);
}

protected function getMessage(string $ruleName, string $subject, string $target): string
{
return $this->prepareMessage(
$ruleName,
sprintf('%s should have attribute %s', $subject, $target),
);
}
}
8 changes: 8 additions & 0 deletions src/Test/Builder/AssertionStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PHPat\Rule\Assertion\Declaration\ShouldNotBeFinal\ShouldNotBeFinal;
use PHPat\Rule\Assertion\Relation\CanOnlyDepend\CanOnlyDepend;
use PHPat\Rule\Assertion\Relation\ShouldExtend\ShouldExtend;
use PHPat\Rule\Assertion\Relation\ShouldHaveAttribute\ShouldHaveAttribute;
use PHPat\Rule\Assertion\Relation\ShouldImplement\ShouldImplement;
use PHPat\Rule\Assertion\Relation\ShouldNotConstruct\ShouldNotConstruct;
use PHPat\Rule\Assertion\Relation\ShouldNotDepend\ShouldNotDepend;
Expand Down Expand Up @@ -108,4 +109,11 @@ public function shouldHaveOnlyOnePublicMethod(): Rule

return new BuildStep($this->rule);
}

public function shouldHaveAttribute(): TargetStep
{
$this->rule->assertion = ShouldHaveAttribute::class;

return new TargetStep($this->rule);
}
}
6 changes: 6 additions & 0 deletions tests/fixtures/Simple/SimpleAttributeTwo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php declare(strict_types=1);

namespace Tests\PHPat\fixtures\Simple;

#[\Attribute(\Attribute::TARGET_CLASS)]
class SimpleAttributeTwo {}
49 changes: 49 additions & 0 deletions tests/unit/rules/ShouldHaveAttribute/ClassAttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types=1);

namespace Tests\PHPat\unit\rules\ShouldHaveAttribute;

use PHPat\Configuration;
use PHPat\Rule\Assertion\Relation\ShouldHaveAttribute\ClassAttributeRule;
use PHPat\Rule\Assertion\Relation\ShouldHaveAttribute\ShouldHaveAttribute;
use PHPat\Selector\Classname;
use PHPat\Statement\Builder\StatementBuilderFactory;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\FileTypeMapper;
use Tests\PHPat\fixtures\FixtureClass;
use Tests\PHPat\fixtures\Simple\SimpleAttributeTwo;
use Tests\PHPat\unit\FakeTestParser;

/**
* @extends RuleTestCase<ClassAttributeRule>
* @internal
* @coversNothing
*/
class ClassAttributeTest extends RuleTestCase
{
public const RULE_NAME = 'test_FixtureClassShouldHaveSimpleAttributeTwo';

public function testRule(): void
{
$this->analyse(['tests/fixtures/FixtureClass.php'], [
[sprintf('%s should have attribute %s', FixtureClass::class, SimpleAttributeTwo::class), 29],
]);
}

protected function getRule(): Rule
{
$testParser = FakeTestParser::create(
self::RULE_NAME,
ShouldHaveAttribute::class,
[new Classname(FixtureClass::class, false)],
[new Classname(SimpleAttributeTwo::class, false)]
);

return new ClassAttributeRule(
new StatementBuilderFactory($testParser),
new Configuration(false, true, false),
$this->createReflectionProvider(),
self::getContainer()->getByType(FileTypeMapper::class)
);
}
}
49 changes: 49 additions & 0 deletions tests/unit/rules/ShouldHaveAttribute/SimpleClassAttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types=1);

namespace Tests\PHPat\unit\rules\ShouldHaveAttribute;

use PHPat\Configuration;
use PHPat\Rule\Assertion\Relation\ShouldHaveAttribute\ClassAttributeRule;
use PHPat\Rule\Assertion\Relation\ShouldHaveAttribute\ShouldHaveAttribute;
use PHPat\Selector\Classname;
use PHPat\Statement\Builder\StatementBuilderFactory;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\FileTypeMapper;
use Tests\PHPat\fixtures\Simple\SimpleAttribute;
use Tests\PHPat\fixtures\Simple\SimpleClass;
use Tests\PHPat\unit\FakeTestParser;

/**
* @extends RuleTestCase<ClassAttributeRule>
* @internal
* @coversNothing
*/
class SimpleClassAttributeTest extends RuleTestCase
{
public const RULE_NAME = 'test_SimpleClassShouldHaveSimpleAttribute';

public function testRule(): void
{
$this->analyse(['tests/fixtures/Simple/SimpleClass.php'], [
[sprintf('%s should have attribute %s', SimpleClass::class, SimpleAttribute::class), 5],
]);
}

protected function getRule(): Rule
{
$testParser = FakeTestParser::create(
self::RULE_NAME,
ShouldHaveAttribute::class,
[new Classname(SimpleClass::class, false)],
[new Classname(SimpleAttribute::class, false)]
);

return new ClassAttributeRule(
new StatementBuilderFactory($testParser),
new Configuration(false, true, false),
$this->createReflectionProvider(),
self::getContainer()->getByType(FileTypeMapper::class)
);
}
}

0 comments on commit f9b2712

Please sign in to comment.