Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 200 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of Psy Shell.
5
 *
200 lars 6
 * (c) 2012-2023 Justin Hileman
148 lars 7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Psy\CodeCleaner;
13
 
14
use PhpParser\Node;
15
use PhpParser\Node\Expr;
16
use PhpParser\Node\Expr\Ternary;
17
use PhpParser\Node\Stmt;
18
use PhpParser\Node\Stmt\Class_;
19
use PhpParser\Node\Stmt\Do_;
20
use PhpParser\Node\Stmt\If_;
21
use PhpParser\Node\Stmt\Interface_;
22
use PhpParser\Node\Stmt\Switch_;
23
use PhpParser\Node\Stmt\Trait_;
24
use PhpParser\Node\Stmt\While_;
25
use Psy\Exception\FatalErrorException;
26
 
27
/**
28
 * Validate that classes exist.
29
 *
30
 * This pass throws a FatalErrorException rather than letting PHP run
31
 * headfirst into a real fatal error and die.
32
 */
33
class ValidClassNamePass extends NamespaceAwarePass
34
{
35
    const CLASS_TYPE = 'class';
36
    const INTERFACE_TYPE = 'interface';
37
    const TRAIT_TYPE = 'trait';
38
 
39
    private $conditionalScopes = 0;
40
 
41
    /**
42
     * Validate class, interface and trait definitions.
43
     *
44
     * Validate them upon entering the node, so that we know about their
45
     * presence and can validate constant fetches and static calls in class or
46
     * trait methods.
47
     *
48
     * @param Node $node
49
     *
50
     * @return int|Node|null Replacement node (or special return value)
51
     */
52
    public function enterNode(Node $node)
53
    {
54
        parent::enterNode($node);
55
 
56
        if (self::isConditional($node)) {
57
            $this->conditionalScopes++;
58
 
59
            return;
60
        }
61
 
62
        if ($this->conditionalScopes === 0) {
63
            if ($node instanceof Class_) {
64
                $this->validateClassStatement($node);
65
            } elseif ($node instanceof Interface_) {
66
                $this->validateInterfaceStatement($node);
67
            } elseif ($node instanceof Trait_) {
68
                $this->validateTraitStatement($node);
69
            }
70
        }
71
    }
72
 
73
    /**
74
     * @param Node $node
75
     *
76
     * @return int|Node|Node[]|null Replacement node (or special return value)
77
     */
78
    public function leaveNode(Node $node)
79
    {
80
        if (self::isConditional($node)) {
81
            $this->conditionalScopes--;
82
 
83
            return;
84
        }
85
    }
86
 
87
    private static function isConditional(Node $node): bool
88
    {
89
        return $node instanceof If_ ||
90
            $node instanceof While_ ||
91
            $node instanceof Do_ ||
92
            $node instanceof Switch_ ||
93
            $node instanceof Ternary;
94
    }
95
 
96
    /**
97
     * Validate a class definition statement.
98
     *
99
     * @param Class_ $stmt
100
     */
101
    protected function validateClassStatement(Class_ $stmt)
102
    {
103
        $this->ensureCanDefine($stmt, self::CLASS_TYPE);
104
        if (isset($stmt->extends)) {
105
            $this->ensureClassExists($this->getFullyQualifiedName($stmt->extends), $stmt);
106
        }
107
        $this->ensureInterfacesExist($stmt->implements, $stmt);
108
    }
109
 
110
    /**
111
     * Validate an interface definition statement.
112
     *
113
     * @param Interface_ $stmt
114
     */
115
    protected function validateInterfaceStatement(Interface_ $stmt)
116
    {
117
        $this->ensureCanDefine($stmt, self::INTERFACE_TYPE);
118
        $this->ensureInterfacesExist($stmt->extends, $stmt);
119
    }
120
 
121
    /**
122
     * Validate a trait definition statement.
123
     *
124
     * @param Trait_ $stmt
125
     */
126
    protected function validateTraitStatement(Trait_ $stmt)
127
    {
128
        $this->ensureCanDefine($stmt, self::TRAIT_TYPE);
129
    }
130
 
131
    /**
132
     * Ensure that no class, interface or trait name collides with a new definition.
133
     *
134
     * @throws FatalErrorException
135
     *
136
     * @param Stmt   $stmt
137
     * @param string $scopeType
138
     */
139
    protected function ensureCanDefine(Stmt $stmt, string $scopeType = self::CLASS_TYPE)
140
    {
141
        // Anonymous classes don't have a name, and uniqueness shouldn't be enforced.
142
        if ($stmt->name === null) {
143
            return;
144
        }
145
 
146
        $name = $this->getFullyQualifiedName($stmt->name);
147
 
148
        // check for name collisions
149
        $errorType = null;
150
        if ($this->classExists($name)) {
151
            $errorType = self::CLASS_TYPE;
152
        } elseif ($this->interfaceExists($name)) {
153
            $errorType = self::INTERFACE_TYPE;
154
        } elseif ($this->traitExists($name)) {
155
            $errorType = self::TRAIT_TYPE;
156
        }
157
 
158
        if ($errorType !== null) {
159
            throw $this->createError(\sprintf('%s named %s already exists', \ucfirst($errorType), $name), $stmt);
160
        }
161
 
162
        // Store creation for the rest of this code snippet so we can find local
163
        // issue too
164
        $this->currentScope[\strtolower($name)] = $scopeType;
165
    }
166
 
167
    /**
168
     * Ensure that a referenced class exists.
169
     *
170
     * @throws FatalErrorException
171
     *
172
     * @param string $name
173
     * @param Stmt   $stmt
174
     */
175
    protected function ensureClassExists(string $name, Stmt $stmt)
176
    {
177
        if (!$this->classExists($name)) {
178
            throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
179
        }
180
    }
181
 
182
    /**
183
     * Ensure that a referenced class _or interface_ exists.
184
     *
185
     * @throws FatalErrorException
186
     *
187
     * @param string $name
188
     * @param Stmt   $stmt
189
     */
190
    protected function ensureClassOrInterfaceExists(string $name, Stmt $stmt)
191
    {
192
        if (!$this->classExists($name) && !$this->interfaceExists($name)) {
193
            throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
194
        }
195
    }
196
 
197
    /**
198
     * Ensure that a referenced class _or trait_ exists.
199
     *
200
     * @throws FatalErrorException
201
     *
202
     * @param string $name
203
     * @param Stmt   $stmt
204
     */
205
    protected function ensureClassOrTraitExists(string $name, Stmt $stmt)
206
    {
207
        if (!$this->classExists($name) && !$this->traitExists($name)) {
208
            throw $this->createError(\sprintf('Class \'%s\' not found', $name), $stmt);
209
        }
210
    }
211
 
212
    /**
213
     * Ensure that a statically called method exists.
214
     *
215
     * @throws FatalErrorException
216
     *
217
     * @param string $class
218
     * @param string $name
219
     * @param Stmt   $stmt
220
     */
221
    protected function ensureMethodExists(string $class, string $name, Stmt $stmt)
222
    {
223
        $this->ensureClassOrTraitExists($class, $stmt);
224
 
225
        // let's pretend all calls to self, parent and static are valid
226
        if (\in_array(\strtolower($class), ['self', 'parent', 'static'])) {
227
            return;
228
        }
229
 
230
        // ... and all calls to classes defined right now
231
        if ($this->findInScope($class) === self::CLASS_TYPE) {
232
            return;
233
        }
234
 
235
        // if method name is an expression, give it a pass for now
236
        if ($name instanceof Expr) {
237
            return;
238
        }
239
 
240
        if (!\method_exists($class, $name) && !\method_exists($class, '__callStatic')) {
241
            throw $this->createError(\sprintf('Call to undefined method %s::%s()', $class, $name), $stmt);
242
        }
243
    }
244
 
245
    /**
246
     * Ensure that a referenced interface exists.
247
     *
248
     * @throws FatalErrorException
249
     *
250
     * @param Interface_[] $interfaces
251
     * @param Stmt         $stmt
252
     */
253
    protected function ensureInterfacesExist(array $interfaces, Stmt $stmt)
254
    {
255
        foreach ($interfaces as $interface) {
256
            /** @var string $name */
257
            $name = $this->getFullyQualifiedName($interface);
258
            if (!$this->interfaceExists($name)) {
259
                throw $this->createError(\sprintf('Interface \'%s\' not found', $name), $stmt);
260
            }
261
        }
262
    }
263
 
264
    /**
265
     * Get a symbol type key for storing in the scope name cache.
266
     *
267
     * @deprecated No longer used. Scope type should be passed into ensureCanDefine directly.
991 lars 268
     *
148 lars 269
     * @codeCoverageIgnore
270
     *
271
     * @throws FatalErrorException
272
     *
273
     * @param Stmt $stmt
274
     */
275
    protected function getScopeType(Stmt $stmt): string
276
    {
277
        if ($stmt instanceof Class_) {
278
            return self::CLASS_TYPE;
279
        } elseif ($stmt instanceof Interface_) {
280
            return self::INTERFACE_TYPE;
281
        } elseif ($stmt instanceof Trait_) {
282
            return self::TRAIT_TYPE;
283
        }
284
 
285
        throw $this->createError('Unsupported statement type', $stmt);
286
    }
287
 
288
    /**
289
     * Check whether a class exists, or has been defined in the current code snippet.
290
     *
291
     * Gives `self`, `static` and `parent` a free pass.
292
     *
293
     * @param string $name
294
     */
295
    protected function classExists(string $name): bool
296
    {
297
        // Give `self`, `static` and `parent` a pass. This will actually let
298
        // some errors through, since we're not checking whether the keyword is
299
        // being used in a class scope.
300
        if (\in_array(\strtolower($name), ['self', 'static', 'parent'])) {
301
            return true;
302
        }
303
 
304
        return \class_exists($name) || $this->findInScope($name) === self::CLASS_TYPE;
305
    }
306
 
307
    /**
308
     * Check whether an interface exists, or has been defined in the current code snippet.
309
     *
310
     * @param string $name
311
     */
312
    protected function interfaceExists(string $name): bool
313
    {
314
        return \interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE;
315
    }
316
 
317
    /**
318
     * Check whether a trait exists, or has been defined in the current code snippet.
319
     *
320
     * @param string $name
321
     */
322
    protected function traitExists(string $name): bool
323
    {
324
        return \trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE;
325
    }
326
 
327
    /**
328
     * Find a symbol in the current code snippet scope.
329
     *
330
     * @param string $name
331
     *
332
     * @return string|null
333
     */
334
    protected function findInScope(string $name)
335
    {
336
        $name = \strtolower($name);
337
        if (isset($this->currentScope[$name])) {
338
            return $this->currentScope[$name];
339
        }
340
    }
341
 
342
    /**
343
     * Error creation factory.
344
     *
345
     * @param string $msg
346
     * @param Stmt   $stmt
347
     */
348
    protected function createError(string $msg, Stmt $stmt): FatalErrorException
349
    {
350
        return new FatalErrorException($msg, 0, \E_ERROR, null, $stmt->getLine());
351
    }
352
}