• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 18114150721

30 Sep 2025 12:00AM UTC coverage: 78.601% (-0.6%) from 79.178%
18114150721

push

github

web-flow
Merge pull request #1284 from PHPCSStandards/dependabot/github_actions/3.x/action-runners-f175f89d6c

GH Actions: Bump actions/cache from 4.2.4 to 4.3.0 in the action-runners group

25341 of 32240 relevant lines covered (78.6%)

74.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.59
/src/Standards/MySource/Sniffs/Objects/CreateWidgetTypeCallbackSniff.php
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/PHPCSStandards/PHP_CodeSniffer/blob/HEAD/licence.txt BSD Licence
8
 *
9
 * @deprecated 3.9.0
10
 */
11

12
namespace PHP_CodeSniffer\Standards\MySource\Sniffs\Objects;
13

14
use PHP_CodeSniffer\Sniffs\DeprecatedSniff;
15
use PHP_CodeSniffer\Sniffs\Sniff;
16
use PHP_CodeSniffer\Files\File;
17
use PHP_CodeSniffer\Util\Tokens;
18

19
class CreateWidgetTypeCallbackSniff implements Sniff, DeprecatedSniff
20
{
21

22
    /**
23
     * A list of tokenizers this sniff supports.
24
     *
25
     * @var array
26
     */
27
    public $supportedTokenizers = ['JS'];
28

29

30
    /**
31
     * Returns an array of tokens this test wants to listen for.
32
     *
33
     * @return array<int|string>
34
     */
35
    public function register()
3✔
36
    {
37
        return [T_OBJECT];
3✔
38

39
    }//end register()
40

41

42
    /**
43
     * Processes this test, when one of its tokens is encountered.
44
     *
45
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
46
     * @param int                         $stackPtr  The position of the current token
47
     *                                               in the stack passed in $tokens.
48
     *
49
     * @return void
50
     */
51
    public function process(File $phpcsFile, $stackPtr)
3✔
52
    {
53
        $tokens = $phpcsFile->getTokens();
3✔
54

55
        $className = $phpcsFile->findPrevious(T_STRING, ($stackPtr - 1));
3✔
56
        if (substr(strtolower($tokens[$className]['content']), -10) !== 'widgettype') {
3✔
57
            return;
3✔
58
        }
59

60
        // Search for a create method.
61
        $create = $phpcsFile->findNext(T_PROPERTY, $stackPtr, $tokens[$stackPtr]['bracket_closer'], null, 'create');
3✔
62
        if ($create === false) {
3✔
63
            return;
3✔
64
        }
65

66
        $function = $phpcsFile->findNext([T_WHITESPACE, T_COLON], ($create + 1), null, true);
3✔
67
        if ($tokens[$function]['code'] !== T_FUNCTION
3✔
68
            && $tokens[$function]['code'] !== T_CLOSURE
3✔
69
        ) {
1✔
70
            return;
×
71
        }
72

73
        $start = ($tokens[$function]['scope_opener'] + 1);
3✔
74
        $end   = ($tokens[$function]['scope_closer'] - 1);
3✔
75

76
        // Check that the first argument is called "callback".
77
        $arg = $phpcsFile->findNext(T_WHITESPACE, ($tokens[$function]['parenthesis_opener'] + 1), null, true);
3✔
78
        if ($tokens[$arg]['content'] !== 'callback') {
3✔
79
            $error = 'The first argument of the create() method of a widget type must be called "callback"';
3✔
80
            $phpcsFile->addError($error, $arg, 'FirstArgNotCallback');
3✔
81
        }
1✔
82

83
        /*
84
            Look for return statements within the function. They cannot return
85
            anything and must be preceded by the callback.call() line. The
86
            callback itself must contain "self" or "this" as the first argument
87
            and there needs to be a call to the callback function somewhere
88
            in the create method. All calls to the callback function must be
89
            followed by a return statement or the end of the method.
90
        */
91

92
        $foundCallback  = false;
3✔
93
        $passedCallback = false;
3✔
94
        $nestedFunction = null;
3✔
95
        for ($i = $start; $i <= $end; $i++) {
3✔
96
            // Keep track of nested functions.
97
            if ($nestedFunction !== null) {
3✔
98
                if ($i === $nestedFunction) {
3✔
99
                    $nestedFunction = null;
3✔
100
                    continue;
3✔
101
                }
102
            } else if (($tokens[$i]['code'] === T_FUNCTION
3✔
103
                || $tokens[$i]['code'] === T_CLOSURE)
3✔
104
                && isset($tokens[$i]['scope_closer']) === true
3✔
105
            ) {
1✔
106
                $nestedFunction = $tokens[$i]['scope_closer'];
3✔
107
                continue;
3✔
108
            }
109

110
            if ($nestedFunction === null && $tokens[$i]['code'] === T_RETURN) {
3✔
111
                // Make sure return statements are not returning anything.
112
                if ($tokens[($i + 1)]['code'] !== T_SEMICOLON) {
3✔
113
                    $error = 'The create() method of a widget type must not return a value';
3✔
114
                    $phpcsFile->addError($error, $i, 'ReturnValue');
3✔
115
                }
1✔
116

117
                continue;
3✔
118
            } else if ($tokens[$i]['code'] !== T_STRING
3✔
119
                || $tokens[$i]['content'] !== 'callback'
3✔
120
            ) {
1✔
121
                continue;
3✔
122
            }
123

124
            // If this is the form "callback.call(" then it is a call
125
            // to the callback function.
126
            if ($tokens[($i + 1)]['code'] !== T_OBJECT_OPERATOR
3✔
127
                || $tokens[($i + 2)]['content'] !== 'call'
3✔
128
                || $tokens[($i + 3)]['code'] !== T_OPEN_PARENTHESIS
3✔
129
            ) {
1✔
130
                // One last chance; this might be the callback function
131
                // being passed to another function, like this
132
                // "this.init(something, callback, something)".
133
                if (isset($tokens[$i]['nested_parenthesis']) === false) {
3✔
134
                    continue;
3✔
135
                }
136

137
                // Just make sure those brackets don't belong to anyone,
138
                // like an IF or FOR statement.
139
                foreach ($tokens[$i]['nested_parenthesis'] as $bracket) {
3✔
140
                    if (isset($tokens[$bracket]['parenthesis_owner']) === true) {
3✔
141
                        continue(2);
3✔
142
                    }
143
                }
1✔
144

145
                // Note that we use this endBracket down further when checking
146
                // for a RETURN statement.
147
                $nestedParens = $tokens[$i]['nested_parenthesis'];
3✔
148
                $endBracket   = end($nestedParens);
3✔
149
                $bracket      = key($nestedParens);
3✔
150

151
                $prev = $phpcsFile->findPrevious(
3✔
152
                    Tokens::$emptyTokens,
3✔
153
                    ($bracket - 1),
3✔
154
                    null,
3✔
155
                    true
2✔
156
                );
2✔
157

158
                if ($tokens[$prev]['code'] !== T_STRING) {
3✔
159
                    // This is not a function passing the callback.
160
                    continue;
×
161
                }
162

163
                $passedCallback = true;
3✔
164
            }//end if
1✔
165

166
            $foundCallback = true;
3✔
167

168
            if ($passedCallback === false) {
3✔
169
                // The first argument must be "this" or "self".
170
                $arg = $phpcsFile->findNext(T_WHITESPACE, ($i + 4), null, true);
3✔
171
                if ($tokens[$arg]['content'] !== 'this'
3✔
172
                    && $tokens[$arg]['content'] !== 'self'
3✔
173
                ) {
1✔
174
                    $error = 'The first argument passed to the callback function must be "this" or "self"';
3✔
175
                    $phpcsFile->addError($error, $arg, 'FirstArgNotSelf');
3✔
176
                }
1✔
177
            }
1✔
178

179
            // Now it must be followed by a return statement or the end of the function.
180
            if ($passedCallback === false) {
3✔
181
                $endBracket = $tokens[($i + 3)]['parenthesis_closer'];
3✔
182
            }
1✔
183

184
            for ($next = $endBracket; $next <= $end; $next++) {
3✔
185
                // Skip whitespace so we find the next content after the call.
186
                if (isset(Tokens::$emptyTokens[$tokens[$next]['code']]) === true) {
3✔
187
                    continue;
3✔
188
                }
189

190
                // Skip closing braces like END IF because it is not executable code.
191
                if ($tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) {
3✔
192
                    continue;
3✔
193
                }
194

195
                // We don't care about anything on the current line, like a
196
                // semicolon. It doesn't matter if there are other statements on the
197
                // line because another sniff will check for those.
198
                if ($tokens[$next]['line'] === $tokens[$endBracket]['line']) {
3✔
199
                    continue;
3✔
200
                }
201

202
                break;
3✔
203
            }
204

205
            if ($next !== $tokens[$function]['scope_closer']
3✔
206
                && $tokens[$next]['code'] !== T_RETURN
3✔
207
            ) {
1✔
208
                $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';
3✔
209
                $phpcsFile->addError($error, $i, 'NoReturn');
3✔
210
            }
1✔
211
        }//end for
1✔
212

213
        if ($foundCallback === false) {
3✔
214
            $error = 'The create() method of a widget type must call the callback function';
3✔
215
            $phpcsFile->addError($error, $create, 'CallbackNotCalled');
3✔
216
        }
1✔
217

218
    }//end process()
2✔
219

220

221
    /**
222
     * Provide the version number in which the sniff was deprecated.
223
     *
224
     * @return string
225
     */
226
    public function getDeprecationVersion()
×
227
    {
228
        return 'v3.9.0';
×
229

230
    }//end getDeprecationVersion()
231

232

233
    /**
234
     * Provide the version number in which the sniff will be removed.
235
     *
236
     * @return string
237
     */
238
    public function getRemovalVersion()
×
239
    {
240
        return 'v4.0.0';
×
241

242
    }//end getRemovalVersion()
243

244

245
    /**
246
     * Provide a custom message to display with the deprecation.
247
     *
248
     * @return string
249
     */
250
    public function getDeprecationMessage()
×
251
    {
252
        return 'The MySource standard will be removed completely in v4.0.0.';
×
253

254
    }//end getDeprecationMessage()
255

256

257
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc