Skip to content

Commit

Permalink
Token classifier & refactor filters (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
rustamwin authored Oct 26, 2023
1 parent 2db3db7 commit 76f5f2f
Show file tree
Hide file tree
Showing 28 changed files with 696 additions and 111 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
os: >-
['ubuntu-latest', 'windows-latest']
php: >-
['8.0', '8.1']
['8.1', '8.2']
2 changes: 1 addition & 1 deletion .github/workflows/composer-require-checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.0']
['8.1']
2 changes: 1 addition & 1 deletion .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.1']
['8.2']
secrets:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
2 changes: 1 addition & 1 deletion .github/workflows/rector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.0']
['8.2']
2 changes: 1 addition & 1 deletion .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.0', '8.1']
['8.1', '8.2']
1 change: 1 addition & 0 deletions .styleci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ finder:
- vendor
not-name:
- wrong_file.php
- namespace.php

enabled:
- alpha_ordered_traits
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": "^8.0",
"php": "^8.1",
"ext-tokenizer": "*",
"symfony/finder": "^5.4|^6.0"
},
"require-dev": {
Expand Down
82 changes: 41 additions & 41 deletions src/AbstractClassifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,24 @@

namespace Yiisoft\Classifier;

use ReflectionClass;
use Symfony\Component\Finder\Finder;
use Yiisoft\Classifier\Filter\FilterInterface;

/**
* Base implementation for {@see ClassifierInterface} with common filters.
*/
abstract class AbstractClassifier implements ClassifierInterface
{
/**
* @var string[]
* @var array<class-string|trait-string, ReflectionClass>
*/
protected array $interfaces = [];
/**
* @var string[]
*/
protected array $attributes = [];
protected static array $reflectionsCache = [];

/**
* @psalm-var class-string
* @var FilterInterface[]
*/
protected ?string $parentClass = null;
private array $filters = [];
/**
* @var string[]
*/
Expand All @@ -33,48 +32,25 @@ public function __construct(string $directory, string ...$directories)
$this->directories = [$directory, ...array_values($directories)];

Check warning on line 32 in src/AbstractClassifier.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "UnwrapArrayValues": --- Original +++ New @@ @@ protected array $directories; public function __construct(string $directory, string ...$directories) { - $this->directories = [$directory, ...array_values($directories)]; + $this->directories = [$directory, ...$directories]; } public function withFilter(FilterInterface ...$filter) : static {

Check warning on line 32 in src/AbstractClassifier.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "UnwrapArrayValues": --- Original +++ New @@ @@ protected array $directories; public function __construct(string $directory, string ...$directories) { - $this->directories = [$directory, ...array_values($directories)]; + $this->directories = [$directory, ...$directories]; } public function withFilter(FilterInterface ...$filter) : static {
}

/**
* @psalm-param class-string ...$interfaces
*/
public function withInterface(string ...$interfaces): self
{
$new = clone $this;
array_push($new->interfaces, ...array_values($interfaces));

return $new;
}

/**
* @psalm-param class-string $parentClass
*/
public function withParentClass(string $parentClass): self
{
$new = clone $this;
$new->parentClass = $parentClass;
return $new;
}

/**
* @psalm-param class-string ...$attributes
*/
public function withAttribute(string ...$attributes): self
public function withFilter(FilterInterface ...$filter): static
{
$new = clone $this;

Check warning on line 37 in src/AbstractClassifier.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "CloneRemoval": --- Original +++ New @@ @@ } public function withFilter(FilterInterface ...$filter) : static { - $new = clone $this; + $new = $this; array_push($new->filters, ...array_values($filter)); return $new; }

Check warning on line 37 in src/AbstractClassifier.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "CloneRemoval": --- Original +++ New @@ @@ } public function withFilter(FilterInterface ...$filter) : static { - $new = clone $this; + $new = $this; array_push($new->filters, ...array_values($filter)); return $new; }
array_push($new->attributes, ...array_values($attributes));
array_push($new->filters, ...array_values($filter));

Check warning on line 38 in src/AbstractClassifier.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "UnwrapArrayValues": --- Original +++ New @@ @@ public function withFilter(FilterInterface ...$filter) : static { $new = clone $this; - array_push($new->filters, ...array_values($filter)); + array_push($new->filters, ...$filter); return $new; } /**

Check warning on line 38 in src/AbstractClassifier.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "UnwrapArrayValues": --- Original +++ New @@ @@ public function withFilter(FilterInterface ...$filter) : static { $new = clone $this; - array_push($new->filters, ...array_values($filter)); + array_push($new->filters, ...$filter); return $new; } /**

return $new;
}

/**
* @psalm-return iterable<class-string>
* @return iterable<class-string>
*/
public function find(): iterable
{
if (empty($this->interfaces) && empty($this->attributes) && $this->parentClass === null) {
return [];
foreach ($this->getAvailableDeclarations() as $declaration) {
if ($this->skipDeclaration($declaration)) {
continue;
}
yield $declaration;
}

yield from $this->getAvailableClasses();
}

protected function getFiles(): Finder
Expand All @@ -87,7 +63,31 @@ protected function getFiles(): Finder
}

/**
* @return iterable<class-string>
* @param class-string|trait-string $declaration
*/
private function skipDeclaration(string $declaration): bool
{
try {
$reflectionClass = self::$reflectionsCache[$declaration] ??= new ReflectionClass($declaration);

Check warning on line 71 in src/AbstractClassifier.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "AssignCoalesce": --- Original +++ New @@ @@ private function skipDeclaration(string $declaration) : bool { try { - $reflectionClass = self::$reflectionsCache[$declaration] ??= new ReflectionClass($declaration); + $reflectionClass = self::$reflectionsCache[$declaration] = new ReflectionClass($declaration); } catch (\Throwable) { return true; }

Check warning on line 71 in src/AbstractClassifier.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "AssignCoalesce": --- Original +++ New @@ @@ private function skipDeclaration(string $declaration) : bool { try { - $reflectionClass = self::$reflectionsCache[$declaration] ??= new ReflectionClass($declaration); + $reflectionClass = self::$reflectionsCache[$declaration] = new ReflectionClass($declaration); } catch (\Throwable) { return true; }
} catch (\Throwable) {
return true;
}

if ($reflectionClass->isInternal() || $reflectionClass->isAnonymous()) {
return true;
}

foreach ($this->filters as $filter) {
if (!$filter->match($reflectionClass)) {
return true;
}
}

return false;
}

/**
* @return iterable<class-string|trait-string>
*/
abstract protected function getAvailableClasses(): iterable;
abstract protected function getAvailableDeclarations(): iterable;
}
7 changes: 5 additions & 2 deletions src/ClassifierInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@

namespace Yiisoft\Classifier;

use Yiisoft\Classifier\Filter\FilterInterface;

/**
* `Classifier` is a class finder that represents the classes found.
*/
interface ClassifierInterface
{
public function withFilter(FilterInterface ...$filter): static;

/**
* Returns all the class names found.
*
* @return iterable List of class names.
* @psalm-return iterable<class-string>
* @return iterable<class-string> List of class names.
*/
public function find(): iterable;
}
33 changes: 33 additions & 0 deletions src/Filter/ClassAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Classifier\Filter;

use ReflectionAttribute;
use ReflectionClass;

final class ClassAttributes implements FilterInterface
{
private array $attributes;

public function __construct(string ...$attributes)
{
$this->attributes = $attributes;
}

public function match(ReflectionClass $reflectionClass): bool
{
if (empty($this->attributes)) {
return false;
}

$attributes = $reflectionClass->getAttributes();
$attributeNames = array_map(
static fn(ReflectionAttribute $attribute) => $attribute->getName(),
$attributes
);

return count(array_intersect($this->attributes, $attributeNames)) === count($this->attributes);
}
}
27 changes: 27 additions & 0 deletions src/Filter/ClassImplements.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Classifier\Filter;

use ReflectionClass;

final class ClassImplements implements FilterInterface
{
private array $interfaces;

public function __construct(string ...$interfaces)
{
$this->interfaces = $interfaces;
}

public function match(ReflectionClass $reflectionClass): bool
{
if (empty($this->interfaces) || $reflectionClass->isInterface()) {
return false;
}
$interfaces = $reflectionClass->getInterfaceNames();

return count(array_intersect($this->interfaces, $interfaces)) === count($this->interfaces);
}
}
19 changes: 19 additions & 0 deletions src/Filter/FilterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Classifier\Filter;

use ReflectionClass;

interface FilterInterface
{
/**
* Tests the filter against class.
*
* @param ReflectionClass $reflectionClass
*
* @return bool `true` if class matches against filter. Otherwise, `false`.
*/
public function match(ReflectionClass $reflectionClass): bool;
}
22 changes: 22 additions & 0 deletions src/Filter/SubclassOf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Classifier\Filter;

use ReflectionClass;

final class SubclassOf implements FilterInterface
{
/**
* @param class-string $class
*/
public function __construct(private string $class)
{
}

public function match(ReflectionClass $reflectionClass): bool
{
return $reflectionClass->isSubclassOf($this->class);
}
}
29 changes: 29 additions & 0 deletions src/Filter/TargetAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Classifier\Filter;

use ReflectionAttribute;
use ReflectionClass;

final class TargetAttribute implements FilterInterface
{
/**
* @param class-string $attribute
*/
public function __construct(private string $attribute)
{
}

public function match(ReflectionClass $reflectionClass): bool
{
$attributes = $reflectionClass->getAttributes($this->attribute, ReflectionAttribute::IS_INSTANCEOF);
$attributeNames = array_map(

Check warning on line 22 in src/Filter/TargetAttribute.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "UnwrapArrayMap": --- Original +++ New @@ @@ public function match(ReflectionClass $reflectionClass) : bool { $attributes = $reflectionClass->getAttributes($this->attribute, ReflectionAttribute::IS_INSTANCEOF); - $attributeNames = array_map(static fn(ReflectionAttribute $attribute) => $attribute->getName(), $attributes); + $attributeNames = $attributes; return !empty($attributeNames); } }

Check warning on line 22 in src/Filter/TargetAttribute.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "UnwrapArrayMap": --- Original +++ New @@ @@ public function match(ReflectionClass $reflectionClass) : bool { $attributes = $reflectionClass->getAttributes($this->attribute, ReflectionAttribute::IS_INSTANCEOF); - $attributeNames = array_map(static fn(ReflectionAttribute $attribute) => $attribute->getName(), $attributes); + $attributeNames = $attributes; return !empty($attributeNames); } }
static fn(ReflectionAttribute $attribute) => $attribute->getName(),
$attributes
);

return !empty($attributeNames);
}
}
Loading

0 comments on commit 76f5f2f

Please sign in to comment.