PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.8.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.8.2
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Dependencies / PhpParser / ParserAbstract.php

ParserAbstract.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.8.2, at includes/Dependencies/PhpParser/ParserAbstract.php

731 lines 29.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:ignoreFile -- Bundled third-party (Mozart) dependency; exempt from plugin coding standards.
3
4 namespace WPDeveloper\BetterDocs\Dependencies\PhpParser;
5
6 /*
7 * This parser is based on a skeleton written by Moriyoshi Koizumi, which in
8 * turn is based on work by Masato Bito.
9 */
10 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Name;
11 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Param;
12 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Scalar\LNumber;
13 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Scalar\String_;
14 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Stmt\Class_;
15 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Stmt\ClassConst;
16 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Stmt\ClassMethod;
17 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Stmt\Interface_;
18 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Stmt\Namespace_;
19 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Stmt\Property;
20 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Stmt\TryCatch;
21 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Stmt\UseUse;
22
23 abstract class ParserAbstract implements Parser
24 {
25 const SYMBOL_NONE = -1;
26
27 /*
28 * The following members will be filled with generated parsing data:
29 */
30
31 /** @var int Size of $tokenToSymbol map */
32 protected $tokenToSymbolMapSize;
33 /** @var int Size of $action table */
34 protected $actionTableSize;
35 /** @var int Size of $goto table */
36 protected $gotoTableSize;
37
38 /** @var int Symbol number signifying an invalid token */
39 protected $invalidSymbol;
40 /** @var int Symbol number of error recovery token */
41 protected $errorSymbol;
42 /** @var int Action number signifying default action */
43 protected $defaultAction;
44 /** @var int Rule number signifying that an unexpected token was encountered */
45 protected $unexpectedTokenRule;
46
47 protected $YY2TBLSTATE;
48 protected $YYNLSTATES;
49
50 /** @var array Map of lexer tokens to internal symbols */
51 protected $tokenToSymbol;
52 /** @var array Map of symbols to their names */
53 protected $symbolToName;
54 /** @var array Names of the production rules (only necessary for debugging) */
55 protected $productions;
56
57 /** @var array Map of states to a displacement into the $action table. The corresponding action for this
58 * state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
59 action is defaulted, i.e. $actionDefault[$state] should be used instead. */
60 protected $actionBase;
61 /** @var array Table of actions. Indexed according to $actionBase comment. */
62 protected $action;
63 /** @var array Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol
64 * then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */
65 protected $actionCheck;
66 /** @var array Map of states to their default action */
67 protected $actionDefault;
68
69 /** @var array Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this
70 * non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */
71 protected $gotoBase;
72 /** @var array Table of states to goto after reduction. Indexed according to $gotoBase comment. */
73 protected $goto;
74 /** @var array Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal
75 * then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */
76 protected $gotoCheck;
77 /** @var array Map of non-terminals to the default state to goto after their reduction */
78 protected $gotoDefault;
79
80 /** @var array Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for
81 * determining the state to goto after reduction. */
82 protected $ruleToNonTerminal;
83 /** @var array Map of rules to the length of their right-hand side, which is the number of elements that have to
84 * be popped from the stack(s) on reduction. */
85 protected $ruleToLength;
86
87 /*
88 * The following members are part of the parser state:
89 */
90
91 /** @var Lexer Lexer that is used when parsing */
92 protected $lexer;
93 /** @var mixed Temporary value containing the result of last semantic action (reduction) */
94 protected $semValue;
95 /** @var int Position in stacks (state stack, semantic value stack, attribute stack) */
96 protected $stackPos;
97 /** @var array Semantic value stack (contains values of tokens and semantic action results) */
98 protected $semStack;
99 /** @var array[] Start attribute stack */
100 protected $startAttributeStack;
101 /** @var array[] End attribute stack */
102 protected $endAttributeStack;
103 /** @var array End attributes of last *shifted* token */
104 protected $endAttributes;
105 /** @var array Start attributes of last *read* token */
106 protected $lookaheadStartAttributes;
107
108 /** @var ErrorHandler Error handler */
109 protected $errorHandler;
110 /** @var Error[] Errors collected during last parse */
111 protected $errors;
112 /** @var int Error state, used to avoid error floods */
113 protected $errorState;
114
115 /**
116 * Creates a parser instance.
117 *
118 * @param Lexer $lexer A lexer
119 * @param array $options Options array. Currently no options are supported.
120 */
121 public function __construct(Lexer $lexer, array $options = array()) {
122 $this->lexer = $lexer;
123 $this->errors = array();
124
125 if (isset($options['throwOnError'])) {
126 throw new \LogicException(
127 '"throwOnError" is no longer supported, use "errorHandler" instead');
128 }
129 }
130
131 /**
132 * Parses PHP code into a node tree.
133 *
134 * If a non-throwing error handler is used, the parser will continue parsing after an error
135 * occurred and attempt to build a partial AST.
136 *
137 * @param string $code The source code to parse
138 * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
139 * to ErrorHandler\Throwing.
140 *
141 * @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
142 * unable to recover from an error).
143 */
144 public function parse($code, ?ErrorHandler $errorHandler = null) {
145 $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
146
147 // Initialize the lexer
148 $this->lexer->startLexing($code, $this->errorHandler);
149
150 // We start off with no lookahead-token
151 $symbol = self::SYMBOL_NONE;
152
153 // The attributes for a node are taken from the first and last token of the node.
154 // From the first token only the startAttributes are taken and from the last only
155 // the endAttributes. Both are merged using the array union operator (+).
156 $startAttributes = '*POISON';
157 $endAttributes = '*POISON';
158 $this->endAttributes = $endAttributes;
159
160 // Keep stack of start and end attributes
161 $this->startAttributeStack = array();
162 $this->endAttributeStack = array($endAttributes);
163
164 // Start off in the initial state and keep a stack of previous states
165 $state = 0;
166 $stateStack = array($state);
167
168 // Semantic value stack (contains values of tokens and semantic action results)
169 $this->semStack = array();
170
171 // Current position in the stack(s)
172 $this->stackPos = 0;
173
174 $this->errorState = 0;
175
176 for (;;) {
177 //$this->traceNewState($state, $symbol);
178
179 if ($this->actionBase[$state] == 0) {
180 $rule = $this->actionDefault[$state];
181 } else {
182 if ($symbol === self::SYMBOL_NONE) {
183 // Fetch the next token id from the lexer and fetch additional info by-ref.
184 // The end attributes are fetched into a temporary variable and only set once the token is really
185 // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
186 // reduced after a token was read but not yet shifted.
187 $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);
188
189 // map the lexer token id to the internally used symbols
190 $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
191 ? $this->tokenToSymbol[$tokenId]
192 : $this->invalidSymbol;
193
194 if ($symbol === $this->invalidSymbol) {
195 throw new \RangeException(sprintf(
196 'The lexer returned an invalid token (id=%d, value=%s)',
197 $tokenId, $tokenValue
198 ));
199 }
200
201 // This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
202 // the attributes of the next token, even though they don't contain it themselves.
203 $this->startAttributeStack[$this->stackPos+1] = $startAttributes;
204 $this->endAttributeStack[$this->stackPos+1] = $endAttributes;
205 $this->lookaheadStartAttributes = $startAttributes;
206
207 //$this->traceRead($symbol);
208 }
209
210 $idx = $this->actionBase[$state] + $symbol;
211 if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol)
212 || ($state < $this->YY2TBLSTATE
213 && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
214 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol))
215 && ($action = $this->action[$idx]) != $this->defaultAction) {
216 /*
217 * >= YYNLSTATES: shift and reduce
218 * > 0: shift
219 * = 0: accept
220 * < 0: reduce
221 * = -YYUNEXPECTED: error
222 */
223 if ($action > 0) {
224 /* shift */
225 //$this->traceShift($symbol);
226
227 ++$this->stackPos;
228 $stateStack[$this->stackPos] = $state = $action;
229 $this->semStack[$this->stackPos] = $tokenValue;
230 $this->startAttributeStack[$this->stackPos] = $startAttributes;
231 $this->endAttributeStack[$this->stackPos] = $endAttributes;
232 $this->endAttributes = $endAttributes;
233 $symbol = self::SYMBOL_NONE;
234
235 if ($this->errorState) {
236 --$this->errorState;
237 }
238
239 if ($action < $this->YYNLSTATES) {
240 continue;
241 }
242
243 /* $yyn >= YYNLSTATES means shift-and-reduce */
244 $rule = $action - $this->YYNLSTATES;
245 } else {
246 $rule = -$action;
247 }
248 } else {
249 $rule = $this->actionDefault[$state];
250 }
251 }
252
253 for (;;) {
254 if ($rule === 0) {
255 /* accept */
256 //$this->traceAccept();
257 return $this->semValue;
258 } elseif ($rule !== $this->unexpectedTokenRule) {
259 /* reduce */
260 //$this->traceReduce($rule);
261
262 try {
263 $this->{'reduceRule' . $rule}();
264 } catch (Error $e) {
265 if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
266 $e->setStartLine($startAttributes['startLine']);
267 }
268
269 $this->emitError($e);
270 // Can't recover from this type of error
271 return null;
272 }
273
274 /* Goto - shift nonterminal */
275 $lastEndAttributes = $this->endAttributeStack[$this->stackPos];
276 $this->stackPos -= $this->ruleToLength[$rule];
277 $nonTerminal = $this->ruleToNonTerminal[$rule];
278 $idx = $this->gotoBase[$nonTerminal] + $stateStack[$this->stackPos];
279 if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] == $nonTerminal) {
280 $state = $this->goto[$idx];
281 } else {
282 $state = $this->gotoDefault[$nonTerminal];
283 }
284
285 ++$this->stackPos;
286 $stateStack[$this->stackPos] = $state;
287 $this->semStack[$this->stackPos] = $this->semValue;
288 $this->endAttributeStack[$this->stackPos] = $lastEndAttributes;
289 } else {
290 /* error */
291 switch ($this->errorState) {
292 case 0:
293 $msg = $this->getErrorMessage($symbol, $state);
294 $this->emitError(new Error($msg, $startAttributes + $endAttributes));
295 // Break missing intentionally
296 case 1:
297 case 2:
298 $this->errorState = 3;
299
300 // Pop until error-expecting state uncovered
301 while (!(
302 (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
303 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
304 || ($state < $this->YY2TBLSTATE
305 && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $this->errorSymbol) >= 0
306 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
307 ) || ($action = $this->action[$idx]) == $this->defaultAction) { // Not totally sure about this
308 if ($this->stackPos <= 0) {
309 // Could not recover from error
310 return null;
311 }
312 $state = $stateStack[--$this->stackPos];
313 //$this->tracePop($state);
314 }
315
316 //$this->traceShift($this->errorSymbol);
317 ++$this->stackPos;
318 $stateStack[$this->stackPos] = $state = $action;
319
320 // We treat the error symbol as being empty, so we reset the end attributes
321 // to the end attributes of the last non-error symbol
322 $this->endAttributeStack[$this->stackPos] = $this->endAttributeStack[$this->stackPos - 1];
323 $this->endAttributes = $this->endAttributeStack[$this->stackPos - 1];
324 break;
325
326 case 3:
327 if ($symbol === 0) {
328 // Reached EOF without recovering from error
329 return null;
330 }
331
332 //$this->traceDiscard($symbol);
333 $symbol = self::SYMBOL_NONE;
334 break 2;
335 }
336 }
337
338 if ($state < $this->YYNLSTATES) {
339 break;
340 }
341
342 /* >= YYNLSTATES means shift-and-reduce */
343 $rule = $state - $this->YYNLSTATES;
344 }
345 }
346
347 throw new \RuntimeException('Reached end of parser loop');
348 }
349
350 protected function emitError(Error $error) {
351 $this->errorHandler->handleError($error);
352 }
353
354 protected function getErrorMessage($symbol, $state) {
355 $expectedString = '';
356 if ($expected = $this->getExpectedTokens($state)) {
357 $expectedString = ', expecting ' . implode(' or ', $expected);
358 }
359
360 return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
361 }
362
363 protected function getExpectedTokens($state) {
364 $expected = array();
365
366 $base = $this->actionBase[$state];
367 foreach ($this->symbolToName as $symbol => $name) {
368 $idx = $base + $symbol;
369 if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
370 || $state < $this->YY2TBLSTATE
371 && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
372 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
373 ) {
374 if ($this->action[$idx] != $this->unexpectedTokenRule
375 && $this->action[$idx] != $this->defaultAction
376 && $symbol != $this->errorSymbol
377 ) {
378 if (count($expected) == 4) {
379 /* Too many expected tokens */
380 return array();
381 }
382
383 $expected[] = $name;
384 }
385 }
386 }
387
388 return $expected;
389 }
390
391 /*
392 * Tracing functions used for debugging the parser.
393 */
394
395 /*
396 protected function traceNewState($state, $symbol) {
397 echo '% State ' . $state
398 . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
399 }
400
401 protected function traceRead($symbol) {
402 echo '% Reading ' . $this->symbolToName[$symbol] . "\n";
403 }
404
405 protected function traceShift($symbol) {
406 echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
407 }
408
409 protected function traceAccept() {
410 echo "% Accepted.\n";
411 }
412
413 protected function traceReduce($n) {
414 echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
415 }
416
417 protected function tracePop($state) {
418 echo '% Recovering, uncovered state ' . $state . "\n";
419 }
420
421 protected function traceDiscard($symbol) {
422 echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
423 }
424 */
425
426 /*
427 * Helper functions invoked by semantic actions
428 */
429
430 /**
431 * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions.
432 *
433 * @param Node[] $stmts
434 * @return Node[]
435 */
436 protected function handleNamespaces(array $stmts) {
437 $hasErrored = false;
438 $style = $this->getNamespacingStyle($stmts);
439 if (null === $style) {
440 // not namespaced, nothing to do
441 return $stmts;
442 } elseif ('brace' === $style) {
443 // For braced namespaces we only have to check that there are no invalid statements between the namespaces
444 $afterFirstNamespace = false;
445 foreach ($stmts as $stmt) {
446 if ($stmt instanceof Node\Stmt\Namespace_) {
447 $afterFirstNamespace = true;
448 } elseif (!$stmt instanceof Node\Stmt\HaltCompiler
449 && !$stmt instanceof Node\Stmt\Nop
450 && $afterFirstNamespace && !$hasErrored) {
451 $this->emitError(new Error(
452 'No code may exist outside of namespace {}', $stmt->getAttributes()));
453 $hasErrored = true; // Avoid one error for every statement
454 }
455 }
456 return $stmts;
457 } else {
458 // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
459 $resultStmts = array();
460 $targetStmts =& $resultStmts;
461 foreach ($stmts as $stmt) {
462 if ($stmt instanceof Node\Stmt\Namespace_) {
463 if ($stmt->stmts === null) {
464 $stmt->stmts = array();
465 $targetStmts =& $stmt->stmts;
466 $resultStmts[] = $stmt;
467 } else {
468 // This handles the invalid case of mixed style namespaces
469 $resultStmts[] = $stmt;
470 $targetStmts =& $resultStmts;
471 }
472 } elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
473 // __halt_compiler() is not moved into the namespace
474 $resultStmts[] = $stmt;
475 } else {
476 $targetStmts[] = $stmt;
477 }
478 }
479 return $resultStmts;
480 }
481 }
482
483 private function getNamespacingStyle(array $stmts) {
484 $style = null;
485 $hasNotAllowedStmts = false;
486 foreach ($stmts as $i => $stmt) {
487 if ($stmt instanceof Node\Stmt\Namespace_) {
488 $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace';
489 if (null === $style) {
490 $style = $currentStyle;
491 if ($hasNotAllowedStmts) {
492 $this->emitError(new Error(
493 'Namespace declaration statement has to be the very first statement in the script',
494 $stmt->getLine() // Avoid marking the entire namespace as an error
495 ));
496 }
497 } elseif ($style !== $currentStyle) {
498 $this->emitError(new Error(
499 'Cannot mix bracketed namespace declarations with unbracketed namespace declarations',
500 $stmt->getLine() // Avoid marking the entire namespace as an error
501 ));
502 // Treat like semicolon style for namespace normalization
503 return 'semicolon';
504 }
505 continue;
506 }
507
508 /* declare(), __halt_compiler() and nops can be used before a namespace declaration */
509 if ($stmt instanceof Node\Stmt\Declare_
510 || $stmt instanceof Node\Stmt\HaltCompiler
511 || $stmt instanceof Node\Stmt\Nop) {
512 continue;
513 }
514
515 /* There may be a hashbang line at the very start of the file */
516 if ($i == 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) {
517 continue;
518 }
519
520 /* Everything else if forbidden before namespace declarations */
521 $hasNotAllowedStmts = true;
522 }
523 return $style;
524 }
525
526 protected function handleBuiltinTypes(Name $name) {
527 $scalarTypes = [
528 'bool' => true,
529 'int' => true,
530 'float' => true,
531 'string' => true,
532 'iterable' => true,
533 'void' => true,
534 'object' => true,
535 ];
536
537 if (!$name->isUnqualified()) {
538 return $name;
539 }
540
541 $lowerName = strtolower($name->toString());
542 return isset($scalarTypes[$lowerName]) ? $lowerName : $name;
543 }
544
545 protected static $specialNames = array(
546 'self' => true,
547 'parent' => true,
548 'static' => true,
549 );
550
551 protected function getAttributesAt($pos) {
552 return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos];
553 }
554
555 protected function parseLNumber($str, $attributes, $allowInvalidOctal = false) {
556 try {
557 return LNumber::fromString($str, $attributes, $allowInvalidOctal);
558 } catch (Error $error) {
559 $this->emitError($error);
560 // Use dummy value
561 return new LNumber(0, $attributes);
562 }
563 }
564
565 protected function parseNumString($str, $attributes) {
566 if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) {
567 return new String_($str, $attributes);
568 }
569
570 $num = +$str;
571 if (!is_int($num)) {
572 return new String_($str, $attributes);
573 }
574
575 return new LNumber($num, $attributes);
576 }
577
578 protected function checkModifier($a, $b, $modifierPos) {
579 // Jumping through some hoops here because verifyModifier() is also used elsewhere
580 try {
581 Class_::verifyModifier($a, $b);
582 } catch (Error $error) {
583 $error->setAttributes($this->getAttributesAt($modifierPos));
584 $this->emitError($error);
585 }
586 }
587
588 protected function checkParam(Param $node) {
589 if ($node->variadic && null !== $node->default) {
590 $this->emitError(new Error(
591 'Variadic parameter cannot have a default value',
592 $node->default->getAttributes()
593 ));
594 }
595 }
596
597 protected function checkTryCatch(TryCatch $node) {
598 if (empty($node->catches) && null === $node->finally) {
599 $this->emitError(new Error(
600 'Cannot use try without catch or finally', $node->getAttributes()
601 ));
602 }
603 }
604
605 protected function checkNamespace(Namespace_ $node) {
606 if (isset(self::$specialNames[strtolower($node->name)])) {
607 $this->emitError(new Error(
608 sprintf('Cannot use \'%s\' as namespace name', $node->name),
609 $node->name->getAttributes()
610 ));
611 }
612
613 if (null !== $node->stmts) {
614 foreach ($node->stmts as $stmt) {
615 if ($stmt instanceof Namespace_) {
616 $this->emitError(new Error(
617 'Namespace declarations cannot be nested', $stmt->getAttributes()
618 ));
619 }
620 }
621 }
622 }
623
624 protected function checkClass(Class_ $node, $namePos) {
625 if (null !== $node->name && isset(self::$specialNames[strtolower($node->name)])) {
626 $this->emitError(new Error(
627 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
628 $this->getAttributesAt($namePos)
629 ));
630 }
631
632 if (isset(self::$specialNames[strtolower($node->extends)])) {
633 $this->emitError(new Error(
634 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
635 $node->extends->getAttributes()
636 ));
637 }
638
639 foreach ($node->implements as $interface) {
640 if (isset(self::$specialNames[strtolower($interface)])) {
641 $this->emitError(new Error(
642 sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
643 $interface->getAttributes()
644 ));
645 }
646 }
647 }
648
649 protected function checkInterface(Interface_ $node, $namePos) {
650 if (null !== $node->name && isset(self::$specialNames[strtolower($node->name)])) {
651 $this->emitError(new Error(
652 sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
653 $this->getAttributesAt($namePos)
654 ));
655 }
656
657 foreach ($node->extends as $interface) {
658 if (isset(self::$specialNames[strtolower($interface)])) {
659 $this->emitError(new Error(
660 sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
661 $interface->getAttributes()
662 ));
663 }
664 }
665 }
666
667 protected function checkClassMethod(ClassMethod $node, $modifierPos) {
668 if ($node->flags & Class_::MODIFIER_STATIC) {
669 switch (strtolower($node->name)) {
670 case '__construct':
671 $this->emitError(new Error(
672 sprintf('Constructor %s() cannot be static', $node->name),
673 $this->getAttributesAt($modifierPos)));
674 break;
675 case '__destruct':
676 $this->emitError(new Error(
677 sprintf('Destructor %s() cannot be static', $node->name),
678 $this->getAttributesAt($modifierPos)));
679 break;
680 case '__clone':
681 $this->emitError(new Error(
682 sprintf('Clone method %s() cannot be static', $node->name),
683 $this->getAttributesAt($modifierPos)));
684 break;
685 }
686 }
687 }
688
689 protected function checkClassConst(ClassConst $node, $modifierPos) {
690 if ($node->flags & Class_::MODIFIER_STATIC) {
691 $this->emitError(new Error(
692 "Cannot use 'static' as constant modifier",
693 $this->getAttributesAt($modifierPos)));
694 }
695 if ($node->flags & Class_::MODIFIER_ABSTRACT) {
696 $this->emitError(new Error(
697 "Cannot use 'abstract' as constant modifier",
698 $this->getAttributesAt($modifierPos)));
699 }
700 if ($node->flags & Class_::MODIFIER_FINAL) {
701 $this->emitError(new Error(
702 "Cannot use 'final' as constant modifier",
703 $this->getAttributesAt($modifierPos)));
704 }
705 }
706
707 protected function checkProperty(Property $node, $modifierPos) {
708 if ($node->flags & Class_::MODIFIER_ABSTRACT) {
709 $this->emitError(new Error('Properties cannot be declared abstract',
710 $this->getAttributesAt($modifierPos)));
711 }
712
713 if ($node->flags & Class_::MODIFIER_FINAL) {
714 $this->emitError(new Error('Properties cannot be declared final',
715 $this->getAttributesAt($modifierPos)));
716 }
717 }
718
719 protected function checkUseUse(UseUse $node, $namePos) {
720 if ('self' == strtolower($node->alias) || 'parent' == strtolower($node->alias)) {
721 $this->emitError(new Error(
722 sprintf(
723 'Cannot use %s as %s because \'%2$s\' is a special class name',
724 $node->name, $node->alias
725 ),
726 $this->getAttributesAt($namePos)
727 ));
728 }
729 }
730 }
731