Skip to content

Commit db9c015

Browse files
committed
Can re-allow namespace if in a class with given attributes
And can add `allowInUse` which is handy when using the `allowExceptIn...` (or `disallowIn...`) config option.
1 parent 92c3b5b commit db9c015

15 files changed

+315
-82
lines changed

docs/allow-in-class-with-attributes.md

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
## Allow in class with given attributes
22

3-
It is possible to allow a previously disallowed function, method call or an attribute usage when done in a class with specified attributes.
3+
It is possible to allow a previously disallowed item when done in a class with specified attributes.
44
You can use the `allowInClassWithAttributes` configuration option.
55

6+
This is supported for the following items:
7+
- function calls
8+
- method calls
9+
- attribute usages
10+
- namespace usages
11+
- classname usages
12+
613
For example, if you'd have a configuration like this:
714

815
```neon
@@ -27,7 +34,7 @@ class Foo
2734
}
2835
```
2936

30-
On the other hand, if you need to disallow a method call, a function call, or an attribute usage only when present in a method from a class with a given attribute,
37+
On the other hand, if you need to disallow an item only when present in a method from a class with a given attribute,
3138
use `allowExceptInClassWithAttributes` (or the `disallowInClassWithAttributes` alias):
3239

3340
```neon
@@ -65,3 +72,51 @@ class Foo
6572
```
6673

6774
The attribute names in the _allow_ directives support [fnmatch()](https://www.php.net/function.fnmatch) patterns.
75+
76+
### Allow namespace or classname use in `use` imports
77+
78+
You can allow a namespace or a classname to be used in `use` imports with `allowInUse: true`.
79+
This can be useful when you want to disallow a namespace usage in a class with an attribute (with `allowExceptInClassWithAttributes` or `disallowInClassWithAttributes`),
80+
but don't want the error to be reported on line with the `use` statement.
81+
82+
Let's have a class like this:
83+
84+
```php
85+
use Foo\Bar\DisallowedClass; // line 1
86+
87+
#[MyAttribute]
88+
class Waldo
89+
{
90+
91+
public function fred(DisallowedClass $param) // line 7
92+
{
93+
}
94+
95+
}
96+
```
97+
98+
Then with a configuration like this:
99+
100+
```neon
101+
parameters:
102+
disallowedNamespace:
103+
-
104+
namespace: 'Foo\Bar\DisallowedClass'
105+
allowExceptInClassWithAttributes:
106+
- MyAttribute
107+
```
108+
109+
the error would be reported both on line 1, because `use Foo\Bar\DisallowedClass;` uses a disallowed namespace, and on line 7 because `$param` has the disallowed type.
110+
But maybe you'd expect the error to be reported only on line 7, because _that_ is a disallowed class used in a class with the `MyAttribute` attribute.
111+
112+
To omit the `use` finding, you can add the `allowInUse` line, like this:
113+
114+
```neon
115+
parameters:
116+
disallowedNamespace:
117+
-
118+
namespace: 'Foo\Bar\DisallowedClass'
119+
allowExceptInClassWithAttributes:
120+
- MyAttribute
121+
allowInUse: true
122+
```

extension.neon

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ parametersSchema:
2424
?allowIn: listOf(string()),
2525
?allowExceptIn: listOf(string()),
2626
?disallowIn: listOf(string()),
27+
?allowInClassWithAttributes: listOf(string()),
28+
?allowExceptInClassWithAttributes: listOf(string()),
29+
?disallowInClassWithAttributes: listOf(string()),
30+
?allowInCallWithAttributes: listOf(string()),
31+
?allowExceptInCallWithAttributes: listOf(string()),
32+
?disallowInCallWithAttributes: listOf(string()),
33+
?allowInClassWithMethodAttributes: listOf(string()),
34+
?allowExceptInClassWithMethodAttributes: listOf(string()),
35+
?disallowInClassWithMethodAttributes: listOf(string()),
36+
?allowInUse: bool(),
2737
?errorIdentifier: string(),
2838
?errorTip: string(),
2939
])
@@ -37,6 +47,16 @@ parametersSchema:
3747
?allowIn: listOf(string()),
3848
?allowExceptIn: listOf(string()),
3949
?disallowIn: listOf(string()),
50+
?allowInClassWithAttributes: listOf(string()),
51+
?allowExceptInClassWithAttributes: listOf(string()),
52+
?disallowInClassWithAttributes: listOf(string()),
53+
?allowInCallWithAttributes: listOf(string()),
54+
?allowExceptInCallWithAttributes: listOf(string()),
55+
?disallowInCallWithAttributes: listOf(string()),
56+
?allowInClassWithMethodAttributes: listOf(string()),
57+
?allowExceptInClassWithMethodAttributes: listOf(string()),
58+
?disallowInClassWithMethodAttributes: listOf(string()),
59+
?allowInUse: bool(),
4060
?errorIdentifier: string(),
4161
?errorTip: string(),
4262
])
@@ -298,6 +318,7 @@ services:
298318
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedCallsRuleErrors
299319
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedVariableRuleErrors
300320
- Spaze\PHPStan\Rules\Disallowed\Type\TypeResolver
321+
- Spaze\PHPStan\Rules\Disallowed\Usages\NamespaceUsageFactory
301322
-
302323
factory: Spaze\PHPStan\Rules\Disallowed\Usages\NamespaceUsages(forbiddenNamespaces: %disallowedNamespaces%)
303324
tags:

