diff --git a/README.md b/README.md index e256666..648923c 100644 --- a/README.md +++ b/README.md @@ -320,6 +320,10 @@ parameters: Calling `waldo()` is disallowed, and allowed back again only when the file is in the `views/` subdirectory **and** `waldo()` is called in the file with a 2nd parameter being the string `quux`. +## Case-(in)sensitivity + +Function names, method names, namespaces are matched irrespective of their case (disallowing `print_r` will also find `print_R` calls), while anything else like constants, file names, paths are not. + ## Detect disallowed calls without any other PHPStan rules If you want to use this PHPStan extension without running any other PHPStan rules, you can use `phpstan.neon` config file that looks like this (the `customRulesetUsed: true` and the missing `level` key are the important bits): diff --git a/src/DisallowedHelper.php b/src/DisallowedHelper.php index 65903c6..d772292 100644 --- a/src/DisallowedHelper.php +++ b/src/DisallowedHelper.php @@ -40,7 +40,7 @@ private function isAllowed(Scope $scope, ?CallLike $node, DisallowedCall $disall } else { $name = ''; } - if (fnmatch($call, $name, FNM_NOESCAPE)) { + if (fnmatch($call, $name, FNM_NOESCAPE | FNM_CASEFOLD)) { return $this->hasAllowedParamsInAllowed($scope, $node, $disallowedCall); } } @@ -133,7 +133,7 @@ public function getDisallowedMessage(?CallLike $node, Scope $scope, string $name private function callMatches(DisallowedCall $disallowedCall, string $name): bool { - if ($name === $disallowedCall->getCall() || fnmatch($disallowedCall->getCall(), $name, FNM_NOESCAPE)) { + if ($name === $disallowedCall->getCall() || fnmatch($disallowedCall->getCall(), $name, FNM_NOESCAPE | FNM_CASEFOLD)) { return true; } return false; diff --git a/src/DisallowedNamespaceHelper.php b/src/DisallowedNamespaceHelper.php index 7db2d18..6bd393b 100644 --- a/src/DisallowedNamespaceHelper.php +++ b/src/DisallowedNamespaceHelper.php @@ -72,7 +72,7 @@ private function matchesNamespace(string $pattern, string $value): bool return true; } - if (fnmatch($pattern, $value, FNM_NOESCAPE)) { + if (fnmatch($pattern, $value, FNM_NOESCAPE | FNM_CASEFOLD)) { return true; } diff --git a/tests/Calls/FunctionCallsAllowInMethodsTest.php b/tests/Calls/FunctionCallsAllowInMethodsTest.php index a3bb904..1e31d7c 100644 --- a/tests/Calls/FunctionCallsAllowInMethodsTest.php +++ b/tests/Calls/FunctionCallsAllowInMethodsTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule [ 'function' => 'sha1_file()', 'allowInFunctions' => [ - '\\Fiction\\Pulp\\Royale::withoutCheese()', + '\\Fiction\\Pulp\\Royale::WithoutCheese()', ], 'allowParamsInAllowed' => [ 2 => true, diff --git a/tests/Calls/FunctionCallsTest.php b/tests/Calls/FunctionCallsTest.php index 1b4768e..ea6250f 100644 --- a/tests/Calls/FunctionCallsTest.php +++ b/tests/Calls/FunctionCallsTest.php @@ -161,7 +161,7 @@ public function testRule(): void 7, ], [ - 'Calling print_r() is forbidden, nope', + 'Calling print_R() is forbidden, nope [print_R() matches print_r()]', 8, ], [ diff --git a/tests/Calls/MethodCallsTest.php b/tests/Calls/MethodCallsTest.php index 9012428..ae443eb 100644 --- a/tests/Calls/MethodCallsTest.php +++ b/tests/Calls/MethodCallsTest.php @@ -32,6 +32,30 @@ protected function getRule(): Rule 3 => '909', ], ], + [ + 'method' => 'Waldo\Quux\Blade::movie()', + 'message' => 'was good', + 'allowIn' => [ + '../src/disallowed-allowed/*.php', + '../src/*-allow/*.*', + ], + ], + [ + 'method' => 'Waldo\Quux\Blade::sequel()', + 'message' => 'too', + 'allowIn' => [ + '../src/disallowed-allowed/*.php', + '../src/*-allow/*.*', + ], + ], + [ + 'method' => 'Waldo\Quux\Blade::Trinity()', + 'message' => 'holy trinity', + 'allowIn' => [ + '../src/disallowed-allowed/*.php', + '../src/*-allow/*.*', + ], + ], [ 'method' => 'Inheritance\Base::x*()', 'message' => 'Base::x*() methods are dangerous', @@ -135,6 +159,30 @@ public function testRule(): void 'Calling DateTime::format() is forbidden, why too kay', 55, ], + [ + 'Calling Waldo\Quux\Blade::movie() is forbidden, was good', + 60, + ], + [ + 'Calling Waldo\Quux\Blade::movie() is forbidden, was good', + 61, + ], + [ + 'Calling Waldo\Quux\Blade::Sequel() is forbidden, too [Waldo\Quux\Blade::Sequel() matches Waldo\Quux\Blade::sequel()]', + 62, + ], + [ + 'Calling Waldo\Quux\Blade::Sequel() is forbidden, too [Waldo\Quux\Blade::Sequel() matches Waldo\Quux\Blade::sequel()]', + 63, + ], + [ + 'Calling Waldo\Quux\Blade::trinity() is forbidden, holy trinity [Waldo\Quux\Blade::trinity() matches Waldo\Quux\Blade::Trinity()]', + 64, + ], + [ + 'Calling Waldo\Quux\Blade::trinity() is forbidden, holy trinity [Waldo\Quux\Blade::trinity() matches Waldo\Quux\Blade::Trinity()]', + 65, + ], ]); $this->analyse([__DIR__ . '/../src/disallowed-allow/methodCalls.php'], [ [ diff --git a/tests/Calls/StaticCallsTest.php b/tests/Calls/StaticCallsTest.php index b7a64bb..8030ee6 100644 --- a/tests/Calls/StaticCallsTest.php +++ b/tests/Calls/StaticCallsTest.php @@ -38,7 +38,7 @@ protected function getRule(): Rule 'allowParamsInAllowed' => [], ], [ - 'method' => 'Fiction\Pulp\Royale::withoutCheese', + 'method' => 'Fiction\Pulp\Royale::WithoutCheese', 'message' => 'a Quarter Pounder without Cheese?', 'allowIn' => [ '../src/disallowed-allowed/*.php', @@ -123,19 +123,19 @@ public function testRule(): void 8, ], [ - 'Calling Fiction\Pulp\Royale::withBadCheese() is forbidden, a Quarter Pounder with Cheese? [Fiction\Pulp\Royale::withBadCheese() matches Fiction\Pulp\*::withBad*()]', + 'Calling Fiction\Pulp\Royale::WithBadCheese() is forbidden, a Quarter Pounder with Cheese? [Fiction\Pulp\Royale::WithBadCheese() matches Fiction\Pulp\*::withBad*()]', 9, ], [ - 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese?', + 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese? [Fiction\Pulp\Royale::withoutCheese() matches Fiction\Pulp\Royale::WithoutCheese()]', 12, ], [ - 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese?', + 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese? [Fiction\Pulp\Royale::withoutCheese() matches Fiction\Pulp\Royale::WithoutCheese()]', 14, ], [ - 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese?', + 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese? [Fiction\Pulp\Royale::withoutCheese() matches Fiction\Pulp\Royale::WithoutCheese()]', 18, ], [ @@ -165,11 +165,11 @@ public function testRule(): void ]); $this->analyse([__DIR__ . '/../src/disallowed-allow/staticCalls.php'], [ [ - 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese?', + 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese? [Fiction\Pulp\Royale::withoutCheese() matches Fiction\Pulp\Royale::WithoutCheese()]', 18, ], [ - 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese?', + 'Calling Fiction\Pulp\Royale::withoutCheese() is forbidden, a Quarter Pounder without Cheese? [Fiction\Pulp\Royale::withoutCheese() matches Fiction\Pulp\Royale::WithoutCheese()]', 21, ], ]); diff --git a/tests/Usages/NamespaceUsagesTest.php b/tests/Usages/NamespaceUsagesTest.php index cccc76b..7596c0a 100644 --- a/tests/Usages/NamespaceUsagesTest.php +++ b/tests/Usages/NamespaceUsagesTest.php @@ -52,7 +52,7 @@ protected function getRule(): Rule ], ], [ - 'namespace' => 'Waldo\Foo\Bar', + 'namespace' => 'Waldo\Foo\bar', 'message' => 'no FooBar', 'allowIn' => [ '../src/disallowed-allowed/*.php', @@ -95,11 +95,11 @@ public function testRule(): void 9, ], [ - 'Namespace Waldo\Foo\Bar is forbidden, no FooBar', + 'Namespace Waldo\Foo\Bar is forbidden, no FooBar [Waldo\Foo\Bar matches Waldo\Foo\bar]', 10, ], [ - 'Namespace Waldo\Quux\Blade is forbidden, no blade', + 'Namespace Waldo\Quux\blade is forbidden, no blade [Waldo\Quux\blade matches Waldo\Quux\Blade]', 11, ], [ @@ -115,16 +115,16 @@ public function testRule(): void 16, ], [ - 'Namespace Waldo\Quux\Blade is forbidden, no blade', - 22, + 'Namespace Waldo\Quux\blade is forbidden, no blade [Waldo\Quux\blade matches Waldo\Quux\Blade]', + 23, ], [ 'Namespace Inheritance\Sub is forbidden, no sub', - 30, + 31, ], [ - 'Namespace Waldo\Foo\Bar is forbidden, no FooBar', - 36, + 'Namespace Waldo\Foo\Bar is forbidden, no FooBar [Waldo\Foo\Bar matches Waldo\Foo\bar]', + 37, ], ]); $this->analyse([__DIR__ . '/../src/disallowed-allow/namespaceUsages.php'], []); diff --git a/tests/libs/Blade.php b/tests/libs/Blade.php index 977aaba..d54dc43 100644 --- a/tests/libs/Blade.php +++ b/tests/libs/Blade.php @@ -20,4 +20,19 @@ public function server(): void { } + + public function movie(): void + { + } + + + public function Sequel(): void + { + } + + + public function trinity(): void + { + } + } diff --git a/tests/libs/Royale.php b/tests/libs/Royale.php index 1ec1ae4..e8f21db 100644 --- a/tests/libs/Royale.php +++ b/tests/libs/Royale.php @@ -16,7 +16,7 @@ public static function withCheese(): void } - public static function withBadCheese(): void + public static function WithBadCheese(): void { $foo = md5_file(__FILE__); } diff --git a/tests/src/disallowed-allow/functionCalls.php b/tests/src/disallowed-allow/functionCalls.php index c8a0f7e..ab19651 100644 --- a/tests/src/disallowed-allow/functionCalls.php +++ b/tests/src/disallowed-allow/functionCalls.php @@ -5,7 +5,7 @@ // allowed by path var_dump('foo', true); -print_r('bar'); +print_R('bar'); \printf('foobar'); \Foo\Bar\waldo(); waldo(123); diff --git a/tests/src/disallowed-allow/methodCalls.php b/tests/src/disallowed-allow/methodCalls.php index ce5de1f..138be82 100644 --- a/tests/src/disallowed-allow/methodCalls.php +++ b/tests/src/disallowed-allow/methodCalls.php @@ -26,7 +26,7 @@ $testClass->x(); $testClassToo = new Traits\AnotherTestClass(); $testClassToo->y(); -$testClassToo->zzTop(); +$testClassToo->zZTop(); // object creation allowed by path new ClassWithConstructor(); @@ -55,3 +55,11 @@ (new DateTime())->format('y'); (new DateTime())->format('Y'); new DateTime('tOmOrRoW'); + +// case-insensitive methods allowed by path +$blade->movie(); +$blade->Movie(); +$blade->sequel(); +$blade->Sequel(); +$blade->trinity(); +$blade->Trinity(); diff --git a/tests/src/disallowed-allow/namespaceUsages.php b/tests/src/disallowed-allow/namespaceUsages.php index 5b0d688..b899a30 100644 --- a/tests/src/disallowed-allow/namespaceUsages.php +++ b/tests/src/disallowed-allow/namespaceUsages.php @@ -8,7 +8,7 @@ use Inheritance\Sub; use Traits\TestTrait; use Waldo\Foo\Bar; -use Waldo\Quux\Blade; +use Waldo\Quux\blade; class Service extends Base implements SomeInterface { @@ -19,6 +19,7 @@ class Service extends Base implements SomeInterface private $blade; + // phpcs:ignore SlevomatCodingStandard.Namespaces.UnusedUses.MismatchingCaseSensitivity public function __construct(Blade $blade) { $this->blade = $blade; diff --git a/tests/src/disallowed-allow/staticCalls.php b/tests/src/disallowed-allow/staticCalls.php index 0dba795..998e180 100644 --- a/tests/src/disallowed-allow/staticCalls.php +++ b/tests/src/disallowed-allow/staticCalls.php @@ -5,13 +5,13 @@ // allowed by path \Fiction\Pulp\Royale::withCheese(); -Pulp\Royale::withCheese(); +Pulp\Royale::WithCheese(); Pulp\Royale::withBadCheese(); // allowed by path and only with these params Pulp\Royale::withoutCheese(1, 2, 3); $a = 3; -Pulp\Royale::withoutCheese(1, 2, $a); +Pulp\Royale::WithoutCheese(1, 2, $a); // disallowed call, allowed by path but params don't match allowed $a = 5; diff --git a/tests/src/disallowed/functionCalls.php b/tests/src/disallowed/functionCalls.php index e0f2806..b32e142 100644 --- a/tests/src/disallowed/functionCalls.php +++ b/tests/src/disallowed/functionCalls.php @@ -5,7 +5,7 @@ // disallowed var_dump('foo', true); -print_r('bar'); +print_R('bar'); \printf('foobar'); \Foo\Bar\waldo(); waldo(123); diff --git a/tests/src/disallowed/methodCalls.php b/tests/src/disallowed/methodCalls.php index 0b79b1d..b6afc87 100644 --- a/tests/src/disallowed/methodCalls.php +++ b/tests/src/disallowed/methodCalls.php @@ -26,7 +26,7 @@ $testClass->x(); $testClassToo = new Traits\AnotherTestClass(); $testClassToo->y(); -$testClassToo->zzTop(); +$testClassToo->zZTop(); // disallowed object creation new ClassWithConstructor(); @@ -55,3 +55,11 @@ (new DateTime())->format('y'); (new DateTime())->format('Y'); new DateTime('tOmOrRoW'); + +// disallowed case-insensitive methods +$blade->movie(); +$blade->Movie(); +$blade->sequel(); +$blade->Sequel(); +$blade->trinity(); +$blade->Trinity(); diff --git a/tests/src/disallowed/namespaceUsages.php b/tests/src/disallowed/namespaceUsages.php index 5b0d688..b899a30 100644 --- a/tests/src/disallowed/namespaceUsages.php +++ b/tests/src/disallowed/namespaceUsages.php @@ -8,7 +8,7 @@ use Inheritance\Sub; use Traits\TestTrait; use Waldo\Foo\Bar; -use Waldo\Quux\Blade; +use Waldo\Quux\blade; class Service extends Base implements SomeInterface { @@ -19,6 +19,7 @@ class Service extends Base implements SomeInterface private $blade; + // phpcs:ignore SlevomatCodingStandard.Namespaces.UnusedUses.MismatchingCaseSensitivity public function __construct(Blade $blade) { $this->blade = $blade; diff --git a/tests/src/disallowed/staticCalls.php b/tests/src/disallowed/staticCalls.php index b76bdde..8e92799 100644 --- a/tests/src/disallowed/staticCalls.php +++ b/tests/src/disallowed/staticCalls.php @@ -5,13 +5,13 @@ // disallowed method \Fiction\Pulp\Royale::withCheese(); -Pulp\Royale::withCheese(); +Pulp\Royale::WithCheese(); Pulp\Royale::withBadCheese(); // disallowed call, params match allowed but path doesn't Pulp\Royale::withoutCheese(1, 2, 3); $a = 3; -Pulp\Royale::withoutCheese(1, 2, $a); +Pulp\Royale::WithoutCheese(1, 2, $a); // disallowed call, params don't match allowed $a = 5;