Skip to content

Commit 23e5d35

Browse files
floriankraemerFlorian Krämer
andauthored
Improving the MethodMustReturnType to support unions (#7)
* Improving the MethodMustReturnType rule * Fixing edge cases * Add regex support for method return type validation - Introduced new classes for testing regex patterns in method return types. - Enhanced the MethodMustReturnTypeRule to support regex matching for expected return types. - Updated documentation to reflect regex usage in return type rules. - Added comprehensive tests for various valid and invalid cases involving regex patterns. --------- Co-authored-by: Florian Krämer <[email protected]>
1 parent ba9dd3c commit 23e5d35

16 files changed

+700
-33
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
class AnyOfTestClass
4+
{
5+
// Valid cases
6+
public function validObject(): SomeObject { return new SomeObject(); }
7+
public function validVoid(): void { return; }
8+
9+
// Invalid cases
10+
public function invalidType(): int { return 1; }
11+
}
12+
13+
class SomeObject {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
class EntityRegexTestClass
4+
{
5+
// Valid cases - these should match the regex pattern
6+
public function getUser(): UserEntity { return new UserEntity(); }
7+
public function getProduct(): ProductEntity { return new ProductEntity(); }
8+
public function getOrder(): OrderEntity { return new OrderEntity(); }
9+
public function getVoid(): void { return; }
10+
11+
// Invalid cases - these should not match
12+
public function getOther(): OtherClass { return new OtherClass(); }
13+
public function getString(): string { return 'test'; }
14+
}
15+
16+
class UserEntity {}
17+
class ProductEntity {}
18+
class OrderEntity {}
19+
class OtherClass {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
class Facade
4+
{
5+
public function someMethod(): SomeObject { return new SomeObject(); }
6+
public function anotherMethod(): void { return; }
7+
public function invalidMethod(): int { return 1; }
8+
}
9+
10+
class SomeObject {}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
class RegexAllOfTestClass
4+
{
5+
// Valid cases for allOf with regex patterns
6+
public function validUnionWithUser(): UserEntity|int { return new UserEntity(); }
7+
public function validUnionWithProduct(): ProductEntity|string { return 'test'; }
8+
9+
// Invalid cases
10+
public function invalidUnionMissingUser(): OtherClass|int { return 1; }
11+
public function invalidUnionMissingProduct(): UserEntity|OtherClass { return new UserEntity(); }
12+
}
13+
14+
class UserEntity {}
15+
class ProductEntity {}
16+
class OtherClass {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
class RegexTestClass
4+
{
5+
// Valid cases for regex patterns
6+
public function validUserEntity(): UserEntity { return new UserEntity(); }
7+
public function validProductEntity(): ProductEntity { return new ProductEntity(); }
8+
public function validVoid(): void { return; }
9+
public function validInt(): int { return 1; }
10+
11+
// Invalid cases
12+
public function invalidOtherClass(): OtherClass { return new OtherClass(); }
13+
public function invalidString(): string { return 'test'; }
14+
}
15+
16+
class UserEntity {}
17+
class ProductEntity {}
18+
class OtherClass {}

data/MethodMustReturnType/ReturnTypeTestClass.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
class ReturnTypeTestClass
44
{
5-
public function mustReturnInt(): void {}
6-
public function mustReturnNullableString(): string {}
7-
public function mustReturnVoid(): int {}
8-
public function mustReturnSpecificObject(): OtherObject {}
5+
// Invalid cases (should trigger errors)
6+
public function mustReturnInt(): void { return; }
7+
public function mustReturnNullableString(): string { return 'test'; }
8+
public function mustReturnVoid(): int { return 1; }
9+
public function mustReturnVoidLegacy(): int { return 1; }
10+
public function mustReturnSpecificObject(): OtherObject { return new OtherObject(); }
11+
public function mustReturnOneOf(): float { return 1.0; }
12+
public function mustReturnAllOf(): int { return 1; }
13+
public function mustReturnOneOfNullable(): string { return 'test'; }
914
}
1015

1116
class SomeObject {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
class UnionTypeTestClass
4+
{
5+
// Test cases for oneOf functionality
6+
public function validOneOfInt(): int { return 1; }
7+
public function validOneOfString(): string { return 'test'; }
8+
public function validOneOfBool(): bool { return true; }
9+
10+
// Test cases for allOf functionality (simplified)
11+
public function validAllOfInt(): int { return 1; }
12+
public function validAllOfString(): string { return 'test'; }
13+
14+
// Invalid cases
15+
public function invalidOneOf(): float { return 1.0; }
16+
public function invalidAllOf(): bool { return true; }
17+
}

docs/Rules.md

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Add them to your `phpstan.neon` configuration file under the section `services`.
77
Ensures that the nesting level of `if` and `try-catch` statements does not exceed a specified limit.
88

99
**Configuration Example:**
10+
1011
```neon
1112
-
1213
class: Phauthentic\PHPStanRules\CleanCode\ControlStructureNestingRule
@@ -21,6 +22,7 @@ Ensures that the nesting level of `if` and `try-catch` statements does not excee
2122
Checks that methods do not have more than a specified number of arguments.
2223

2324
**Configuration Example:**
25+
2426
```neon
2527
-
2628
class: Phauthentic\PHPStanRules\CleanCode\TooManyArgumentsRule
@@ -35,6 +37,7 @@ Checks that methods do not have more than a specified number of arguments.
3537
Ensures that classes matching specified patterns are declared as `readonly`.
3638

3739
**Configuration Example:**
40+
3841
```neon
3942
-
4043
class: Phauthentic\PHPStanRules\Architecture\ClassMustBeReadonlyRule
@@ -53,6 +56,7 @@ The constructor takes an array of namespace dependencies. The key is the namespa
5356
In the example below nothing from `App\Domain` can depend on anything from `App\Controller`.
5457

5558
**Configuration Example:**
59+
5660
```neon
5761
-
5862
class: Phauthentic\PHPStanRules\Architecture\DependencyConstraintsRule
@@ -69,6 +73,7 @@ In the example below nothing from `App\Domain` can depend on anything from `App\
6973
Ensures that classes matching specified patterns are declared as `final`.
7074

7175
**Configuration Example:**
76+
7277
```neon
7378
-
7479
class: Phauthentic\PHPStanRules\Architecture\ClassMustBeFinalRule
@@ -83,6 +88,7 @@ Ensures that classes matching specified patterns are declared as `final`.
8388
Ensures that classes inside namespaces matching a given regex must have names matching at least one of the provided patterns.
8489

8590
**Configuration Example:**
91+
8692
```neon
8793
-
8894
class: Phauthentic\PHPStanRules\Architecture\ClassnameMustMatchPatternRule
@@ -104,6 +110,7 @@ Ensures that classes inside namespaces matching a given regex must have names ma
104110
Ensures that specific exception types are not caught in catch blocks. This is useful for preventing the catching of overly broad exception types like `Exception`, `Error`, or `Throwable`.
105111

106112
**Configuration Example:**
113+
107114
```neon
108115
-
109116
class: Phauthentic\PHPStanRules\Architecture\CatchExceptionOfTypeNotAllowedRule
@@ -118,6 +125,7 @@ Ensures that specific exception types are not caught in catch blocks. This is us
118125
Ensures that methods returning boolean values follow a specific naming convention. By default, boolean methods should start with `is`, `has`, `can`, `should`, `was`, or `will`.
119126

120127
**Configuration Example:**
128+
121129
```neon
122130
-
123131
class: Phauthentic\PHPStanRules\Architecture\MethodsReturningBoolMustFollowNamingConventionRule
@@ -132,6 +140,7 @@ Ensures that methods returning boolean values follow a specific naming conventio
132140
Ensures that methods matching a class and method name pattern have a specific signature, including parameter types, names, and count.
133141

134142
**Configuration Example:**
143+
135144
```neon
136145
-
137146
class: Phauthentic\PHPStanRules\Architecture\MethodSignatureMustMatchRule
@@ -151,16 +160,18 @@ Ensures that methods matching a class and method name pattern have a specific si
151160
tags:
152161
- phpstan.rules.rule
153162
```
163+
154164
- `pattern`: Regex for `ClassName::methodName`.
155165
- `minParameters`/`maxParameters`: Minimum/maximum number of parameters.
156166
- `signature`: List of expected parameter types and (optionally) name patterns.
157167
- `visibilityScope`: Optional visibility scope (e.g., `public`, `protected`, `private`).
158168

159169
## Method Must Return Type Rule {#method-must-return-type-rule}
160170

161-
Ensures that methods matching a class and method name pattern have a specific return type, nullability, or are void.
171+
Ensures that methods matching a class and method name pattern have a specific return type, nullability, or are void. Supports union types with "oneOf" and "allOf" configurations.
162172

163173
**Configuration Example:**
174+
164175
```neon
165176
-
166177
class: Phauthentic\PHPStanRules\Architecture\MethodMustReturnTypeRule
@@ -182,13 +193,41 @@ Ensures that methods matching a class and method name pattern have a specific re
182193
pattern: '/^MyClass::reset$/'
183194
type: 'void'
184195
nullable: false
185-
void: true
196+
void: false
197+
objectTypePattern: null
198+
-
199+
pattern: '/^MyClass::getValue$/'
200+
nullable: false
201+
void: false
202+
oneOf: ['int', 'string', 'bool']
203+
objectTypePattern: null
204+
-
205+
pattern: '/^MyClass::getUnionType$/'
206+
nullable: false
207+
void: false
208+
allOf: ['int', 'string']
209+
objectTypePattern: null
210+
-
211+
pattern: '/^MyClass::getAnyType$/'
212+
anyOf: ['object', 'void']
213+
objectTypePattern: null
214+
-
215+
pattern: '/^MyClass::getEntity$/'
216+
anyOf: ['regex:/^App\\Entity\\/', 'void']
186217
objectTypePattern: null
187218
tags:
188219
- phpstan.rules.rule
189220
```
221+
190222
- `pattern`: Regex for `ClassName::methodName`.
191-
- `type`: Expected return type (`int`, `string`, `object`, etc.).
223+
- `type`: Expected return type (`int`, `string`, `object`, `void`, etc.). When using `oneOf` or `allOf`, this field is optional.
192224
- `nullable`: Whether the return type must be nullable.
193-
- `void`: Whether the method must return void.
225+
- `void`: Legacy field for void return types (use `type: 'void'` instead).
194226
- `objectTypePattern`: Regex for object return types (if `type` is `object`).
227+
- `oneOf`: Array of types where one must match (for union types).
228+
- `allOf`: Array of types where all must be present in the union type.
229+
- `anyOf`: Alias for `oneOf` - array of types where one must match.
230+
231+
**Regex Support**: You can use regex patterns in `oneOf`, `allOf`, and `anyOf` arrays by prefixing them with `regex:`. For example:
232+
- `'regex:/^App\\Entity\\/'` - matches any class starting with "App\Entity\"
233+
- `'regex:/^UserEntity$/'` - matches exactly "UserEntity"

0 commit comments

Comments
 (0)