-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
177 changed files
with
621 additions
and
260 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace LastDragon_ru\LaraASP\Dev\PhpStan\ClassMustBeFinal; | ||
|
||
use Override; | ||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\Class_; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\InClassNode; | ||
use PHPStan\Rules\Rule as RuleContract; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
|
||
use function sprintf; | ||
|
||
/** | ||
* Makes the `final` keyword required for all subclasses of the specified class. | ||
* | ||
* @internal | ||
* @implements RuleContract<InClassNode> | ||
*/ | ||
class Rule implements RuleContract { | ||
public function __construct( | ||
/** | ||
* @var array<array-key, class-string> | ||
*/ | ||
protected readonly array $classes, | ||
) { | ||
// empty | ||
} | ||
|
||
#[Override] | ||
public function getNodeType(): string { | ||
return InClassNode::class; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
#[Override] | ||
public function processNode(Node $node, Scope $scope): array { | ||
// Skip? | ||
$origin = $node->getOriginalNode(); | ||
|
||
if (!($origin instanceof Class_) || $origin->isFinal() || $origin->isAbstract() || $origin->isAnonymous()) { | ||
return []; | ||
} | ||
|
||
// Must be final? | ||
$reflection = $node->getClassReflection(); | ||
$mustBeFinal = false; | ||
|
||
foreach ($this->classes as $class) { | ||
if ($reflection->is($class) || $reflection->implementsInterface($class)) { | ||
$mustBeFinal = true; | ||
break; | ||
} | ||
} | ||
|
||
if ($mustBeFinal) { | ||
return [ | ||
RuleErrorBuilder::message( | ||
sprintf('Class `%s` must be `final`.', $reflection->getName()), | ||
)->build(), | ||
]; | ||
} | ||
|
||
// Nope | ||
return []; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace LastDragon_ru\LaraASP\Dev\PhpStan\ClassMustBeFinal; | ||
|
||
use Override; | ||
use PHPStan\Rules\Rule as RuleContract; | ||
use PHPStan\Testing\RuleTestCase; | ||
use PHPUnit\Framework\Attributes\CoversClass; | ||
|
||
use function sprintf; | ||
|
||
/** | ||
* @internal | ||
* @extends RuleTestCase<Rule> | ||
*/ | ||
#[CoversClass(Rule::class)] | ||
final class RuleTest extends RuleTestCase { | ||
#[Override] | ||
protected function getRule(): RuleContract { | ||
return new Rule([ | ||
RuleTest_MustBeFinalMarker::class, | ||
]); | ||
} | ||
|
||
public function testRule(): void { | ||
$this->analyse([__FILE__], [ | ||
[ | ||
sprintf('Class `%s` must be `final`.', RuleTest_MustBeFinal::class), | ||
50, | ||
], | ||
]); | ||
} | ||
} | ||
|
||
// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses | ||
// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps | ||
|
||
/** | ||
* @internal | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
interface RuleTest_MustBeFinalMarker { | ||
// empty | ||
} | ||
|
||
/** | ||
* @internal | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
class RuleTest_MustBeFinal implements RuleTest_MustBeFinalMarker { | ||
// empty | ||
} | ||
|
||
/** | ||
* @internal | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
final class RuleTest_AlreadyFinal implements RuleTest_MustBeFinalMarker { | ||
// empty | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
parameters: | ||
classMustBeFinal: [] | ||
|
||
parametersSchema: | ||
classMustBeFinal: structure({ | ||
classes: listOf(string()) | ||
}) | ||
|
||
services: | ||
- | ||
class: LastDragon_ru\LaraASP\Dev\PhpStan\ClassMustBeFinal\Rule | ||
arguments: | ||
classes: %classMustBeFinal.classes% | ||
tags: | ||
- phpstan.rules.rule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace LastDragon_ru\LaraASP\Dev\PhpStan\ClassMustBeInternal; | ||
|
||
use Override; | ||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\Class_; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\InClassNode; | ||
use PHPStan\Reflection\ClassReflection; | ||
use PHPStan\Rules\Rule as RuleContract; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
|
||
use function in_array; | ||
use function pathinfo; | ||
use function sprintf; | ||
use function str_ends_with; | ||
use function str_starts_with; | ||
|
||
use const PATHINFO_FILENAME; | ||
|
||
/** | ||
* Makes the `@internal` tag required for all subclasses of the specified class. | ||
* | ||
* @internal | ||
* @implements RuleContract<InClassNode> | ||
*/ | ||
class Rule implements RuleContract { | ||
public function __construct( | ||
/** | ||
* @var array<array-key, class-string> | ||
*/ | ||
protected readonly array $classes, | ||
/** | ||
* @var array<array-key, class-string> | ||
*/ | ||
protected readonly array $ignored = [], | ||
) { | ||
// empty | ||
} | ||
|
||
#[Override] | ||
public function getNodeType(): string { | ||
return InClassNode::class; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
#[Override] | ||
public function processNode(Node $node, Scope $scope): array { | ||
// Skip? | ||
$origin = $node->getOriginalNode(); | ||
|
||
if (!($origin instanceof Class_) || $origin->isAnonymous()) { | ||
return []; | ||
} | ||
|
||
// Ignored? | ||
if (in_array((string) $origin->namespacedName, $this->ignored, true)) { | ||
return []; | ||
} | ||
|
||
// Internal? | ||
$reflection = $node->getClassReflection(); | ||
$isInternal = (bool) $reflection->getResolvedPhpDoc()?->isInternal(); | ||
|
||
if ($isInternal) { | ||
return []; | ||
} | ||
|
||
// Must be internal? | ||
if ($this->mustBe($reflection)) { | ||
return [ | ||
RuleErrorBuilder::message( | ||
sprintf('Class `%s` must be marked by `@internal`.', $reflection->getName()), | ||
)->build(), | ||
]; | ||
} | ||
|
||
// Return | ||
return []; | ||
} | ||
|
||
private function mustBe(ClassReflection $reflection): bool { | ||
return $this->mustBeIsTestInternal($reflection) | ||
|| $this->mustBeIsInstanceOf($reflection); | ||
} | ||
|
||
private function mustBeIsInstanceOf(ClassReflection $reflection): bool { | ||
$mustBe = false; | ||
|
||
foreach ($this->classes as $class) { | ||
// Instance? | ||
if ($reflection->is($class) || $reflection->implementsInterface($class)) { | ||
$mustBe = true; | ||
break; | ||
} | ||
} | ||
|
||
return $mustBe; | ||
} | ||
|
||
private function mustBeIsTestInternal(ClassReflection $reflection): bool { | ||
$classname = $reflection->getNativeReflection()->getShortName(); | ||
$filename = pathinfo((string) $reflection->getFileName(), PATHINFO_FILENAME); | ||
$mustBe = $filename | ||
&& str_ends_with($filename, 'Test') | ||
&& str_starts_with($classname, "{$filename}_"); | ||
|
||
return $mustBe; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace LastDragon_ru\LaraASP\Dev\PhpStan\ClassMustBeInternal; | ||
|
||
use Override; | ||
use PHPStan\Rules\Rule as RuleContract; | ||
use PHPStan\Testing\RuleTestCase; | ||
use PHPUnit\Framework\Attributes\CoversClass; | ||
|
||
use function sprintf; | ||
|
||
/** | ||
* @internal | ||
* @extends RuleTestCase<Rule> | ||
*/ | ||
#[CoversClass(Rule::class)] | ||
final class RuleTest extends RuleTestCase { | ||
#[Override] | ||
protected function getRule(): RuleContract { | ||
return new Rule( | ||
[ | ||
RuleTest_MustBeInternalMarker::class, | ||
], | ||
[ | ||
RuleTest_MustBeIgnored::class, | ||
], | ||
); | ||
} | ||
|
||
public function testRule(): void { | ||
$this->analyse([__FILE__], [ | ||
[ | ||
sprintf('Class `%s` must be marked by `@internal`.', RuleTest_MustBeInternal::class), | ||
58, | ||
], | ||
[ | ||
sprintf('Class `%s` must be marked by `@internal`.', RuleTest_TestMustBeInternal::class), | ||
80, | ||
], | ||
]); | ||
} | ||
} | ||
|
||
// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses | ||
// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps | ||
|
||
/** | ||
* @internal | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
interface RuleTest_MustBeInternalMarker { | ||
// empty | ||
} | ||
|
||
/** | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
class RuleTest_MustBeInternal implements RuleTest_MustBeInternalMarker { | ||
// empty | ||
} | ||
|
||
/** | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
class RuleTest_MustBeIgnored implements RuleTest_MustBeInternalMarker { | ||
// empty | ||
} | ||
|
||
/** | ||
* @internal | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
class RuleTest_AlreadyInternal implements RuleTest_MustBeInternalMarker { | ||
// empty | ||
} | ||
|
||
/** | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
class RuleTest_TestMustBeInternal { | ||
// empty | ||
} | ||
|
||
/** | ||
* @internal | ||
* @noinspection PhpMultipleClassesDeclarationsInOneFile | ||
*/ | ||
class RuleTest_TestAlreadyInternal { | ||
// empty | ||
} |
Oops, something went wrong.