src/Allowed/Allowed.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Reflection\MethodReflection;
1212
use PHPStan\Type\Type;
1313
use PHPStan\Type\UnionType;
14+
use Spaze\PHPStan\Rules\Disallowed\Disallowed;
1415
use Spaze\PHPStan\Rules\Disallowed\DisallowedWithParams;
1516
use Spaze\PHPStan\Rules\Disallowed\Exceptions\UnsupportedParamTypeException;
1617
use Spaze\PHPStan\Rules\Disallowed\Formatter\Formatter;
@@ -36,14 +37,15 @@ public function __construct(
3637
/**
3738
* @param Scope $scope
3839
* @param array<Arg>|null $args
39-
* @param DisallowedWithParams $disallowed
40+
* @param Disallowed|DisallowedWithParams $disallowed
4041
* @return bool
4142
*/
42-
public function isAllowed(Scope $scope, ?array $args, DisallowedWithParams $disallowed): bool
43+
public function isAllowed(Scope $scope, ?array $args, Disallowed $disallowed): bool
4344
{
45+
$hasParams = $disallowed instanceof DisallowedWithParams;
4446
foreach ($disallowed->getAllowInCalls() as $call) {
4547
if ($this->callMatches($scope, $call)) {
46-
return $this->hasAllowedParamsInAllowed($scope, $args, $disallowed);
48+
return !$hasParams || $this->hasAllowedParamsInAllowed($scope, $args, $disallowed);
4749
}
4850
}
4951
foreach ($disallowed->getAllowExceptInCalls() as $call) {
@@ -53,7 +55,7 @@ public function isAllowed(Scope $scope, ?array $args, DisallowedWithParams $disa
5355
}
5456
foreach ($disallowed->getAllowIn() as $allowedPath) {
5557
if ($this->allowedPath->matches($scope, $allowedPath)) {
56-
return $this->hasAllowedParamsInAllowed($scope, $args, $disallowed);
58+
return !$hasParams || $this->hasAllowedParamsInAllowed($scope, $args, $disallowed);
5759
}
5860
}
5961
if ($disallowed->getAllowExceptIn()) {
@@ -64,10 +66,10 @@ public function isAllowed(Scope $scope, ?array $args, DisallowedWithParams $disa
6466
}
6567
return true;
6668
}
67-
if ($disallowed->getAllowExceptParams()) {
69+
if ($hasParams && $disallowed->getAllowExceptParams()) {
6870
return $this->hasAllowedParams($scope, $args, $disallowed->getAllowExceptParams(), false);
6971
}
70-
if ($disallowed->getAllowParamsAnywhere()) {
72+
if ($hasParams && $disallowed->getAllowParamsAnywhere()) {
7173
return $this->hasAllowedParams($scope, $args, $disallowed->getAllowParamsAnywhere(), true);
7274
}
7375
if ($disallowed->getAllowInClassWithAttributes() && $scope->isInClass()) {

src/DisallowedNamespace.php

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Spaze\PHPStan\Rules\Disallowed;
55

6-
use Spaze\PHPStan\Rules\Disallowed\Exceptions\NotImplementedYetException;
6+
use Spaze\PHPStan\Rules\Disallowed\Allowed\AllowedConfig;
77

88
class DisallowedNamespace implements Disallowed
99
{
@@ -15,40 +15,37 @@ class DisallowedNamespace implements Disallowed
1515

1616
private ?string $message;
1717

18-
/** @var list<string> */
19-
private array $allowIn;
20-
21-
/** @var list<string> */
22-
private array $allowExceptIn;
23-
2418
private ?string $errorIdentifier;
2519

2620
private ?string $errorTip;
2721

22+
private AllowedConfig $allowedConfig;
23+
24+
private bool $allowInUse;
25+
2826

2927
/**
3028
* @param string $namespace
3129
* @param list<string> $excludes
3230
* @param string|null $message
33-
* @param list<string> $allowIn
34-
* @param list<string> $allowExceptIn
31+
* @param AllowedConfig $allowedConfig
3532
* @param string|null $errorIdentifier
3633
* @param string|null $errorTip
3734
*/
3835
public function __construct(
3936
string $namespace,
4037
array $excludes,
4138
?string $message,
42-
array $allowIn,
43-
array $allowExceptIn,
39+
AllowedConfig $allowedConfig,
40+
bool $allowInUse,
4441
?string $errorIdentifier,
4542
?string $errorTip
4643
) {
4744
$this->namespace = $namespace;
4845
$this->excludes = $excludes;
4946
$this->message = $message;
50-
$this->allowIn = $allowIn;
51-
$this->allowExceptIn = $allowExceptIn;
47+
$this->allowedConfig = $allowedConfig;
48+
$this->allowInUse = $allowInUse;
5249
$this->errorIdentifier = $errorIdentifier;
5350
$this->errorTip = $errorTip;
5451
}
@@ -78,38 +75,44 @@ public function getMessage(): ?string
7875
/** @inheritDoc */
7976
public function getAllowIn(): array
8077
{
81-
return $this->allowIn;
78+
return $this->allowedConfig->getAllowIn();
8279
}
8380

8481

8582
/** @inheritDoc */
8683
public function getAllowExceptIn(): array
8784
{
88-
return $this->allowExceptIn;
85+
return $this->allowedConfig->getAllowExceptIn();
8986
}
9087

9188

9289
public function getAllowInCalls(): array
9390
{
94-
throw new NotImplementedYetException();
91+
return $this->allowedConfig->getAllowInCalls();
9592
}
9693

9794

9895
public function getAllowExceptInCalls(): array
9996
{
100-
throw new NotImplementedYetException();
97+
return $this->allowedConfig->getAllowExceptInCalls();
10198
}
10299

103100

104101
public function getAllowInClassWithAttributes(): array
105102
{
106-
throw new NotImplementedYetException();
103+
return $this->allowedConfig->getAllowInClassWithAttributes();
107104
}
108105

109106

110107
public function getAllowExceptInClassWithAttributes(): array
111108
{
112-
throw new NotImplementedYetException();
109+
return $this->allowedConfig->getAllowExceptInClassWithAttributes();
110+
}
111+
112+
113+
public function isAllowInUse(): bool
114+
{
115+
return $this->allowInUse;
113116
}
114117

115118

src/DisallowedNamespaceFactory.php

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,33 @@
44
namespace Spaze\PHPStan\Rules\Disallowed;
55

66
use PHPStan\ShouldNotHappenException;
7+
use Spaze\PHPStan\Rules\Disallowed\Allowed\AllowedConfigFactory;
8+
use Spaze\PHPStan\Rules\Disallowed\Exceptions\UnsupportedParamTypeInConfigException;
9+
use Spaze\PHPStan\Rules\Disallowed\Formatter\Formatter;
710
use Spaze\PHPStan\Rules\Disallowed\Normalizer\Normalizer;
811

912
class DisallowedNamespaceFactory
1013
{
1114

15+
private Formatter $formatter;
16+
1217
private Normalizer $normalizer;
1318

19+
private AllowedConfigFactory $allowedConfigFactory;
20+
1421

15-
public function __construct(Normalizer $normalizer)
22+
public function __construct(Formatter $formatter, Normalizer $normalizer, AllowedConfigFactory $allowedConfigFactory)
1623
{
24+
$this->formatter = $formatter;
1725
$this->normalizer = $normalizer;
26+
$this->allowedConfigFactory = $allowedConfigFactory;
1827
}
1928

2029

2130
/**
22-
* @param array<array{namespace?:string|list<string>, class?:string|list<string>, exclude?:string|list<string>, message?:string, allowIn?:list<string>, allowExceptIn?:list<string>, disallowIn?:list<string>, errorIdentifier?:string, errorTip?:string}> $config
31+
* @param array<array{namespace?:string|list<string>, class?:string|list<string>, exclude?:string|list<string>, message?:string, allowIn?:list<string>, allowExceptIn?:list<string>, disallowIn?:list<string>, allowInUse?:bool, errorIdentifier?:string, errorTip?:string}> $config
2332
* @return list<DisallowedNamespace>
33+
* @throws ShouldNotHappenException
2434
*/
2535
public function createFromConfig(array $config): array
2636
{
@@ -35,17 +45,22 @@ public function createFromConfig(array $config): array
3545
foreach ((array)($disallowed['exclude'] ?? []) as $exclude) {
3646
$excludes[] = $this->normalizer->normalizeNamespace($exclude);
3747
}
38-
foreach ((array)$namespaces as $namespace) {
39-
$disallowedNamespace = new DisallowedNamespace(
40-
$this->normalizer->normalizeNamespace($namespace),
41-
$excludes,
42-
$disallowed['message'] ?? null,
43-
$disallowed['allowIn'] ?? [],
44-
$disallowed['allowExceptIn'] ?? $disallowed['disallowIn'] ?? [],
45-
$disallowed['errorIdentifier'] ?? null,
46-
$disallowed['errorTip'] ?? null
47-
);
48-
$disallowedNamespaces[$disallowedNamespace->getNamespace()] = $disallowedNamespace;
48+
$namespaces = (array)$namespaces;
49+
try {
50+
foreach ($namespaces as $namespace) {
51+
$disallowedNamespace = new DisallowedNamespace(
52+
$this->normalizer->normalizeNamespace($namespace),
53+
$excludes,
54+
$disallowed['message'] ?? null,
55+
$this->allowedConfigFactory->getConfig($disallowed),
56+
$disallowed['allowInUse'] ?? false,
57+
$disallowed['errorIdentifier'] ?? null,
58+
$disallowed['errorTip'] ?? null
59+
);
60+
$disallowedNamespaces[$disallowedNamespace->getNamespace()] = $disallowedNamespace;
61+
}
62+
} catch (UnsupportedParamTypeInConfigException $e) {
63+
throw new ShouldNotHappenException(sprintf('%s: %s', $this->formatter->formatIdentifier($namespaces), $e->getMessage()));
4964
}
5065
}
5166
return array_values($disallowedNamespaces);

0 commit comments

Comments
 (0)