RequiredToOptionalFunctionParametersSniff.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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\FunctionUse;
  11. use PHPCompatibility\AbstractComplexVersionSniff;
  12. use PHP_CodeSniffer_File as File;
  13. use PHP_CodeSniffer_Tokens as Tokens;
  14. /**
  15. * Detect missing required function parameters in calls to native PHP functions.
  16. *
  17. * Specifically when those function parameters are no longer required in more recent PHP versions.
  18. *
  19. * PHP version All
  20. *
  21. * @link https://www.php.net/manual/en/doc.changelog.php
  22. *
  23. * @since 7.0.3
  24. * @since 7.1.0 Now extends the `AbstractComplexVersionSniff` instead of the base `Sniff` class.
  25. * @since 9.0.0 Renamed from `RequiredOptionalFunctionParametersSniff` to `RequiredToOptionalFunctionParametersSniff`.
  26. */
  27. class RequiredToOptionalFunctionParametersSniff extends AbstractComplexVersionSniff
  28. {
  29. /**
  30. * A list of function parameters, which were required in older versions and became optional later on.
  31. *
  32. * The array lists : version number with true (required) and false (optional).
  33. *
  34. * The index is the location of the parameter in the parameter list, starting at 0 !
  35. * If's sufficient to list the last version in which the parameter was still required.
  36. *
  37. * @since 7.0.3
  38. *
  39. * @var array
  40. */
  41. protected $functionParameters = array(
  42. 'array_merge' => array(
  43. 0 => array(
  44. 'name' => 'array(s) to merge',
  45. '7.3' => true,
  46. '7.4' => false,
  47. ),
  48. ),
  49. 'array_merge_recursive' => array(
  50. 0 => array(
  51. 'name' => 'array(s) to merge',
  52. '7.3' => true,
  53. '7.4' => false,
  54. ),
  55. ),
  56. 'array_push' => array(
  57. 1 => array(
  58. 'name' => 'element to push',
  59. '7.2' => true,
  60. '7.3' => false,
  61. ),
  62. ),
  63. 'array_unshift' => array(
  64. 1 => array(
  65. 'name' => 'element to prepend',
  66. '7.2' => true,
  67. '7.3' => false,
  68. ),
  69. ),
  70. 'bcscale' => array(
  71. 0 => array(
  72. 'name' => 'scale',
  73. '7.2' => true,
  74. '7.3' => false,
  75. ),
  76. ),
  77. 'ftp_fget' => array(
  78. 3 => array(
  79. 'name' => 'mode',
  80. '7.2' => true,
  81. '7.3' => false,
  82. ),
  83. ),
  84. 'ftp_fput' => array(
  85. 3 => array(
  86. 'name' => 'mode',
  87. '7.2' => true,
  88. '7.3' => false,
  89. ),
  90. ),
  91. 'ftp_get' => array(
  92. 3 => array(
  93. 'name' => 'mode',
  94. '7.2' => true,
  95. '7.3' => false,
  96. ),
  97. ),
  98. 'ftp_nb_fget' => array(
  99. 3 => array(
  100. 'name' => 'mode',
  101. '7.2' => true,
  102. '7.3' => false,
  103. ),
  104. ),
  105. 'ftp_nb_fput' => array(
  106. 3 => array(
  107. 'name' => 'mode',
  108. '7.2' => true,
  109. '7.3' => false,
  110. ),
  111. ),
  112. 'ftp_nb_get' => array(
  113. 3 => array(
  114. 'name' => 'mode',
  115. '7.2' => true,
  116. '7.3' => false,
  117. ),
  118. ),
  119. 'ftp_nb_put' => array(
  120. 3 => array(
  121. 'name' => 'mode',
  122. '7.2' => true,
  123. '7.3' => false,
  124. ),
  125. ),
  126. 'ftp_put' => array(
  127. 3 => array(
  128. 'name' => 'mode',
  129. '7.2' => true,
  130. '7.3' => false,
  131. ),
  132. ),
  133. 'getenv' => array(
  134. 0 => array(
  135. 'name' => 'varname',
  136. '7.0' => true,
  137. '7.1' => false,
  138. ),
  139. ),
  140. 'preg_match_all' => array(
  141. 2 => array(
  142. 'name' => 'matches',
  143. '5.3' => true,
  144. '5.4' => false,
  145. ),
  146. ),
  147. 'stream_socket_enable_crypto' => array(
  148. 2 => array(
  149. 'name' => 'crypto_type',
  150. '5.5' => true,
  151. '5.6' => false,
  152. ),
  153. ),
  154. );
  155. /**
  156. * Returns an array of tokens this test wants to listen for.
  157. *
  158. * @since 7.0.3
  159. *
  160. * @return array
  161. */
  162. public function register()
  163. {
  164. // Handle case-insensitivity of function names.
  165. $this->functionParameters = $this->arrayKeysToLowercase($this->functionParameters);
  166. return array(\T_STRING);
  167. }
  168. /**
  169. * Processes this test, when one of its tokens is encountered.
  170. *
  171. * @since 7.0.3
  172. *
  173. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  174. * @param int $stackPtr The position of the current token in
  175. * the stack passed in $tokens.
  176. *
  177. * @return void
  178. */
  179. public function process(File $phpcsFile, $stackPtr)
  180. {
  181. $tokens = $phpcsFile->getTokens();
  182. $ignore = array(
  183. \T_DOUBLE_COLON => true,
  184. \T_OBJECT_OPERATOR => true,
  185. \T_FUNCTION => true,
  186. \T_CONST => true,
  187. \T_NEW => true,
  188. );
  189. $prevToken = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
  190. if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
  191. // Not a call to a PHP function.
  192. return;
  193. }
  194. $function = $tokens[$stackPtr]['content'];
  195. $functionLc = strtolower($function);
  196. if (isset($this->functionParameters[$functionLc]) === false) {
  197. return;
  198. }
  199. $parameterCount = $this->getFunctionCallParameterCount($phpcsFile, $stackPtr);
  200. $openParenthesis = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
  201. // If the parameter count returned > 0, we know there will be valid open parenthesis.
  202. if ($parameterCount === 0 && $tokens[$openParenthesis]['code'] !== \T_OPEN_PARENTHESIS) {
  203. return;
  204. }
  205. $parameterOffsetFound = $parameterCount - 1;
  206. foreach ($this->functionParameters[$functionLc] as $offset => $parameterDetails) {
  207. if ($offset > $parameterOffsetFound) {
  208. $itemInfo = array(
  209. 'name' => $function,
  210. 'nameLc' => $functionLc,
  211. 'offset' => $offset,
  212. );
  213. $this->handleFeature($phpcsFile, $openParenthesis, $itemInfo);
  214. }
  215. }
  216. }
  217. /**
  218. * Determine whether an error/warning should be thrown for an item based on collected information.
  219. *
  220. * @since 7.1.0
  221. *
  222. * @param array $errorInfo Detail information about an item.
  223. *
  224. * @return bool
  225. */
  226. protected function shouldThrowError(array $errorInfo)
  227. {
  228. return ($errorInfo['requiredVersion'] !== '');
  229. }
  230. /**
  231. * Get the relevant sub-array for a specific item from a multi-dimensional array.
  232. *
  233. * @since 7.1.0
  234. *
  235. * @param array $itemInfo Base information about the item.
  236. *
  237. * @return array Version and other information about the item.
  238. */
  239. public function getItemArray(array $itemInfo)
  240. {
  241. return $this->functionParameters[$itemInfo['nameLc']][$itemInfo['offset']];
  242. }
  243. /**
  244. * Get an array of the non-PHP-version array keys used in a sub-array.
  245. *
  246. * @since 7.1.0
  247. *
  248. * @return array
  249. */
  250. protected function getNonVersionArrayKeys()
  251. {
  252. return array('name');
  253. }
  254. /**
  255. * Retrieve the relevant detail (version) information for use in an error message.
  256. *
  257. * @since 7.1.0
  258. *
  259. * @param array $itemArray Version and other information about the item.
  260. * @param array $itemInfo Base information about the item.
  261. *
  262. * @return array
  263. */
  264. public function getErrorInfo(array $itemArray, array $itemInfo)
  265. {
  266. $errorInfo = array(
  267. 'paramName' => '',
  268. 'requiredVersion' => '',
  269. );
  270. $versionArray = $this->getVersionArray($itemArray);
  271. if (empty($versionArray) === false) {
  272. foreach ($versionArray as $version => $required) {
  273. if ($required === true && $this->supportsBelow($version) === true) {
  274. $errorInfo['requiredVersion'] = $version;
  275. }
  276. }
  277. }
  278. $errorInfo['paramName'] = $itemArray['name'];
  279. return $errorInfo;
  280. }
  281. /**
  282. * Get the error message template for this sniff.
  283. *
  284. * @since 7.1.0
  285. *
  286. * @return string
  287. */
  288. protected function getErrorMsgTemplate()
  289. {
  290. return 'The "%s" parameter for function %s() is missing, but was required for PHP version %s and lower';
  291. }
  292. /**
  293. * Generates the error or warning for this item.
  294. *
  295. * @since 7.1.0
  296. *
  297. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  298. * @param int $stackPtr The position of the relevant token in
  299. * the stack.
  300. * @param array $itemInfo Base information about the item.
  301. * @param array $errorInfo Array with detail (version) information
  302. * relevant to the item.
  303. *
  304. * @return void
  305. */
  306. public function addError(File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo)
  307. {
  308. $error = $this->getErrorMsgTemplate();
  309. $errorCode = $this->stringToErrorCode($itemInfo['name'] . '_' . $errorInfo['paramName']) . 'Missing';
  310. $data = array(
  311. $errorInfo['paramName'],
  312. $itemInfo['name'],
  313. $errorInfo['requiredVersion'],
  314. );
  315. $phpcsFile->addError($error, $stackPtr, $errorCode, $data);
  316. }
  317. }