Skip to content

Commit ac328f8

Browse files
floriankraemerFlorian Krämer
andauthored
PHPStan Level 8 (#29)
* Refactor rules to improve type handling and documentation - Updated various rule classes to use IdentifierRuleError for error handling instead of RuleError. - Enhanced method signatures with type hints for better clarity and type safety. - Added parameter type annotations in processNode methods for improved documentation. - Removed unnecessary instance checks for nodes in processNode methods to streamline logic. - Improved error handling and reporting consistency across rules. * Add PHPStan and PHPCS workflows to CI configuration - Introduced new jobs for PHPStan and PHPCS in the CI pipeline to enhance code quality checks. - Configured both jobs to run on Ubuntu 24.04 with PHP 8.4. - Added steps for checking out the code, setting up PHP, installing Composer dependencies, and running the respective analysis tools. - Ensured that the workflows are set up to improve static analysis and coding standards enforcement. * Remove trailing whitespace in ForbiddenDependenciesRule.php --------- Co-authored-by: Florian Krämer <f.kraemer@clipmyhorse.tv>
1 parent cff3ef4 commit ac328f8

17 files changed

Lines changed: 257 additions & 143 deletions

.github/workflows/ci.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,45 @@ jobs:
4343
- name: Code Coverage Report
4444
if: success() && matrix.php-version == '8.3'
4545
uses: codecov/codecov-action@v4
46+
47+
phpstan:
48+
name: PHPStan
49+
runs-on: ubuntu-24.04
50+
51+
steps:
52+
- uses: actions/checkout@v4
53+
with:
54+
fetch-depth: 1
55+
56+
- name: Setup PHP
57+
uses: shivammathur/setup-php@v2
58+
with:
59+
php-version: '8.4'
60+
extensions: json, fileinfo
61+
62+
- name: Composer install
63+
run: composer install
64+
65+
- name: Run PHPStan
66+
run: bin/phpstan analyse --no-progress
67+
68+
phpcs:
69+
name: PHPCS
70+
runs-on: ubuntu-24.04
71+
72+
steps:
73+
- uses: actions/checkout@v4
74+
with:
75+
fetch-depth: 1
76+
77+
- name: Setup PHP
78+
uses: shivammathur/setup-php@v2
79+
with:
80+
php-version: '8.4'
81+
extensions: json, fileinfo
82+
83+
- name: Composer install
84+
run: composer install
85+
86+
- name: Run PHPCS
87+
run: bin/phpcs

src/Architecture/CatchExceptionOfTypeNotAllowedRule.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,11 @@ public function getNodeType(): string
5555
return Catch_::class;
5656
}
5757

58+
/**
59+
* @param Catch_ $node
60+
*/
5861
public function processNode(Node $node, Scope $scope): array
5962
{
60-
if (!$node instanceof Catch_) {
61-
return [];
62-
}
63-
6463
$errors = [];
6564

6665
foreach ($node->types as $type) {

src/Architecture/CircularModuleDependencyRule.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
use PhpParser\Node;
2020
use PhpParser\Node\Stmt\Use_;
2121
use PHPStan\Analyser\Scope;
22+
use PHPStan\Rules\IdentifierRuleError;
2223
use PHPStan\Rules\Rule;
23-
use PHPStan\Rules\RuleError;
2424
use PHPStan\Rules\RuleErrorBuilder;
2525
use PHPStan\ShouldNotHappenException;
2626

@@ -55,6 +55,7 @@ public function getNodeType(): string
5555
}
5656

5757
/**
58+
* @return list<IdentifierRuleError>
5859
* @throws ShouldNotHappenException
5960
*/
6061
public function processNode(Node $node, Scope $scope): array
@@ -153,7 +154,7 @@ private function detectCircularDependency(
153154
string $sourceModule,
154155
string $targetModule,
155156
int $line
156-
): ?RuleError {
157+
): ?IdentifierRuleError {
157158
// Check if adding this dependency would create a cycle
158159
// We're adding: sourceModule → targetModule
159160
// Check if there's already a path: targetModule → ... → sourceModule

src/Architecture/ClassMustBeFinalRule.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use PhpParser\Node;
2020
use PhpParser\Node\Stmt\Class_;
2121
use PHPStan\Analyser\Scope;
22+
use PHPStan\Rules\IdentifierRuleError;
2223
use PHPStan\Rules\Rule;
2324
use PHPStan\Rules\RuleErrorBuilder;
2425
use PHPStan\ShouldNotHappenException;
@@ -53,11 +54,12 @@ public function getNodeType(): string
5354
}
5455

5556
/**
57+
* @param Class_ $node
5658
* @throws ShouldNotHappenException
5759
*/
5860
public function processNode(Node $node, Scope $scope): array
5961
{
60-
if (!$node instanceof Class_ || !isset($node->name)) {
62+
if (!isset($node->name)) {
6163
return [];
6264
}
6365

@@ -79,7 +81,7 @@ public function processNode(Node $node, Scope $scope): array
7981
return [];
8082
}
8183

82-
private function buildRuleError(string $fullClassName)
84+
private function buildRuleError(string $fullClassName): IdentifierRuleError
8385
{
8486
return RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $fullClassName))
8587
->identifier(self::IDENTIFIER)

src/Architecture/ClassMustBeReadonlyRule.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,12 @@ public function getNodeType(): string
5454
return Class_::class;
5555
}
5656

