PageRenderTime 51ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/PHPCompatibility/Sniffs/Extensions/RemovedExtensionsSniff.php

http://github.com/wimg/PHPCompatibility
PHP | 347 lines | 210 code | 26 blank | 111 comment | 12 complexity | 1dc8a15c126c2d723eadda011ebe22eb MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. /**
  3. * PHPCompatibility, an external standard for PHP_CodeSniffer.
  4. *
  5. * @package PHPCompatibility
  6. * @copyright 2012-2020 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\Files\File;
  13. use PHP_CodeSniffer\Util\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 = [
  66. 'activescript' => [
  67. '5.1' => true,
  68. 'alternative' => 'pecl/activescript',
  69. ],
  70. 'cpdf' => [
  71. '5.1' => true,
  72. 'alternative' => 'pecl/pdflib',
  73. ],
  74. 'dbase' => [
  75. '5.3' => true,
  76. 'alternative' => null,
  77. ],
  78. 'dbx' => [
  79. '5.1' => true,
  80. 'alternative' => 'pecl/dbx',
  81. ],
  82. 'dio' => [
  83. '5.1' => true,
  84. 'alternative' => 'pecl/dio',
  85. ],
  86. 'ereg' => [
  87. '5.3' => false,
  88. '7.0' => true,
  89. 'alternative' => 'pcre',
  90. ],
  91. 'fam' => [
  92. '5.1' => true,
  93. 'alternative' => null,
  94. ],
  95. 'fbsql' => [
  96. '5.3' => true,
  97. 'alternative' => null,
  98. ],
  99. 'fdf' => [
  100. '5.3' => true,
  101. 'alternative' => 'pecl/fdf',
  102. ],
  103. 'filepro' => [
  104. '5.2' => true,
  105. 'alternative' => null,
  106. ],
  107. 'hw_api' => [
  108. '5.2' => true,
  109. 'alternative' => null,
  110. ],
  111. 'ibase' => [
  112. '7.4' => true,
  113. 'alternative' => 'pecl/ibase',
  114. ],
  115. 'ingres' => [
  116. '5.1' => true,
  117. 'alternative' => 'pecl/ingres',
  118. ],
  119. 'ircg' => [
  120. '5.1' => true,
  121. 'alternative' => null,
  122. ],
  123. 'mcrypt' => [
  124. '7.1' => false,
  125. '7.2' => true,
  126. 'alternative' => 'openssl (preferred) or pecl/mcrypt once available',
  127. ],
  128. 'mcve' => [
  129. '5.1' => true,
  130. 'alternative' => 'pecl/mcve',
  131. ],
  132. 'ming' => [
  133. '5.3' => true,
  134. 'alternative' => 'pecl/ming',
  135. ],
  136. 'mnogosearch' => [
  137. '5.1' => true,
  138. 'alternative' => null,
  139. ],
  140. 'msql' => [
  141. '5.3' => true,
  142. 'alternative' => null,
  143. ],
  144. 'mssql' => [
  145. '7.0' => true,
  146. 'alternative' => null,
  147. ],
  148. 'mysql_' => [
  149. '5.5' => false,
  150. '7.0' => true,
  151. 'alternative' => 'mysqli',
  152. ],
  153. 'ncurses' => [
  154. '5.3' => true,
  155. 'alternative' => 'pecl/ncurses',
  156. ],
  157. 'oracle' => [
  158. '5.1' => true,
  159. 'alternative' => 'oci8 or pdo_oci',
  160. ],
  161. 'ovrimos' => [
  162. '5.1' => true,
  163. 'alternative' => null,
  164. ],
  165. 'pfpro_' => [
  166. '5.1' => true,
  167. 'alternative' => null,
  168. ],
  169. 'recode' => [
  170. '7.4' => true,
  171. 'alternative' => 'iconv or mbstring',
  172. ],
  173. 'sqlite' => [
  174. '5.4' => true,
  175. 'alternative' => null,
  176. ],
  177. // Has to be before `sybase` as otherwise it will never match.
  178. 'sybase_ct' => [
  179. '7.0' => true,
  180. 'alternative' => null,
  181. ],
  182. 'sybase' => [
  183. '5.3' => true,
  184. 'alternative' => 'sybase_ct',
  185. ],
  186. 'w32api' => [
  187. '5.1' => true,
  188. 'alternative' => 'pecl/ffi',
  189. ],
  190. 'wddx' => [
  191. '7.4' => true,
  192. 'alternative' => 'pecl/wddx',
  193. ],
  194. 'yp' => [
  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 = \array_change_key_case($this->removedExtensions, \CASE_LOWER);
  210. return [\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\Files\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 = [
  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. }