Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.51% covered (warning)
69.51%
57 / 82
61.90% covered (warning)
61.90%
13 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 1
DiscoveredSymbols
69.51% covered (warning)
69.51%
57 / 82
61.90% covered (warning)
61.90%
13 / 21
61.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 add
78.57% covered (warning)
78.57%
11 / 14
0.00% covered (danger)
0.00%
0 / 1
8.63
 getSymbols
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getConstants
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNamespaces
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNamespace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getGlobalClasses
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getGlobalClassChanges
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getAllClasses
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDiscoveredNamespaces
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getDiscoveredNamespaceChanges
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getDiscoveredClasses
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getDiscoveredConstants
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getDiscoveredConstantChanges
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getDiscoveredFunctions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDiscoveredFunctionChanges
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getAll
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDiscoveredTraits
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDiscoveredInterfaces
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getClassmapSymbols
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getNamespaceSymbolByString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @see \BrianHenryIE\Strauss\Pipeline\FileSymbolScanner
4 */
5
6namespace BrianHenryIE\Strauss\Types;
7
8use BrianHenryIE\Strauss\Files\File;
9use InvalidArgumentException;
10
11class DiscoveredSymbols
12{
13    private const CLASS_SYMBOL = 'CLASS';
14    private const CONST_SYMBOL = 'CONST';
15    private const NAMESPACE_SYMBOL = 'NAMESPACE';
16    private const FUNCTION_SYMBOL = 'FUNCTION';
17    private const TRAIT_SYMBOL = 'TRAIT';
18    private const INTERFACE_SYMBOL = 'INTERFACE';
19
20    /**
21     * All discovered symbols, grouped by type, indexed by original name.
22     *
23     * @var array{'NAMESPACE':array<string,NamespaceSymbol>, 'CONST':array<string,ConstantSymbol>, 'CLASS':array<string,ClassSymbol>, 'FUNCTION':array<string,FunctionSymbol>, 'TRAIT':array<string,TraitSymbol>, 'INTERFACE':array<string,InterfaceSymbol>}
24     */
25    protected array $types = [
26        self::CLASS_SYMBOL => [],
27        self::CONST_SYMBOL => [],
28        self::NAMESPACE_SYMBOL => [],
29        self::FUNCTION_SYMBOL => [],
30        self::TRAIT_SYMBOL => [],
31        self::INTERFACE_SYMBOL => [],
32    ];
33
34    public function __construct()
35    {
36        // TODO: Should this have the root package?
37        $this->types[self::NAMESPACE_SYMBOL]['\\'] = new NamespaceSymbol('\\', new File('', ''));
38    }
39
40    /**
41     * TODO: This should merge the symbols instead of overwriting them.
42     *
43     * @param DiscoveredSymbol $symbol
44     */
45    public function add(DiscoveredSymbol $symbol): void
46    {
47        switch (get_class($symbol)) {
48            case NamespaceSymbol::class:
49                $this->types[self::NAMESPACE_SYMBOL][$symbol->getOriginalSymbol()] = $symbol;
50                return;
51            case ConstantSymbol::class:
52                $this->types[self::CONST_SYMBOL][$symbol->getOriginalSymbol()] = $symbol;
53                return;
54            case ClassSymbol::class:
55                $this->types[self::CLASS_SYMBOL][$symbol->getOriginalSymbol()] = $symbol;
56                return;
57            case FunctionSymbol::class:
58                $this->types[self::FUNCTION_SYMBOL][$symbol->getOriginalSymbol()] = $symbol;
59                return;
60            case InterfaceSymbol::class:
61                $this->types[self::INTERFACE_SYMBOL][$symbol->getOriginalSymbol()] = $symbol;
62                return;
63            case TraitSymbol::class:
64                $this->types[self::TRAIT_SYMBOL][$symbol->getOriginalSymbol()] = $symbol;
65                return;
66            default:
67                throw new InvalidArgumentException('Unknown symbol type: ' . get_class($symbol));
68        }
69    }
70
71    /**
72     * @return DiscoveredSymbol[]
73     */
74    public function getSymbols(): array
75    {
76        return array_merge(
77            array_values($this->getNamespaces()),
78            array_values($this->getGlobalClasses()),
79            array_values($this->getConstants()),
80            array_values($this->getDiscoveredFunctions()),
81        );
82    }
83
84    /**
85     * @return array<string, ConstantSymbol>
86     */
87    public function getConstants(): array
88    {
89        return $this->types[self::CONST_SYMBOL];
90    }
91
92    /**
93     * @return array<string, NamespaceSymbol>
94     */
95    public function getNamespaces(): array
96    {
97        return $this->types[self::NAMESPACE_SYMBOL];
98    }
99
100    public function getNamespace(string $namespace): ?NamespaceSymbol
101    {
102        return $this->types[self::NAMESPACE_SYMBOL][$namespace] ?? null;
103    }
104
105    /**
106     * @return array<string, ClassSymbol>
107     */
108    public function getGlobalClasses(): array
109    {
110        return array_filter(
111            $this->types[self::CLASS_SYMBOL],
112            fn($classSymbol) => '\\' === $classSymbol->getNamespace()
113        );
114    }
115
116    /**
117     * @return array<string, ClassSymbol>
118     */
119    public function getGlobalClassChanges(): array
120    {
121        return array_filter(
122            $this->getGlobalClasses(),
123            fn($classSymbol) => $classSymbol->isDoRename()
124        );
125    }
126
127    /**
128     * @return array<string, ClassSymbol>
129     */
130    public function getAllClasses(): array
131    {
132        return $this->types[self::CLASS_SYMBOL];
133    }
134
135    /**
136     * TODO: Order by longest string first. (or instead, record classnames with their namespaces)
137     *
138     * @return array<string, NamespaceSymbol>
139     */
140    public function getDiscoveredNamespaces(?string $namespacePrefix = ''): array
141    {
142        $discoveredNamespaceReplacements = [];
143
144        // When running subsequent times, try to discover the original namespaces.
145        // This is naive: it will not work where namespace replacement patterns have been used.
146        foreach ($this->getNamespaces() as $namespaceSymbol) {
147            $discoveredNamespaceReplacements[ $namespaceSymbol->getOriginalSymbol() ] = $namespaceSymbol;
148        }
149
150        uksort($discoveredNamespaceReplacements, function ($a, $b) {
151            return strlen($a) <=> strlen($b);
152        });
153
154        unset($discoveredNamespaceReplacements['\\']);
155
156        return $discoveredNamespaceReplacements;
157    }
158
159    /**
160     * @return array<string, NamespaceSymbol>
161     */
162    public function getDiscoveredNamespaceChanges(?string $namespacePrefix = ''): array
163    {
164        return array_filter(
165            $this->getdiscoveredNamespaces($namespacePrefix),
166            fn($namespaceSymbol) => $namespaceSymbol->isDoRename()
167        );
168    }
169
170    /**
171     * @return string[]
172     */
173    public function getDiscoveredClasses(?string $classmapPrefix = ''): array
174    {
175        $discoveredClasses = $this->getGlobalClasses();
176
177        return array_filter(
178            array_keys($discoveredClasses),
179            function (string $replacement) use ($classmapPrefix) {
180                return empty($classmapPrefix) || ! str_starts_with($replacement, $classmapPrefix);
181            }
182        );
183    }
184
185    /**
186     * @return string[]
187     */
188    public function getDiscoveredConstants(?string $constantsPrefix = ''): array
189    {
190        return array_filter(
191            array_keys($this->getConstants()),
192            function (string $replacement) use ($constantsPrefix) {
193                return empty($constantsPrefix) || ! str_starts_with($replacement, $constantsPrefix);
194            }
195        );
196    }
197
198    /**
199     * Constant names that should be prefixed (symbol has isDoRename()).
200     *
201     * @return string[]
202     */
203    public function getDiscoveredConstantChanges(?string $constantsPrefix = ''): array
204    {
205        $constantsToRename = array_filter(
206            $this->getConstants(),
207            fn(ConstantSymbol $symbol) => $symbol->isDoRename()
208        );
209        return array_filter(
210            array_keys($constantsToRename),
211            function (string $replacement) use ($constantsPrefix) {
212                return empty($constantsPrefix) || ! str_starts_with($replacement, $constantsPrefix);
213            }
214        );
215    }
216
217    /**
218     * @return FunctionSymbol[]
219     */
220    public function getDiscoveredFunctions(): array
221    {
222        return $this->types[self::FUNCTION_SYMBOL];
223    }
224
225    /**
226     * @return FunctionSymbol[]
227     */
228    public function getDiscoveredFunctionChanges(): array
229    {
230        return array_filter(
231            $this->getDiscoveredFunctions(),
232            fn($discoveredFunction) => $discoveredFunction->isDoRename()
233        );
234    }
235
236    /**
237     * @return array<string,DiscoveredSymbol>
238     */
239    public function getAll(): array
240    {
241        return array_merge(...array_values($this->types));
242    }
243
244    /**
245     * @return array<string,TraitSymbol>
246     */
247    public function getDiscoveredTraits(): array
248    {
249        return (array) $this->types[self::TRAIT_SYMBOL];
250    }
251
252    /**
253     * @return array<string,InterfaceSymbol>
254     */
255    public function getDiscoveredInterfaces(): array
256    {
257        return (array) $this->types[self::INTERFACE_SYMBOL];
258    }
259
260    /**
261     * Get all discovered symbols that are classes, interfaces, or traits, i.e. only those that are autoloadable.
262     *
263     * @return array<DiscoveredSymbol>
264     */
265    public function getClassmapSymbols(): array
266    {
267        return array_merge(
268            $this->getGlobalClasses(),
269            $this->getDiscoveredInterfaces(),
270            $this->getDiscoveredTraits(),
271        );
272    }
273
274    public function getNamespaceSymbolByString(string $namespace): ?NamespaceSymbol
275    {
276        return $this->types[self::NAMESPACE_SYMBOL][$namespace] ?? null;
277    }
278}