diff --git a/src/Analyser/ClassNode.php b/src/Analyser/ClassNode.php index 592335e5..51f215b3 100644 --- a/src/Analyser/ClassNode.php +++ b/src/Analyser/ClassNode.php @@ -6,6 +6,7 @@ use function array_filter; use function in_array; +use function ltrim; use function preg_match; use function strcasecmp; use function strncasecmp; @@ -92,6 +93,8 @@ public function __construct( */ public function usesClass(string $class): bool { + $class = ltrim($class, '\\'); + if (! $this->dependsOn($class, isCaseSensitive: false)) { return false; } @@ -111,6 +114,7 @@ public function usesClass(string $class): bool */ public function usesConstant(string $constant): bool { + $constant = ltrim($constant, '\\'); $separatorPosition = strrpos($constant, '\\'); // a constant name is case-sensitive @@ -224,6 +228,8 @@ public function nameMatches(string $pattern, bool $isFullName = false): bool */ public function implementsInterface(string $interface): bool { + $interface = ltrim($interface, '\\'); + return $this->matchesAnyClassLike($interface, $this->implements) || $this->matchesAnyClassLike($interface, $this->interfaceExtends) || $this->matchesAnyClassLike($interface, $this->parentInterfaces); diff --git a/src/Analyser/NodeQueryTrait.php b/src/Analyser/NodeQueryTrait.php index 5ab6d03d..6a97e281 100644 --- a/src/Analyser/NodeQueryTrait.php +++ b/src/Analyser/NodeQueryTrait.php @@ -5,11 +5,12 @@ namespace Boundwize\StructArmed\Analyser; use function in_array; -use function rtrim; +use function ltrim; use function strcasecmp; use function strlen; use function strncasecmp; use function strtolower; +use function trim; /** * Query helpers shared by {@see ClassNode}, {@see AnonymousClassNode}, @@ -35,6 +36,8 @@ public function isInLayer(string $layer): bool public function dependsOn(string $dependency, bool $isCaseSensitive = true): bool { + $dependency = ltrim($dependency, '\\'); + if ($isCaseSensitive) { return in_array($dependency, $this->dependencies, true); } @@ -50,7 +53,7 @@ public function dependsOn(string $dependency, bool $isCaseSensitive = true): boo public function dependsOnNamespace(string $namespace): bool { - $prefix = rtrim($namespace, '\\') . '\\'; + $prefix = trim($namespace, '\\') . '\\'; $prefixLength = strlen($prefix); // namespace names are case-insensitive; only the prefix is compared, @@ -78,6 +81,8 @@ public function setFunctionCalls(array $functionCalls): void public function callsFunction(string $function): bool { + $function = ltrim($function, '\\'); + foreach ($this->functionCalls as $functionCall) { if (strcasecmp($functionCall, $function) === 0) { return true; diff --git a/src/Analyser/RecursiveParentsTrait.php b/src/Analyser/RecursiveParentsTrait.php index 4ddab395..dd663cbb 100644 --- a/src/Analyser/RecursiveParentsTrait.php +++ b/src/Analyser/RecursiveParentsTrait.php @@ -4,6 +4,7 @@ namespace Boundwize\StructArmed\Analyser; +use function ltrim; use function strcasecmp; /** @@ -36,12 +37,16 @@ public function setRecursiveParents(array $parentClasses, array $parentInterface */ public function implementsInterface(string $interface): bool { + $interface = ltrim($interface, '\\'); + return $this->matchesAnyClassLike($interface, $this->implements) || $this->matchesAnyClassLike($interface, $this->parentInterfaces); } public function extendsClass(string $class): bool { + $class = ltrim($class, '\\'); + if ($this->extends !== null && strcasecmp($this->extends, $class) === 0) { return true; } diff --git a/tests/Analyser/AnonymousClassNodeTest.php b/tests/Analyser/AnonymousClassNodeTest.php index aa1a5ea2..a468ec05 100644 --- a/tests/Analyser/AnonymousClassNodeTest.php +++ b/tests/Analyser/AnonymousClassNodeTest.php @@ -37,6 +37,8 @@ public function testParentChainQueriesAreCaseInsensitive(): void $this->assertSame(['App\\Support\\baseclass', 'App\\Support\\rootclass'], $anonymousClassNode->parentClasses); $this->assertTrue($anonymousClassNode->extendsClass('App\\Support\\RootClass')); + $this->assertTrue($anonymousClassNode->extendsClass('\\App\\Support\\RootClass')); + $this->assertTrue($anonymousClassNode->implementsInterface('\\App\\Contracts\\RootInterface')); $this->assertFalse($anonymousClassNode->extendsClass('App\\Support\\OtherClass')); $this->assertTrue($anonymousClassNode->implementsInterface('App\\Contracts\\RootInterface')); $this->assertFalse($anonymousClassNode->implementsInterface('App\\Contracts\\OtherInterface')); diff --git a/tests/Analyser/ClassNodeTest.php b/tests/Analyser/ClassNodeTest.php index 0ba97197..dab7276a 100644 --- a/tests/Analyser/ClassNodeTest.php +++ b/tests/Analyser/ClassNodeTest.php @@ -196,6 +196,7 @@ interfaceExtends: ['App\\Contracts\\BaseOrderService'], $this->assertTrue($classNode->implementsInterface('App\\Contracts\\BaseOrderService')); $this->assertTrue($classNode->callsFunction('var_dump')); $this->assertTrue($classNode->callsFunction('VAR_DUMP')); + $this->assertTrue($classNode->callsFunction('\\var_dump')); $this->assertFalse($classNode->callsFunction('array_map')); $this->assertTrue($classNode->accessesSuperglobals()); $this->assertTrue($classNode->usesLanguageConstruct('exit')); @@ -318,6 +319,7 @@ className: Foo::class, $this->assertTrue($classNode->implementsInterface('App\\Contracts\\FooInterface')); $this->assertTrue($classNode->implementsInterface('App\\Contracts\\RootInterface')); + $this->assertTrue($classNode->implementsInterface('\\App\\Contracts\\FooInterface')); $this->assertFalse($classNode->implementsInterface('App\\Contracts\\OtherInterface')); } @@ -338,6 +340,8 @@ className: Foo::class, $this->assertTrue($classNode->extendsClass('App\\Support\\BaseClass')); $this->assertTrue($classNode->extendsClass('App\\Support\\RootClass')); + $this->assertTrue($classNode->extendsClass('\\App\\Support\\BaseClass')); + $this->assertTrue($classNode->extendsClass('\\App\\Support\\RootClass')); $this->assertFalse($classNode->extendsClass('App\\Support\\OtherClass')); } @@ -358,6 +362,7 @@ interfaceExtends: ['App\\Contracts\\baseinterface'], ); $this->assertTrue($classNode->implementsInterface('App\\Contracts\\BaseInterface')); + $this->assertTrue($classNode->implementsInterface('\\App\\Contracts\\BaseInterface')); $this->assertTrue($classNode->implementsInterface('App\\Contracts\\RootInterface')); $this->assertFalse($classNode->implementsInterface('App\\Contracts\\OtherInterface')); } @@ -514,6 +519,7 @@ className: 'App\\Domain\\OrderService', $this->assertFalse($classNode->dependsOn('datetimeimmutable')); $this->assertTrue($classNode->dependsOn('datetimeimmutable', isCaseSensitive: false)); + $this->assertTrue($classNode->dependsOn('\\' . DateTimeImmutable::class)); $this->assertFalse($classNode->dependsOn('datetime', isCaseSensitive: false)); } @@ -535,7 +541,9 @@ className: 'App\\Domain\\OrderService', $this->assertTrue($classNode->usesClass('datetimeimmutable')); $this->assertTrue($classNode->usesClass('DATETIMEIMMUTABLE')); + $this->assertTrue($classNode->usesClass('\\' . DateTimeImmutable::class)); $this->assertFalse($classNode->usesClass('VENDOR\\HELPER')); + $this->assertFalse($classNode->usesClass('\\Vendor\\helper')); } public function testUsesClassIgnoresDependencyOnlyUsedAsFunctionOrConstant(): void @@ -577,6 +585,8 @@ className: 'App\\Domain\\OrderService', ); $this->assertTrue($classNode->usesConstant('STDIN')); + $this->assertTrue($classNode->usesConstant('\\STDIN')); + $this->assertTrue($classNode->usesConstant('\\Vendor\\Config\\DEBUG')); $this->assertFalse($classNode->usesConstant('stdin')); $this->assertTrue($classNode->usesConstant('vendor\\config\\DEBUG')); $this->assertFalse($classNode->usesConstant('Vendor\\Config\\debug')); @@ -621,6 +631,8 @@ className: 'App\\Domain\\OrderService', ); $this->assertTrue($classNode->dependsOnNamespace('App\\Bar')); + $this->assertTrue($classNode->dependsOnNamespace('\\App\\Bar')); + $this->assertTrue($classNode->dependsOnNamespace('\\App\\Bar\\')); $this->assertFalse($classNode->dependsOnNamespace('App\\Baz')); }