Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/carlosas/phpat
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosas committed Aug 14, 2023
2 parents 21dba7b + 49865ea commit 75d6e45
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 1 deletion.
4 changes: 4 additions & 0 deletions ci/psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,9 @@
<DeprecatedMethod>
<errorLevel type="suppress"><file name="../src/Rule/Extractor/Relation/DocComment/ClassScope/PropertyTagExtractor.php" /></errorLevel>
</DeprecatedMethod>

<ArgumentTypeCoercion>
<errorLevel type="suppress"><directory name="../src/Selector/" /></errorLevel>
</ArgumentTypeCoercion>
</issueHandlers>
</psalm>
6 changes: 6 additions & 0 deletions doc/SELECTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ Select all abstract classes.

### `Selector::final()`
Select all final classes.

### `Selector::readonly()`
Select all readonly classes.

### `Selector::attribute()`
Select all attribute classes.
8 changes: 7 additions & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ services:
show_rule_names: %phpat.show_rule_names%,
)

# # # # # DECLARATION RULES # # # # #
# # # # # DECLARATION RULES # # # # #

# ShouldBeReadonly rules
-
class: PHPat\Rule\Assertion\Declaration\ShouldBeReadonly\IsReadonlyRule
tags:
- phpstan.rules.rule

# ShouldBeAbstract rules
-
Expand Down
17 changes: 17 additions & 0 deletions src/Rule/Assertion/Declaration/ShouldBeReadonly/IsReadonlyRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace PHPat\Rule\Assertion\Declaration\ShouldBeReadonly;

use PHPat\Rule\Extractor\Declaration\ReadonlyExtractor;
use PHPStan\Node\InClassNode;
use PHPStan\Rules\Rule;

/**
* @implements Rule<InClassNode>
*/
class IsReadonlyRule extends ShouldBeReadonly implements Rule
{
use ReadonlyExtractor;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace PHPat\Rule\Assertion\Declaration\ShouldBeReadonly;

use PHPat\Configuration;
use PHPat\Rule\Assertion\Declaration\DeclarationAssertion;
use PHPat\Rule\Assertion\Declaration\ValidationTrait;
use PHPat\Statement\Builder\StatementBuilderFactory;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\RuleError;
use PHPStan\Type\FileTypeMapper;

abstract class ShouldBeReadonly extends DeclarationAssertion
{
use ValidationTrait;

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

/**
* @param string $ruleName
* @param ClassReflection $subject
* @param bool $meetsDeclaration
* @param string[] $tips
* @return array<RuleError>
*/
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
{
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips);
}

protected function getMessage(string $ruleName, string $subject): string
{
return $this->prepareMessage(
$ruleName,
sprintf('%s should be readonly', $subject)
);
}
}
25 changes: 25 additions & 0 deletions src/Rule/Extractor/Declaration/ReadonlyExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace PHPat\Rule\Extractor\Declaration;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;

trait ReadonlyExtractor
{
public function getNodeType(): string
{
return InClassNode::class;
}

/**
* @param InClassNode $node
*/
protected function meetsDeclaration(Node $node, Scope $scope): bool
{
return $node->getClassReflection()->isReadOnly();
}
}
20 changes: 20 additions & 0 deletions src/Selector/IsReadonly.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace PHPat\Selector;

use PHPStan\Reflection\ClassReflection;

class IsReadonly implements SelectorInterface
{
public function getName(): string
{
return '-all readonly classes-';
}

public function matches(ClassReflection $classReflection): bool
{
return $classReflection->isReadOnly();
}
}
17 changes: 17 additions & 0 deletions src/Selector/SelectorPrimitive.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public static function final(): IsFinal
return new IsFinal();
}

public static function readonly(): IsReadonly
{
return new IsReadonly();
}

/**
* @deprecated use Selector::NOT(Selector::final())
*/
Expand All @@ -52,21 +57,33 @@ public static function attribute(): IsAttribute
return new IsAttribute();
}

/**
* @param class-string|non-empty-string $namespace
*/
public static function namespace(string $namespace, bool $regex = false): ClassNamespace
{
return new ClassNamespace($namespace, $regex);
}

/**
* @param class-string|non-empty-string $fqcn
*/
public static function classname(string $fqcn, bool $regex = false): Classname
{
return new Classname($fqcn, $regex);
}

/**
* @param class-string|non-empty-string $fqcn
*/
public static function implements(string $fqcn, bool $regex = false): ClassImplements
{
return new ClassImplements($fqcn, $regex);
}

/**
* @param class-string|non-empty-string $fqcn
*/
public static function extends(string $fqcn, bool $regex = false): ClassExtends
{
return new ClassExtends($fqcn, $regex);
Expand Down
8 changes: 8 additions & 0 deletions src/Test/Builder/AssertionStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PHPat\Rule\Assertion\Declaration\ShouldBeAbstract\ShouldBeAbstract;
use PHPat\Rule\Assertion\Declaration\ShouldBeFinal\ShouldBeFinal;
use PHPat\Rule\Assertion\Declaration\ShouldBeReadonly\ShouldBeReadonly;
use PHPat\Rule\Assertion\Declaration\ShouldNotBeAbstract\ShouldNotBeAbstract;
use PHPat\Rule\Assertion\Declaration\ShouldNotBeFinal\ShouldNotBeFinal;
use PHPat\Rule\Assertion\Relation\CanOnlyDepend\CanOnlyDepend;
Expand All @@ -32,6 +33,13 @@ public function shouldNotBeAbstract(): Rule
return new BuildStep($this->rule);
}

public function shouldBeReadonly(): Rule
{
$this->rule->assertion = ShouldBeReadonly::class;

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

public function shouldBeFinal(): Rule
{
$this->rule->assertion = ShouldBeFinal::class;
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/rules/ShouldBeReadonly/ReadonlyClassTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Tests\PHPat\unit\rules\ShouldBeReadonly;

use PHPat\Configuration;
use PHPat\Rule\Assertion\Declaration\ShouldBeReadonly\IsReadonlyRule;
use PHPat\Rule\Assertion\Declaration\ShouldBeReadonly\ShouldBeReadonly;
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\unit\FakeTestParser;

/**
* @extends RuleTestCase<IsReadonlyRule>
*/
class ReadonlyClassTest extends RuleTestCase
{
public const RULE_NAME = 'test_FixtureClassShouldBeReadonly';
public function testRule(): void
{
$this->analyse(['tests/fixtures/FixtureClass.php'], [
[sprintf('%s should be readonly', FixtureClass::class), 31],
]);
}

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

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

0 comments on commit 75d6e45

Please sign in to comment.