Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Build
env:
Expand Down Expand Up @@ -48,8 +46,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set service name
run: |
Expand Down Expand Up @@ -82,8 +78,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Build
env:
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependabot-automerge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions config/common.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -40,6 +42,7 @@ services:
class: IWFWeb\PhpstanRules\Common\AttributeRequirementsRule
arguments:
attributeDefinitions: %iwfWeb.attributeRequirements.attributeDefinitions%
excludedClasses: %iwfWeb.attributeRequirements.excludedClasses%
tags:
- phpstan.rules.rule

Expand Down
7 changes: 7 additions & 0 deletions src/Common/AttributeRequirementsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@

/**
* @param list<array{attribute: string, requires: list<string>}> $attributeDefinitions
* @param list<string> $excludedClasses Fully-qualified class names to skip
*/
public function __construct(
private array $attributeDefinitions = [],
private array $excludedClasses = [],
) {}

#[\Override]
Expand All @@ -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) {
Expand Down
49 changes: 49 additions & 0 deletions tests/AttributeRequirementsRuleExcludedClassesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types=1);

/**
* PHPStan Rules
*
* @package PHPStan Rules
* @author IWF Web Solutions <web-solutions@iwf.ch>
* @copyright Copyright (c) 2025-2026 IWF Web Solutions <web-solutions@iwf.ch>
* @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<AttributeRequirementsRule>
*
* @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);
}
}
13 changes: 13 additions & 0 deletions tests/data/attribute-requirements-excluded.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php /** @noinspection ALL */ declare(strict_types=1);

namespace App\Controller\Api\Security;

use Symfony\Component\Routing\Attribute\Route;

class LoginController
{
#[Route('/login')]
public function __invoke(): void
{
}
}
Loading