57+
/**
58+
* @param Class_ $node
59+
*/
5760
public function processNode(Node $node, Scope $scope): array
5861
{
59-
if (!$node instanceof Class_ || !isset($node->name)) {
62+
if (!isset($node->name)) {
6063
return [];
6164
}
6265

src/Architecture/ClassMustHaveSpecificationDocblockRule.php

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ private function extractDocblockLines(string $text): array
182182
$lines = explode("\n", $cleaned);
183183

184184
// Remove leading * and whitespace from each line
185-
$lines = array_map(function (string $line): string {
185+
/** @var list<string> $result */
186+
$result = [];
187+
foreach ($lines as $line) {
186188
$line = ltrim($line);
187189
if (strpos($line, '*') === 0) {
188190
$line = substr($line, 1);
@@ -191,19 +193,22 @@ private function extractDocblockLines(string $text): array
191193
$line = substr($line, 1);
192194
}
193195
}
194-
return $line;
195-
}, $lines);
196+
$result[] = $line;
197+
}
196198

197199
// Remove empty first and last lines that might be from the /** */ delimiters
198-
if ($lines !== [] && trim($lines[0]) === '') {
199-
array_shift($lines);
200+
// The list is guaranteed to have at least one element (explode always returns at least one)
201+
if (trim($result[0]) === '') {
202+
array_shift($result);
200203
}
201-
$lastIndex = count($lines) - 1;
202-
if ($lastIndex >= 0 && trim($lines[$lastIndex]) === '') {
203-
array_pop($lines);
204+
205+
// Check last element only if list still has elements after possible removal
206+
$resultCount = count($result);
207+
if ($resultCount > 0 && trim($result[$resultCount - 1]) === '') {
208+
array_pop($result);
204209
}
205210

206-
return $lines;
211+
return $result;
207212
}
208213

209214
/**

src/Architecture/ClassnameMustMatchPatternRule.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use PhpParser\Node\Stmt\Class_;
2121
use PhpParser\Node\Stmt\Namespace_;
2222
use PHPStan\Analyser\Scope;
23+
use PHPStan\Rules\IdentifierRuleError;
2324
use PHPStan\Rules\Rule;
2425
use PHPStan\Rules\RuleErrorBuilder;
2526
use PHPStan\ShouldNotHappenException;
@@ -51,13 +52,14 @@ public function getNodeType(): string
5152
return Namespace_::class;
5253
}
5354

55+
/**
56+
* @param Namespace_ $node
57+
* @return list<IdentifierRuleError>
58+
*/
5459
public function processNode(Node $node, Scope $scope): array
5560
{
56-
if (!$node instanceof Namespace_) {
57-
return [];
58-
}
59-
6061
$namespaceName = $node->name ? $node->name->toString() : '';
62+
/** @var list<IdentifierRuleError> $errors */
6163
$errors = [];
6264

6365
foreach ($this->namespaceClassPatterns as $config) {
@@ -118,16 +120,16 @@ private function buildErrorMessage(string $fqcn, string $namespace, array $patte
118120
/**
119121
* @param string $namespaceName
120122
* @param string $className
121-
* @param $classPatterns
123+
* @param array<string> $classPatterns
122124
* @param Class_ $stmt
123-
* @param array $errors
124-
* @return array
125+
* @param list<IdentifierRuleError> $errors
126+
* @return list<IdentifierRuleError>
125127
* @throws ShouldNotHappenException
126128
*/
127129
public function buildRuleError(
128130
string $namespaceName,
129131
string $className,
130-
$classPatterns,
132+
array $classPatterns,
131133
Class_ $stmt,
132134
array $errors
133135
): array {

src/Architecture/ForbiddenDependenciesRule.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,4 +456,3 @@ private function validateClassReference(string $className, string $currentNamesp
456456
return $errors;
457457
}
458458
}
459-

src/Architecture/ForbiddenNamespacesRule.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,11 @@ public function getNodeType(): string
5959
}
6060

6161
/**
62+
* @param Namespace_ $node
6263
* @throws ShouldNotHappenException
6364
*/
6465
public function processNode(Node $node, Scope $scope): array
6566
{
66-
if (!$node instanceof Namespace_) {
67-
return [];
68-
}
69-
7067
$namespaceName = $node->name ? $node->name->toString() : '';
7168

7269
// Empty namespace is allowed (global namespace)

0 commit comments

Comments
 (0)