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

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

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