NewClassesSniff.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. <?php
  2. /**
  3. * PHPCompatibility, an external standard for PHP_CodeSniffer.
  4. *
  5. * @package PHPCompatibility
  6. * @copyright 2012-2019 PHPCompatibility Contributors
  7. * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
  8. * @link https://github.com/PHPCompatibility/PHPCompatibility
  9. */
  10. namespace PHPCompatibility\Sniffs\Classes;
  11. use PHPCompatibility\AbstractNewFeatureSniff;
  12. use PHP_CodeSniffer_File as File;
  13. /**
  14. * Detect use of new PHP native classes.
  15. *
  16. * The sniff analyses the following constructs to find usage of new classes:
  17. * - Class instantiation using the `new` keyword.
  18. * - (Anonymous) Class declarations to detect new classes being extended by userland classes.
  19. * - Static use of class properties, constants or functions using the double colon.
  20. * - Function/closure declarations to detect new classes used as parameter type declarations.
  21. * - Function/closure declarations to detect new classes used as return type declarations.
  22. * - Try/catch statements to detect new exception classes being caught.
  23. *
  24. * PHP version All
  25. *
  26. * @since 5.5
  27. * @since 5.6 Now extends the base `Sniff` class.
  28. * @since 7.1.0 Now extends the `AbstractNewFeatureSniff` class.
  29. */
  30. class NewClassesSniff extends AbstractNewFeatureSniff
  31. {
  32. /**
  33. * A list of new classes, not present in older versions.
  34. *
  35. * The array lists : version number with false (not present) or true (present).
  36. * If's sufficient to list the first version where the class appears.
  37. *
  38. * @since 5.5
  39. *
  40. * @var array(string => array(string => bool))
  41. */
  42. protected $newClasses = array(
  43. 'ArrayObject' => array(
  44. '4.4' => false,
  45. '5.0' => true,
  46. ),
  47. 'ArrayIterator' => array(
  48. '4.4' => false,
  49. '5.0' => true,
  50. ),
  51. 'CachingIterator' => array(
  52. '4.4' => false,
  53. '5.0' => true,
  54. ),
  55. 'DirectoryIterator' => array(
  56. '4.4' => false,
  57. '5.0' => true,
  58. ),
  59. 'RecursiveDirectoryIterator' => array(
  60. '4.4' => false,
  61. '5.0' => true,
  62. ),
  63. 'RecursiveIteratorIterator' => array(
  64. '4.4' => false,
  65. '5.0' => true,
  66. ),
  67. 'php_user_filter' => array(
  68. '4.4' => false,
  69. '5.0' => true,
  70. ),
  71. 'tidy' => array(
  72. '4.4' => false,
  73. '5.0' => true,
  74. ),
  75. 'SimpleXMLElement' => array(
  76. '5.0.0' => false,
  77. '5.0.1' => true,
  78. ),
  79. 'tidyNode' => array(
  80. '5.0.0' => false,
  81. '5.0.1' => true,
  82. ),
  83. 'libXMLError' => array(
  84. '5.0' => false,
  85. '5.1' => true,
  86. ),
  87. 'PDO' => array(
  88. '5.0' => false,
  89. '5.1' => true,
  90. ),
  91. 'PDOStatement' => array(
  92. '5.0' => false,
  93. '5.1' => true,
  94. ),
  95. 'AppendIterator' => array(
  96. '5.0' => false,
  97. '5.1' => true,
  98. ),
  99. 'EmptyIterator' => array(
  100. '5.0' => false,
  101. '5.1' => true,
  102. ),
  103. 'FilterIterator' => array(
  104. '5.0' => false,
  105. '5.1' => true,
  106. ),
  107. 'InfiniteIterator' => array(
  108. '5.0' => false,
  109. '5.1' => true,
  110. ),
  111. 'IteratorIterator' => array(
  112. '5.0' => false,
  113. '5.1' => true,
  114. ),
  115. 'LimitIterator' => array(
  116. '5.0' => false,
  117. '5.1' => true,
  118. ),
  119. 'NoRewindIterator' => array(
  120. '5.0' => false,
  121. '5.1' => true,
  122. ),
  123. 'ParentIterator' => array(
  124. '5.0' => false,
  125. '5.1' => true,
  126. ),
  127. 'RecursiveArrayIterator' => array(
  128. '5.0' => false,
  129. '5.1' => true,
  130. ),
  131. 'RecursiveCachingIterator' => array(
  132. '5.0' => false,
  133. '5.1' => true,
  134. ),
  135. 'RecursiveFilterIterator' => array(
  136. '5.0' => false,
  137. '5.1' => true,
  138. ),
  139. 'SimpleXMLIterator' => array(
  140. '5.0' => false,
  141. '5.1' => true,
  142. ),
  143. 'SplFileObject' => array(
  144. '5.0' => false,
  145. '5.1' => true,
  146. ),
  147. 'XMLReader' => array(
  148. '5.0' => false,
  149. '5.1' => true,
  150. ),
  151. 'SplFileInfo' => array(
  152. '5.1.1' => false,
  153. '5.1.2' => true,
  154. ),
  155. 'SplTempFileObject' => array(
  156. '5.1.1' => false,
  157. '5.1.2' => true,
  158. ),
  159. 'XMLWriter' => array(
  160. '5.1.1' => false,
  161. '5.1.2' => true,
  162. ),
  163. 'DateTime' => array(
  164. '5.1' => false,
  165. '5.2' => true,
  166. ),
  167. 'DateTimeZone' => array(
  168. '5.1' => false,
  169. '5.2' => true,
  170. ),
  171. 'RegexIterator' => array(
  172. '5.1' => false,
  173. '5.2' => true,
  174. ),
  175. 'RecursiveRegexIterator' => array(
  176. '5.1' => false,
  177. '5.2' => true,
  178. ),
  179. 'ReflectionFunctionAbstract' => array(
  180. '5.1' => false,
  181. '5.2' => true,
  182. ),
  183. 'ZipArchive' => array(
  184. '5.1' => false,
  185. '5.2' => true,
  186. ),
  187. 'Closure' => array(
  188. '5.2' => false,
  189. '5.3' => true,
  190. ),
  191. 'DateInterval' => array(
  192. '5.2' => false,
  193. '5.3' => true,
  194. ),
  195. 'DatePeriod' => array(
  196. '5.2' => false,
  197. '5.3' => true,
  198. ),
  199. 'finfo' => array(
  200. '5.2' => false,
  201. '5.3' => true,
  202. ),
  203. 'Collator' => array(
  204. '5.2' => false,
  205. '5.3' => true,
  206. ),
  207. 'NumberFormatter' => array(
  208. '5.2' => false,
  209. '5.3' => true,
  210. ),
  211. 'Locale' => array(
  212. '5.2' => false,
  213. '5.3' => true,
  214. ),
  215. 'Normalizer' => array(
  216. '5.2' => false,
  217. '5.3' => true,
  218. ),
  219. 'MessageFormatter' => array(
  220. '5.2' => false,
  221. '5.3' => true,
  222. ),
  223. 'IntlDateFormatter' => array(
  224. '5.2' => false,
  225. '5.3' => true,
  226. ),
  227. 'Phar' => array(
  228. '5.2' => false,
  229. '5.3' => true,
  230. ),
  231. 'PharData' => array(
  232. '5.2' => false,
  233. '5.3' => true,
  234. ),
  235. 'PharFileInfo' => array(
  236. '5.2' => false,
  237. '5.3' => true,
  238. ),
  239. 'FilesystemIterator' => array(
  240. '5.2' => false,
  241. '5.3' => true,
  242. ),
  243. 'GlobIterator' => array(
  244. '5.2' => false,
  245. '5.3' => true,
  246. ),
  247. 'MultipleIterator' => array(
  248. '5.2' => false,
  249. '5.3' => true,
  250. ),
  251. 'RecursiveTreeIterator' => array(
  252. '5.2' => false,
  253. '5.3' => true,
  254. ),
  255. 'SplDoublyLinkedList' => array(
  256. '5.2' => false,
  257. '5.3' => true,
  258. ),
  259. 'SplFixedArray' => array(
  260. '5.2' => false,
  261. '5.3' => true,
  262. ),
  263. 'SplHeap' => array(
  264. '5.2' => false,
  265. '5.3' => true,
  266. ),
  267. 'SplMaxHeap' => array(
  268. '5.2' => false,
  269. '5.3' => true,
  270. ),
  271. 'SplMinHeap' => array(
  272. '5.2' => false,
  273. '5.3' => true,
  274. ),
  275. 'SplObjectStorage' => array(
  276. '5.2' => false,
  277. '5.3' => true,
  278. ),
  279. 'SplPriorityQueue' => array(
  280. '5.2' => false,
  281. '5.3' => true,
  282. ),
  283. 'SplQueue' => array(
  284. '5.2' => false,
  285. '5.3' => true,
  286. ),
  287. 'SplStack' => array(
  288. '5.2' => false,
  289. '5.3' => true,
  290. ),
  291. 'ResourceBundle' => array(
  292. '5.3.1' => false,
  293. '5.3.2' => true,
  294. ),
  295. 'CallbackFilterIterator' => array(
  296. '5.3' => false,
  297. '5.4' => true,
  298. ),
  299. 'RecursiveCallbackFilterIterator' => array(
  300. '5.3' => false,
  301. '5.4' => true,
  302. ),
  303. 'ReflectionZendExtension' => array(
  304. '5.3' => false,
  305. '5.4' => true,
  306. ),
  307. 'SessionHandler' => array(
  308. '5.3' => false,
  309. '5.4' => true,
  310. ),
  311. 'SNMP' => array(
  312. '5.3' => false,
  313. '5.4' => true,
  314. ),
  315. 'Transliterator' => array(
  316. '5.3' => false,
  317. '5.4' => true,
  318. ),
  319. 'Spoofchecker' => array(
  320. '5.3' => false,
  321. '5.4' => true,
  322. ),
  323. 'Generator' => array(
  324. '5.4' => false,
  325. '5.5' => true,
  326. ),
  327. 'CURLFile' => array(
  328. '5.4' => false,
  329. '5.5' => true,
  330. ),
  331. 'DateTimeImmutable' => array(
  332. '5.4' => false,
  333. '5.5' => true,
  334. ),
  335. 'IntlCalendar' => array(
  336. '5.4' => false,
  337. '5.5' => true,
  338. ),
  339. 'IntlGregorianCalendar' => array(
  340. '5.4' => false,
  341. '5.5' => true,
  342. ),
  343. 'IntlTimeZone' => array(
  344. '5.4' => false,
  345. '5.5' => true,
  346. ),
  347. 'IntlBreakIterator' => array(
  348. '5.4' => false,
  349. '5.5' => true,
  350. ),
  351. 'IntlRuleBasedBreakIterator' => array(
  352. '5.4' => false,
  353. '5.5' => true,
  354. ),
  355. 'IntlCodePointBreakIterator' => array(
  356. '5.4' => false,
  357. '5.5' => true,
  358. ),
  359. 'UConverter' => array(
  360. '5.4' => false,
  361. '5.5' => true,
  362. ),
  363. 'GMP' => array(
  364. '5.5' => false,
  365. '5.6' => true,
  366. ),
  367. 'IntlChar' => array(
  368. '5.6' => false,
  369. '7.0' => true,
  370. ),
  371. 'ReflectionType' => array(
  372. '5.6' => false,
  373. '7.0' => true,
  374. ),
  375. 'ReflectionGenerator' => array(
  376. '5.6' => false,
  377. '7.0' => true,
  378. ),
  379. 'ReflectionClassConstant' => array(
  380. '7.0' => false,
  381. '7.1' => true,
  382. ),
  383. 'FFI' => array(
  384. '7.3' => false,
  385. '7.4' => true,
  386. ),
  387. 'FFI\CData' => array(
  388. '7.3' => false,
  389. '7.4' => true,
  390. ),
  391. 'FFI\CType' => array(
  392. '7.3' => false,
  393. '7.4' => true,
  394. ),
  395. 'ReflectionReference' => array(
  396. '7.3' => false,
  397. '7.4' => true,
  398. ),
  399. 'WeakReference' => array(
  400. '7.3' => false,
  401. '7.4' => true,
  402. ),
  403. );
  404. /**
  405. * A list of new Exception classes, not present in older versions.
  406. *
  407. * The array lists : version number with false (not present) or true (present).
  408. * If's sufficient to list the first version where the class appears.
  409. *
  410. * {@internal Classes listed here do not need to be added to the $newClasses
  411. * property as well.
  412. * This list is automatically added to the $newClasses property
  413. * in the `register()` method.}
  414. *
  415. * {@internal Helper to update this list: https://3v4l.org/MhlUp}
  416. *
  417. * @since 7.1.4
  418. *
  419. * @var array(string => array(string => bool))
  420. */
  421. protected $newExceptions = array(
  422. 'com_exception' => array(
  423. '4.4' => false,
  424. '5.0' => true,
  425. ),
  426. 'DOMException' => array(
  427. '4.4' => false,
  428. '5.0' => true,
  429. ),
  430. 'Exception' => array(
  431. // According to the docs introduced in PHP 5.1, but this appears to be.
  432. // an error. Class was introduced with try/catch keywords in PHP 5.0.
  433. '4.4' => false,
  434. '5.0' => true,
  435. ),
  436. 'ReflectionException' => array(
  437. '4.4' => false,
  438. '5.0' => true,
  439. ),
  440. 'SoapFault' => array(
  441. '4.4' => false,
  442. '5.0' => true,
  443. ),
  444. 'SQLiteException' => array(
  445. '4.4' => false,
  446. '5.0' => true,
  447. ),
  448. 'ErrorException' => array(
  449. '5.0' => false,
  450. '5.1' => true,
  451. ),
  452. 'BadFunctionCallException' => array(
  453. '5.0' => false,
  454. '5.1' => true,
  455. ),
  456. 'BadMethodCallException' => array(
  457. '5.0' => false,
  458. '5.1' => true,
  459. ),
  460. 'DomainException' => array(
  461. '5.0' => false,
  462. '5.1' => true,
  463. ),
  464. 'InvalidArgumentException' => array(
  465. '5.0' => false,
  466. '5.1' => true,
  467. ),
  468. 'LengthException' => array(
  469. '5.0' => false,
  470. '5.1' => true,
  471. ),
  472. 'LogicException' => array(
  473. '5.0' => false,
  474. '5.1' => true,
  475. ),
  476. 'mysqli_sql_exception' => array(
  477. '5.0' => false,
  478. '5.1' => true,
  479. ),
  480. 'OutOfBoundsException' => array(
  481. '5.0' => false,
  482. '5.1' => true,
  483. ),
  484. 'OutOfRangeException' => array(
  485. '5.0' => false,
  486. '5.1' => true,
  487. ),
  488. 'OverflowException' => array(
  489. '5.0' => false,
  490. '5.1' => true,
  491. ),
  492. 'PDOException' => array(
  493. '5.0' => false,
  494. '5.1' => true,
  495. ),
  496. 'RangeException' => array(
  497. '5.0' => false,
  498. '5.1' => true,
  499. ),
  500. 'RuntimeException' => array(
  501. '5.0' => false,
  502. '5.1' => true,
  503. ),
  504. 'UnderflowException' => array(
  505. '5.0' => false,
  506. '5.1' => true,
  507. ),
  508. 'UnexpectedValueException' => array(
  509. '5.0' => false,
  510. '5.1' => true,
  511. ),
  512. 'PharException' => array(
  513. '5.2' => false,
  514. '5.3' => true,
  515. ),
  516. 'SNMPException' => array(
  517. '5.3' => false,
  518. '5.4' => true,
  519. ),
  520. 'IntlException' => array(
  521. '5.4' => false,
  522. '5.5' => true,
  523. ),
  524. 'Error' => array(
  525. '5.6' => false,
  526. '7.0' => true,
  527. ),
  528. 'ArithmeticError' => array(
  529. '5.6' => false,
  530. '7.0' => true,
  531. ),
  532. 'AssertionError' => array(
  533. '5.6' => false,
  534. '7.0' => true,
  535. ),
  536. 'DivisionByZeroError' => array(
  537. '5.6' => false,
  538. '7.0' => true,
  539. ),
  540. 'ParseError' => array(
  541. '5.6' => false,
  542. '7.0' => true,
  543. ),
  544. 'TypeError' => array(
  545. '5.6' => false,
  546. '7.0' => true,
  547. ),
  548. 'ClosedGeneratorException' => array(
  549. '5.6' => false,
  550. '7.0' => true,
  551. ),
  552. 'UI\Exception\InvalidArgumentException' => array(
  553. '5.6' => false,
  554. '7.0' => true,
  555. ),
  556. 'UI\Exception\RuntimeException' => array(
  557. '5.6' => false,
  558. '7.0' => true,
  559. ),
  560. 'ArgumentCountError' => array(
  561. '7.0' => false,
  562. '7.1' => true,
  563. ),
  564. 'SodiumException' => array(
  565. '7.1' => false,
  566. '7.2' => true,
  567. ),
  568. 'CompileError' => array(
  569. '7.2' => false,
  570. '7.3' => true,
  571. ),
  572. 'JsonException' => array(
  573. '7.2' => false,
  574. '7.3' => true,
  575. ),
  576. 'FFI\Exception' => array(
  577. '7.3' => false,
  578. '7.4' => true,
  579. ),
  580. 'FFI\ParserException' => array(
  581. '7.3' => false,
  582. '7.4' => true,
  583. ),
  584. );
  585. /**
  586. * Returns an array of tokens this test wants to listen for.
  587. *
  588. * @since 5.5
  589. * @since 7.0.3 - Now also targets the `class` keyword to detect extended classes.
  590. * - Now also targets double colons to detect static class use.
  591. * @since 7.1.4 - Now also targets anonymous classes to detect extended classes.
  592. * - Now also targets functions/closures to detect new classes used
  593. * as parameter type declarations.
  594. * - Now also targets the `catch` control structure to detect new
  595. * exception classes being caught.
  596. * @since 8.2.0 Now also targets the `T_RETURN_TYPE` token to detect new classes used
  597. * as return type declarations.
  598. *
  599. * @return array
  600. */
  601. public function register()
  602. {
  603. // Handle case-insensitivity of class names.
  604. $this->newClasses = $this->arrayKeysToLowercase($this->newClasses);
  605. $this->newExceptions = $this->arrayKeysToLowercase($this->newExceptions);
  606. // Add the Exception classes to the Classes list.
  607. $this->newClasses = array_merge($this->newClasses, $this->newExceptions);
  608. $targets = array(
  609. \T_NEW,
  610. \T_CLASS,
  611. \T_DOUBLE_COLON,
  612. \T_FUNCTION,
  613. \T_CLOSURE,
  614. \T_CATCH,
  615. );
  616. if (\defined('T_ANON_CLASS')) {
  617. $targets[] = \T_ANON_CLASS;
  618. }
  619. if (\defined('T_RETURN_TYPE')) {
  620. $targets[] = \T_RETURN_TYPE;
  621. }
  622. return $targets;
  623. }
  624. /**
  625. * Processes this test, when one of its tokens is encountered.
  626. *
  627. * @since 5.5
  628. *
  629. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  630. * @param int $stackPtr The position of the current token in
  631. * the stack passed in $tokens.
  632. *
  633. * @return void
  634. */
  635. public function process(File $phpcsFile, $stackPtr)
  636. {
  637. $tokens = $phpcsFile->getTokens();
  638. switch ($tokens[$stackPtr]['type']) {
  639. case 'T_FUNCTION':
  640. case 'T_CLOSURE':
  641. $this->processFunctionToken($phpcsFile, $stackPtr);
  642. // Deal with older PHPCS version which don't recognize return type hints
  643. // as well as newer PHPCS versions (3.3.0+) where the tokenization has changed.
  644. $returnTypeHint = $this->getReturnTypeHintToken($phpcsFile, $stackPtr);
  645. if ($returnTypeHint !== false) {
  646. $this->processReturnTypeToken($phpcsFile, $returnTypeHint);
  647. }
  648. break;
  649. case 'T_CATCH':
  650. $this->processCatchToken($phpcsFile, $stackPtr);
  651. break;
  652. case 'T_RETURN_TYPE':
  653. $this->processReturnTypeToken($phpcsFile, $stackPtr);
  654. break;
  655. default:
  656. $this->processSingularToken($phpcsFile, $stackPtr);
  657. break;
  658. }
  659. }
  660. /**
  661. * Processes this test for when a token resulting in a singular class name is encountered.
  662. *
  663. * @since 7.1.4
  664. *
  665. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  666. * @param int $stackPtr The position of the current token in
  667. * the stack passed in $tokens.
  668. *
  669. * @return void
  670. */
  671. private function processSingularToken(File $phpcsFile, $stackPtr)
  672. {
  673. $tokens = $phpcsFile->getTokens();
  674. $FQClassName = '';
  675. if ($tokens[$stackPtr]['type'] === 'T_NEW') {
  676. $FQClassName = $this->getFQClassNameFromNewToken($phpcsFile, $stackPtr);
  677. } elseif ($tokens[$stackPtr]['type'] === 'T_CLASS' || $tokens[$stackPtr]['type'] === 'T_ANON_CLASS') {
  678. $FQClassName = $this->getFQExtendedClassName($phpcsFile, $stackPtr);
  679. } elseif ($tokens[$stackPtr]['type'] === 'T_DOUBLE_COLON') {
  680. $FQClassName = $this->getFQClassNameFromDoubleColonToken($phpcsFile, $stackPtr);
  681. }
  682. if ($FQClassName === '') {
  683. return;
  684. }
  685. $className = substr($FQClassName, 1); // Remove global namespace indicator.
  686. $classNameLc = strtolower($className);
  687. if (isset($this->newClasses[$classNameLc]) === false) {
  688. return;
  689. }
  690. $itemInfo = array(
  691. 'name' => $className,
  692. 'nameLc' => $classNameLc,
  693. );
  694. $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
  695. }
  696. /**
  697. * Processes this test for when a function token is encountered.
  698. *
  699. * - Detect new classes when used as a parameter type declaration.
  700. *
  701. * @since 7.1.4
  702. *
  703. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  704. * @param int $stackPtr The position of the current token in
  705. * the stack passed in $tokens.
  706. *
  707. * @return void
  708. */
  709. private function processFunctionToken(File $phpcsFile, $stackPtr)
  710. {
  711. // Retrieve typehints stripped of global NS indicator and/or nullable indicator.
  712. $typeHints = $this->getTypeHintsFromFunctionDeclaration($phpcsFile, $stackPtr);
  713. if (empty($typeHints) || \is_array($typeHints) === false) {
  714. return;
  715. }
  716. foreach ($typeHints as $hint) {
  717. $typeHintLc = strtolower($hint);
  718. if (isset($this->newClasses[$typeHintLc]) === true) {
  719. $itemInfo = array(
  720. 'name' => $hint,
  721. 'nameLc' => $typeHintLc,
  722. );
  723. $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
  724. }
  725. }
  726. }
  727. /**
  728. * Processes this test for when a catch token is encountered.
  729. *
  730. * - Detect exceptions when used in a catch statement.
  731. *
  732. * @since 7.1.4
  733. *
  734. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  735. * @param int $stackPtr The position of the current token in
  736. * the stack passed in $tokens.
  737. *
  738. * @return void
  739. */
  740. private function processCatchToken(File $phpcsFile, $stackPtr)
  741. {
  742. $tokens = $phpcsFile->getTokens();
  743. // Bow out during live coding.
  744. if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
  745. return;
  746. }
  747. $opener = $tokens[$stackPtr]['parenthesis_opener'];
  748. $closer = ($tokens[$stackPtr]['parenthesis_closer'] + 1);
  749. $name = '';
  750. $listen = array(
  751. // Parts of a (namespaced) class name.
  752. \T_STRING => true,
  753. \T_NS_SEPARATOR => true,
  754. // End/split tokens.
  755. \T_VARIABLE => false,
  756. \T_BITWISE_OR => false,
  757. \T_CLOSE_CURLY_BRACKET => false, // Shouldn't be needed as we expect a var before this.
  758. );
  759. for ($i = ($opener + 1); $i < $closer; $i++) {
  760. if (isset($listen[$tokens[$i]['code']]) === false) {
  761. continue;
  762. }
  763. if ($listen[$tokens[$i]['code']] === true) {
  764. $name .= $tokens[$i]['content'];
  765. continue;
  766. } else {
  767. if (empty($name) === true) {
  768. // Weird, we should have a name by the time we encounter a variable or |.
  769. // So this may be the closer.
  770. continue;
  771. }
  772. $name = ltrim($name, '\\');
  773. $nameLC = strtolower($name);
  774. if (isset($this->newExceptions[$nameLC]) === true) {
  775. $itemInfo = array(
  776. 'name' => $name,
  777. 'nameLc' => $nameLC,
  778. );
  779. $this->handleFeature($phpcsFile, $i, $itemInfo);
  780. }
  781. // Reset for a potential multi-catch.
  782. $name = '';
  783. }
  784. }
  785. }
  786. /**
  787. * Processes this test for when a return type token is encountered.
  788. *
  789. * - Detect new classes when used as a return type declaration.
  790. *
  791. * @since 8.2.0
  792. *
  793. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  794. * @param int $stackPtr The position of the current token in
  795. * the stack passed in $tokens.
  796. *
  797. * @return void
  798. */
  799. private function processReturnTypeToken(File $phpcsFile, $stackPtr)
  800. {
  801. $returnTypeHint = $this->getReturnTypeHintName($phpcsFile, $stackPtr);
  802. if (empty($returnTypeHint)) {
  803. return;
  804. }
  805. $returnTypeHint = ltrim($returnTypeHint, '\\');
  806. $returnTypeHintLc = strtolower($returnTypeHint);
  807. if (isset($this->newClasses[$returnTypeHintLc]) === false) {
  808. return;
  809. }
  810. // Still here ? Then this is a return type declaration using a new class.
  811. $itemInfo = array(
  812. 'name' => $returnTypeHint,
  813. 'nameLc' => $returnTypeHintLc,
  814. );
  815. $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
  816. }
  817. /**
  818. * Get the relevant sub-array for a specific item from a multi-dimensional array.
  819. *
  820. * @since 7.1.0
  821. *
  822. * @param array $itemInfo Base information about the item.
  823. *
  824. * @return array Version and other information about the item.
  825. */
  826. public function getItemArray(array $itemInfo)
  827. {
  828. return $this->newClasses[$itemInfo['nameLc']];
  829. }
  830. /**
  831. * Get the error message template for this sniff.
  832. *
  833. * @since 7.1.0
  834. *
  835. * @return string
  836. */
  837. protected function getErrorMsgTemplate()
  838. {
  839. return 'The built-in class ' . parent::getErrorMsgTemplate();
  840. }
  841. }