Skip to content

Commit

Permalink
Add ShouldBeNamed assertion
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosas authored Dec 23, 2023
1 parent d282233 commit 93e2164
Show file tree
Hide file tree
Showing 27 changed files with 248 additions and 49 deletions.
4 changes: 4 additions & 0 deletions ci/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ parameters:
paths:
- ../src
- ../tests/unit/rules
ignoreErrors:
-
message: "#no value type specified in iterable type array\\.$#"
path: *
6 changes: 6 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ services:

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

# ShouldBeNamed rules
-
class: PHPat\Rule\Assertion\Declaration\ShouldBeNamed\ClassnameRule
tags:
- phpstan.rules.rule

# ShouldBeReadonly rules
-
class: PHPat\Rule\Assertion\Declaration\ShouldBeReadonly\IsReadonlyRule
Expand Down
20 changes: 13 additions & 7 deletions src/Rule/Assertion/Declaration/DeclarationAssertion.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

abstract class DeclarationAssertion implements Assertion
{
/** @var array<array{string, SelectorInterface, array<SelectorInterface>, array<string>}> */
/** @var array<array{string, SelectorInterface, array<SelectorInterface>, array<string>, array<string, mixed>}> */
protected array $statements;
protected Configuration $configuration;
protected ReflectionProvider $reflectionProvider;
Expand Down Expand Up @@ -47,30 +47,36 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

$meetsDeclaration = $this->meetsDeclaration($node, $scope);
$meetsDeclaration = $this->meetsDeclaration($node, $scope, $this->getParams());

return $this->validateGetErrors($scope, $meetsDeclaration);
}

// TODO: This is a temporary hack, the 'statement' concept needs to be reworked
public function getParams(): array
{
return $this->statements[0][4] ?? [];
}

public function prepareMessage(string $ruleName, string $message): string
{
return $this->configuration->showRuleNames()
? sprintf('%s: %s', $ruleName, $message)
: $message;
}

abstract protected function meetsDeclaration(Node $node, Scope $scope): bool;
abstract protected function meetsDeclaration(Node $node, Scope $scope, array $params = []): bool;

/**
* @param class-string $subject
*/
abstract protected function getMessage(string $ruleName, string $subject): string;
abstract protected function getMessage(string $ruleName, string $subject, array $params = []): string;

/**
* @param array<string> $tips
* @return array<RuleError>
*/
abstract protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array;
abstract protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array;

protected function ruleApplies(Scope $scope): bool
{
Expand All @@ -93,7 +99,7 @@ protected function validateGetErrors(Scope $scope, bool $meetsDeclaration): arra
throw new ShouldNotHappenException();
}

foreach ($this->statements as [$ruleName, $selector, $subjectExcludes, $tips]) {
foreach ($this->statements as [$ruleName, $selector, $subjectExcludes, $tips, $params]) {
if ($subject->isBuiltin() || !$selector->matches($subject)) {
continue;
}
Expand All @@ -103,7 +109,7 @@ protected function validateGetErrors(Scope $scope, bool $meetsDeclaration): arra
}
}

array_push($errors, ...$this->applyValidation($ruleName, $subject, $meetsDeclaration, $tips));
array_push($errors, ...$this->applyValidation($ruleName, $subject, $meetsDeclaration, $tips, $params));
}

return $errors;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public function __construct(
);
}

protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array
{
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips);
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips, $params);
}

protected function getMessage(string $ruleName, string $subject): string
protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
return $this->prepareMessage(
$ruleName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public function __construct(
* @param array<string> $tips
* @return array<RuleError>
*/
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array
{
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips);
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips, $params);
}

protected function getMessage(string $ruleName, string $subject): string
protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
return $this->prepareMessage(
$ruleName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public function __construct(
* @param array<string> $tips
* @return array<RuleError>
*/
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array
{
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips);
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips, $params);
}

protected function getMessage(string $ruleName, string $subject): string
protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
return $this->prepareMessage(
$ruleName,
Expand Down
15 changes: 15 additions & 0 deletions src/Rule/Assertion/Declaration/ShouldBeNamed/ClassnameRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);

namespace PHPat\Rule\Assertion\Declaration\ShouldBeNamed;

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

/**
* @implements Rule<InClassNode>
*/
final class ClassnameRule extends ShouldBeNamed implements Rule
{
use ClassnameExtractor;
}
45 changes: 45 additions & 0 deletions src/Rule/Assertion/Declaration/ShouldBeNamed/ShouldBeNamed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types=1);

namespace PHPat\Rule\Assertion\Declaration\ShouldBeNamed;

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 ShouldBeNamed 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 $params = []): array
{
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips, $params);
}

protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
$message = $params['isRegex'] === true
? sprintf('%s should be named matching the regex %s', $subject, $params['classname'])
: sprintf('%s should be named %s', $subject, $params['classname']);

return $this->prepareMessage($ruleName, $message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public function __construct(
* @param array<string> $tips
* @return array<RuleError>
*/
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array
{
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips);
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips, $params);
}

