diff --git a/CLAUDE.md b/CLAUDE.md index af149bfa..bd6e7f24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,14 +17,19 @@ visibility, and Symfony/Doctrine/PHPUnit proven practices). It is a PHPStan exte every rule, e.g. `symplify.noTestMocks`. - `config/*.neon` — one set per topic. Sets registered globally via the `composer.json` `extra.phpstan.includes` are auto-loaded by - `phpstan/extension-installer`; the rest are opt-in via `includes:` in the user's - `phpstan.neon`. + `phpstan/extension-installer` and toggled via `symplify.*` parameters; only + `rector-rules.neon` stays opt-in via `includes:` in the user's `phpstan.neon`. - `tests/Rules//` — one dir per rule, with `Fixture/` PHP files and a `config/configured_rule.neon`. -## Opt-in parameters +## Toggle parameters -Two rule groups are disabled by default and toggled by a single parameter: +Every auto-loaded set is toggled by a `symplify.*` parameter defined in +`config/phpstan-extensions.neon`, registered via `conditionalTags`. `naming`, +`complexity`, `configurable`, `static`, `symfony`, `symfonyConfig`, `doctrine` and +`phpunit` default to `true`. + +Two rule groups are disabled by default: - `mocks: true` — enables the PHPUnit mock rules in `config/mock-rules.neon` (registered via `conditionalTags` keyed on `%mocks%`). diff --git a/README.md b/README.md index f57a645e..613b0eec 100644 --- a/README.md +++ b/README.md @@ -30,41 +30,27 @@ Configuration should be added to your `phpstan.neon` file.
-Once you have most rules applied, it's best practice to include whole sets: - -```yaml -includes: - - vendor/symplify/phpstan-rules/config/code-complexity-rules.neon - - vendor/symplify/phpstan-rules/config/configurable-rules.neon - - vendor/symplify/phpstan-rules/config/static-rules.neon - - # project specific - - vendor/symplify/phpstan-rules/config/doctrine-rules.neon - - vendor/symplify/phpstan-rules/config/symfony-rules.neon - - # special set for PHP configs - - vendor/symplify/phpstan-rules/config/symfony-config-rules.neon -``` - -
- -The naming rules (class/constant naming and namespace rules, e.g. interface must live in a "Contract" namespace) load automatically and are **enabled by default**. Turn them off with the `naming` parameter: +All rule sets load automatically via `phpstan/extension-installer` - no `includes:` needed. Most are **enabled by default**, toggle them in `phpstan.neon`: ```yaml parameters: symplify: - naming: false -``` - -The `code-complexity-rules.neon`, `configurable-rules.neon` and `static-rules.neon` sets are **enabled by default** once included, and can be toggled the same way: - -```yaml -parameters: - symplify: - complexity: false - configurable: false - static: false -``` + # enabled by default + naming: true + complexity: true + configurable: true + static: true + symfony: true + symfonyConfig: true + doctrine: true + phpunit: true + + # disabled by default + mocks: false + ctor: false +``` + +*Note: If you included the sets manually before, remove those `includes:` lines - PHPStan reports duplicated includes.*
diff --git a/composer.json b/composer.json index c77b678d..7bc91ae1 100644 --- a/composer.json +++ b/composer.json @@ -63,6 +63,13 @@ "includes": [ "config/services/services.neon", "config/naming-rules.neon", + "config/code-complexity-rules.neon", + "config/configurable-rules.neon", + "config/static-rules.neon", + "config/symfony-rules.neon", + "config/symfony-config-rules.neon", + "config/doctrine-rules.neon", + "config/phpunit-rules.neon", "config/ctor-rules.neon", "config/mock-rules.neon", "config/phpstan-extensions.neon" diff --git a/config/doctrine-rules.neon b/config/doctrine-rules.neon index 6f524bef..6dd11ec2 100644 --- a/config/doctrine-rules.neon +++ b/config/doctrine-rules.neon @@ -1,14 +1,32 @@ -rules: +conditionalTags: + Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOutsideServiceRule: + phpstan.rules.rule: %symplify.doctrine% + Symplify\PHPStanRules\Rules\Doctrine\NoParentRepositoryRule: + phpstan.rules.rule: %symplify.doctrine% + Symplify\PHPStanRules\Rules\Doctrine\NoRepositoryCallInDataFixtureRule: + phpstan.rules.rule: %symplify.doctrine% + Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOnServiceRepositoryEntityRule: + phpstan.rules.rule: %symplify.doctrine% + + Symplify\PHPStanRules\Rules\Doctrine\NoDoctrineListenerWithoutContractRule: + phpstan.rules.rule: %symplify.doctrine% + + # test fixtures + Symplify\PHPStanRules\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule: + phpstan.rules.rule: %symplify.doctrine% + + # entity mapping + Symplify\PHPStanRules\Rules\Doctrine\NoStringTargetEntityRule: + phpstan.rules.rule: %symplify.doctrine% + Symplify\PHPStanRules\Rules\Doctrine\NoReadonlyEntityClassRule: + phpstan.rules.rule: %symplify.doctrine% + +services: - Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOutsideServiceRule - Symplify\PHPStanRules\Rules\Doctrine\NoParentRepositoryRule - Symplify\PHPStanRules\Rules\Doctrine\NoRepositoryCallInDataFixtureRule - Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOnServiceRepositoryEntityRule - - Symplify\PHPStanRules\Rules\Doctrine\NoDoctrineListenerWithoutContractRule - - # test fixtures - Symplify\PHPStanRules\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule - - # entity mapping - Symplify\PHPStanRules\Rules\Doctrine\NoStringTargetEntityRule - Symplify\PHPStanRules\Rules\Doctrine\NoReadonlyEntityClassRule diff --git a/config/phpstan-extensions.neon b/config/phpstan-extensions.neon index c48090bd..16b4be11 100644 --- a/config/phpstan-extensions.neon +++ b/config/phpstan-extensions.neon @@ -4,6 +4,10 @@ parameters: complexity: true configurable: true static: true + symfony: true + symfonyConfig: true + doctrine: true + phpunit: true mocks: false ctor: false symfonyReturnType: false @@ -21,6 +25,10 @@ parametersSchema: complexity: bool() configurable: bool() static: bool() + symfony: bool() + symfonyConfig: bool() + doctrine: bool() + phpunit: bool() mocks: bool() ctor: bool() symfonyReturnType: bool() diff --git a/config/phpunit-rules.neon b/config/phpunit-rules.neon index 4c9ff9c7..6d4a3d1a 100644 --- a/config/phpunit-rules.neon +++ b/config/phpunit-rules.neon @@ -1,3 +1,9 @@ -rules: +conditionalTags: + Symplify\PHPStanRules\Rules\PHPUnit\PublicStaticDataProviderRule: + phpstan.rules.rule: %symplify.phpunit% + Symplify\PHPStanRules\Rules\PHPUnit\NoAssertFuncCallInTestsRule: + phpstan.rules.rule: %symplify.phpunit% + +services: - Symplify\PHPStanRules\Rules\PHPUnit\PublicStaticDataProviderRule - Symplify\PHPStanRules\Rules\PHPUnit\NoAssertFuncCallInTestsRule diff --git a/config/symfony-config-rules.neon b/config/symfony-config-rules.neon index 37b65afe..42127216 100644 --- a/config/symfony-config-rules.neon +++ b/config/symfony-config-rules.neon @@ -1,38 +1,66 @@ -rules: - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\ServicesExcludedDirectoryMustExistRule - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoBundleResourceConfigRule - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\AlreadyRegisteredAutodiscoveryServiceRule - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\TaggedIteratorOverRepeatedServiceCallRule +conditionalTags: + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\ServicesExcludedDirectoryMustExistRule: + phpstan.rules.rule: %symplify.symfonyConfig% + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoBundleResourceConfigRule: + phpstan.rules.rule: %symplify.symfonyConfig% + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\AlreadyRegisteredAutodiscoveryServiceRule: + phpstan.rules.rule: %symplify.symfonyConfig% + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\TaggedIteratorOverRepeatedServiceCallRule: + phpstan.rules.rule: %symplify.symfonyConfig% # sync file name and extension name - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\FileNameMatchesExtensionRule + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\FileNameMatchesExtensionRule: + phpstan.rules.rule: %symplify.symfonyConfig% # no autowire duplicate - - Symplify\PHPStanRules\Rules\Symfony\NoServiceAutowireDuplicateRule + Symplify\PHPStanRules\Rules\Symfony\NoServiceAutowireDuplicateRule: + phpstan.rules.rule: %symplify.symfonyConfig% # args() and arg() call - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgsAutowireByTypeRule - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgAutowireByTypeRule + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgsAutowireByTypeRule: + phpstan.rules.rule: %symplify.symfonyConfig% + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgAutowireByTypeRule: + phpstan.rules.rule: %symplify.symfonyConfig% # $services->set('X', 'X') - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule: + phpstan.rules.rule: %symplify.symfonyConfig% # $services->set('X')->class('X') - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoSetClassServiceDuplicationRule + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoSetClassServiceDuplicationRule: + phpstan.rules.rule: %symplify.symfonyConfig% # service('id') where an alias points 'id' at a class - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\PreferClassServiceReferenceRule + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\PreferClassServiceReferenceRule: + phpstan.rules.rule: %symplify.symfonyConfig% # redundant ->tag() that autoconfigure() already adds - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoAutoconfiguredServiceTagRule + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoAutoconfiguredServiceTagRule: + phpstan.rules.rule: %symplify.symfonyConfig% # ->call('setX', [service()]) that should be a #[Required] setter - - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSetterCallRule + Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSetterCallRule: + phpstan.rules.rule: %symplify.symfonyConfig% + + + + + + + + services: - - - class: Symplify\PHPStanRules\Collector\ClassTargetServiceAliasCollector - tags: [phpstan.collector] - - - class: Symplify\PHPStanRules\Collector\ServiceStringReferenceCollector - tags: [phpstan.collector] + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\ServicesExcludedDirectoryMustExistRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoBundleResourceConfigRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\AlreadyRegisteredAutodiscoveryServiceRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\TaggedIteratorOverRepeatedServiceCallRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\FileNameMatchesExtensionRule + - Symplify\PHPStanRules\Rules\Symfony\NoServiceAutowireDuplicateRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgsAutowireByTypeRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoDuplicateArgAutowireByTypeRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSameNameSetClassRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoSetClassServiceDuplicationRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\PreferClassServiceReferenceRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoAutoconfiguredServiceTagRule + - Symplify\PHPStanRules\Rules\Symfony\ConfigClosure\NoServiceSetterCallRule diff --git a/config/symfony-rules.neon b/config/symfony-rules.neon index 6cc7befc..a52203e5 100644 --- a/config/symfony-rules.neon +++ b/config/symfony-rules.neon @@ -1,33 +1,79 @@ -rules: +conditionalTags: + Symplify\PHPStanRules\Rules\Symfony\SingleArgEventDispatchRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoListenerWithoutContractRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoStringInGetSubscribedEventsRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\FormTypeClassNameRule: + phpstan.rules.rule: %symplify.symfony% + + # routing + Symplify\PHPStanRules\Rules\Symfony\NoRoutingPrefixRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoClassLevelRouteRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoRouteTrailingSlashPathRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\RequireRouteNameToGenerateControllerRouteRule: + phpstan.rules.rule: %symplify.symfony% + + # dependency injection + Symplify\PHPStanRules\Rules\Symfony\NoServiceJugglingRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\PreferClassInDefinitionFetchRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoGetInControllerRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoGetInCommandRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoGetDoctrineInControllerRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoFindTaggedServiceIdsCallRule: + phpstan.rules.rule: %symplify.symfony% + + # magic required inject + Symplify\PHPStanRules\Rules\Symfony\SingleRequiredMethodRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoConstructorAndRequiredTogetherRule: + phpstan.rules.rule: %symplify.symfony% + + # attributes + Symplify\PHPStanRules\Rules\Symfony\RequireIsGrantedEnumRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoBareAndSecurityIsGrantedContentsRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\CommandMustHaveAsCommandAttributeRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\ConstraintMustHaveAttributeRule: + phpstan.rules.rule: %symplify.symfony% + + # constructor injection + Symplify\PHPStanRules\Rules\Symfony\PreferInterfaceInConstructorRule: + phpstan.rules.rule: %symplify.symfony% + Symplify\PHPStanRules\Rules\Symfony\NoNullableServiceInConstructorRule: + phpstan.rules.rule: %symplify.symfony% + +services: - Symplify\PHPStanRules\Rules\Symfony\SingleArgEventDispatchRule - Symplify\PHPStanRules\Rules\Symfony\NoListenerWithoutContractRule - Symplify\PHPStanRules\Rules\Symfony\NoStringInGetSubscribedEventsRule - Symplify\PHPStanRules\Rules\Symfony\FormTypeClassNameRule - - # routing - Symplify\PHPStanRules\Rules\Symfony\NoRoutingPrefixRule - Symplify\PHPStanRules\Rules\Symfony\NoClassLevelRouteRule - Symplify\PHPStanRules\Rules\Symfony\NoRouteTrailingSlashPathRule - Symplify\PHPStanRules\Rules\Symfony\RequireRouteNameToGenerateControllerRouteRule - - # dependency injection - Symplify\PHPStanRules\Rules\Symfony\NoServiceJugglingRule - Symplify\PHPStanRules\Rules\Symfony\PreferClassInDefinitionFetchRule - Symplify\PHPStanRules\Rules\Symfony\NoGetInControllerRule - Symplify\PHPStanRules\Rules\Symfony\NoGetInCommandRule - Symplify\PHPStanRules\Rules\Symfony\NoGetDoctrineInControllerRule - Symplify\PHPStanRules\Rules\Symfony\NoFindTaggedServiceIdsCallRule - - # magic required inject - Symplify\PHPStanRules\Rules\Symfony\SingleRequiredMethodRule - Symplify\PHPStanRules\Rules\Symfony\NoConstructorAndRequiredTogetherRule - - # attributes - Symplify\PHPStanRules\Rules\Symfony\RequireIsGrantedEnumRule - Symplify\PHPStanRules\Rules\Symfony\NoBareAndSecurityIsGrantedContentsRule - Symplify\PHPStanRules\Rules\Symfony\CommandMustHaveAsCommandAttributeRule - Symplify\PHPStanRules\Rules\Symfony\ConstraintMustHaveAttributeRule - - # constructor injection - Symplify\PHPStanRules\Rules\Symfony\PreferInterfaceInConstructorRule - Symplify\PHPStanRules\Rules\Symfony\NoNullableServiceInConstructorRule