Skip to content

Commit

Permalink
feat: add ShouldHaveOnlyOnePublicMethod rule
Browse files Browse the repository at this point in the history
  • Loading branch information
rgomezcasas committed Oct 2, 2023
1 parent 8732c57 commit cf8fac1
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 0 deletions.
3 changes: 3 additions & 0 deletions docs/documentation/assertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ It asserts that the selected classes **do not use the constructor** of the targe
It asserts that the selected classes **does not depend** on anything else than the target classes.

This would be equivalent to `shouldNotDependOn()` with the negation of the target classes.

## shouldHaveOnlyOnePublicMethod()
It asserts that the selected classes **only have one public method**.
6 changes: 6 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ services:
tags:
- phpstan.rules.rule

# ShouldHaveOnlyOnePublicMethod rules
-
class: PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\HasOnlyOnePublicMethodRule
tags:
- phpstan.rules.rule

# # # # # RELATION RULES # # # # #

# ShouldImplement rules
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod;

use PHPat\Rule\Extractor\Declaration\OnePublicMethodExtractor;

class HasOnlyOnePublicMethodRule extends ShouldHaveOnlyOnePublicMethod

Check failure on line 7 in src/Rule/Assertion/Declaration/ShouldHaveOnlyOnePublicMethod/HasOnlyOnePublicMethodRule.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

test_non_abstract_classes_are_final: PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\HasOnlyOnePublicMethodRule should be final

Check failure on line 7 in src/Rule/Assertion/Declaration/ShouldHaveOnlyOnePublicMethod/HasOnlyOnePublicMethodRule.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

test_non_abstract_classes_are_final: PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\HasOnlyOnePublicMethodRule should be final
{
use OnePublicMethodExtractor;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod;

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\Type\FileTypeMapper;

abstract class ShouldHaveOnlyOnePublicMethod extends DeclarationAssertion
{
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, 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 have only one public method', $subject));
}
}
29 changes: 29 additions & 0 deletions src/Rule/Extractor/Declaration/OnePublicMethodExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php declare(strict_types=1);

namespace PHPat\Rule\Extractor\Declaration;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Reflection\MethodReflection;
use ReflectionClass;
use ReflectionMethod;

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

/**
* @param InClassNode $node
*/
protected function meetsDeclaration(Node $node, Scope $scope): bool
{
$reflectionClass = new ReflectionClass($node->getClassReflection()->getName());
$methods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);

return count($methods) === 1;
}
}
8 changes: 8 additions & 0 deletions src/Test/Builder/AssertionStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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\ShouldHaveOnlyOnePublicMethod\ShouldHaveOnlyOnePublicMethod;
use PHPat\Rule\Assertion\Declaration\ShouldNotBeAbstract\ShouldNotBeAbstract;
use PHPat\Rule\Assertion\Declaration\ShouldNotBeFinal\ShouldNotBeFinal;
use PHPat\Rule\Assertion\Relation\CanOnlyDepend\CanOnlyDepend;
Expand Down Expand Up @@ -100,4 +101,11 @@ public function shouldExtend(): TargetStep

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

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

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

namespace Tests\PHPat\fixtures\Special;

class ClassWithOnePublicMethod
{
public const CONSTANT = 'constant';
public string $property = 'property';

public function publicMethod(): bool {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Tests\PHPat\unit\rules\ShouldHaveOnlyOnePublicMethod;

use PHPat\Configuration;
use PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\HasOnlyOnePublicMethodRule;
use PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\ShouldHaveOnlyOnePublicMethod;
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<HasOnlyOnePublicMethodRule>
* @internal
* @coversNothing
*/
class ClassWithOnlyOnePublicMethodTest extends RuleTestCase
{
public const RULE_NAME = 'test_FixtureClassShouldHaveOnlyOnePublicMethod';

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

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Tests\PHPat\unit\rules\ShouldHaveOnlyOnePublicMethod;

use PHPat\Configuration;
use PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\HasOnlyOnePublicMethodRule;
use PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\ShouldHaveOnlyOnePublicMethod;
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\Special\ClassWithOnePublicMethod;
use Tests\PHPat\unit\FakeTestParser;

/**
* @extends RuleTestCase<HasOnlyOnePublicMethodRule>
* @internal
* @coversNothing
*/
class GoodImplementationClassWithOnlyOnePublicMethodTest extends RuleTestCase
{
public const RULE_NAME = 'test_FixtureClassShouldHaveOnlyOnePublicMethod';

public function testRule(): void
{
$this->analyse(['tests/fixtures/Special/ClassWithOnePublicMethod.php'], []);
}

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

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

}

0 comments on commit cf8fac1

Please sign in to comment.