From 7b43d803a6121ebbf4d365de719d2088d9652511 Mon Sep 17 00:00:00 2001 From: Simtel Date: Tue, 25 Aug 2026 15:27:35 +0400 Subject: [PATCH] Refactor rules and release 3.0.0 - Rename rules (BC break): * CommandClassShouldHaveCommandHandlerSeeTag (was CommandClassShouldBeHelpCommandHandlerClass) * EventListenerShouldHaveAsEventListenerAttribute (was EventListenerClassShouldBeIncludeAsListenerAttribute) * ShouldNotPhpDocReturnWhenTypeHintExists (was NotShouldPhpdocReturnIfExistTypeHint) - Add AbstractPhpDocRule base class, drop duplicated PHPDoc parsing - EventListener rule inspects node attrGroups instead of reflection - ShouldNotPhpDocReturnWhenTypeHintExists works on ClassMethod nodes: skip union/nullable/generic types instead of bailing out; guard IdentifierTypeNode - Add error identifiers (PHPStan 2.x requirement) - Tests fetch PhpDocParser/Lexer from the container; add regression fixture for complex types - Fix phpstan.neon paths so self-analysis passes at level max - Migrate phpunit.xml.dist to the PHPUnit 12 schema --- .gitignore | 1 + AGENTS.md | 32 +++++++ README.md | 22 +++-- phpstan.neon | 6 +- phpunit.xml.dist | 42 ++++----- rules.neon | 6 +- src/Rule/AbstractPhpDocRule.php | 24 +++++ ...ndClassShouldBeHelpCommandHandlerClass.php | 94 ------------------- ...andClassShouldHaveCommandHandlerSeeTag.php | 84 +++++++++++++++++ ...lassShouldBeIncludeAsListenerAttribute.php | 72 -------------- ...enerShouldHaveAsEventListenerAttribute.php | 47 ++++++++++ .../NotShouldPhpdocReturnIfExistTypeHint.php | 86 ----------------- ...houldNotPhpDocReturnWhenTypeHintExists.php | 66 +++++++++++++ .../Return/MethodsWithComplexTypes.php | 29 ++++++ ...assShouldHaveCommandHandlerSeeTagTest.php} | 28 +++--- ...houldHaveAsEventListenerAttributeTest.php} | 11 +-- ...tShouldPhpdocReturnIfExistTypeHintTest.php | 39 -------- ...dNotPhpDocReturnWhenTypeHintExistsTest.php | 37 ++++++++ 18 files changed, 372 insertions(+), 354 deletions(-) create mode 100644 AGENTS.md create mode 100644 src/Rule/AbstractPhpDocRule.php delete mode 100644 src/Rule/CommandClassShouldBeHelpCommandHandlerClass.php create mode 100644 src/Rule/CommandClassShouldHaveCommandHandlerSeeTag.php delete mode 100644 src/Rule/EventListenerClassShouldBeIncludeAsListenerAttribute.php create mode 100644 src/Rule/EventListenerShouldHaveAsEventListenerAttribute.php delete mode 100644 src/Rule/NotShouldPhpdocReturnIfExistTypeHint.php create mode 100644 src/Rule/ShouldNotPhpDocReturnWhenTypeHintExists.php create mode 100644 tests/Fixture/Return/MethodsWithComplexTypes.php rename tests/Rules/{CommandClassShouldBeHelpCommandHandlerClassTest.php => CommandClassShouldHaveCommandHandlerSeeTagTest.php} (61%) rename tests/Rules/{EventListenerClassShouldBeIncludeAsListenerAttributeTest.php => EventListenerShouldHaveAsEventListenerAttributeTest.php} (64%) delete mode 100644 tests/Rules/NotShouldPhpdocReturnIfExistTypeHintTest.php create mode 100644 tests/Rules/ShouldNotPhpDocReturnWhenTypeHintExistsTest.php diff --git a/.gitignore b/.gitignore index 0304f06..015cee8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor/ /.idea/ /.phpunit.result.cache +/.phpunit.cache/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..246132d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# AGENTS.md + +Custom PHPStan rules package (`simtel/phpstan-rules`). Adds static-analysis rules as a PHPStan extension distributed via `rules.neon`. Namespace: `Simtel\PHPStanRules\` → `src/`, `Simtel\PHPStanRules\Tests\` → `tests/`. PHP >=8.3, PHPStan ^2.0, PHPUnit ^12. + +## Commands + +- Tests: `vendor/bin/phpunit` — all tests pass (8). Single test: `vendor/bin/phpunit tests/Rules/CommandClassShouldHaveCommandHandlerSeeTagTest.php` +- Code style: `vendor/bin/ecs check` (or `ecs check --fix`; there is no `ecs fix`). Uses PSR-12 + symplify + spaces + `NoUnusedImports`. Run after changes. +- Static analysis: `vendor/bin/phpstan analyse` — self-analysis of `src/` at level max, must stay at 0 errors. +- CI (`.github/workflows/php.yml`) only runs `composer validate --strict`, `composer install`, and `vendor/bin/phpunit`. + +## Architecture + +- `AbstractPhpDocRule` (in `src/Rule/`) is the shared base for rules that parse PHPDoc: it injects `PhpDocParser` + `Lexer` and exposes `parsePhpDoc(string $doc): PhpDocNode`. Subclasses implement `Rule` themselves. +- `EventListenerShouldHaveAsEventListenerAttribute` inspects `$node->attrGroups` directly (no reflection, no base class). +- `ShouldNotPhpDocReturnWhenTypeHintExists` works on `ClassMethod` nodes and reads the native type from `$node->returnType` (no reflection needed). Only `Identifier`/`Name` native types and `IdentifierTypeNode` PHPDoc types are compared — union/nullable/generic types are skipped, not errors. +- Errors are reported via `RuleErrorBuilder::message(...)->identifier('rule.group')` (never throw). PHPStan 2.x requires identifiers. + +## Adding a rule + +1. Create `src/Rule/.php` implementing `PHPStan\Rules\Rule` with `@implements Rule` and `getNodeType()` + `processNode()`. Extend `AbstractPhpDocRule` if it parses PHPDoc. +2. Register it in `rules.neon` (this is what consumers include). +3. Add a test extending `PHPStan\Testing\RuleTestCase` in `tests/Rules/`. In `getRule()`, fetch parser/lexer from the container: `self::getContainer()->getByType(PhpDocParser::class)` / `getByType(Lexer::class)` — do not build them manually. +4. Add fixture/data PHP files under `tests/Fixture/` or `tests/data/`. Command-rule data files live in `tests/data/command_handler_data*.php`. +5. Update the README feature list. + +## Gotchas + +- Rule classes are `final`, extend `AbstractPhpDocRule` when parsing PHPDoc, `declare(strict_types=1)`, PSR-12 style, no comments. +- `rules.neon` and the README list the 3 distributed rules — keep in sync when adding/removing. +- Tests use PHPStan's `RuleTestCase::analyse()`; expectations include exact line numbers, so changing a fixture's layout breaks tests. Append new methods to a fixture rather than inserting before existing ones. +- `phpunit.xml.dist` uses the migrated PHPUnit 12 schema (`` instead of ``); `.phpunit.cache/` is gitignored. diff --git a/README.md b/README.md index b54fdbb..d5bb974 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A collection of custom PHPStan rules that enforce coding standards and improve c This package includes three powerful rules that help maintain high code quality: ### 1. Command-Handler Relationship Rule -**Rule**: `CommandClassShouldBeHelpCommandHandlerClass` +**Rule**: `CommandClassShouldHaveCommandHandlerSeeTag` Enforces that classes ending with "Command" must have a `@see` PHPDoc tag pointing to their corresponding CommandHandler class. @@ -29,7 +29,7 @@ class CreateUserCommand **Exception**: Classes with an `__invoke` method are exempt from this rule. ### 2. Event Listener Attribute Rule -**Rule**: `EventListenerClassShouldBeIncludeAsListenerAttribute` +**Rule**: `EventListenerShouldHaveAsEventListenerAttribute` Ensures that classes ending with "EventListener" are properly annotated with the `#[AsEventListener]` attribute. @@ -45,7 +45,7 @@ class UserRegisteredEventListener ``` ### 3. Redundant PHPDoc Return Type Rule -**Rule**: `NotShouldPhpdocReturnIfExistTypeHint` +**Rule**: `ShouldNotPhpDocReturnWhenTypeHintExists` Prevents redundant or conflicting `@return` PHPDoc annotations when native return type hints are already declared. @@ -102,9 +102,9 @@ For granular control, register specific rules: ```neon parameters: rules: - - Simtel\PHPStanRules\Rule\CommandClassShouldBeHelpCommandHandlerClass - - Simtel\PHPStanRules\Rule\EventListenerClassShouldBeIncludeAsListenerAttribute - - Simtel\PHPStanRules\Rule\NotShouldPhpdocReturnIfExistTypeHint + - Simtel\PHPStanRules\Rule\CommandClassShouldHaveCommandHandlerSeeTag + - Simtel\PHPStanRules\Rule\EventListenerShouldHaveAsEventListenerAttribute + - Simtel\PHPStanRules\Rule\ShouldNotPhpDocReturnWhenTypeHintExists ``` ### Complete Configuration Example @@ -123,6 +123,9 @@ parameters: # Exclude specific patterns if needed - '#Command class should be include phpDoc with @see attribute#' path: src/Deprecated/ + # Rules now report error identifiers, e.g.: + # - identifier: commandClass.missingPhpDoc + # path: src/Deprecated/ ``` ## 🔧 Development @@ -171,9 +174,10 @@ vendor/bin/phpstan analyse ``` . ├── src/Rule/ # Rule implementations -│ ├── CommandClassShouldBeHelpCommandHandlerClass.php -│ ├── EventListenerClassShouldBeIncludeAsListenerAttribute.php -│ └── NotShouldPhpdocReturnIfExistTypeHint.php +│ ├── CommandClassShouldHaveCommandHandlerSeeTag.php +│ ├── EventListenerShouldHaveAsEventListenerAttribute.php +│ ├── ShouldNotPhpDocReturnWhenTypeHintExists.php +│ └── AbstractPhpDocRule.php # Shared PHPDoc parsing base ├── tests/ │ ├── Fixture/ # Test code samples │ │ ├── EventListener/ diff --git a/phpstan.neon b/phpstan.neon index b5c5e74..623aad8 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,9 @@ rules: - - Simtel\PHPStanRules\Rule\CommandClassShouldBeHelpCommandHandlerClass + - Simtel\PHPStanRules\Rule\CommandClassShouldHaveCommandHandlerSeeTag + - Simtel\PHPStanRules\Rule\EventListenerShouldHaveAsEventListenerAttribute + - Simtel\PHPStanRules\Rule\ShouldNotPhpDocReturnWhenTypeHintExists parameters: paths: - - src\Examples + - src level: max diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f0613e4..86a583d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,28 +1,20 @@ - - - - - - - - - - - - tests - - - - - - src - - + + + + + + + + + + tests + + + + + src + + diff --git a/rules.neon b/rules.neon index e163cc9..b9c69b3 100644 --- a/rules.neon +++ b/rules.neon @@ -1,4 +1,4 @@ rules: - - Simtel\PHPStanRules\Rule\CommandClassShouldBeHelpCommandHandlerClass - - Simtel\PHPStanRules\Rule\EventListenerClassShouldBeIncludeAsListenerAttribute - - Simtel\PHPStanRules\Rule\NotShouldPhpdocReturnIfExistTypeHint + - Simtel\PHPStanRules\Rule\CommandClassShouldHaveCommandHandlerSeeTag + - Simtel\PHPStanRules\Rule\EventListenerShouldHaveAsEventListenerAttribute + - Simtel\PHPStanRules\Rule\ShouldNotPhpDocReturnWhenTypeHintExists diff --git a/src/Rule/AbstractPhpDocRule.php b/src/Rule/AbstractPhpDocRule.php new file mode 100644 index 0000000..70e2950 --- /dev/null +++ b/src/Rule/AbstractPhpDocRule.php @@ -0,0 +1,24 @@ +phpDocParser->parse(new TokenIterator($this->phpDocLexer->tokenize($doc))); + } +} diff --git a/src/Rule/CommandClassShouldBeHelpCommandHandlerClass.php b/src/Rule/CommandClassShouldBeHelpCommandHandlerClass.php deleted file mode 100644 index 374a57f..0000000 --- a/src/Rule/CommandClassShouldBeHelpCommandHandlerClass.php +++ /dev/null @@ -1,94 +0,0 @@ - - */ -final class CommandClassShouldBeHelpCommandHandlerClass implements Rule -{ - public function __construct( - private readonly PhpDocParser $parser, - private readonly Lexer $phpDocLexer, - ) { - } - - public function getNodeType(): string - { - return Class_::class; - } - - /** - * @param Class_ $node - * @param Scope $scope - * - * @return string[] - */ - public function processNode(Node $node, Scope $scope): array - { - $className = ''; - if ($node->name !== null) { - $className = $node->name->name; - } - - if (! str_ends_with($className, 'Command')) { - return []; - } - - $methods = $node->getMethods(); - foreach ($methods as $method) { - if ($method->name->name === '__invoke') { - return []; - } - } - - $find = false; - $doc = $node->getDocComment()?->getText() ?? ''; - if ($doc === '') { - return [RuleErrorBuilder::message('Command class should be include phpDoc with @see attribute')->build()]; - } - $tokens = new TokenIterator($this->phpDocLexer->tokenize($doc)); - $text = $this->parser->parse($tokens); - - foreach ($text->getTags() as $tag) { - if ($tag->name !== '@see') { - continue; - } - if ($tag->value instanceof GenericTagValueNode) { - $find = true; - $value = $tag->value->value; - if (! str_ends_with($value, 'CommandHandler')) { - return [ - RuleErrorBuilder::message( - sprintf( - 'PhpDoc command class should be include @see attribute with CommandHandler class name, but include %s', - $value - ) - )->build(), - ]; - } - } - } - if ($find === false) { - return [ - RuleErrorBuilder::message( - 'PhpDoc command class should be include @see attribute with CommandHandler class name' - )->build(), - ]; - } - - return []; - } -} diff --git a/src/Rule/CommandClassShouldHaveCommandHandlerSeeTag.php b/src/Rule/CommandClassShouldHaveCommandHandlerSeeTag.php new file mode 100644 index 0000000..0aee6f6 --- /dev/null +++ b/src/Rule/CommandClassShouldHaveCommandHandlerSeeTag.php @@ -0,0 +1,84 @@ + + */ +final class CommandClassShouldHaveCommandHandlerSeeTag extends AbstractPhpDocRule implements Rule +{ + public function getNodeType(): string + { + return Class_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name === null) { + return []; + } + + if (! str_ends_with($node->name->name, 'Command')) { + return []; + } + + foreach ($node->getMethods() as $method) { + if ($method->name->name === '__invoke') { + return []; + } + } + + $doc = $node->getDocComment()?->getText() ?? ''; + if ($doc === '') { + return [ + RuleErrorBuilder::message('Command class should be include phpDoc with @see attribute') + ->identifier('commandClass.missingPhpDoc') + ->build(), + ]; + } + + $hasSeeTag = false; + foreach ($this->parsePhpDoc($doc)->getTags() as $tag) { + if ($tag->name !== '@see') { + continue; + } + if (! $tag->value instanceof GenericTagValueNode) { + continue; + } + $hasSeeTag = true; + if (! str_ends_with($tag->value->value, 'CommandHandler')) { + return [ + RuleErrorBuilder::message( + sprintf( + 'PhpDoc command class should be include @see attribute with CommandHandler class name, but include %s', + $tag->value->value + ) + ) + ->identifier('commandClass.invalidSeeValue') + ->build(), + ]; + } + } + + if (! $hasSeeTag) { + return [ + RuleErrorBuilder::message( + 'PhpDoc command class should be include @see attribute with CommandHandler class name' + ) + ->identifier('commandClass.missingSee') + ->build(), + ]; + } + + return []; + } +} diff --git a/src/Rule/EventListenerClassShouldBeIncludeAsListenerAttribute.php b/src/Rule/EventListenerClassShouldBeIncludeAsListenerAttribute.php deleted file mode 100644 index d949fde..0000000 --- a/src/Rule/EventListenerClassShouldBeIncludeAsListenerAttribute.php +++ /dev/null @@ -1,72 +0,0 @@ - - */ -final class EventListenerClassShouldBeIncludeAsListenerAttribute implements Rule -{ - public function __construct( - private readonly ReflectionProvider $reflectionProvider - ) { - } - - public function getNodeType(): string - { - return Class_::class; - } - - /** - * @param Class_ $node - * @param Scope $scope - * - * @return string[] - */ - public function processNode(Node $node, Scope $scope): array - { - $className = ''; - if ($node->name !== null) { - $className = $node->name->name; - } - - if (! str_ends_with($className, 'EventListener')) { - return []; - } - - $fullyQualifiedClassName = $node->namespacedName?->toString(); - if ($fullyQualifiedClassName === null) { - return []; - } - $attributes = $this->reflectionProvider - ->getClass($fullyQualifiedClassName) - ->getNativeReflection() - ->getAttributes(); - - $find = false; - foreach ($attributes as $attribute) { - if (str_ends_with($attribute->getName(), 'AsEventListener')) { - $find = true; - } - } - - if ($find === false) { - return [ - RuleErrorBuilder::message( - 'Event listener class should be include attribute #[AsEventListener]' - )->build(), - ]; - } - - return []; - } -} diff --git a/src/Rule/EventListenerShouldHaveAsEventListenerAttribute.php b/src/Rule/EventListenerShouldHaveAsEventListenerAttribute.php new file mode 100644 index 0000000..22b9552 --- /dev/null +++ b/src/Rule/EventListenerShouldHaveAsEventListenerAttribute.php @@ -0,0 +1,47 @@ + + */ +final class EventListenerShouldHaveAsEventListenerAttribute implements Rule +{ + public function getNodeType(): string + { + return Class_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name === null) { + return []; + } + + if (! str_ends_with($node->name->name, 'EventListener')) { + return []; + } + + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attribute) { + if (str_ends_with($attribute->name->toString(), 'AsEventListener')) { + return []; + } + } + } + + return [ + RuleErrorBuilder::message('Event listener class should be include attribute #[AsEventListener]') + ->identifier('eventListener.missingAttribute') + ->build(), + ]; + } +} diff --git a/src/Rule/NotShouldPhpdocReturnIfExistTypeHint.php b/src/Rule/NotShouldPhpdocReturnIfExistTypeHint.php deleted file mode 100644 index eae2bfc..0000000 --- a/src/Rule/NotShouldPhpdocReturnIfExistTypeHint.php +++ /dev/null @@ -1,86 +0,0 @@ - - */ -final class NotShouldPhpdocReturnIfExistTypeHint implements Rule -{ - public function __construct( - private readonly ReflectionProvider $reflectionProvider, - private readonly PhpDocParser $parser, - private readonly Lexer $phpDocLexer, - ) { - } - - public function getNodeType(): string - { - return Class_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $fullyQualifiedClassName = $node->namespacedName?->toString(); - if ($fullyQualifiedClassName === null) { - return []; - } - - $reflection = $this->reflectionProvider - ->getClass($fullyQualifiedClassName) - ->getNativeReflection(); - - $methods = $reflection->getMethods(); - $errors = []; - foreach ($methods as $method) { - if (str_starts_with($method->getName(), '__')) { - continue; - } - - $doc = (string) $method->getDocComment(); - if ($doc === '') { - continue; - } - - $returnType = $method->getReturnType(); - if ($returnType === null || ! method_exists($returnType, 'getName')) { - return []; - } - - $returnTypeName = $returnType->getName(); - $tokens = new TokenIterator($this->phpDocLexer->tokenize($doc)); - $text = $this->parser->parse($tokens); - - foreach ($text->getTags() as $tag) { - if ($tag->name !== '@return') { - continue; - } - if ($tag->value instanceof ReturnTagValueNode) { - $value = $tag->value->type->name; - if ($value === $returnTypeName - && $reflection->getName() === $method->getBetterReflection() - ->getLocatedSource() - ->getName()) { - $errors[] = \PHPStan\Rules\RuleErrorBuilder::message( - 'PhpDoc attribute @return for method ' . $method->getName() . ' can be remove' - )->line((int) $method->getStartLine()) - ->build(); - } - } - } - } - return $errors; - } -} diff --git a/src/Rule/ShouldNotPhpDocReturnWhenTypeHintExists.php b/src/Rule/ShouldNotPhpDocReturnWhenTypeHintExists.php new file mode 100644 index 0000000..42ca5de --- /dev/null +++ b/src/Rule/ShouldNotPhpDocReturnWhenTypeHintExists.php @@ -0,0 +1,66 @@ + + */ +final class ShouldNotPhpDocReturnWhenTypeHintExists extends AbstractPhpDocRule implements Rule +{ + public function getNodeType(): string + { + return ClassMethod::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (str_starts_with($node->name->name, '__')) { + return []; + } + + $returnType = $node->returnType; + if ($returnType === null) { + return []; + } + + $nativeTypeName = match (true) { + $returnType instanceof Identifier => $returnType->name, + $returnType instanceof Name => $returnType->toString(), + default => null, + }; + if ($nativeTypeName === null) { + return []; + } + + $doc = $node->getDocComment()?->getText() ?? ''; + if ($doc === '') { + return []; + } + + foreach ($this->parsePhpDoc($doc)->getReturnTagValues() as $returnTag) { + if ($returnTag->type instanceof IdentifierTypeNode && $returnTag->type->name === $nativeTypeName) { + return [ + RuleErrorBuilder::message( + 'PhpDoc attribute @return for method ' . $node->name->name . ' can be remove' + ) + ->line($node->getStartLine()) + ->identifier('returnType.redundantPhpDoc') + ->build(), + ]; + } + } + + return []; + } +} diff --git a/tests/Fixture/Return/MethodsWithComplexTypes.php b/tests/Fixture/Return/MethodsWithComplexTypes.php new file mode 100644 index 0000000..72d0f49 --- /dev/null +++ b/tests/Fixture/Return/MethodsWithComplexTypes.php @@ -0,0 +1,29 @@ + + */ + public function generic(): array + { + return []; + } +} diff --git a/tests/Rules/CommandClassShouldBeHelpCommandHandlerClassTest.php b/tests/Rules/CommandClassShouldHaveCommandHandlerSeeTagTest.php similarity index 61% rename from tests/Rules/CommandClassShouldBeHelpCommandHandlerClassTest.php rename to tests/Rules/CommandClassShouldHaveCommandHandlerSeeTagTest.php index 876df04..bc4fdc5 100644 --- a/tests/Rules/CommandClassShouldBeHelpCommandHandlerClassTest.php +++ b/tests/Rules/CommandClassShouldHaveCommandHandlerSeeTagTest.php @@ -5,26 +5,20 @@ namespace Simtel\PHPStanRules\Tests\Rules; use PHPStan\PhpDocParser\Lexer\Lexer; -use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\PhpDocParser; -use PHPStan\PhpDocParser\Parser\TypeParser; -use PHPStan\PhpDocParser\ParserConfig; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use Simtel\PHPStanRules\Rule\CommandClassShouldBeHelpCommandHandlerClass; +use Simtel\PHPStanRules\Rule\CommandClassShouldHaveCommandHandlerSeeTag; -class CommandClassShouldBeHelpCommandHandlerClassTest extends RuleTestCase +class CommandClassShouldHaveCommandHandlerSeeTagTest extends RuleTestCase { - /** - * @inheritDoc - */ protected function getRule(): Rule { - $config = new ParserConfig(usedAttributes: []); - $constExprParser = new ConstExprParser($config); - return new CommandClassShouldBeHelpCommandHandlerClass( - new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser), - new Lexer($config) + $container = self::getContainer(); + + return new CommandClassShouldHaveCommandHandlerSeeTag( + $container->getByType(PhpDocParser::class), + $container->getByType(Lexer::class), ); } @@ -33,22 +27,22 @@ public function testCorrectSeeAttribute(): void $this->analyse([__DIR__ . '/../data/command_handler_data1.php'], [ [ 'PhpDoc command class should be include @see attribute with CommandHandler class name, but include TestClassCommand', - 10 - ] + 10, + ], ]); } public function testExistsSeeAttribute(): void { $this->analyse([__DIR__ . '/../data/command_handler_data2.php'], [ - ['PhpDoc command class should be include @see attribute with CommandHandler class name', 10] + ['PhpDoc command class should be include @see attribute with CommandHandler class name', 10], ]); } public function testExistsPhpDoc(): void { $this->analyse([__DIR__ . '/../data/command_handler_data3.php'], [ - ['Command class should be include phpDoc with @see attribute', 7] + ['Command class should be include phpDoc with @see attribute', 7], ]); } diff --git a/tests/Rules/EventListenerClassShouldBeIncludeAsListenerAttributeTest.php b/tests/Rules/EventListenerShouldHaveAsEventListenerAttributeTest.php similarity index 64% rename from tests/Rules/EventListenerClassShouldBeIncludeAsListenerAttributeTest.php rename to tests/Rules/EventListenerShouldHaveAsEventListenerAttributeTest.php index 694319f..97af05c 100644 --- a/tests/Rules/EventListenerClassShouldBeIncludeAsListenerAttributeTest.php +++ b/tests/Rules/EventListenerShouldHaveAsEventListenerAttributeTest.php @@ -6,16 +6,13 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use Simtel\PHPStanRules\Rule\EventListenerClassShouldBeIncludeAsListenerAttribute; +use Simtel\PHPStanRules\Rule\EventListenerShouldHaveAsEventListenerAttribute; -class EventListenerClassShouldBeIncludeAsListenerAttributeTest extends RuleTestCase +class EventListenerShouldHaveAsEventListenerAttributeTest extends RuleTestCase { - /** - * @inheritDoc - */ protected function getRule(): Rule { - return new EventListenerClassShouldBeIncludeAsListenerAttribute($this->createReflectionProvider()); + return new EventListenerShouldHaveAsEventListenerAttribute(); } public function testExistsNeedAttribute(): void @@ -26,7 +23,7 @@ public function testExistsNeedAttribute(): void public function testExistsAttribute(): void { $this->analyse([__DIR__ . '/../Fixture/EventListener/TestNotCorrectClassEventListener.php'], [ - ['Event listener class should be include attribute #[AsEventListener]', 7] + ['Event listener class should be include attribute #[AsEventListener]', 7], ]); } } diff --git a/tests/Rules/NotShouldPhpdocReturnIfExistTypeHintTest.php b/tests/Rules/NotShouldPhpdocReturnIfExistTypeHintTest.php deleted file mode 100644 index 6f80407..0000000 --- a/tests/Rules/NotShouldPhpdocReturnIfExistTypeHintTest.php +++ /dev/null @@ -1,39 +0,0 @@ -createReflectionProvider(), - new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser), - new Lexer($config) - ); - } - - public function testWithError(): void - { - $this->analyse([__DIR__ . '/../Fixture/Return/MethodsWithTypeHintAndReturn.php'], [ - ['PhpDoc attribute @return for method someMethod can be remove', 12], - ['PhpDoc attribute @return for method getInt can be remove', 20], - ]); - } -} diff --git a/tests/Rules/ShouldNotPhpDocReturnWhenTypeHintExistsTest.php b/tests/Rules/ShouldNotPhpDocReturnWhenTypeHintExistsTest.php new file mode 100644 index 0000000..a98cd15 --- /dev/null +++ b/tests/Rules/ShouldNotPhpDocReturnWhenTypeHintExistsTest.php @@ -0,0 +1,37 @@ +getByType(PhpDocParser::class), + $container->getByType(Lexer::class), + ); + } + + public function testWithError(): void + { + $this->analyse([__DIR__ . '/../Fixture/Return/MethodsWithTypeHintAndReturn.php'], [ + ['PhpDoc attribute @return for method someMethod can be remove', 12], + ['PhpDoc attribute @return for method getInt can be remove', 20], + ]); + } + + public function testWithComplexTypes(): void + { + $this->analyse([__DIR__ . '/../Fixture/Return/MethodsWithComplexTypes.php'], []); + } +}