protected function getMessage(string $ruleName, string $subject): string
protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
return $this->prepareMessage(
$ruleName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public function __construct(
);
}

protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array
{
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips);
return $this->applyShould($ruleName, $subject, $meetsDeclaration, $tips, $params);
}

protected function getMessage(string $ruleName, string $subject): string
protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
return $this->prepareMessage($ruleName, sprintf('%s should have only one public method', $subject));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public function __construct(
);
}

protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array
{
return $this->applyShouldNot($ruleName, $subject, $meetsDeclaration, $tips);
return $this->applyShouldNot($ruleName, $subject, $meetsDeclaration, $tips, $params);
}

protected function getMessage(string $ruleName, string $subject): string
protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
return $this->prepareMessage(
$ruleName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public function __construct(
);
}

protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array
{
return $this->applyShouldNot($ruleName, $subject, $meetsDeclaration, $tips);
return $this->applyShouldNot($ruleName, $subject, $meetsDeclaration, $tips, $params);
}

protected function getMessage(string $ruleName, string $subject): string
protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
return $this->prepareMessage(
$ruleName,
Expand Down
10 changes: 6 additions & 4 deletions src/Rule/Assertion/Declaration/ValidationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ trait ValidationTrait
{
/**
* @param array<string> $tips
* @param array<string, mixed> $params
* @return array<RuleError>
* @throws ShouldNotHappenException
*/
protected function applyShould(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyShould(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params): array
{
$errors = [];

if (!$meetsDeclaration) {
$ruleError = RuleErrorBuilder::message(
$this->getMessage($ruleName, $subject->getName())
$this->getMessage($ruleName, $subject->getName(), $params)
);

foreach ($tips as $tip) {
Expand All @@ -35,16 +36,17 @@ protected function applyShould(string $ruleName, ClassReflection $subject, bool

/**
* @param array<string> $tips
* @param array<string, mixed> $params
* @return array<RuleError>
* @throws ShouldNotHappenException
*/
protected function applyShouldNot(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips): array
protected function applyShouldNot(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params): array
{
$errors = [];

if ($meetsDeclaration) {
$ruleError = RuleErrorBuilder::message(
$this->getMessage($ruleName, $subject->getName())
$this->getMessage($ruleName, $subject->getName(), $params)
);

foreach ($tips as $tip) {
Expand Down
2 changes: 1 addition & 1 deletion src/Rule/Extractor/Declaration/AbstractExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function getNodeType(): string
/**
* @param InClassNode $node
*/
protected function meetsDeclaration(Node $node, Scope $scope): bool
protected function meetsDeclaration(Node $node, Scope $scope, array $params = []): bool
{
return $node->getClassReflection()->isAbstract();
}
Expand Down
36 changes: 36 additions & 0 deletions src/Rule/Extractor/Declaration/ClassnameExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types=1);

namespace PHPat\Rule\Extractor\Declaration;

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

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

/**
* @param InClassNode $node
*/
protected function meetsDeclaration(Node $node, Scope $scope, array $params = []): bool
{
if (!isset($params['isRegex'], $params['classname'])) {
return false;
}

$pos = mb_strrpos($node->getClassReflection()->getName(), '\\');
$classname = $pos === false
? $node->getClassReflection()->getName()
: mb_substr($node->getClassReflection()->getName(), $pos + 1);

if ($params['isRegex'] === true) {
return preg_match($params['classname'], $classname) === 1;
}

return $classname === $params['classname'];
}
}
2 changes: 1 addition & 1 deletion src/Rule/Extractor/Declaration/FinalExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function getNodeType(): string
/**
* @param InClassNode $node
*/
protected function meetsDeclaration(Node $node, Scope $scope): bool
protected function meetsDeclaration(Node $node, Scope $scope, array $params = []): bool
{
return $node->getClassReflection()->isFinal();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Rule/Extractor/Declaration/InterfaceExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function getNodeType(): string
/**
* @param InClassNode $node
*/
protected function meetsDeclaration(Node $node, Scope $scope): bool
protected function meetsDeclaration(Node $node, Scope $scope, array $params = []): bool
{
return $node->getClassReflection()->isInterface();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function getNodeType(): string
/**
* @param InClassNode $node
*/
protected function meetsDeclaration(Node $node, Scope $scope): bool
protected function meetsDeclaration(Node $node, Scope $scope, array $params = []): bool
{
$reflectionClass = $node->getClassReflection()->getNativeReflection();
$methods = $reflectionClass->getMethods(1);
Expand Down
2 changes: 1 addition & 1 deletion src/Rule/Extractor/Declaration/ReadonlyExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function getNodeType(): string
/**
* @param InClassNode $node
*/
protected function meetsDeclaration(Node $node, Scope $scope): bool
protected function meetsDeclaration(Node $node, Scope $scope, array $params = []): bool
{
return $node->getClassReflection()->isReadOnly();
}
Expand Down
Loading

0 comments on commit 93e2164

Please sign in to comment.