Skip to content

Commit

Permalink
You can now allow some items in instanceof listed classes (#306)
Browse files Browse the repository at this point in the history
Use `allowInInstanceOf`, named after the PHP's `instanceof` operator, if you want to allow a function or a method call, an attribute, a classname, or a namespace in
- a class of given name
- a class that inherits from a class of given name
- a class that implements given interface

In case of a namespace, you may also use `allowInUse`, because if you allow a namespace/class `A` in a class `B` using `allowInInstanceOf`, you will also get error on line with `use A;` even if you will not get any error when `A` is used in `B`.

Close #302
  • Loading branch information
spaze authored Feb 20, 2025
2 parents bb164b0 + aa05d4d commit a72f6b2
Show file tree
Hide file tree
Showing 21 changed files with 455 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Let's say you have disallowed `foo()` with custom rules. But you want to re-allo
- [Allow in methods or functions](docs/allow-in-methods.md)
- [Allow with specified parameters](docs/allow-with-parameters.md)
- [Allow with specified flags](docs/allow-with-flags.md)
- [Allow in classes, child classes, classes implementing an interface](docs/allow-in-instance-of.md) (same as the `instanceof` operator)
- [Allow in class with given attributes](docs/allow-in-class-with-attributes.md)
- [Allow in methods or functions with given attributes](docs/allow-in-methods.md)
- [Allow in class with given attributes on any method](docs/allow-in-class-with-method-attributes.md)
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
},
"scripts": {
"lint": "vendor/bin/parallel-lint --colors src/ tests/",
"lint-7.x": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/functionCallsNamedParams.php --exclude tests/src/disallowed-allow/functionCallsNamedParams.php --exclude tests/src/disallowed/attributeUsages.php --exclude tests/src/disallowed-allow/attributeUsages.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php --exclude tests/src/Enums.php --exclude tests/src/disallowed/controlStructures.php --exclude tests/src/disallowed-allow/controlStructures.php --exclude tests/src/disallowed/firstClassCallable.php --exclude tests/src/disallowed-allow/firstClassCallable.php --exclude tests/src/disallowed/callableParameters.php --exclude tests/src/disallowed-allow/callableParameters.php",
"lint-7.x": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/functionCallsNamedParams.php --exclude tests/src/disallowed-allow/functionCallsNamedParams.php --exclude tests/src/disallowed/attributeUsages.php --exclude tests/src/disallowed-allow/attributeUsages.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php --exclude tests/src/Bar.php --exclude tests/src/Enums.php --exclude tests/src/disallowed/controlStructures.php --exclude tests/src/disallowed-allow/controlStructures.php --exclude tests/src/disallowed/firstClassCallable.php --exclude tests/src/disallowed-allow/firstClassCallable.php --exclude tests/src/disallowed/callableParameters.php --exclude tests/src/disallowed-allow/callableParameters.php",
"lint-8.0": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/TypesEverywhere.php --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php --exclude tests/src/Enums.php --exclude tests/src/disallowed/firstClassCallable.php --exclude tests/src/disallowed-allow/firstClassCallable.php",
"lint-8.1": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/AttributesEverywhere.php --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php --exclude tests/src/disallowed/firstClassCallable.php --exclude tests/src/disallowed-allow/firstClassCallable.php",
"lint-8.2": "vendor/bin/parallel-lint --colors src/ tests/ --exclude tests/src/disallowed/constantDynamicUsages.php --exclude tests/src/disallowed-allow/constantDynamicUsages.php",
Expand Down
45 changes: 45 additions & 0 deletions docs/allow-in-instance-of.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## Allow in classes, child classes, classes implementing an interface

Use `allowInInstanceOf`, named after the PHP's `instanceof` operator, if you want to allow a function or a method call, an attribute, a classname, or a namespace in
- a class of given name
- a class that inherits from a class of given name
- a class that implements given interface

This is useful for example when you want to allow properties or parameters of class `ClassName` in all classes that extend `ParentClass`:

```neon
parameters:
disallowedClasses:
-
class: 'ClassName'
allowInInstanceOf:
- 'ParentClass'
```
Another example could be if you'd like to disallow a `function()` in all classes that implement the `MyInterface` interface.
You can use the `allowExceptInInstanceOf` counterpart (or the `disallowInInstanceOf` alias) for that, like this:

```neon
parameters:
disallowedFunctionCalls:
-
function: 'function()'
disallowInInstanceOf:
- 'MyInterface'
```

### Allow in `use` imports
The `allowInInstanceOf` configuration above will also report an error on the line with the import, if present:
```php
use ClassName;
```
To omit the `use` finding, you can add the `allowInUse` line, like this:

```neon
parameters:
disallowedClasses:
-
class: 'ClassName'
allowInInstanceOf:
- 'ParentClass'
allowInUse: true
```
18 changes: 18 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ parametersSchema:
?allowIn: listOf(string()),
?allowExceptIn: listOf(string()),
?disallowIn: listOf(string()),
?allowInInstanceOf: listOf(string()),
?allowExceptInInstanceOf: listOf(string()),
?disallowInInstanceOf: listOf(string()),
?allowInClassWithAttributes: listOf(string()),
?allowExceptInClassWithAttributes: listOf(string()),
?disallowInClassWithAttributes: listOf(string()),
Expand All @@ -50,6 +53,9 @@ parametersSchema:
?allowIn: listOf(string()),
?allowExceptIn: listOf(string()),
?disallowIn: listOf(string()),
?allowInInstanceOf: listOf(string()),
?allowExceptInInstanceOf: listOf(string()),
?disallowInInstanceOf: listOf(string()),
?allowInClassWithAttributes: listOf(string()),
?allowExceptInClassWithAttributes: listOf(string()),
?disallowInClassWithAttributes: listOf(string()),
Expand Down Expand Up @@ -83,6 +89,9 @@ parametersSchema:
?allowExceptInMethods: listOf(string()),
?disallowInFunctions: listOf(string()),
?disallowInMethods: listOf(string()),
?allowInInstanceOf: listOf(string()),
?allowExceptInInstanceOf: listOf(string()),
?disallowInInstanceOf: listOf(string()),
?allowParamsInAllowed: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowParamsInAllowedAnyValue: arrayOf(anyOf(int(), structure([position: int(), ?name: string()])), anyOf(int(), string())),
?allowParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
Expand Down Expand Up @@ -133,6 +142,9 @@ parametersSchema:
?allowExceptInMethods: listOf(string()),
?disallowInFunctions: listOf(string()),
?disallowInMethods: listOf(string()),
?allowInInstanceOf: listOf(string()),
?allowExceptInInstanceOf: listOf(string()),
?disallowInInstanceOf: listOf(string()),
?allowParamsInAllowed: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowParamsInAllowedAnyValue: arrayOf(anyOf(int(), structure([position: int(), ?name: string()])), anyOf(int(), string())),
?allowParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
Expand Down Expand Up @@ -183,6 +195,9 @@ parametersSchema:
?allowExceptInMethods: listOf(string()),
?disallowInFunctions: listOf(string()),
?disallowInMethods: listOf(string()),
?allowInInstanceOf: listOf(string()),
?allowExceptInInstanceOf: listOf(string()),
?disallowInInstanceOf: listOf(string()),
?allowParamsInAllowed: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowParamsInAllowedAnyValue: arrayOf(anyOf(int(), structure([position: int(), ?name: string()])), anyOf(int(), string())),
?allowParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
Expand Down Expand Up @@ -265,6 +280,9 @@ parametersSchema:
?allowExceptInMethods: listOf(string()),
?disallowInFunctions: listOf(string()),
?disallowInMethods: listOf(string()),
?allowInInstanceOf: listOf(string()),
?allowExceptInInstanceOf: listOf(string()),
?disallowInInstanceOf: listOf(string()),
?allowParamsInAllowed: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowParamsInAllowedAnyValue: arrayOf(anyOf(int(), structure([position: int(), ?name: string()])), anyOf(int(), string())),
?allowParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ parameters:
CallParamFlagAnyValueConfig: 'array<int|string, int|array{position:int, value?:int, name?:string}>'
AllowParamDirectives: 'allowParamsInAllowed?:CallParamConfig, allowParamsInAllowedAnyValue?:CallParamAnyValueConfig, allowParamFlagsInAllowed?:CallParamFlagAnyValueConfig, allowParamsAnywhere?:CallParamConfig, allowParamsAnywhereAnyValue?:CallParamAnyValueConfig, allowParamFlagsAnywhere?:CallParamFlagAnyValueConfig, allowExceptParamsInAllowed?:CallParamConfig, allowExceptParamFlagsInAllowed?:CallParamFlagAnyValueConfig, disallowParamFlagsInAllowed?:CallParamFlagAnyValueConfig, disallowParamsInAllowed?:CallParamConfig, allowExceptParams?:CallParamConfig, disallowParams?:CallParamConfig, allowExceptParamsAnyValue?:CallParamAnyValueConfig, disallowParamsAnyValue?:CallParamAnyValueConfig, allowExceptParamFlags?:CallParamFlagAnyValueConfig, disallowParamFlags?:CallParamFlagAnyValueConfig, allowExceptCaseInsensitiveParams?:CallParamConfig, disallowCaseInsensitiveParams?:CallParamConfig'
AllowAttributesDirectives: 'allowInClassWithAttributes?:list<string>, allowExceptInClassWithAttributes?:list<string>, disallowInClassWithAttributes?:list<string>, allowInFunctionsWithAttributes?:list<string>, allowInMethodsWithAttributes?:list<string>, allowExceptInFunctionsWithAttributes?:list<string>, allowExceptInMethodsWithAttributes?:list<string>, disallowInFunctionsWithAttributes?:list<string>, disallowInMethodsWithAttributes?:list<string>, allowInClassWithMethodAttributes?:list<string>, allowExceptInClassWithMethodAttributes?:list<string>, disallowInClassWithMethodAttributes?:list<string>'
AllowDirectives: 'allowIn?:list<string>, allowExceptIn?:list<string>, disallowIn?:list<string>, allowInFunctions?:list<string>, allowInMethods?:list<string>, allowExceptInFunctions?:list<string>, allowExceptInMethods?:list<string>, disallowInFunctions?:list<string>, disallowInMethods?:list<string>, %typeAliases.AllowParamDirectives%, %typeAliases.AllowAttributesDirectives%'
AllowDirectives: 'allowIn?:list<string>, allowExceptIn?:list<string>, disallowIn?:list<string>, allowInFunctions?:list<string>, allowInMethods?:list<string>, allowExceptInFunctions?:list<string>, allowExceptInMethods?:list<string>, disallowInFunctions?:list<string>, disallowInMethods?:list<string>, allowInInstanceOf?:list<string>, allowExceptInInstanceOf?:list<string>, disallowInInstanceOf?:list<string>, %typeAliases.AllowParamDirectives%, %typeAliases.AllowAttributesDirectives%'
ForbiddenCallsConfig: 'array<array{function?:string|list<string>, method?:string|list<string>, exclude?:string|list<string>, definedIn?:string|list<string>, message?:string, %typeAliases.AllowDirectives%, errorIdentifier?:string, errorTip?:string}>'
DisallowedAttributesConfig: 'array<array{attribute:string|list<string>, exclude?:string|list<string>, message?:string, %typeAliases.AllowDirectives%, errorIdentifier?:string, errorTip?:string}>'
AllowDirectivesConfig: 'array{%typeAliases.AllowDirectives%}'
Expand Down
22 changes: 22 additions & 0 deletions src/Allowed/Allowed.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ public function isAllowed(?Node $node, Scope $scope, ?array $args, Disallowed $d
}
return true;
}
if ($disallowed->getAllowInInstanceOf()) {
return $this->isInstanceOf($scope, $disallowed->getAllowInInstanceOf());
}
if ($disallowed->getAllowExceptInInstanceOf()) {
return !$this->isInstanceOf($scope, $disallowed->getAllowExceptInInstanceOf());
}
if ($hasParams && $disallowed->getAllowExceptParams()) {
return $this->hasAllowedParams($scope, $args, $disallowed->getAllowExceptParams(), false);
}
Expand Down Expand Up @@ -122,6 +128,22 @@ private function callMatches(Scope $scope, string $call): bool
}


