PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/psy/psysh/src/Psy/CodeCleaner/ValidClassNamePass.php

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