Token.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <?php
  2. /*
  3. * This file is part of the PHP CS utility.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Symfony\CS\Tokenizer;
  11. use Symfony\CS\Utils;
  12. /**
  13. * Representation of single token.
  14. * As a token prototype you should understand a single element generated by token_get_all.
  15. *
  16. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  17. */
  18. class Token
  19. {
  20. /**
  21. * Content of token prototype.
  22. *
  23. * @var string
  24. */
  25. private $content;
  26. /**
  27. * ID of token prototype, if available.
  28. *
  29. * @var int|null
  30. */
  31. private $id;
  32. /**
  33. * If token prototype is an array.
  34. *
  35. * @var bool
  36. */
  37. private $isArray;
  38. /**
  39. * Line of token prototype occurrence, if available.
  40. *
  41. * @var int|null
  42. */
  43. private $line;
  44. /**
  45. * Constructor.
  46. *
  47. * @param string|array $token token prototype
  48. */
  49. public function __construct($token)
  50. {
  51. if (is_array($token)) {
  52. $this->isArray = true;
  53. $this->id = $token[0];
  54. $this->content = $token[1];
  55. $this->line = isset($token[2]) ? $token[2] : null;
  56. } else {
  57. $this->isArray = false;
  58. $this->content = $token;
  59. }
  60. }
  61. /**
  62. * Clear token at given index.
  63. *
  64. * Clearing means override token by empty string.
  65. */
  66. public function clear()
  67. {
  68. $this->override('');
  69. }
  70. /**
  71. * Check if token is equals to given one.
  72. *
  73. * If tokens are arrays, then only keys defined in parameter token are checked.
  74. *
  75. * @param Token|array|string $other token or it's prototype
  76. * @param bool $caseSensitive perform a case sensitive comparison
  77. *
  78. * @return bool
  79. */
  80. public function equals($other, $caseSensitive = true)
  81. {
  82. $otherPrototype = $other instanceof self ? $other->getPrototype() : $other;
  83. if ($this->isArray() !== is_array($otherPrototype)) {
  84. return false;
  85. }
  86. if (!$this->isArray()) {
  87. return $this->content === $otherPrototype;
  88. }
  89. $selfPrototype = $this->getPrototype();
  90. foreach ($otherPrototype as $key => $val) {
  91. // make sure the token has such key
  92. if (!isset($selfPrototype[$key])) {
  93. return false;
  94. }
  95. if (1 === $key && !$caseSensitive) {
  96. // case-insensitive comparison only applies to the content (key 1)
  97. if (0 !== strcasecmp($val, $selfPrototype[1])) {
  98. return false;
  99. }
  100. } else {
  101. // regular comparison
  102. if ($selfPrototype[$key] !== $val) {
  103. return false;
  104. }
  105. }
  106. }
  107. return true;
  108. }
  109. /**
  110. * Check if token is equals to one of given.
  111. *
  112. * @param array $others array of tokens or token prototypes
  113. * @param bool|bool[] $caseSensitive global case sensitiveness or an array of booleans, whose keys should match
  114. * the ones used in $others. If any is missing, the default case-sensitive
  115. * comparison is used.
  116. *
  117. * @return bool
  118. */
  119. public function equalsAny(array $others, $caseSensitive = true)
  120. {
  121. foreach ($others as $key => $other) {
  122. $cs = self::isKeyCaseSensitive($caseSensitive, $key);
  123. if ($this->equals($other, $cs)) {
  124. return true;
  125. }
  126. }
  127. return false;
  128. }
  129. /**
  130. * A helper method used to find out whether or not a certain input token has to be case-sensitively matched.
  131. *
  132. * @param bool|bool[] $caseSensitive global case sensitiveness or an array of booleans, whose keys should match
  133. * the ones used in $others. If any is missing, the default case-sensitive
  134. * comparison is used.
  135. * @param int $key the key of the token that has to be looked up
  136. *
  137. * @return bool
  138. */
  139. public static function isKeyCaseSensitive($caseSensitive, $key)
  140. {
  141. if (is_array($caseSensitive)) {
  142. return isset($caseSensitive[$key]) ? $caseSensitive[$key] : true;
  143. }
  144. return $caseSensitive;
  145. }
  146. /**
  147. * Get token prototype.
  148. *
  149. * @return string|array token prototype
  150. */
  151. public function getPrototype()
  152. {
  153. if (!$this->isArray) {
  154. return $this->content;
  155. }
  156. return array(
  157. $this->id,
  158. $this->content,
  159. $this->line,
  160. );
  161. }
  162. /**
  163. * Get token's content.
  164. *
  165. * @return string
  166. */
  167. public function getContent()
  168. {
  169. return $this->content;
  170. }
  171. /**
  172. * Get token's id.
  173. *
  174. * @return int
  175. */
  176. public function getId()
  177. {
  178. return $this->id;
  179. }
  180. /**
  181. * Get token's line.
  182. *
  183. * @return int
  184. */
  185. public function getLine()
  186. {
  187. return $this->line;
  188. }
  189. /**
  190. * Get token name.
  191. *
  192. * @return null|string token name
  193. */
  194. public function getName()
  195. {
  196. if (!isset($this->id)) {
  197. return;
  198. }
  199. $transformers = Transformers::create();
  200. if ($transformers->hasCustomToken($this->id)) {
  201. return $transformers->getCustomToken($this->id);
  202. }
  203. return token_name($this->id);
  204. }
  205. /**
  206. * Generate keywords array contains all keywords that exists in used PHP version.
  207. *
  208. * @return array
  209. */
  210. public static function getKeywords()
  211. {
  212. static $keywords = null;
  213. if (null === $keywords) {
  214. $keywords = array();
  215. $keywordsStrings = array('T_ABSTRACT', 'T_ARRAY', 'T_AS', 'T_BREAK', 'T_CALLABLE', 'T_CASE',
  216. 'T_CATCH', 'T_CLASS', 'T_CLONE', 'T_CONST', 'T_CONTINUE', 'T_DECLARE', 'T_DEFAULT', 'T_DO',
  217. 'T_ECHO', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENDDECLARE', 'T_ENDFOR', 'T_ENDFOREACH',
  218. 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_EVAL', 'T_EXIT', 'T_EXTENDS', 'T_FINAL',
  219. 'T_FINALLY', 'T_FOR', 'T_FOREACH', 'T_FUNCTION', 'T_GLOBAL', 'T_GOTO', 'T_HALT_COMPILER',
  220. 'T_IF', 'T_IMPLEMENTS', 'T_INCLUDE', 'T_INCLUDE_ONCE', 'T_INSTANCEOF', 'T_INSTEADOF',
  221. 'T_INTERFACE', 'T_ISSET', 'T_LIST', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR',
  222. 'T_NAMESPACE', 'T_NEW', 'T_PRINT', 'T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC', 'T_REQUIRE',
  223. 'T_REQUIRE_ONCE', 'T_RETURN', 'T_STATIC', 'T_SWITCH', 'T_THROW', 'T_TRAIT', 'T_TRY',
  224. 'T_UNSET', 'T_USE', 'T_VAR', 'T_WHILE', 'T_YIELD',
  225. );
  226. foreach ($keywordsStrings as $keywordName) {
  227. if (defined($keywordName)) {
  228. $keyword = constant($keywordName);
  229. $keywords[$keyword] = $keyword;
  230. }
  231. }
  232. }
  233. return $keywords;
  234. }
  235. /**
  236. * Check if token prototype is an array.
  237. *
  238. * @return bool is array
  239. */
  240. public function isArray()
  241. {
  242. return $this->isArray;
  243. }
  244. /**
  245. * Check if token is one of type cast tokens.
  246. *
  247. * @return bool
  248. */
  249. public function isCast()
  250. {
  251. static $castTokens = array(T_ARRAY_CAST, T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, T_OBJECT_CAST, T_STRING_CAST, T_UNSET_CAST);
  252. return $this->isGivenKind($castTokens);
  253. }
  254. /**
  255. * Check if token is one of classy tokens: T_CLASS, T_INTERFACE or T_TRAIT.
  256. *
  257. * @return bool
  258. */
  259. public function isClassy()
  260. {
  261. static $classTokens = null;
  262. if (null === $classTokens) {
  263. $classTokens = array(T_CLASS, T_INTERFACE);
  264. if (defined('T_TRAIT')) {
  265. $classTokens[] = constant('T_TRAIT');
  266. }
  267. }
  268. return $this->isGivenKind($classTokens);
  269. }
  270. /**
  271. * Check if token is one of comment tokens: T_COMMENT or T_DOC_COMMENT.
  272. *
  273. * @return bool
  274. */
  275. public function isComment()
  276. {
  277. static $commentTokens = array(T_COMMENT, T_DOC_COMMENT);
  278. return $this->isGivenKind($commentTokens);
  279. }
  280. /**
  281. * Check if token is empty, e.g. because of clearing.
  282. *
  283. * @return bool
  284. */
  285. public function isEmpty()
  286. {
  287. return null === $this->id && ('' === $this->content || null === $this->content);
  288. }
  289. /**
  290. * Check if token is one of given kind.
  291. *
  292. * @param int|int[] $possibleKind kind or array of kinds
  293. *
  294. * @return bool
  295. */
  296. public function isGivenKind($possibleKind)
  297. {
  298. return $this->isArray && (is_array($possibleKind) ? in_array($this->id, $possibleKind, true) : $this->id === $possibleKind);
  299. }
  300. /**
  301. * Check if token is a keyword.
  302. *
  303. * @return bool
  304. */
  305. public function isKeyword()
  306. {
  307. $keywords = static::getKeywords();
  308. return $this->isArray && isset($keywords[$this->id]);
  309. }
  310. /**
  311. * Check if token is a native PHP constant: true, false or null.
  312. *
  313. * @return bool
  314. */
  315. public function isNativeConstant()
  316. {
  317. static $nativeConstantStrings = array('true', 'false', 'null');
  318. return $this->isArray && in_array(strtolower($this->content), $nativeConstantStrings, true);
  319. }
  320. /**
  321. * Check if token is one of structure alternative end syntax (T_END...).
  322. *
  323. * @return bool
  324. */
  325. public function isStructureAlternativeEnd()
  326. {
  327. static $tokens = array(T_ENDDECLARE, T_ENDFOR, T_ENDFOREACH, T_ENDIF, T_ENDSWITCH, T_ENDWHILE, T_END_HEREDOC);
  328. return $this->isGivenKind($tokens);
  329. }
  330. /**
  331. * Check if token is a whitespace.
  332. *
  333. * @param array $opts Extra options, $opts['whitespaces'] string
  334. * determining whitespaces chars,
  335. * default is " \t\n\r\0\x0B"
  336. *
  337. * @return bool
  338. */
  339. public function isWhitespace(array $opts = array())
  340. {
  341. if ($this->isArray && !$this->isGivenKind(T_WHITESPACE)) {
  342. return false;
  343. }
  344. $whitespaces = isset($opts['whitespaces']) ? $opts['whitespaces'] : " \t\n\r\0\x0B";
  345. return '' === trim($this->content, $whitespaces);
  346. }
  347. /**
  348. * Override token.
  349. *
  350. * If called on Token inside Tokens collection please use `Tokens::overrideAt` instead.
  351. *
  352. * @param string|array $prototype token prototype
  353. */
  354. public function override($prototype)
  355. {
  356. if (is_array($prototype)) {
  357. $this->isArray = true;
  358. $this->id = $prototype[0];
  359. $this->content = $prototype[1];
  360. $this->line = isset($prototype[2]) ? $prototype[2] : null;
  361. return;
  362. }
  363. $this->isArray = false;
  364. $this->id = null;
  365. $this->content = $prototype;
  366. $this->line = null;
  367. }
  368. /**
  369. * Set token's content.
  370. *
  371. * @param string $content
  372. */
  373. public function setContent($content)
  374. {
  375. // setting empty content is clearing the token
  376. if ('' === $content) {
  377. $this->clear();
  378. return;
  379. }
  380. $this->content = $content;
  381. }
  382. public function toArray()
  383. {
  384. return array(
  385. 'id' => $this->id,
  386. 'name' => $this->getName(),
  387. 'content' => $this->content,
  388. 'line' => $this->line,
  389. 'isArray' => $this->isArray,
  390. );
  391. }
  392. public function toJson()
  393. {
  394. static $options = null;
  395. if (null === $options) {
  396. $options = Utils::calculateBitmask(array('JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK'));
  397. }
  398. return json_encode($this->toArray(), $options);
  399. }
  400. }