Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Add ShouldBeNamed assertion #251

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
32 changes: 32 additions & 0 deletions src/Rule/Extractor/Declaration/ClassnameExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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
{
$pos = mb_strrpos($node->getClassReflection()->getName(), '\\');
$classname = $pos === false
? $node->getClassReflection()->getName()
: mb_substr($node->getClassReflection()->getName(), $pos + 1);

if ($params['isRegex'] === true) {

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (7.4, highest)

Undefined index: isRegex

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.0, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"

Check failure on line 26 in src/Rule/Extractor/Declaration/ClassnameExtractor.php

View workflow job for this annotation

GitHub Actions / Architecture analysis (8.1, highest)

Undefined array key "isRegex"
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
Loading