PageRenderTime 42ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/php/PHP_CodeSniffer/src/Standards/MySource/Sniffs/Objects/CreateWidgetTypeCallbackSniff.php

http://github.com/jonswar/perl-code-tidyall
PHP | 218 lines | 127 code | 36 blank | 55 comment | 35 complexity | a44631e4e27bfae6fbf9e9125425f4d3 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, 0BSD, MIT
  1. <?php
  2. /**
  3. * Ensures the create() method of widget types properly uses callbacks.
  4. *
  5. * @author Greg Sherwood <gsherwood@squiz.net>
  6. * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  7. * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  8. */
  9. namespace PHP_CodeSniffer\Standards\MySource\Sniffs\Objects;
  10. use PHP_CodeSniffer\Sniffs\Sniff;
  11. use PHP_CodeSniffer\Files\File;
  12. use PHP_CodeSniffer\Util\Tokens;
  13. class CreateWidgetTypeCallbackSniff implements Sniff
  14. {
  15. /**
  16. * A list of tokenizers this sniff supports.
  17. *
  18. * @var array
  19. */
  20. public $supportedTokenizers = ['JS'];
  21. /**
  22. * Returns an array of tokens this test wants to listen for.
  23. *
  24. * @return array
  25. */
  26. public function register()
  27. {
  28. return [T_OBJECT];
  29. }//end register()
  30. /**
  31. * Processes this test, when one of its tokens is encountered.
  32. *
  33. * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  34. * @param int $stackPtr The position of the current token
  35. * in the stack passed in $tokens.
  36. *
  37. * @return void
  38. */
  39. public function process(File $phpcsFile, $stackPtr)
  40. {
  41. $tokens = $phpcsFile->getTokens();
  42. $className = $phpcsFile->findPrevious(T_STRING, ($stackPtr - 1));
  43. if (substr(strtolower($tokens[$className]['content']), -10) !== 'widgettype') {
  44. return;
  45. }
  46. // Search for a create method.
  47. $create = $phpcsFile->findNext(T_PROPERTY, $stackPtr, $tokens[$stackPtr]['bracket_closer'], null, 'create');
  48. if ($create === false) {
  49. return;
  50. }
  51. $function = $phpcsFile->findNext([T_WHITESPACE, T_COLON], ($create + 1), null, true);
  52. if ($tokens[$function]['code'] !== T_FUNCTION
  53. && $tokens[$function]['code'] !== T_CLOSURE
  54. ) {
  55. return;
  56. }
  57. $start = ($tokens[$function]['scope_opener'] + 1);
  58. $end = ($tokens[$function]['scope_closer'] - 1);
  59. // Check that the first argument is called "callback".
  60. $arg = $phpcsFile->findNext(T_WHITESPACE, ($tokens[$function]['parenthesis_opener'] + 1), null, true);
  61. if ($tokens[$arg]['content'] !== 'callback') {
  62. $error = 'The first argument of the create() method of a widget type must be called "callback"';
  63. $phpcsFile->addError($error, $arg, 'FirstArgNotCallback');
  64. }
  65. /*
  66. Look for return statements within the function. They cannot return
  67. anything and must be preceded by the callback.call() line. The
  68. callback itself must contain "self" or "this" as the first argument
  69. and there needs to be a call to the callback function somewhere
  70. in the create method. All calls to the callback function must be
  71. followed by a return statement or the end of the method.
  72. */
  73. $foundCallback = false;
  74. $passedCallback = false;
  75. $nestedFunction = null;
  76. for ($i = $start; $i <= $end; $i++) {
  77. // Keep track of nested functions.
  78. if ($nestedFunction !== null) {
  79. if ($i === $nestedFunction) {
  80. $nestedFunction = null;
  81. continue;
  82. }
  83. } else if (($tokens[$i]['code'] === T_FUNCTION
  84. || $tokens[$i]['code'] === T_CLOSURE)
  85. && isset($tokens[$i]['scope_closer']) === true
  86. ) {
  87. $nestedFunction = $tokens[$i]['scope_closer'];
  88. continue;
  89. }
  90. if ($nestedFunction === null && $tokens[$i]['code'] === T_RETURN) {
  91. // Make sure return statements are not returning anything.
  92. if ($tokens[($i + 1)]['code'] !== T_SEMICOLON) {
  93. $error = 'The create() method of a widget type must not return a value';
  94. $phpcsFile->addError($error, $i, 'ReturnValue');
  95. }
  96. continue;
  97. } else if ($tokens[$i]['code'] !== T_STRING
  98. || $tokens[$i]['content'] !== 'callback'
  99. ) {
  100. continue;
  101. }
  102. // If this is the form "callback.call(" then it is a call
  103. // to the callback function.
  104. if ($tokens[($i + 1)]['code'] !== T_OBJECT_OPERATOR
  105. || $tokens[($i + 2)]['content'] !== 'call'
  106. || $tokens[($i + 3)]['code'] !== T_OPEN_PARENTHESIS
  107. ) {
  108. // One last chance; this might be the callback function
  109. // being passed to another function, like this
  110. // "this.init(something, callback, something)".
  111. if (isset($tokens[$i]['nested_parenthesis']) === false) {
  112. continue;
  113. }
  114. // Just make sure those brackets dont belong to anyone,
  115. // like an IF or FOR statement.
  116. foreach ($tokens[$i]['nested_parenthesis'] as $bracket) {
  117. if (isset($tokens[$bracket]['parenthesis_owner']) === true) {
  118. continue(2);
  119. }
  120. }
  121. // Note that we use this endBracket down further when checking
  122. // for a RETURN statement.
  123. $nestedParens = $tokens[$i]['nested_parenthesis'];
  124. $endBracket = end($nestedParens);
  125. $bracket = key($nestedParens);
  126. $prev = $phpcsFile->findPrevious(
  127. Tokens::$emptyTokens,
  128. ($bracket - 1),
  129. null,
  130. true
  131. );
  132. if ($tokens[$prev]['code'] !== T_STRING) {
  133. // This is not a function passing the callback.
  134. continue;
  135. }
  136. $passedCallback = true;
  137. }//end if
  138. $foundCallback = true;
  139. if ($passedCallback === false) {
  140. // The first argument must be "this" or "self".
  141. $arg = $phpcsFile->findNext(T_WHITESPACE, ($i + 4), null, true);
  142. if ($tokens[$arg]['content'] !== 'this'
  143. && $tokens[$arg]['content'] !== 'self'
  144. ) {
  145. $error = 'The first argument passed to the callback function must be "this" or "self"';
  146. $phpcsFile->addError($error, $arg, 'FirstArgNotSelf');
  147. }
  148. }
  149. // Now it must be followed by a return statement or the end of the function.
  150. if ($passedCallback === false) {
  151. $endBracket = $tokens[($i + 3)]['parenthesis_closer'];
  152. }
  153. for ($next = $endBracket; $next <= $end; $next++) {
  154. // Skip whitespace so we find the next content after the call.
  155. if (isset(Tokens::$emptyTokens[$tokens[$next]['code']]) === true) {
  156. continue;
  157. }
  158. // Skip closing braces like END IF because it is not executable code.
  159. if ($tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) {
  160. continue;
  161. }
  162. // We don't care about anything on the current line, like a
  163. // semicolon. It doesn't matter if there are other statements on the
  164. // line because another sniff will check for those.
  165. if ($tokens[$next]['line'] === $tokens[$endBracket]['line']) {
  166. continue;
  167. }
  168. break;
  169. }
  170. if ($next !== $tokens[$function]['scope_closer']
  171. && $tokens[$next]['code'] !== T_RETURN
  172. ) {
  173. $error = 'The call to the callback function must be followed by a return statement if it is not the last statement in the create() method';
  174. $phpcsFile->addError($error, $i, 'NoReturn');
  175. }
  176. }//end for
  177. if ($foundCallback === false) {
  178. $error = 'The create() method of a widget type must call the callback function';
  179. $phpcsFile->addError($error, $create, 'CallbackNotCalled');
  180. }
  181. }//end process()
  182. }//end class