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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Test classes live in `tests/Calls/`, `tests/Usages/`, `tests/ControlStructures/`

`extension.neon` is the entry point wiring all rules together. It also defines the NEON schema for all config options — type declarations like `string()`, `int()`, `bool()` are enforced by NEON at config parse time, before any PHP runs. This means defensive runtime checks for wrong-typed config values (e.g. an array where a string is expected) are unnecessary when using the extension normally through PHPStan. Each feature has its own documentation file in `docs/` — new features get their own doc or extend an existing one.

`disallow*` config keys are generally aliases for their `allowExcept*` counterparts, handled in `AllowedConfigFactory`.
`disallow*` and `allowExcept*` config keys are equally valid alternatives for the same behaviour, handled in `AllowedConfigFactory`. Config keys follow a `*Anywhere` / `*InAllowed` naming convention — bare names without these suffixes (e.g. `allowExceptParams`, `disallowParams`) are legacy and kept for backwards compatibility; new keys should use the suffixed form (`allowExceptParamsAnywhere` and `disallowParamsAnywhere` are equally valid). Each new alias should have its own test — the schema and factory both use plain strings so a typo wouldn't be caught by PHPStan.

## Commit message style

Expand Down
8 changes: 5 additions & 3 deletions docs/allow-with-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Such configuration only makes sense when both the parameters of `log()` are opti
Sometimes, it's handy to disallow a function or a method call only when a parameter matches a configured value but allow it otherwise. _Please note that currently only scalar values are supported, not arrays._

For example the `hash()` function, it's fine using it with algorithm families like SHA-2 & SHA-3 (not for passwords though) but you'd like PHPStan to report when it's used with MD5 like `hash('md5', ...)`.
You can use `allowExceptParams` (or `disallowParams`), `allowExceptCaseInsensitiveParams` (or `disallowCaseInsensitiveParams`), `allowExceptParamsInAllowed` (or `disallowParamsInAllowed`) config options to disallow only some calls:
You can use `allowExceptParamsAnywhere` (or `disallowParamsAnywhere`), `allowExceptCaseInsensitiveParams` (or `disallowCaseInsensitiveParams`), `allowExceptParamsInAllowed` (or `disallowParamsInAllowed`) config options to disallow only some calls:

```neon
parameters:
Expand All @@ -85,20 +85,22 @@ parameters:
```

This will disallow `hash()` call where the first parameter (or the named parameter `algo`) is `'md5'`. `allowExceptCaseInsensitiveParams` is used because the first parameter of `hash()` is case-insensitive (so you can also use `'MD5'`, or even `'Md5'` & `'mD5'` if you wish).
To disallow only exact matches, use `allowExceptParams`:
To disallow only exact matches, use `allowExceptParamsAnywhere` (or `disallowParamsAnywhere`):

```neon
parameters:
disallowedFunctionCalls:
-
function: 'foo()'
allowExceptParams:
allowExceptParamsAnywhere:
-
position: 2
value: 'baz'
```
will disallow `foo('bar', 'baz')` but not `foo('bar', 'BAZ')`.

The older `allowExceptParams` (or `disallowParams`) config option is still supported but new configs should use `allowExceptParamsAnywhere` (or `disallowParamsAnywhere`) as they are more self-describing.

