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

Merge master into 0.11 #271

Closed
wants to merge 12 commits into from
51 changes: 9 additions & 42 deletions .github/workflows/integrate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ jobs:
strategy:
matrix:
php-version:
- "7.4"
- "8.0"
- "8.1"
- "8.3"
dependencies:
- "highest"
steps:
Expand Down Expand Up @@ -51,50 +49,17 @@ jobs:
- name: "Run friendsofphp/php-cs-fixer"
run: "vendor/bin/php-cs-fixer check --config ./ci/php-cs-fixer.php"

architecture-analysis:
name: "Architecture analysis"
runs-on: "ubuntu-latest"
strategy:
matrix:
php-version:
- "7.4"
- "8.0"
- "8.1"
dependencies:
- "highest"
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
- name: "Install PHP with extensions"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
extensions: "${{ env.PHP_EXTENSIONS }}"
php-version: "${{ matrix.php-version }}"
- name: "Determine composer cache directory"
uses: "./.github/actions/composer/composer/determine-cache-directory"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v2"
with:
path: "${{ env.COMPOSER_CACHE_DIR }}"
key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-"
- name: "Install ${{ matrix.dependencies }} dependencies with composer"
uses: "./.github/actions/composer/composer/install"
with:
dependencies: "${{ matrix.dependencies }}"
- name: "Run phpstan+phpat for architecture tests"
run: "vendor/bin/phpstan analyse -c ci/phpstan-phpat.neon"

static-code-analysis:
name: "Static Code Analysis"
name: "Static Code and Architecture analysis"
runs-on: "ubuntu-latest"
strategy:
matrix:
php-version:
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
dependencies:
- "highest"
steps:
Expand All @@ -118,9 +83,9 @@ jobs:
uses: "./.github/actions/composer/composer/install"
with:
dependencies: "${{ matrix.dependencies }}"
- name: "Run phpstan/phpstan"
run: "vendor/bin/phpstan analyse -c ci/phpstan.neon"
- name: "Run vimeo/psalm"
- name: "Run phpstan+phpat"
run: "vendor/bin/phpstan analyse -c ci/phpstan-phpat.neon"
- name: "Run psalm"
run: "vendor/bin/psalm -c ci/psalm.xml"

tests:
Expand All @@ -132,6 +97,8 @@ jobs:
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
dependencies:
- "highest"
steps:
Expand Down
7 changes: 6 additions & 1 deletion ci/phpstan-phpat.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ includes:
- ../extension.neon

parameters:
level: 0
level: 8
paths:
- ../src
- ../tests/unit/rules
- ../tests/architecture
ignoreErrors:
-
message: "#no value type specified in iterable type array\\.$#"
path: *
phpat:
ignore_built_in_classes: false
show_rule_names: true
Expand Down
9 changes: 0 additions & 9 deletions ci/phpstan.neon

This file was deleted.

11 changes: 10 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.

## 0.10.15
* Add `shouldNotBeReadonly` assertion.
* Fix return types when building declaration rules with tips.

## 0.10.14
* Add `shouldInclude()` and `shouldNotInclude` assertions.
* Detect catch blocks in dependency assertions.
* Fix shouldBeNamed assertion not functioning on second run.

## 0.10.13
* Fix namespace selector matching similar namespaces
* Fix namespace selector matching similar namespaces.