/**
* @param Scope $scope
* @param list<string> $allowConfig
* @return bool
*/
private function isInstanceOf(Scope $scope, array $allowConfig): bool
{
foreach ($allowConfig as $allowInstanceOf) {
if ($scope->isInClass() && $scope->getClassReflection()->is($allowInstanceOf)) {
return true;
}
}
return false;
}


/**
* @param Scope $scope
* @param array<Arg>|null $args
Expand Down
30 changes: 30 additions & 0 deletions src/Allowed/AllowedConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ class AllowedConfig
/** @var list<string> */
private array $allowExceptInCalls;

/** @var list<string> */
private array $allowInInstanceOf;

/** @var list<string> */
private array $allowExceptInInstanceOf;

/** @var list<string> */
private array $allowInClassWithAttributes;

Expand Down Expand Up @@ -56,6 +62,8 @@ class AllowedConfig
* @param list<string> $allowExceptIn
* @param list<string> $allowInCalls
* @param list<string> $allowExceptInCalls
* @param list<string> $allowInInstanceOf
* @param list<string> $allowExceptInInstanceOf
* @param list<string> $allowInClassWithAttributes
* @param list<string> $allowExceptInClassWithAttributes
* @param list<string> $allowInCallsWithAttributes
Expand All @@ -72,6 +80,8 @@ public function __construct(
array $allowExceptIn,
array $allowInCalls,
array $allowExceptInCalls,
array $allowInInstanceOf,
array $allowExceptInInstanceOf,
array $allowInClassWithAttributes,
array $allowExceptInClassWithAttributes,
array $allowInCallsWithAttributes,
Expand All @@ -87,6 +97,8 @@ public function __construct(
$this->allowExceptIn = $allowExceptIn;
$this->allowInCalls = $allowInCalls;
$this->allowExceptInCalls = $allowExceptInCalls;
$this->allowInInstanceOf = $allowInInstanceOf;
$this->allowExceptInInstanceOf = $allowExceptInInstanceOf;
$this->allowInClassWithAttributes = $allowInClassWithAttributes;
$this->allowExceptInClassWithAttributes = $allowExceptInClassWithAttributes;
$this->allowInCallsWithAttributes = $allowInCallsWithAttributes;
Expand Down Expand Up @@ -136,6 +148,24 @@ public function getAllowExceptInCalls(): array
}


/**
* @return list<string>
*/
public function getAllowInInstanceOf(): array
{
return $this->allowInInstanceOf;
}


/**
* @return list<string>
*/
public function getAllowExceptInInstancesOf(): array
{
return $this->allowExceptInInstanceOf;
}


/**
* @return list<string>
*/
Expand Down
10 changes: 9 additions & 1 deletion src/Allowed/AllowedConfigFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function __construct(
*/
public function getConfig(array $allowed): AllowedConfig
{
$allowInCalls = $allowExceptInCalls = $allowInClassWithAttributes = $allowExceptInClassWithAttributes = [];
$allowInCalls = $allowExceptInCalls = $allowInInstanceOf = $allowExceptInInstanceOf = $allowInClassWithAttributes = $allowExceptInClassWithAttributes = [];
$allowInCallsWithAttributes = $allowExceptInCallsWithAttributes = $allowInClassWithMethodAttributes = $allowExceptInClassWithMethodAttributes = [];
$allowParamsInAllowed = $allowParamsAnywhere = $allowExceptParamsInAllowed = $allowExceptParams = [];

Expand All @@ -56,6 +56,12 @@ public function getConfig(array $allowed): AllowedConfig
foreach ($allowed['allowExceptInFunctions'] ?? $allowed['allowExceptInMethods'] ?? $allowed['disallowInFunctions'] ?? $allowed['disallowInMethods'] ?? [] as $disallowedCall) {
$allowExceptInCalls[] = $this->normalizer->normalizeCall($disallowedCall);
}
foreach ($allowed['allowInInstanceOf'] ?? [] as $allowedInstanceOf) {
$allowInInstanceOf[] = $this->normalizer->normalizeNamespace($allowedInstanceOf);
}
foreach ($allowed['allowExceptInInstanceOf'] ?? $allowed['disallowInInstanceOf'] ?? [] as $disallowedInstanceOf) {
$allowExceptInInstanceOf[] = $this->normalizer->normalizeNamespace($disallowedInstanceOf);
}
foreach ($allowed['allowInClassWithAttributes'] ?? [] as $allowInClassAttribute) {
$allowInClassWithAttributes[] = $this->normalizer->normalizeAttribute($allowInClassAttribute);
}
Expand Down Expand Up @@ -115,6 +121,8 @@ public function getConfig(array $allowed): AllowedConfig
$allowed['allowExceptIn'] ?? $allowed['disallowIn'] ?? [],
$allowInCalls,
$allowExceptInCalls,
$allowInInstanceOf,
$allowExceptInInstanceOf,
$allowInClassWithAttributes,
$allowExceptInClassWithAttributes,
$allowInCallsWithAttributes,
Expand Down
12 changes: 12 additions & 0 deletions src/Disallowed.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ public function getAllowInCalls(): array;
public function getAllowExceptInCalls(): array;


/**
* @return list<string>
*/
public function getAllowInInstanceOf(): array;


/**
* @return list<string>
*/
public function getAllowExceptInInstanceOf(): array;


/**
* @return list<string>
*/
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ public function getAllowExceptInCalls(): array
}


public function getAllowInInstanceOf(): array
{
return $this->allowedConfig->getAllowInInstanceOf();
}


public function getAllowExceptInInstanceOf(): array
{
return $this->allowedConfig->getAllowExceptInInstancesOf();
}


public function getAllowParamsInAllowed(): array
{
return $this->allowedConfig->getAllowParamsInAllowed();
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ public function getAllowExceptInCalls(): array
}


public function getAllowInInstanceOf(): array
{
return $this->allowedConfig->getAllowInInstanceOf();
}


public function getAllowExceptInInstanceOf(): array
{
return $this->allowedConfig->getAllowExceptInInstancesOf();
}


public function getAllowParamsInAllowed(): array
{
return $this->allowedConfig->getAllowParamsInAllowed();
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedConstant.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ public function getAllowExceptInCalls(): array
}


public function getAllowInInstanceOf(): array
{
throw new NotImplementedYetException();
}


public function getAllowExceptInInstanceOf(): array
{
throw new NotImplementedYetException();
}


public function getAllowInClassWithAttributes(): array
{
throw new NotImplementedYetException();
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedControlStructure.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ public function getAllowExceptInCalls(): array
}


public function getAllowInInstanceOf(): array
{
throw new NotImplementedYetException();
}


public function getAllowExceptInInstanceOf(): array
{
throw new NotImplementedYetException();
}


public function getAllowInClassWithAttributes(): array
{
throw new NotImplementedYetException();
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedNamespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ public function getAllowInCalls(): array
}


public function getAllowInInstanceOf(): array
{
return $this->allowedConfig->getAllowInInstanceOf();
}


public function getAllowExceptInInstanceOf(): array
{
return $this->allowedConfig->getAllowExceptInInstancesOf();
}


public function getAllowExceptInCalls(): array
{
return $this->allowedConfig->getAllowExceptInCalls();
Expand Down
12 changes: 12 additions & 0 deletions src/DisallowedVariable.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ public function getAllowInCalls(): array
}


public function getAllowInInstanceOf(): array
{
throw new NotImplementedYetException();
}


public function getAllowExceptInInstanceOf(): array
{
throw new NotImplementedYetException();
}


public function getAllowExceptInCalls(): array
{
throw new NotImplementedYetException();
Expand Down
Loading

0 comments on commit a72f6b2

Please sign in to comment.