From 68a562c79a6d1db1748f9d8a4f0a64c8b3fb166d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9o=20FIDRY?=
 <5175937+theofidry@users.noreply.github.com>
Date: Sat, 12 Nov 2022 18:12:26 +0100
Subject: [PATCH] Fix regex cannot have the `i` flag (#759)

If the regex had the `i` flag it would have been appended still resulting in an invalid regex.
---
 .../SymbolsConfigurationFactory.php           | 29 ++++++++++++-
 .../SymbolsConfigurationFactoryTest.php       | 42 +++++++++++++++++++
 2 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/src/Configuration/SymbolsConfigurationFactory.php b/src/Configuration/SymbolsConfigurationFactory.php
index bb9b1361..7ef3040e 100644
--- a/src/Configuration/SymbolsConfigurationFactory.php
+++ b/src/Configuration/SymbolsConfigurationFactory.php
@@ -25,6 +25,9 @@
 use function is_bool;
 use function is_string;
 use function sprintf;
+use function str_contains;
+use function strrpos;
+use function substr;
 
 final class SymbolsConfigurationFactory
 {
@@ -203,12 +206,31 @@ private function getRegex(string $regex, string $key, int|string $index): string
             );
         }
 
-        // TODO: double check that we are not adding it twice or that adding it twice does not break anything
-        $regex .= 'i';
+        $flags = self::getRegexFlags($regex);
+
+        if (!str_contains($flags, 'i')) {
+            // Ensure namespace comparisons are always case-insensitive
+            $regex .= 'i';
+        }
 
         return $regex;
     }
 
+    /**
+     * @param non-empty-string $regex
+     */
+    private static function getRegexFlags(string $regex): string
+    {
+        $separator = $regex[0];
+        $lastSeparatorPosition = strrpos($regex, $separator);
+
+        if (false === $lastSeparatorPosition) {
+            return '';
+        }
+
+        return substr($regex, $lastSeparatorPosition);
+    }
+
     /**
      * @psalm-assert string[] $value
      */
@@ -240,6 +262,9 @@ private static function assertIsArrayOfStrings(mixed $value, string $key): void
         }
     }
 
+    /**
+     * @phpstan-assert non-empty-string $regex
+     */
     private function assertValidRegex(string $regex, string $key, string $index): void
     {
         $errorMessage = $this->regexChecker->validateRegex($regex);
diff --git a/tests/Configuration/SymbolsConfigurationFactoryTest.php b/tests/Configuration/SymbolsConfigurationFactoryTest.php
index 918656f4..52c7c936 100644
--- a/tests/Configuration/SymbolsConfigurationFactoryTest.php
+++ b/tests/Configuration/SymbolsConfigurationFactoryTest.php
@@ -116,6 +116,48 @@ public static function configProvider(): iterable
             ),
         ];
 
+        yield 'exclude namespace regex with flags' => [
+            [
+                ConfigurationKeys::EXCLUDE_NAMESPACES_KEYWORD => [
+                    '~^PHPUnit\\Runner(\\.*)?$~u',
+                ],
+            ],
+            SymbolsConfiguration::create(
+                excludedNamespaces: NamespaceRegistry::create(
+                    [],
+                    ['~^PHPUnit\\Runner(\\.*)?$~ui'],
+                ),
+            ),
+        ];
+
+        yield 'exclude namespace regex with case insensitive flag' => [
+            [
+                ConfigurationKeys::EXCLUDE_NAMESPACES_KEYWORD => [
+                    '~^PHPUnit\\Runner(\\.*)?$~i',
+                ],
+            ],
+            SymbolsConfiguration::create(
+                excludedNamespaces: NamespaceRegistry::create(
+                    [],
+                    ['~^PHPUnit\\Runner(\\.*)?$~i'],
+                ),
+            ),
+        ];
+
+        yield 'exclude namespace regex with several flags flag' => [
+            [
+                ConfigurationKeys::EXCLUDE_NAMESPACES_KEYWORD => [
+                    '~^PHPUnit\\Runner(\\.*)?$~uiA',
+                ],
+            ],
+            SymbolsConfiguration::create(
+                excludedNamespaces: NamespaceRegistry::create(
+                    [],
+                    ['~^PHPUnit\\Runner(\\.*)?$~uiA'],
+                ),
+            ),
+        ];
+
         yield 'nominal' => [
             [
                 ConfigurationKeys::EXPOSE_GLOBAL_CONSTANTS_KEYWORD => false,