Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
13.51% covered (danger)
13.51%
10 / 74
9.09% covered (danger)
9.09%
1 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
MarkSymbolsForRenaming
13.51% covered (danger)
13.51%
10 / 74
9.09% covered (danger)
9.09%
1 / 11
874.40
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
 scanSymbols
64.29% covered (warning)
64.29%
9 / 14
0.00% covered (danger)
0.00%
0 / 1
9.23
 fileIsAutoloaded
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 excludeFromPrefix
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 isSymbolFoundInFileThatIsNotCopied
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 isSymbolFoundInFileThatIsCopied
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 isExcludeFromCopyPackage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isExcludeFromPrefixPackage
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 isExcludeFromPrefixNamespace
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 isExcludedFromPrefixFilePattern
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 preparePattern
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * Symbols found in autoloaded files should be prefixed, unless:
5 * * The `exclude_from_prefix` rules apply to the discovered symbols.
6 * * The file is in `exclude_from_copy`
7 */
8
9namespace BrianHenryIE\Strauss\Pipeline;
10
11use BrianHenryIE\Strauss\Config\MarkSymbolsForRenamingConfigInterface;
12use BrianHenryIE\Strauss\Files\File;
13use BrianHenryIE\Strauss\Files\FileBase;
14use BrianHenryIE\Strauss\Helpers\FileSystem;
15use BrianHenryIE\Strauss\Types\DiscoveredSymbol;
16use BrianHenryIE\Strauss\Types\DiscoveredSymbols;
17use BrianHenryIE\Strauss\Types\NamespaceSymbol;
18use Psr\Log\LoggerAwareTrait;
19use Psr\Log\LoggerInterface;
20
21class MarkSymbolsForRenaming
22{
23    use LoggerAwareTrait;
24
25    protected MarkSymbolsForRenamingConfigInterface $config;
26
27    protected FileSystem $filesystem;
28
29    public function __construct(
30        MarkSymbolsForRenamingConfigInterface $config,
31        FileSystem                            $filesystem,
32        LoggerInterface                       $logger
33    ) {
34        $this->config = $config;
35        $this->filesystem = $filesystem;
36        $this->setLogger($logger);
37    }
38
39    public function scanSymbols(DiscoveredSymbols $symbols): void
40    {
41        $allSymbols = $symbols->getSymbols();
42        foreach ($allSymbols as $symbol) {
43            // $this->config->getFlatDependencyTree
44
45            if (!$this->fileIsAutoloaded($symbol)) {
46//                $this->logger->debug()
47                $symbol->setDoRename(false);
48                continue;
49            }
50
51            // If the symbol's package is excluded from copy, don't prefix it
52            if ($this->isExcludeFromCopyPackage($symbol->getPackageName())) {
53                $symbol->setDoRename(false);
54                continue;
55            }
56
57            if ($this->excludeFromPrefix($symbol)) {
58                $symbol->setDoRename(false);
59                continue;
60            }
61
62//            if ($this->isSymbolFoundInFileThatIsNotCopied($symbol)) {
63//                if (count($symbol->getSourceFiles())===1) {
64//                    $symbol->setDoRename(false);
65//                }
66//            }
67            if ($this->config->getVendorDirectory() !== $this->config->getTargetDirectory()
68                && !$this->isSymbolFoundInFileThatIsCopied($symbol)) {
69                $symbol->setDoRename(false);
70            }
71        }
72    }
73
74    /**
75     * If all the files a symbol is defined in are autoloaded, prefix the symbol.
76     *
77     * There are packages where a class may be defined in two different files and they are conditionally loaded.
78     * TODO: How best to handle this scenario?
79     */
80    protected function fileIsAutoloaded(DiscoveredSymbol $symbol): bool
81    {
82        // The same namespace symbols are found in lots of files so this test isn't useful.
83        if ($symbol instanceof NamespaceSymbol) {
84            return true;
85        }
86
87        $sourceFiles = array_filter(
88            $symbol->getSourceFiles(),
89            fn (FileBase $file) => basename($file->getVendorRelativePath()) !== 'composer.json'
90        );
91
92        return array_reduce(
93            $sourceFiles,
94            fn(bool $carry, FileBase $fileBase) => $carry && $fileBase->isAutoloaded(),
95            true
96        );
97    }
98
99    /**
100     * Check the `exclude_from_prefix` rules for this symbol's package name, namespace and file-paths.
101     */
102    protected function excludeFromPrefix(DiscoveredSymbol $symbol): bool
103    {
104        return $this->isExcludeFromPrefixPackage($symbol->getPackageName())
105            || $this->isExcludeFromPrefixNamespace($symbol->getNamespace())
106            || $this->isExcludedFromPrefixFilePattern($symbol->getSourceFiles());
107    }
108
109    /**
110     * If any of the files the symbol was found in are marked not to prefix, don't prefix the symbol.
111     *
112     * `config.strauss.exclude_from_copy`.
113     *
114     * This requires {@see FileCopyScanner} to have been run first.
115     */
116    protected function isSymbolFoundInFileThatIsNotCopied(DiscoveredSymbol $symbol): bool
117    {
118        if ($this->config->getVendorDirectory() === $this->config->getTargetDirectory()) {
119            return false;
120        }
121
122        return !array_reduce(
123            $symbol->getSourceFiles(),
124            fn(bool $carry, FileBase $file) => $carry && $file->isDoCopy(),
125            true
126        );
127    }
128
129    protected function isSymbolFoundInFileThatIsCopied(DiscoveredSymbol $symbol): bool
130    {
131        if ($this->config->getVendorDirectory() === $this->config->getTargetDirectory()) {
132            return false;
133        }
134
135        return array_reduce(
136            $symbol->getSourceFiles(),
137            fn(bool $carry, FileBase $file) => $carry || $file->isDoCopy(),
138            false
139        );
140    }
141
142    /**
143     * Config: `extra.strauss.exclude_from_copy.packages`.
144     */
145    protected function isExcludeFromCopyPackage(?string $packageName): bool
146    {
147        return !is_null($packageName) && in_array($packageName, $this->config->getExcludePackagesFromCopy(), true);
148    }
149
150    /**
151     * Config: `extra.strauss.exclude_from_prefix.packages`.
152     */
153    protected function isExcludeFromPrefixPackage(?string $packageName): bool
154    {
155        if (is_null($packageName)) {
156            return false;
157        }
158
159        if (in_array(
160            $packageName,
161            $this->config->getExcludePackagesFromPrefixing(),
162            true
163        )) {
164            return true;
165        }
166
167        return false;
168    }
169
170    /**
171     * Config: `extra.strauss.exclude_from_prefix.namespaces`.
172     */
173    protected function isExcludeFromPrefixNamespace(?string $namespace): bool
174    {
175        if (empty($namespace)) {
176            return false;
177        }
178
179        foreach ($this->config->getExcludeNamespacesFromPrefixing() as $excludeNamespace) {
180            if (str_starts_with($namespace, $excludeNamespace)) {
181                return true;
182            }
183        }
184
185        return false;
186    }
187
188    /**
189     * Compares the relative path from the vendor dir with `exclude_file_patterns` config.
190     *
191     * Config: `extra.strauss.exclude_from_prefix.file_patterns`.
192     *
193     * @param array<FileBase> $files
194     */
195    protected function isExcludedFromPrefixFilePattern(array $files): bool
196    {
197        /** @var File $file */
198        foreach ($files as $file) {
199            $absoluteFilePath = $file->getAbsoluteTargetPath();
200            if (empty($absoluteFilePath)) {
201                // root namespace is in a fake file.
202                continue;
203            }
204            $vendorRelativePath = $file->getVendorRelativePath();
205            foreach ($this->config->getExcludeFilePatternsFromPrefixing() as $excludeFilePattern) {
206                if (1 === preg_match($this->preparePattern($excludeFilePattern), $vendorRelativePath)) {
207                    return true;
208                }
209            }
210        }
211        return false;
212    }
213
214    /**
215     * TODO: This should be moved into the class parsing the config.
216     */
217    private function preparePattern(string $pattern): string
218    {
219        $delimiter = '#';
220
221        if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
222            $pattern = $delimiter . $pattern . $delimiter;
223        }
224
225        return $pattern;
226    }
227}