Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
MarkSymbolsForRenaming
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 10
1122
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
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 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
 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 ($this->excludeFromPrefix($symbol)) {
52                $symbol->setDoRename(false);
53                continue;
54            }
55
56//            if ($this->isSymbolFoundInFileThatIsNotCopied($symbol)) {
57//                if (count($symbol->getSourceFiles())===1) {
58//                    $symbol->setDoRename(false);
59//                }
60//            }
61            if ($this->config->getVendorDirectory() !== $this->config->getTargetDirectory()
62                && !$this->isSymbolFoundInFileThatIsCopied($symbol)) {
63                $symbol->setDoRename(false);
64            }
65        }
66    }
67
68    /**
69     * If all the files a symbol is defined in are autoloaded, prefix the symbol.
70     *
71     * There are packages where a class may be defined in two different files and they are conditionally loaded.
72     * TODO: How best to handle this scenario?
73     */
74    protected function fileIsAutoloaded(DiscoveredSymbol $symbol): bool
75    {
76        // The same namespace symbols are found in lots of files so this test isn't useful.
77        if ($symbol instanceof NamespaceSymbol) {
78            return true;
79        }
80
81        $sourceFiles = array_filter(
82            $symbol->getSourceFiles(),
83            fn (FileBase $file) => basename($file->getVendorRelativePath()) !== 'composer.json'
84        );
85
86        return array_reduce(
87            $sourceFiles,
88            fn(bool $carry, FileBase $fileBase) => $carry && $fileBase->isAutoloaded(),
89            true
90        );
91    }
92
93    /**
94     * Check the `exclude_from_prefix` rules for this symbol's package name, namespace and file-paths.
95     */
96    protected function excludeFromPrefix(DiscoveredSymbol $symbol): bool
97    {
98        return $this->isExcludeFromPrefixPackage($symbol->getPackageName())
99            || $this->isExcludeFromPrefixNamespace($symbol->getNamespace())
100            || $this->isExcludedFromPrefixFilePattern($symbol->getSourceFiles());
101    }
102
103    /**
104     * If any of the files the symbol was found in are marked not to prefix, don't prefix the symbol.
105     *
106     * `config.strauss.exclude_from_copy`.
107     *
108     * This requires {@see FileCopyScanner} to have been run first.
109     */
110    protected function isSymbolFoundInFileThatIsNotCopied(DiscoveredSymbol $symbol): bool
111    {
112        if ($this->config->getVendorDirectory() === $this->config->getTargetDirectory()) {
113            return false;
114        }
115
116        return !array_reduce(
117            $symbol->getSourceFiles(),
118            fn(bool $carry, FileBase $file) => $carry && $file->isDoCopy(),
119            true
120        );
121    }
122
123    protected function isSymbolFoundInFileThatIsCopied(DiscoveredSymbol $symbol): bool
124    {
125        if ($this->config->getVendorDirectory() === $this->config->getTargetDirectory()) {
126            return false;
127        }
128
129        return array_reduce(
130            $symbol->getSourceFiles(),
131            fn(bool $carry, FileBase $file) => $carry || $file->isDoCopy(),
132            false
133        );
134    }
135
136    /**
137     * Config: `extra.strauss.exclude_from_prefix.packages`.
138     */
139    protected function isExcludeFromPrefixPackage(?string $packageName): bool
140    {
141        if (is_null($packageName)) {
142            return false;
143        }
144
145        if (in_array(
146            $packageName,
147            $this->config->getExcludePackagesFromPrefixing(),
148            true
149        )) {
150            return true;
151        }
152
153        return false;
154    }
155
156    /**
157     * Config: `extra.strauss.exclude_from_prefix.namespaces`.
158     */
159    protected function isExcludeFromPrefixNamespace(?string $namespace): bool
160    {
161        if (empty($namespace)) {
162            return false;
163        }
164
165        foreach ($this->config->getExcludeNamespacesFromPrefixing() as $excludeNamespace) {
166            if (str_starts_with($namespace, $excludeNamespace)) {
167                return true;
168            }
169        }
170
171        return false;
172    }
173
174    /**
175     * Compares the relative path from the vendor dir with `exclude_file_patterns` config.
176     *
177     * Config: `extra.strauss.exclude_from_prefix.file_patterns`.
178     *
179     * @param array<FileBase> $files
180     */
181    protected function isExcludedFromPrefixFilePattern(array $files): bool
182    {
183        /** @var File $file */
184        foreach ($files as $file) {
185            $absoluteFilePath = $file->getAbsoluteTargetPath();
186            if (empty($absoluteFilePath)) {
187                // root namespace is in a fake file.
188                continue;
189            }
190            $vendorRelativePath = $file->getVendorRelativePath();
191            foreach ($this->config->getExcludeFilePatternsFromPrefixing() as $excludeFilePattern) {
192                if (1 === preg_match($this->preparePattern($excludeFilePattern), $vendorRelativePath)) {
193                    return true;
194                }
195            }
196        }
197        return false;
198    }
199
200    /**
201     * TODO: This should be moved into the class parsing the config.
202     */
203    private function preparePattern(string $pattern): string
204    {
205        $delimiter = '#';
206
207        if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
208            $pattern = $delimiter . $pattern . $delimiter;
209        }
210
211        return $pattern;
212    }
213}