diff --git a/src/Analyser/NodeQueryTrait.php b/src/Analyser/NodeQueryTrait.php index 76b78f65..59a21298 100644 --- a/src/Analyser/NodeQueryTrait.php +++ b/src/Analyser/NodeQueryTrait.php @@ -6,8 +6,9 @@ use function in_array; use function rtrim; -use function str_starts_with; use function strcasecmp; +use function strlen; +use function strncasecmp; /** * Query helpers shared by {@see ClassNode}, {@see AnonymousClassNode}, @@ -48,10 +49,13 @@ public function dependsOn(string $class, bool $isCaseSensitive = true): bool public function dependsOnNamespace(string $namespace): bool { - $prefix = rtrim($namespace, '\\') . '\\'; + $prefix = rtrim($namespace, '\\') . '\\'; + $prefixLength = strlen($prefix); + // namespace names are case-insensitive; only the prefix is compared, + // so a case-sensitive constant name after it is left untouched foreach ($this->dependencies as $dependency) { - if (str_starts_with($dependency, $prefix)) { + if (strncasecmp($dependency, $prefix, $prefixLength) === 0) { return true; } } diff --git a/tests/Rule/Usage/MayNotUseNamespaceRuleTest.php b/tests/Rule/Usage/MayNotUseNamespaceRuleTest.php index 9406b1ec..4539ef74 100644 --- a/tests/Rule/Usage/MayNotUseNamespaceRuleTest.php +++ b/tests/Rule/Usage/MayNotUseNamespaceRuleTest.php @@ -100,6 +100,22 @@ public function testViolatesWhenForbiddenNamespaceHasTrailingBackslash(): void $this->assertInstanceOf(RuleViolation::class, $mayNotUseNamespaceRule->evaluate($classNode)); } + public function testViolatesWhenDepIsInForbiddenNamespaceWithDifferentCase(): void + { + $mayNotUseNamespaceRule = new MayNotUseNamespaceRule(layer: 'Domain', forbiddenNamespace: 'App'); + $classNode = $this->makeNode(['app\\B']); + + $this->assertInstanceOf(RuleViolation::class, $mayNotUseNamespaceRule->evaluate($classNode)); + } + + public function testPassesWhenDepIsTheForbiddenNamespaceItself(): void + { + $mayNotUseNamespaceRule = new MayNotUseNamespaceRule(layer: 'Domain', forbiddenNamespace: 'App'); + $classNode = $this->makeNode(['App']); + + $this->assertNotInstanceOf(RuleViolation::class, $mayNotUseNamespaceRule->evaluate($classNode)); + } + public function testPassesWhenDepOnlySharesNamespacePrefix(): void { $mayNotUseNamespaceRule = new MayNotUseNamespaceRule(layer: 'Domain', forbiddenNamespace: 'Doctrine\\ORM');