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