## 0.10.12
* Add `shouldBeNamed()` assertion.
Expand Down
1 change: 0 additions & 1 deletion docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ There are several ways to help out:
```bash
composer validate --strict
vendor/bin/php-cs-fixer fix --config ./ci/php-cs-fixer.php
vendor/bin/phpstan analyse -c ci/phpstan.neon
vendor/bin/phpstan analyse -c ci/phpstan-phpat.neon
vendor/bin/psalm -c ci/psalm.xml
vendor/bin/phpunit tests/unit/
Expand Down
7 changes: 7 additions & 0 deletions docs/documentation/assertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ It asserts that the selected classes are **interfaces**.
## shouldBeReadonly()
It asserts that the selected classes are declared as **readonly**.

Also available: `shouldNotBeReadonly()`

## shouldHaveOnlyOnePublicMethod()
It asserts that the selected classes only have **one public method** (besides constructor).

Expand All @@ -34,6 +36,11 @@ It asserts that the selected classes **implement** the target interfaces.

Also available: `shouldNotImplement()`

## shouldInclude()
It asserts that the selected classes **include** the target traits.

Also available: `shouldNotInclude()`

## shouldNotDependOn()
It asserts that the selected classes **do not depend** on the target classes.

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

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

# ShouldBeAbstract rules
-
class: PHPat\Rule\Assertion\Declaration\ShouldBeAbstract\AbstractRule
Expand Down Expand Up @@ -80,6 +86,12 @@ services:
tags:
- phpstan.rules.rule

# ShouldInclude rules
-
class: PHPat\Rule\Assertion\Relation\ShouldInclude\IncludedTraitsRule
tags:
- phpstan.rules.rule

# ShouldNotDepend rules
-
class: PHPat\Rule\Assertion\Relation\ShouldNotDepend\DirectInterfacesRule
Expand Down Expand Up @@ -145,6 +157,10 @@ services:
class: PHPat\Rule\Assertion\Relation\ShouldNotDepend\StaticMethodRule
tags:
- phpstan.rules.rule
-
class: PHPat\Rule\Assertion\Relation\ShouldNotDepend\CatchBlockRule
tags:
- phpstan.rules.rule

# CanOnlyDepend rules
-
Expand Down Expand Up @@ -211,6 +227,10 @@ services:
class: PHPat\Rule\Assertion\Relation\CanOnlyDepend\StaticMethodRule
tags:
- phpstan.rules.rule
-
class: PHPat\Rule\Assertion\Relation\CanOnlyDepend\CatchBlockRule
tags:
- phpstan.rules.rule

# ShouldNotConstruct rules
-
Expand All @@ -230,6 +250,12 @@ services:
tags:
- phpstan.rules.rule

# ShouldNotInclude rules
-
class: PHPat\Rule\Assertion\Relation\ShouldNotInclude\IncludedTraitsRule
tags:
- phpstan.rules.rule

# ShouldApplyAttribute rules
-
class: PHPat\Rule\Assertion\Relation\ShouldApplyAttribute\ClassAttributeRule
Expand Down
75 changes: 30 additions & 45 deletions src/Rule/Assertion/Declaration/DeclarationAssertion.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,41 @@ public function __construct(
*/
public function processNode(Node $node, Scope $scope): array
{
if (!$this->ruleApplies($scope)) {
$subject = $scope->getClassReflection();
if ($subject === null) {
return [];
}

$meetsDeclaration = $this->meetsDeclaration($node, $scope, $this->getParams());
$applicableStatements = array_filter(
$this->statements,
static function (array $statement) use ($subject): bool {
[$ruleName, $selector, $subjectExcludes, $tips, $params] = $statement;

return $this->validateGetErrors($scope, $meetsDeclaration);
}
if ($subject->isBuiltin() || !$selector->matches($subject)) {
return false;
}
foreach ($subjectExcludes as $exclude) {
if ($exclude->matches($subject)) {
return false;
}
}

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

return array_reduce(
$applicableStatements,
function (array $errors, array $statement) use ($node, $scope, $subject): array {
[$ruleName, $selector, $subjectExcludes, $tips, $params] = $statement;

$meetsDeclaration = $this->meetsDeclaration($node, $scope, $statement[4]);
array_push($errors, ...$this->applyValidation($ruleName, $subject, $meetsDeclaration, $tips, $params));

return $errors;
},
[]
);
}

public function prepareMessage(string $ruleName, string $message): string
Expand All @@ -77,41 +99,4 @@ abstract protected function getMessage(string $ruleName, string $subject, array
* @return array<RuleError>
*/
abstract protected function applyValidation(string $ruleName, ClassReflection $subject, bool $meetsDeclaration, array $tips, array $params = []): array;

protected function ruleApplies(Scope $scope): bool
{
if (!$scope->isInClass()) {
return false;
}

return $scope->getClassReflection() !== null;
}

/**
* @return array<RuleError>
* @throws ShouldNotHappenException
*/
protected function validateGetErrors(Scope $scope, bool $meetsDeclaration): array
{
$errors = [];
$subject = $scope->getClassReflection();
if ($subject === null) {
throw new ShouldNotHappenException();
}

foreach ($this->statements as [$ruleName, $selector, $subjectExcludes, $tips, $params]) {
if ($subject->isBuiltin() || !$selector->matches($subject)) {
continue;
}
foreach ($subjectExcludes as $exclude) {
if ($exclude->matches($subject)) {
continue 2;
}
}

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

return $errors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);

namespace PHPat\Rule\Assertion\Declaration\ShouldNotBeReadonly;

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

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

namespace PHPat\Rule\Assertion\Declaration\ShouldNotBeReadonly;

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 ShouldNotBeReadonly extends DeclarationAssertion
{
use ValidationTrait;

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

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

protected function getMessage(string $ruleName, string $subject, array $params = []): string
{
return $this->prepareMessage(
$ruleName,
sprintf('%s should not be readonly', $subject)
);
}
}
15 changes: 15 additions & 0 deletions src/Rule/Assertion/Relation/CanOnlyDepend/CatchBlockRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);

namespace PHPat\Rule\Assertion\Relation\CanOnlyDepend;

use PHPat\Rule\Extractor\Relation\CatchBlockExtractor;
use PhpParser\Node;
use PHPStan\Rules\Rule;

/**
* @implements Rule<Node\Stmt\Catch_>
*/
final class CatchBlockRule extends CanOnlyDepend implements Rule
{
use CatchBlockExtractor;
}
Loading
Loading