diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3030ec..cdf9457 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,8 +15,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - with: - fetch-depth: 0 - name: Build env: @@ -48,8 +46,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - with: - fetch-depth: 0 - name: Set service name run: | @@ -82,8 +78,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - with: - fetch-depth: 0 - name: Build env: @@ -104,7 +98,7 @@ jobs: run: bin/test.sh --coverage 8.3 - name: Upload coverage - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-report path: coverage.xml @@ -117,10 +111,8 @@ jobs: needs: [coverage] steps: - uses: actions/checkout@v6 - with: - fetch-depth: 0 - name: Download coverage - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: coverage-report - name: SonarQube Scan diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 6ecf7e0..a64646f 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v6 - name: Fetch Dependabot metadata 🔍 - uses: dependabot/fetch-metadata@v2 + uses: dependabot/fetch-metadata@v3 id: metadata with: github-token: ${{ github.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 387f35f..579fbe3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Create Release PR - uses: googleapis/release-please-action@v4 + uses: googleapis/release-please-action@v5 with: # Bot PAT with access to Workflows # The built-in GITHUB_TOKEN has cannot trigger other workflows diff --git a/README.md b/README.md index b35e7a1..b20fe59 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ includes: ### Configuration -Several rules require or accept configuration parameters under the `iwf` key. +Several rules require or accept configuration parameters under the `iwfWeb` key. #### Controller rules diff --git a/config/common.neon b/config/common.neon index 64103be..03ba430 100644 --- a/config/common.neon +++ b/config/common.neon @@ -9,6 +9,8 @@ parameters: - { namespace: 'Symfony\Component\Validator\Constraints', alias: 'Assert' } - { namespace: 'Symfony\Component\Serializer\Attribute', alias: 'Serializer' } attributeRequirements: + excludedClasses: + - 'App\Controller\Api\Security\LoginController' attributeDefinitions: - attribute: 'Symfony\Component\Routing\Attribute\Route' @@ -40,6 +42,7 @@ services: class: IWFWeb\PhpstanRules\Common\AttributeRequirementsRule arguments: attributeDefinitions: %iwfWeb.attributeRequirements.attributeDefinitions% + excludedClasses: %iwfWeb.attributeRequirements.excludedClasses% tags: - phpstan.rules.rule diff --git a/src/Common/AttributeRequirementsRule.php b/src/Common/AttributeRequirementsRule.php index 180c4f9..fb3d060 100644 --- a/src/Common/AttributeRequirementsRule.php +++ b/src/Common/AttributeRequirementsRule.php @@ -31,9 +31,11 @@ /** * @param list}> $attributeDefinitions + * @param list $excludedClasses Fully-qualified class names to skip */ public function __construct( private array $attributeDefinitions = [], + private array $excludedClasses = [], ) {} #[\Override] @@ -56,6 +58,11 @@ public function processNode(Node $node, Scope $scope): array return []; } + $classReflection = $scope->getClassReflection(); + if ($classReflection !== null && \in_array($classReflection->getName(), $this->excludedClasses, true)) { + return []; + } + $presentAttributes = []; foreach ($node->attrGroups as $attrGroup) { diff --git a/tests/AttributeRequirementsRuleExcludedClassesTest.php b/tests/AttributeRequirementsRuleExcludedClassesTest.php new file mode 100644 index 0000000..343cdd7 --- /dev/null +++ b/tests/AttributeRequirementsRuleExcludedClassesTest.php @@ -0,0 +1,49 @@ + + * @copyright Copyright (c) 2025-2026 IWF Web Solutions + * @license https://github.com/iwf-web/phpstan-rules/blob/main/LICENSE.txt MIT License + * @link https://github.com/iwf-web/phpstan-rules + */ + +namespace IWFWeb\PhpstanRules\Tests; + +use App\Controller\Api\Security\LoginController; +use IWFWeb\PhpstanRules\Common\AttributeRequirementsRule; +use PHPStan\Rules\Rule; +use Symfony\Component\Routing\Attribute\Route; + +/** + * @extends AbstractRuleTestCase + * + * @internal + */ +final class AttributeRequirementsRuleExcludedClassesTest extends AbstractRuleTestCase +{ + protected function getRule(): Rule + { + return new AttributeRequirementsRule( + attributeDefinitions: [ + [ + 'attribute' => Route::class, + 'requires' => [ + 'OpenApi\Attributes\Tag', + 'Symfony\Component\Security\Http\Attribute\IsGranted', + ], + ], + ], + excludedClasses: [LoginController::class], + ); + } + + public function testExcludedClassIsIgnored(): void + { + $files = [__DIR__.'/data/attribute-requirements-excluded.php']; + $errors = $this->gatherAnalyserErrors($files); + self::assertNoRuleErrors($errors); + } +} diff --git a/tests/data/attribute-requirements-excluded.php b/tests/data/attribute-requirements-excluded.php new file mode 100644 index 0000000..bab0b62 --- /dev/null +++ b/tests/data/attribute-requirements-excluded.php @@ -0,0 +1,13 @@ +