Repository: humbug/php-scoper (php)

Issue:
Namespace being added to a numbered string
Came across this line:
```
$body = RegEx::replace('...(some).*?(regex)...', '\\1\\2', $body);
```

Which got replaced with:
```
$body = \MyPhpScoperPrefix\RegEx::replace('...(some).*?(regex)...', '\MyPhpScoperPrefix\\1\\2', $body);
```

I worked around this issue by adding this to my `scoper.inc.php`:
```
    'exclude-namespaces' => [
        ...
        '1'
    ],
```

But it is of course a bug.

Pull request:
fix: Do not scope class-like strings that cannot be class names
Closes #951.

Patch:
diff --git a/specs/string-literal/var.php b/specs/string-literal/var.php
index c342c59..5765a2c 100644
--- a/specs/string-literal/var.php
+++ b/specs/string-literal/var.php
@@ -31,6 +31,7 @@ return [
         $x = '\\Symfony\\Component\\Yaml\\Ya_1';
         $x = 'Humbug\\Symfony\\Component\\Yaml\\Ya_1';
         $x = '\\Humbug\\Symfony\\Component\\Yaml\\Ya_1';
+        $x = '1\2';
 
         ----
         <?php
@@ -45,6 +46,7 @@ return [
         $x = 'Humbug\Symfony\Component\Yaml\Ya_1';
         $x = 'Humbug\Symfony\Component\Yaml\Ya_1';
         $x = 'Humbug\Symfony\Component\Yaml\Ya_1';
+        $x = '1\2';
 
         PHP,
 
diff --git a/src/PhpParser/NodeVisitor/StringScalarPrefixer.php b/src/PhpParser/NodeVisitor/StringScalarPrefixer.php
index a0adbda..302fdd4 100644
--- a/src/PhpParser/NodeVisitor/StringScalarPrefixer.php
+++ b/src/PhpParser/NodeVisitor/StringScalarPrefixer.php
@@ -102,7 +102,8 @@ final class StringScalarPrefixer extends NodeVisitorAbstract
         /^
             (\\)?               # leading backslash
             (
-                [\p{L}_\d]+     # class-like name
+                [\p{L}_]        # class-like name first character
+                [\p{L}_\d]*     # class-like name
                 \\              # separator
             )*
             [\p{L}_\d]+         # class-like name
diff --git a/src/Scoper/Symfony/XmlScoper.php b/src/Scoper/Symfony/XmlScoper.php
index e15dd2f..fa7b150 100644
--- a/src/Scoper/Symfony/XmlScoper.php
+++ b/src/Scoper/Symfony/XmlScoper.php
@@ -35,7 +35,7 @@ final readonly class XmlScoper implements Scoper
 {
     private const XML_EXTENSION_REGEX = '/\.xml$/i';
     private const NAMESPACE_PATTERN = '/<prototype.*\snamespace="(?:(?<namespace>(?:[^\\\\]+(?<separator>\\\\(?:\\\\)?))))"/';
-    private const SINGLE_CLASS_PATTERN = '/(?:(?<singleClass>(?:[\p{L}_\d]+(?<singleSeparator>\\\\(?:\\\\)?))):)|(?<class>(?:[\p{L}_\d]+(?<separator>\\\\(?:\\\\)?)+)+[\p{L}_\d]+)/u';
+    private const SINGLE_CLASS_PATTERN = '/(?:(?<singleClass>(?:[\p{L}_][\p{L}_\d]*(?<singleSeparator>\\\\(?:\\\\)?))):)|(?<class>(?:[\p{L}_][\p{L}_\d]*(?<separator>\\\\(?:\\\\)?)+)+[\p{L}_\d]+)/u';
 
     public function __construct(
         private Scoper $decoratedScoper,
diff --git a/src/Scoper/Symfony/YamlScoper.php b/src/Scoper/Symfony/YamlScoper.php
index 8d703ba..67f1549 100644
--- a/src/Scoper/Symfony/YamlScoper.php
+++ b/src/Scoper/Symfony/YamlScoper.php
@@ -34,7 +34,7 @@ use function substr;
 final readonly class YamlScoper implements Scoper
 {
     private const YAML_EXTENSION_REGEX = '/\.ya?ml$/i';
-    private const CLASS_PATTERN = '/(?:(?<singleClass>(?:[\p{L}_\d]+(?<singleSeparator>\\\\(?:\\\\)?))):)|(?<class>(?:[\p{L}_\d]+(?<separator>\\\\(?:\\\\)?)+)+[\p{L}_\d]+)/u';
+    private const CLASS_PATTERN = '/(?:(?<singleClass>(?:[\p{L}_][\p{L}_\d]*(?<singleSeparator>\\\\(?:\\\\)?))):)|(?<class>(?:[\p{L}_][\p{L}_\d]*(?<separator>\\\\(?:\\\\)?)+)+[\p{L}_\d]+)/u';
 
     public function __construct(
         private Scoper $decoratedScoper,


Test patch:
diff --git a/tests/Scoper/Symfony/XmlScoperTest.php b/tests/Scoper/Symfony/XmlScoperTest.php
index 314204f..7f22081 100644
--- a/tests/Scoper/Symfony/XmlScoperTest.php
+++ b/tests/Scoper/Symfony/XmlScoperTest.php
@@ -387,6 +387,45 @@ class XmlScoperTest extends TestCase
             [],
         ];
 
+        yield 'class-like pattern' => [
+            <<<'XML'
+                <!-- config/services.xml -->
+                <?xml version="1.0" encoding="UTF-8" ?>
+                <container xmlns="http://symfony.com/schema/dic/services"
+                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                    xsi:schemaLocation="http://symfony.com/schema/dic/services
+                        http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+                    <services>
+                        <service id="App\Mail\PhpMailer" />
+                        <service id="1\2" />
+
+                        <service id="s1" class="App\Mail\PhpMailer" alias="App\Mail\PhpMailer" />
+                        <service id="s2" class="1\2" alias="1\2" />
+                    </services>
+                </container>
+                XML,
+            SymbolsConfiguration::create(),
+            <<<'XML'
+                <!-- config/services.xml -->
+                <?xml version="1.0" encoding="UTF-8" ?>
+                <container xmlns="http://symfony.com/schema/dic/services"
+                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                    xsi:schemaLocation="http://symfony.com/schema/dic/services
+                        http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+                    <services>
+                        <service id="Humbug\App\Mail\PhpMailer" />
+                        <service id="1\2" />
+
+                        <service id="s1" class="Humbug\App\Mail\PhpMailer" alias="Humbug\App\Mail\PhpMailer" />
+                        <service id="s2" class="1\2" alias="1\2" />
+                    </services>
+                </container>
+                XML,
+            [],
+        ];
+
         yield 'service with argument' => [
             <<<'XML'
                 <!-- app/config/services.xml -->
diff --git a/tests/Scoper/Symfony/YamlScoperTest.php b/tests/Scoper/Symfony/YamlScoperTest.php
index a54bc5d..ec7275a 100644
--- a/tests/Scoper/Symfony/YamlScoperTest.php
+++ b/tests/Scoper/Symfony/YamlScoperTest.php
@@ -152,6 +152,11 @@ class YamlScoperTest extends TestCase
                     Symfony\Component\Console\Input\InputInterface:
                         alias: 'Symfony\Component\Console\Input\ArgvInput'
                     Symfony\Component\Console\Output\OutputInterface: '@Symfony\Component\Console\Output\ConsoleOutput'
+                    1\2: ~
+                    Service2:
+                        class: 1\2
+                    Service3:
+                        alias: 1\2
                 YAML,
             SymbolsConfiguration::create(),
             <<<'YAML'
@@ -160,6 +165,11 @@ class YamlScoperTest extends TestCase
                     Humbug\Symfony\Component\Console\Input\InputInterface:
                         alias: 'Humbug\Symfony\Component\Console\Input\ArgvInput'
                     Humbug\Symfony\Component\Console\Output\OutputInterface: '@Humbug\Symfony\Component\Console\Output\ConsoleOutput'
+                    1\2: ~
+                    Service2:
+                        class: 1\2
+                    Service3:
+                        alias: 1\2
                 YAML,
             [],
         ];