Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
17.86% covered (danger)
17.86%
15 / 84
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ChangeEnumerator
17.86% covered (danger)
17.86%
15 / 84
0.00% covered (danger)
0.00%
0 / 3
400.68
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 markFilesForExclusion
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 determineReplacements
21.74% covered (danger)
21.74%
15 / 69
0.00% covered (danger)
0.00%
0 / 1
192.04
1<?php
2/**
3 * Determine the replacements to be made to the discovered symbols.
4 *
5 * Typically, this will just be a prefix, but more complex rules allow for replacements specific to individual symbols/namespaces.
6 */
7
8namespace BrianHenryIE\Strauss\Pipeline;
9
10use BrianHenryIE\Strauss\Config\ChangeEnumeratorConfigInterface;
11use BrianHenryIE\Strauss\Files\DiscoveredFiles;
12use BrianHenryIE\Strauss\Files\FileWithDependency;
13use BrianHenryIE\Strauss\Types\ClassSymbol;
14use BrianHenryIE\Strauss\Types\DiscoveredSymbols;
15use BrianHenryIE\Strauss\Types\FunctionSymbol;
16use BrianHenryIE\Strauss\Types\NamespaceSymbol;
17use League\Flysystem\FilesystemReader;
18use Psr\Log\LoggerAwareTrait;
19use Psr\Log\LoggerInterface;
20use Psr\Log\NullLogger;
21
22class ChangeEnumerator
23{
24    use LoggerAwareTrait;
25
26    protected ChangeEnumeratorConfigInterface $config;
27    protected FilesystemReader $filesystem;
28
29    public function __construct(
30        ChangeEnumeratorConfigInterface $config,
31        FilesystemReader $filesystem,
32        ?LoggerInterface $logger = null
33    ) {
34        $this->config = $config;
35        $this->filesystem = $filesystem;
36        $this->setLogger($logger ?? new NullLogger());
37    }
38
39    public function markFilesForExclusion(DiscoveredFiles $files)
40    {
41
42        foreach ($files->getFiles() as $file) {
43            if ($file instanceof FileWithDependency) {
44                if (in_array(
45                    $file->getDependency()->getPackageName(),
46                    $this->config->getExcludePackagesFromPrefixing(),
47                    true
48                )) {
49                    $file->setDoPrefix(false);
50                    continue;
51                }
52
53                foreach ($this->config->getExcludeFilePatternsFromPrefixing() as $excludeFilePattern) {
54                    // TODO: This source relative path should be from the vendor dir.
55                    // TODO: Should the target path be used here?
56                    if (1 === preg_match($excludeFilePattern, $file->getVendorRelativePath())) {
57                        $file->setDoPrefix(false);
58                    }
59                }
60            }
61        }
62    }
63
64    public function determineReplacements(DiscoveredSymbols $discoveredSymbols): void
65    {
66        $discoveredNamespaces = $discoveredSymbols->getDiscoveredNamespaces();
67
68        foreach ($discoveredNamespaces as $symbol) {
69            if ($symbol instanceof NamespaceSymbol) {
70                $namespaceReplacementPatterns = $this->config->getNamespaceReplacementPatterns();
71
72                // `namespace_prefix` is just a shorthand for a replacement pattern that applies to all namespaces.
73
74                // TODO: Maybe need to preg_quote and add regex delimiters to the patterns here.
75                foreach ($namespaceReplacementPatterns as $pattern => $replacement) {
76                    if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
77                        unset($namespaceReplacementPatterns[ $pattern ]);
78                        $pattern                                  = '~' . preg_quote($pattern, '~') . '~';
79                        $namespaceReplacementPatterns[ $pattern ] = $replacement;
80                    }
81                    unset($pattern, $replacement);
82                }
83
84                if (! is_null($this->config->getNamespacePrefix())) {
85                    $stripPattern   = '~^(' . preg_quote($this->config->getNamespacePrefix(), '~') . '\\\\*)*(.*)~';
86                    $strippedSymbol = preg_replace(
87                        $stripPattern,
88                        '$2',
89                        $symbol->getOriginalSymbol()
90                    );
91                    $namespaceReplacementPatterns[ "~(" . preg_quote($this->config->getNamespacePrefix(), '~') . '\\\\*)*' . preg_quote($strippedSymbol, '~') . '~' ]
92                                    = "{$this->config->getNamespacePrefix()}\\{$strippedSymbol}";
93                    unset($stripPattern, $strippedSymbol);
94                }
95
96                // `namespace_replacement_patterns` should be ordered by priority.
97                foreach ($namespaceReplacementPatterns as $namespaceReplacementPattern => $replacement) {
98                    $prefixed = preg_replace(
99                        $namespaceReplacementPattern,
100                        $replacement,
101                        $symbol->getOriginalSymbol()
102                    );
103
104                    if ($prefixed !== $symbol->getOriginalSymbol()) {
105                        $symbol->setReplacement($prefixed);
106                        continue 2;
107                    }
108                }
109                $this->logger->debug("Namespace {$symbol->getOriginalSymbol()} not changed.");
110            }
111        }
112
113        $classmapPrefix = $this->config->getClassmapPrefix();
114
115
116        $classesTraitsInterfaces = array_merge(
117            $discoveredSymbols->getDiscoveredTraits(),
118            $discoveredSymbols->getDiscoveredInterfaces(),
119            $discoveredSymbols->getAllClasses()
120        );
121
122        foreach ($classesTraitsInterfaces as $theclass) {
123            if (str_starts_with($theclass->getOriginalSymbol(), $classmapPrefix)) {
124                // Already prefixed / second scan.
125                continue;
126            }
127
128            if ($theclass->getNamespace() === '\\') {
129                if ($symbol instanceof ClassSymbol) {
130                    // Don't double-prefix classnames.
131                    if (str_starts_with($symbol->getOriginalSymbol(), $this->config->getClassmapPrefix())) {
132                        continue;
133                    }
134
135                    $symbol->setReplacement($this->config->getClassmapPrefix() . $symbol->getOriginalSymbol());
136                }
137            }
138
139            if ($theclass->getNamespace() !== '\\') {
140                $newNamespace = $discoveredNamespaces[$theclass->getNamespace()];
141                if ($newNamespace) {
142                    // TODO: This should be in ChangeEnumerator.
143                    // `str_replace` was replacing multiple. This stops after one. Maybe should be tied to start of string.
144                    $determineReplacement = function ($originalNamespace, $newNamespace, $fqdnClassname) {
145                        $search = '/'.preg_quote($originalNamespace, '/').'/';
146                        return preg_replace($search, $newNamespace, $fqdnClassname, 1);
147                    };
148                    $theclass->setReplacement(
149                        $determineReplacement(
150                            $newNamespace->getOriginalSymbol(),
151                            $newNamespace->getReplacement(),
152                            $theclass->getOriginalSymbol()
153                        )
154                    );
155                    unset($newNamespace);
156                }
157                continue;
158            }
159            $theclass->setReplacement($classmapPrefix . $theclass->getOriginalSymbol());
160        }
161
162        $functionsSymbols = $discoveredSymbols->getDiscoveredFunctions();
163
164        foreach ($functionsSymbols as $symbol) {
165            // Don't prefix functions in a namespace – that will be addressed by the namespace prefix.
166            if ($symbol->getNamespace() !== '\\') {
167                continue;
168            }
169            $functionPrefix = $this->config->getFunctionsPrefix();
170            if (empty($functionPrefix) || str_starts_with($symbol->getOriginalSymbol(), $functionPrefix)) {
171                continue;
172            }
173
174            $symbol->setReplacement($functionPrefix . $symbol->getOriginalSymbol());
175        }
176    }
177}