RemovedExtensionsSniff.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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\Extensions;
  11. use PHPCompatibility\AbstractRemovedFeatureSniff;
  12. use PHP_CodeSniffer_File as File;
  13. use PHP_CodeSniffer_Tokens as Tokens;
  14. /**
  15. * Detect the use of deprecated and/or removed PHP extensions.
  16. *
  17. * This sniff examines function calls made and flags function calls to functions
  18. * prefixed with the dedicated prefix from a deprecated/removed native PHP extension.
  19. *
  20. * Suggests alternative extensions if available.
  21. *
  22. * As userland functions may be prefixed with a prefix also used by a native
  23. * PHP extension, the sniff offers the ability to whitelist specific functions
  24. * from being flagged by this sniff via a property in a custom ruleset
  25. * (since PHPCompatibility 7.0.2).
  26. *
  27. * {@internal This sniff is a candidate for removal once all functions from all
  28. * deprecated/removed extensions have been added to the RemovedFunctions sniff.}
  29. *
  30. * PHP version All
  31. *
  32. * @since 5.5
  33. * @since 7.1.0 Now extends the `AbstractRemovedFeatureSniff` instead of the base `Sniff` class.
  34. */
  35. class RemovedExtensionsSniff extends AbstractRemovedFeatureSniff
  36. {
  37. /**
  38. * A list of functions to whitelist, if any.
  39. *
  40. * This is intended for projects using functions which start with the same
  41. * prefix as one of the removed extensions.
  42. *
  43. * This property can be set from the ruleset, like so:
  44. * <rule ref="PHPCompatibility.Extensions.RemovedExtensions">
  45. * <properties>
  46. * <property name="functionWhitelist" type="array" value="mysql_to_rfc3339,mysql_another_function" />
  47. * </properties>
  48. * </rule>
  49. *
  50. * @since 7.0.2
  51. *
  52. * @var array
  53. */
  54. public $functionWhitelist;
  55. /**
  56. * A list of removed extensions with their alternative, if any.
  57. *
  58. * The array lists : version number with false (deprecated) and true (removed).
  59. * If's sufficient to list the first version where the extension was deprecated/removed.
  60. *
  61. * @since 5.5
  62. *
  63. * @var array(string => array(string => bool|string|null))
  64. */
  65. protected $removedExtensions = array(
  66. 'activescript' => array(
  67. '5.1' => true,
  68. 'alternative' => 'pecl/activescript',
  69. ),
  70. 'cpdf' => array(
  71. '5.1' => true,
  72. 'alternative' => 'pecl/pdflib',
  73. ),
  74. 'dbase' => array(
  75. '5.3' => true,
  76. 'alternative' => null,
  77. ),
  78. 'dbx' => array(
  79. '5.1' => true,
  80. 'alternative' => 'pecl/dbx',
  81. ),
  82. 'dio' => array(
  83. '5.1' => true,
  84. 'alternative' => 'pecl/dio',
  85. ),
  86. 'ereg' => array(
  87. '5.3' => false,
  88. '7.0' => true,
  89. 'alternative' => 'pcre',
  90. ),
  91. 'fam' => array(
  92. '5.1' => true,
  93. 'alternative' => null,
  94. ),
  95. 'fbsql' => array(
  96. '5.3' => true,
  97. 'alternative' => null,
  98. ),
  99. 'fdf' => array(
  100. '5.3' => true,
  101. 'alternative' => 'pecl/fdf',
  102. ),
  103. 'filepro' => array(
  104. '5.2' => true,
  105. 'alternative' => null,
  106. ),
  107. 'hw_api' => array(
  108. '5.2' => true,
  109. 'alternative' => null,
  110. ),
  111. 'ibase' => array(
  112. '7.4' => true,
  113. 'alternative' => 'pecl/ibase',
  114. ),
  115. 'ingres' => array(
  116. '5.1' => true,
  117. 'alternative' => 'pecl/ingres',
  118. ),
  119. 'ircg' => array(
  120. '5.1' => true,
  121. 'alternative' => null,
  122. ),
  123. 'mcrypt' => array(
  124. '7.1' => false,
  125. '7.2' => true,
  126. 'alternative' => 'openssl (preferred) or pecl/mcrypt once available',
  127. ),
  128. 'mcve' => array(
  129. '5.1' => true,
  130. 'alternative' => 'pecl/mcve',
  131. ),
  132. 'ming' => array(
  133. '5.3' => true,
  134. 'alternative' => 'pecl/ming',
  135. ),
  136. 'mnogosearch' => array(
  137. '5.1' => true,
  138. 'alternative' => null,
  139. ),
  140. 'msql' => array(
  141. '5.3' => true,
  142. 'alternative' => null,
  143. ),
  144. 'mssql' => array(
  145. '7.0' => true,
  146. 'alternative' => null,
  147. ),
  148. 'mysql_' => array(
  149. '5.5' => false,
  150. '7.0' => true,
  151. 'alternative' => 'mysqli',
  152. ),
  153. 'ncurses' => array(
  154. '5.3' => true,
  155. 'alternative' => 'pecl/ncurses',
  156. ),
  157. 'oracle' => array(
  158. '5.1' => true,
  159. 'alternative' => 'oci8 or pdo_oci',
  160. ),
  161. 'ovrimos' => array(
  162. '5.1' => true,
  163. 'alternative' => null,
  164. ),
  165. 'pfpro_' => array(
  166. '5.1' => true,
  167. 'alternative' => null,
  168. ),
  169. 'recode' => array(
  170. '7.4' => true,
  171. 'alternative' => 'iconv or mbstring',
  172. ),
  173. 'sqlite' => array(
  174. '5.4' => true,
  175. 'alternative' => null,
  176. ),
  177. // Has to be before `sybase` as otherwise it will never match.
  178. 'sybase_ct' => array(
  179. '7.0' => true,
  180. 'alternative' => null,
  181. ),
  182. 'sybase' => array(
  183. '5.3' => true,
  184. 'alternative' => 'sybase_ct',
  185. ),
  186. 'w32api' => array(
  187. '5.1' => true,
  188. 'alternative' => 'pecl/ffi',
  189. ),
  190. 'wddx' => array(
  191. '7.4' => true,
  192. 'alternative' => 'pecl/wddx',
  193. ),
  194. 'yp' => array(
  195. '5.1' => true,
  196. 'alternative' => null,
  197. ),
  198. );
  199. /**
  200. * Returns an array of tokens this test wants to listen for.
  201. *
  202. * @since 5.5
  203. *
  204. * @return array
  205. */
  206. public function register()
  207. {
  208. // Handle case-insensitivity of function names.
  209. $this->removedExtensions = $this->arrayKeysToLowercase($this->removedExtensions);
  210. return array(\T_STRING);
  211. }
  212. /**
  213. * Processes this test, when one of its tokens is encountered.
  214. *
  215. * @since 5.5
  216. *
  217. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  218. * @param int $stackPtr The position of the current token in the
  219. * stack passed in $tokens.
  220. *
  221. * @return void
  222. */
  223. public function process(File $phpcsFile, $stackPtr)
  224. {
  225. $tokens = $phpcsFile->getTokens();
  226. // Find the next non-empty token.
  227. $openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
  228. if ($tokens[$openBracket]['code'] !== \T_OPEN_PARENTHESIS) {
  229. // Not a function call.
  230. return;
  231. }
  232. if (isset($tokens[$openBracket]['parenthesis_closer']) === false) {
  233. // Not a function call.
  234. return;
  235. }
  236. // Find the previous non-empty token.
  237. $search = Tokens::$emptyTokens;
  238. $search[] = \T_BITWISE_AND;
  239. $previous = $phpcsFile->findPrevious($search, ($stackPtr - 1), null, true);
  240. if ($tokens[$previous]['code'] === \T_FUNCTION) {
  241. // It's a function definition, not a function call.
  242. return;
  243. }
  244. if ($tokens[$previous]['code'] === \T_NEW) {
  245. // We are creating an object, not calling a function.
  246. return;
  247. }
  248. if ($tokens[$previous]['code'] === \T_OBJECT_OPERATOR) {
  249. // We are calling a method of an object.
  250. return;
  251. }
  252. $function = $tokens[$stackPtr]['content'];
  253. $functionLc = strtolower($function);
  254. if ($this->isWhiteListed($functionLc) === true) {
  255. // Function is whitelisted.
  256. return;
  257. }
  258. foreach ($this->removedExtensions as $extension => $versionList) {
  259. if (strpos($functionLc, $extension) === 0) {
  260. $itemInfo = array(
  261. 'name' => $extension,
  262. );
  263. $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
  264. break;
  265. }
  266. }
  267. }
  268. /**
  269. * Is the current function being checked whitelisted ?
  270. *
  271. * Parsing the list late as it may be provided as a property, but also inline.
  272. *
  273. * @since 7.0.2
  274. *
  275. * @param string $content Content of the current token.
  276. *
  277. * @return bool
  278. */
  279. protected function isWhiteListed($content)
  280. {
  281. if (isset($this->functionWhitelist) === false) {
  282. return false;
  283. }
  284. if (\is_string($this->functionWhitelist) === true) {
  285. if (strpos($this->functionWhitelist, ',') !== false) {
  286. $this->functionWhitelist = explode(',', $this->functionWhitelist);
  287. } else {
  288. $this->functionWhitelist = (array) $this->functionWhitelist;
  289. }
  290. }
  291. if (\is_array($this->functionWhitelist) === true) {
  292. $this->functionWhitelist = array_map('strtolower', $this->functionWhitelist);
  293. return \in_array($content, $this->functionWhitelist, true);
  294. }
  295. return false;
  296. }
  297. /**
  298. * Get the relevant sub-array for a specific item from a multi-dimensional array.
  299. *
  300. * @since 7.1.0
  301. *
  302. * @param array $itemInfo Base information about the item.
  303. *
  304. * @return array Version and other information about the item.
  305. */
  306. public function getItemArray(array $itemInfo)
  307. {
  308. return $this->removedExtensions[$itemInfo['name']];
  309. }
  310. /**
  311. * Get the error message template for this sniff.
  312. *
  313. * @since 7.1.0
  314. *
  315. * @return string
  316. */
  317. protected function getErrorMsgTemplate()
  318. {
  319. return "Extension '%s' is ";
  320. }
  321. }