If you don't care about the value but would like to disallow a call based just on the parameter presence, you can use `allowExceptParamsAnyValue` (or `disallowParamsAnyValue`):
```neon
parameters:
Expand Down
8 changes: 8 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ parametersSchema:
?allowExceptParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamsInAllowed: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParamsAnywhere: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamsAnywhere: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParamsAnyValue: arrayOf(anyOf(int(), structure([position: int(), ?name: string()])), anyOf(int(), string())),
Expand Down Expand Up @@ -171,6 +173,8 @@ parametersSchema:
?allowExceptParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamsInAllowed: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParamsAnywhere: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamsAnywhere: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParamsAnyValue: arrayOf(anyOf(int(), structure([position: int(), ?name: string()])), anyOf(int(), string())),
Expand Down Expand Up @@ -224,6 +228,8 @@ parametersSchema:
?allowExceptParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamsInAllowed: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParamsAnywhere: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamsAnywhere: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParamsAnyValue: arrayOf(anyOf(int(), structure([position: int(), ?name: string()])), anyOf(int(), string())),
Expand Down Expand Up @@ -372,6 +378,8 @@ parametersSchema:
?allowExceptParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamFlagsInAllowed: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamsInAllowed: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParamsAnywhere: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParamsAnywhere: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?disallowParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
?allowExceptParamsAnyValue: arrayOf(anyOf(int(), structure([position: int(), ?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:
CallParamConfig: 'array<int|string, int|bool|string|array{position:int, value?:int|bool|string, name?:string}>'
CallParamAnyValueConfig: 'array<int|string, int|string|array{position:int, value?:int|bool|string, name?:string}>'
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'
AllowParamDirectives: 'allowParamsInAllowed?:CallParamConfig, allowParamsInAllowedAnyValue?:CallParamAnyValueConfig, allowParamFlagsInAllowed?:CallParamFlagAnyValueConfig, allowParamsAnywhere?:CallParamConfig, allowParamsAnywhereAnyValue?:CallParamAnyValueConfig, allowParamFlagsAnywhere?:CallParamFlagAnyValueConfig, allowExceptParamsInAllowed?:CallParamConfig, allowExceptParamFlagsInAllowed?:CallParamFlagAnyValueConfig, disallowParamFlagsInAllowed?:CallParamFlagAnyValueConfig, disallowParamsInAllowed?:CallParamConfig, allowExceptParamsAnywhere?:CallParamConfig, disallowParamsAnywhere?: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>, allowInInstanceOf?:list<string>, allowExceptInInstanceOf?:list<string>, disallowInInstanceOf?:list<string>, allowInParamTypes?:bool, allowExceptInParamTypes?:bool, disallowInParamTypes?:bool, allowInReturnType?:bool, allowExceptInReturnType?:bool, disallowInReturnType?:bool, %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|list<string>}>'
Expand Down
2 changes: 1 addition & 1 deletion src/Allowed/AllowedConfigFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public function getConfig(array $allowed): AllowedConfig
foreach ($allowed['allowExceptParamFlagsInAllowed'] ?? $allowed['disallowParamFlagsInAllowed'] ?? [] as $param => $value) {
$allowExceptParamsInAllowed[$param] = $this->paramFactory(ParamValueFlagExcept::class, $param, $value);
}
foreach ($allowed['allowExceptParams'] ?? $allowed['disallowParams'] ?? [] as $param => $value) {
foreach ($allowed['allowExceptParamsAnywhere'] ?? $allowed['disallowParamsAnywhere'] ?? $allowed['allowExceptParams'] ?? $allowed['disallowParams'] ?? [] as $param => $value) {
$allowExceptParams[$param] = $this->paramFactory(ParamValueExcept::class, $param, $value);
}
foreach ($allowed['allowExceptParamsAnyValue'] ?? $allowed['disallowParamsAnyValue'] ?? [] as $param => $value) {
Expand Down
34 changes: 34 additions & 0 deletions tests/Calls/FunctionCallsTypeStringParamsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,24 @@ protected function getRule(): Rule
],
],
],
[
'function' => '\Foo\Bar\Waldo\paramsAnywhereAlias()',
'allowExceptParamsAnywhere' => [
[
'position' => 1,
'typeString' => "'forbidden'",
],
],
],
Comment thread
spaze marked this conversation as resolved.
[
'function' => '\Foo\Bar\Waldo\paramsAnywhereDisallowAlias()',
'disallowParamsAnywhere' => [
[
'position' => 1,
'typeString' => "'forbidden'",
],
],
],
[
'function' => '\Foo\Bar\Waldo\mixedParam1()',
'allowIn' => [
Expand Down Expand Up @@ -305,6 +323,14 @@ public function testRule(): void
'Calling Foo\Bar\Waldo\zeroParam() is forbidden.',
35,
],
[
'Calling Foo\Bar\Waldo\paramsAnywhereAlias() is forbidden.',
36,
],
[
'Calling Foo\Bar\Waldo\paramsAnywhereDisallowAlias() is forbidden.',
38,
],
]);
$this->analyse([__DIR__ . '/../src/disallowed-allow/functionCallsTypeStringParams.php'], [
[
Expand Down Expand Up @@ -339,6 +365,14 @@ public function testRule(): void
'Calling Foo\Bar\Waldo\zeroParam() is forbidden.',
35,
],
[
'Calling Foo\Bar\Waldo\paramsAnywhereAlias() is forbidden.',
36,
],
[
'Calling Foo\Bar\Waldo\paramsAnywhereDisallowAlias() is forbidden.',
38,
],
]);
}

Expand Down
12 changes: 6 additions & 6 deletions tests/Usages/NamespaceUsagesAllowInClassWithAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,27 +137,27 @@ public function testRule(): void
$this->analyse([__DIR__ . '/../src/Functions.php'], [
[
'Class PhpOption\None is forbidden.',
88,
96,
],
[
'Class PhpOption\Some is forbidden.',
88,
96,
],
[
'Class PhpOption\None is forbidden.',
88,
96,
],
[
'Class PhpOption\Some is forbidden.',
88,
96,
],
[
'Class PhpOption\None is forbidden.',
90,
98,
],
[
'Class PhpOption\Some is forbidden.',
91,
99,
],
]);
}
Expand Down
8 changes: 8 additions & 0 deletions tests/src/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ function zeroParam(int $param): void
{
}

function paramsAnywhereAlias(string $param): void
{
}

function paramsAnywhereDisallowAlias(string $param): void
{
}

use PhpOption\None;
use PhpOption\Some;
use Waldo\Quux\Blade;
Expand Down
4 changes: 4 additions & 0 deletions tests/src/disallowed-allow/functionCallsTypeStringParams.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@
\Foo\Bar\Waldo\mixedParam1(new Exception); // not a disallowed param
\Foo\Bar\Waldo\zeroParam(0); // allowed param
\Foo\Bar\Waldo\zeroParam(1); // disallowed param
\Foo\Bar\Waldo\paramsAnywhereAlias('forbidden'); // disallowed param
\Foo\Bar\Waldo\paramsAnywhereAlias('ok'); // allowed param
\Foo\Bar\Waldo\paramsAnywhereDisallowAlias('forbidden'); // disallowed param
\Foo\Bar\Waldo\paramsAnywhereDisallowAlias('ok'); // allowed param
4 changes: 4 additions & 0 deletions tests/src/disallowed/functionCallsTypeStringParams.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@
\Foo\Bar\Waldo\mixedParam1(new Exception); // disallowed
\Foo\Bar\Waldo\zeroParam(0); // allowed param
\Foo\Bar\Waldo\zeroParam(1); // disallowed param
\Foo\Bar\Waldo\paramsAnywhereAlias('forbidden'); // disallowed param
\Foo\Bar\Waldo\paramsAnywhereAlias('ok'); // allowed param
\Foo\Bar\Waldo\paramsAnywhereDisallowAlias('forbidden'); // disallowed param
\Foo\Bar\Waldo\paramsAnywhereDisallowAlias('ok'); // allowed param