Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.67% covered (danger)
16.67%
15 / 90
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ChangeEnumerator
16.67% covered (danger)
16.67%
15 / 90
0.00% covered (danger)
0.00%
0 / 4
515.69
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 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 determineReplacements
21.13% covered (danger)
21.13%
15 / 71
0.00% covered (danger)
0.00%
0 / 1
216.27
 determineNamespaceReplacement
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
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                        foreach ($file->getDiscoveredSymbols() as $discoveredSymbol) {
59                            $discoveredSymbol->setDoRename(false);
60                        }
61                    }
62                }
63            }
64        }
65    }
66
67    public function determineReplacements(DiscoveredSymbols $discoveredSymbols): void
68    {
69        $discoveredNamespaces = $discoveredSymbols->getDiscoveredNamespaces();
70
71        foreach ($discoveredNamespaces as $symbol) {
72            // This line seems redundant.
73            if ($symbol instanceof NamespaceSymbol) {
74                $namespaceReplacementPatterns = $this->config->getNamespaceReplacementPatterns();
75
76                if (in_array(
77                    $symbol->getOriginalSymbol(),
78                    $this->config->getExcludeNamespacesFromPrefixing(),
79                    true
80                )) {
81                    $symbol->setDoRename(false);
82                }
83
84                // `namespace_prefix` is just a shorthand for a replacement pattern that applies to all namespaces.
85
86                // TODO: Maybe need to preg_quote and add regex delimiters to the patterns here.
87                foreach ($namespaceReplacementPatterns as $pattern => $replacement) {
88                    if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
89                        unset($namespaceReplacementPatterns[ $pattern ]);
90                        $pattern                                  = '~' . preg_quote($pattern, '~') . '~';
91                        $namespaceReplacementPatterns[ $pattern ] = $replacement;
92                    }
93                    unset($pattern, $replacement);
94                }
95
96                if (! is_null($this->config->getNamespacePrefix())) {
97                    $stripPattern   = '~^(' . preg_quote($this->config->getNamespacePrefix(), '~') . '\\\\*)*(.*)~';
98                    $strippedSymbol = preg_replace(
99                        $stripPattern,
100                        '$2',
101                        $symbol->getOriginalSymbol()
102                    );
103                    $namespaceReplacementPatterns[ "~(" . preg_quote($this->config->getNamespacePrefix(), '~') . '\\\\*)*' . preg_quote($strippedSymbol, '~') . '~' ]
104                                    = "{$this->config->getNamespacePrefix()}\\{$strippedSymbol}";
105                    unset($stripPattern, $strippedSymbol);
106                }
107
108                // `namespace_replacement_patterns` should be ordered by priority.
109                foreach ($namespaceReplacementPatterns as $namespaceReplacementPattern => $replacement) {
110                    $prefixed = preg_replace(
111                        $namespaceReplacementPattern,
112                        $replacement,
113                        $symbol->getOriginalSymbol()
114                    );
115
116                    if ($prefixed !== $symbol->getOriginalSymbol()) {
117                        $symbol->setReplacement($prefixed);
118                        continue 2;
119                    }
120                }
121                $this->logger->debug("Namespace {$symbol->getOriginalSymbol()} not changed.");
122            }
123        }
124
125        $classmapPrefix = $this->config->getClassmapPrefix();
126
127
128        $classesTraitsInterfaces = array_merge(
129            $discoveredSymbols->getDiscoveredTraits(),
130            $discoveredSymbols->getDiscoveredInterfaces(),
131            $discoveredSymbols->getAllClasses()
132        );
133
134        foreach ($classesTraitsInterfaces as $theclass) {
135            if (str_starts_with($theclass->getOriginalSymbol(), $classmapPrefix)) {
136                // Already prefixed / second scan.
137                continue;
138            }
139
140            if ($theclass->getNamespace() === '\\') {
141                if ($symbol instanceof ClassSymbol) {
142                    // Don't double-prefix classnames.
143                    if (str_starts_with($symbol->getOriginalSymbol(), $this->config->getClassmapPrefix())) {
144                        continue;
145                    }
146
147                    $symbol->setReplacement($this->config->getClassmapPrefix() . $symbol->getOriginalSymbol());
148                }
149            }
150
151            // If we're a namespaced class, apply the fqdnchange.
152            if ($theclass->getNamespace() !== '\\') {
153                $newNamespace = $discoveredNamespaces[$theclass->getNamespace()];
154                if ($newNamespace) {
155                    $replacement = $this->determineNamespaceReplacement(
156                        $newNamespace->getOriginalSymbol(),
157                        $newNamespace->getReplacement(),
158                        $theclass->getOriginalSymbol()
159                    );
160
161                    $theclass->setReplacement($replacement);
162
163                    unset($newNamespace, $replacement);
164                }
165                continue;
166            } else {
167                // Global class.
168                $replacement = $classmapPrefix . $theclass->getOriginalSymbol();
169                $theclass->setReplacement($replacement);
170            }
171        }
172
173        $functionsSymbols = $discoveredSymbols->getDiscoveredFunctions();
174
175        foreach ($functionsSymbols as $symbol) {
176            // Don't prefix functions in a namespace – that will be addressed by the namespace prefix.
177            if ($symbol->getNamespace() !== '\\') {
178                continue;
179            }
180            $functionPrefix = $this->config->getFunctionsPrefix();
181            if (empty($functionPrefix) || str_starts_with($symbol->getOriginalSymbol(), $functionPrefix)) {
182                continue;
183            }
184
185            $symbol->setReplacement($functionPrefix . $symbol->getOriginalSymbol());
186        }
187    }
188
189    /**
190     *`str_replace` was replacing multiple. This stops after one. Maybe should be tied to start of string.
191     */
192    protected function determineNamespaceReplacement(string $originalNamespace, string $newNamespace, string $fqdnClassname): string
193    {
194            $search = '/' . preg_quote($originalNamespace, '/') . '/';
195
196            return preg_replace($search, $newNamespace, $fqdnClassname, 1);
197    }
198}