PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/phpcompatibility/php-compatibility/PHPCompatibility/Sniffs/FunctionDeclarations/NewClosureSniff.php

http://github.com/spotweb/spotweb
PHP | 264 lines | 118 code | 33 blank | 113 comment | 20 complexity | c68d1c47a94947aabec444c2b0f765f2 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, Apache-2.0, LGPL-3.0
  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\FunctionDeclarations;
  11. use PHPCompatibility\Sniff;
  12. use PHP_CodeSniffer_File as File;
  13. use PHP_CodeSniffer_Tokens as Tokens;
  14. /**
  15. * Detect closures and verify that the features used are supported.
  16. *
  17. * Version based checks:
  18. * - Closures are available since PHP 5.3.
  19. * - Closures can be declared as `static` since PHP 5.4.
  20. * - Closures can use the `$this` variable within a class context since PHP 5.4.
  21. * - Closures can use `self`/`parent`/`static` since PHP 5.4.
  22. *
  23. * Version independent checks:
  24. * - Static closures don't have access to the `$this` variable.
  25. * - Closures declared outside of a class context don't have access to the `$this`
  26. * variable unless bound to an object.
  27. *
  28. * PHP version 5.3
  29. * PHP version 5.4
  30. *
  31. * @link https://www.php.net/manual/en/functions.anonymous.php
  32. * @link https://wiki.php.net/rfc/closures
  33. * @link https://wiki.php.net/rfc/closures/object-extension
  34. *
  35. * @since 7.0.0
  36. */
  37. class NewClosureSniff extends Sniff
  38. {
  39. /**
  40. * Returns an array of tokens this test wants to listen for.
  41. *
  42. * @since 7.0.0
  43. *
  44. * @return array
  45. */
  46. public function register()
  47. {
  48. return array(\T_CLOSURE);
  49. }
  50. /**
  51. * Processes this test, when one of its tokens is encountered.
  52. *
  53. * @since 7.0.0
  54. * @since 7.1.4 - Added check for closure being declared as static < 5.4.
  55. * - Added check for use of `$this` variable in class context < 5.4.
  56. * - Added check for use of `$this` variable in static closures (unsupported).
  57. * - Added check for use of `$this` variable outside class context (unsupported).
  58. * @since 8.2.0 Added check for use of `self`/`static`/`parent` < 5.4.
  59. *
  60. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  61. * @param int $stackPtr The position of the current token
  62. * in the stack passed in $tokens.
  63. *
  64. * @return int|void Integer stack pointer to skip forward or void to continue
  65. * normal file processing.
  66. */
  67. public function process(File $phpcsFile, $stackPtr)
  68. {
  69. if ($this->supportsBelow('5.2')) {
  70. $phpcsFile->addError(
  71. 'Closures / anonymous functions are not available in PHP 5.2 or earlier',
  72. $stackPtr,
  73. 'Found'
  74. );
  75. }
  76. /*
  77. * Closures can only be declared as static since PHP 5.4.
  78. */
  79. $isStatic = $this->isClosureStatic($phpcsFile, $stackPtr);
  80. if ($this->supportsBelow('5.3') && $isStatic === true) {
  81. $phpcsFile->addError(
  82. 'Closures / anonymous functions could not be declared as static in PHP 5.3 or earlier',
  83. $stackPtr,
  84. 'StaticFound'
  85. );
  86. }
  87. $tokens = $phpcsFile->getTokens();
  88. if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
  89. // Live coding or parse error.
  90. return;
  91. }
  92. $scopeStart = ($tokens[$stackPtr]['scope_opener'] + 1);
  93. $scopeEnd = $tokens[$stackPtr]['scope_closer'];
  94. $usesThis = $this->findThisUsageInClosure($phpcsFile, $scopeStart, $scopeEnd);
  95. if ($this->supportsBelow('5.3')) {
  96. /*
  97. * Closures declared within classes only have access to $this since PHP 5.4.
  98. */
  99. if ($usesThis !== false) {
  100. $thisFound = $usesThis;
  101. do {
  102. $phpcsFile->addError(
  103. 'Closures / anonymous functions did not have access to $this in PHP 5.3 or earlier',
  104. $thisFound,
  105. 'ThisFound'
  106. );
  107. $thisFound = $this->findThisUsageInClosure($phpcsFile, ($thisFound + 1), $scopeEnd);
  108. } while ($thisFound !== false);
  109. }
  110. /*
  111. * Closures declared within classes only have access to self/parent/static since PHP 5.4.
  112. */
  113. $usesClassRef = $this->findClassRefUsageInClosure($phpcsFile, $scopeStart, $scopeEnd);
  114. if ($usesClassRef !== false) {
  115. do {
  116. $phpcsFile->addError(
  117. 'Closures / anonymous functions could not use "%s::" in PHP 5.3 or earlier',
  118. $usesClassRef,
  119. 'ClassRefFound',
  120. array(strtolower($tokens[$usesClassRef]['content']))
  121. );
  122. $usesClassRef = $this->findClassRefUsageInClosure($phpcsFile, ($usesClassRef + 1), $scopeEnd);
  123. } while ($usesClassRef !== false);
  124. }
  125. }
  126. /*
  127. * Check for correct usage.
  128. */
  129. if ($this->supportsAbove('5.4') && $usesThis !== false) {
  130. $thisFound = $usesThis;
  131. do {
  132. /*
  133. * Closures only have access to $this if not declared as static.
  134. */
  135. if ($isStatic === true) {
  136. $phpcsFile->addError(
  137. 'Closures / anonymous functions declared as static do not have access to $this',
  138. $thisFound,
  139. 'ThisFoundInStatic'
  140. );
  141. }
  142. /*
  143. * Closures only have access to $this if used within a class context.
  144. */
  145. elseif ($this->inClassScope($phpcsFile, $stackPtr, false) === false) {
  146. $phpcsFile->addWarning(
  147. 'Closures / anonymous functions only have access to $this if used within a class or when bound to an object using bindTo(). Please verify.',
  148. $thisFound,
  149. 'ThisFoundOutsideClass'
  150. );
  151. }
  152. $thisFound = $this->findThisUsageInClosure($phpcsFile, ($thisFound + 1), $scopeEnd);
  153. } while ($thisFound !== false);
  154. }
  155. // Prevent double reporting for nested closures.
  156. return $scopeEnd;
  157. }
  158. /**
  159. * Check whether the closure is declared as static.
  160. *
  161. * @since 7.1.4
  162. *
  163. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  164. * @param int $stackPtr The position of the current token
  165. * in the stack passed in $tokens.
  166. *
  167. * @return bool
  168. */
  169. protected function isClosureStatic(File $phpcsFile, $stackPtr)
  170. {
  171. $tokens = $phpcsFile->getTokens();
  172. $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
  173. return ($prevToken !== false && $tokens[$prevToken]['code'] === \T_STATIC);
  174. }
  175. /**
  176. * Check if the code within a closure uses the $this variable.
  177. *
  178. * @since 7.1.4
  179. *
  180. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  181. * @param int $startToken The position within the closure to continue searching from.
  182. * @param int $endToken The closure scope closer to stop searching at.
  183. *
  184. * @return int|false The stackPtr to the first $this usage if found or false if
  185. * $this is not used.
  186. */
  187. protected function findThisUsageInClosure(File $phpcsFile, $startToken, $endToken)
  188. {
  189. // Make sure the $startToken is valid.
  190. if ($startToken >= $endToken) {
  191. return false;
  192. }
  193. return $phpcsFile->findNext(
  194. \T_VARIABLE,
  195. $startToken,
  196. $endToken,
  197. false,
  198. '$this'
  199. );
  200. }
  201. /**
  202. * Check if the code within a closure uses "self/parent/static".
  203. *
  204. * @since 8.2.0
  205. *
  206. * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
  207. * @param int $startToken The position within the closure to continue searching from.
  208. * @param int $endToken The closure scope closer to stop searching at.
  209. *
  210. * @return int|false The stackPtr to the first classRef usage if found or false if
  211. * they are not used.
  212. */
  213. protected function findClassRefUsageInClosure(File $phpcsFile, $startToken, $endToken)
  214. {
  215. // Make sure the $startToken is valid.
  216. if ($startToken >= $endToken) {
  217. return false;
  218. }
  219. $tokens = $phpcsFile->getTokens();
  220. $classRef = $phpcsFile->findNext(array(\T_SELF, \T_PARENT, \T_STATIC), $startToken, $endToken);
  221. if ($classRef === false || $tokens[$classRef]['code'] !== \T_STATIC) {
  222. return $classRef;
  223. }
  224. // T_STATIC, make sure it is used as a class reference.
  225. $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($classRef + 1), $endToken, true);
  226. if ($next === false || $tokens[$next]['code'] !== \T_DOUBLE_COLON) {
  227. return false;
  228. }
  229. return $classRef;
  230. }
  231. }