Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHPCSUtils / 280

11 Feb 2020 - 6:18 coverage: 97.698% (+2.3%) from 95.415%
280

Pull #79

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Improve on previous arrow function implementation in various utilities

* Change checking of the return value of the `FunctionDeclarations::getArrowFunctionOpenClose()` function.
    - This function now either returns `false` or a array with all indexes set to the relevant stack pointers.
* Improved the message of the `RuntimeException` for the `FunctionDeclarations::getParameters()` and the `FunctionDeclarations::getProperties()` methods.
* Update unit tests to reflect the change in tokenization of "fn" tokens.
* Remove test skipping for select arrow function related tests on select PHP/PHPCS versions.
    This is no longer needed.
* Add/improve documentation about arrow function support.

Includes a few additional unit tests to safeguard cross-version compatibility with PHPCS 3.5.3 and 3.5.4.
Pull Request #79: Improve support for arrow functions / sync with phpcs 3.5.5

58 of 60 new or added lines in 5 files covered. (96.67%)

2292 of 2346 relevant lines covered (97.7%)

69.95 hits per line

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

97.79
/PHPCSUtils/Utils/FunctionDeclarations.php
1
<?php
2
/**
3
 * PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4
 *
5
 * @package   PHPCSUtils
6
 * @copyright 2019-2020 PHPCSUtils Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCSStandards/PHPCSUtils
9
 */
10

11
namespace PHPCSUtils\Utils;
12

13
use PHP_CodeSniffer\Exceptions\RuntimeException;
14
use PHP_CodeSniffer\Files\File;
15
use PHP_CodeSniffer\Util\Tokens;
16
use PHPCSUtils\BackCompat\BCTokens;
17
use PHPCSUtils\BackCompat\Helper;
18
use PHPCSUtils\Tokens\Collections;
19
use PHPCSUtils\Utils\Conditions;
20
use PHPCSUtils\Utils\GetTokensAsString;
21
use PHPCSUtils\Utils\ObjectDeclarations;
22
use PHPCSUtils\Utils\Scopes;
23
use PHPCSUtils\Utils\UseStatements;
24

25
/**
26
 * Utility functions for use when examining function declaration statements.
27
 *
28
 * @since 1.0.0 The `getProperties()` and the `getParameters()` methods are
29
 *              based on and inspired by respectively the `getMethodProperties()`
30
 *              and `getMethodParameters()` methods in the PHPCS native `File` class.
31
 *              Also see {@see \PHPCSUtils\BackCompat\BCFile}.
32
 */
33
class FunctionDeclarations
34
{
35

36
    /**
37
     * A list of all PHP magic functions.
38
     *
39
     * The array keys are the function names, the values as well, but without the double underscore.
40
     *
41
     * The function names are listed in lowercase as function names in PHP are case-insensitive
42
     * and comparisons against this list should therefore always be done in a case-insensitive manner.
43
     *
44
     * @since 1.0.0
45
     *
46
     * @var array <string> => <string>
47
     */
48
    public static $magicFunctions = [
49
        '__autoload' => 'autoload',
50
    ];
51

52
    /**
53
     * A list of all PHP magic methods.
54
     *
55
     * The array keys are the method names, the values as well, but without the double underscore.
56
     *
57
     * The method names are listed in lowercase as function names in PHP are case-insensitive
58
     * and comparisons against this list should therefore always be done in a case-insensitive manner.
59
     *
60
     * @since 1.0.0
61
     *
62
     * @var array <string> => <string>
63
     */
64
    public static $magicMethods = [
65
        '__construct'   => 'construct',
66
        '__destruct'    => 'destruct',
67
        '__call'        => 'call',
68
        '__callstatic'  => 'callstatic',
69
        '__get'         => 'get',
70
        '__set'         => 'set',
71
        '__isset'       => 'isset',
72
        '__unset'       => 'unset',
73
        '__sleep'       => 'sleep',
74
        '__wakeup'      => 'wakeup',
75
        '__tostring'    => 'tostring',
76
        '__set_state'   => 'set_state',
77
        '__clone'       => 'clone',
78
        '__invoke'      => 'invoke',
79
        '__debuginfo'   => 'debuginfo', // PHP 5.6.
80
        '__serialize'   => 'serialize', // PHP 7.4.
81
        '__unserialize' => 'unserialize', // PHP 7.4.
82
    ];
83

84
    /**
85
     * A list of all PHP native non-magic methods starting with a double underscore.
86
     *
87
     * These come from PHP modules such as SOAPClient.
88
     *
89
     * The array keys are the method names, the values the name of the PHP extension containing
90
     * the function.
91
     *
92
     * The method names are listed in lowercase as function names in PHP are case-insensitive
93
     * and comparisons against this list should therefore always be done in a case-insensitive manner.
94
     *
95
     * @since 1.0.0
96
     *
97
     * @var array <string> => <string>
98
     */
99
    public static $methodsDoubleUnderscore = [
100
        '__dorequest'              => 'SOAPClient',
101
        '__getcookies'             => 'SOAPClient',
102
        '__getfunctions'           => 'SOAPClient',
103
        '__getlastrequest'         => 'SOAPClient',
104
        '__getlastrequestheaders'  => 'SOAPClient',
105
        '__getlastresponse'        => 'SOAPClient',
106
        '__getlastresponseheaders' => 'SOAPClient',
107
        '__gettypes'               => 'SOAPClient',
108
        '__setcookie'              => 'SOAPClient',
109
        '__setlocation'            => 'SOAPClient',
110
        '__setsoapheaders'         => 'SOAPClient',
111
        '__soapcall'               => 'SOAPClient',
112
    ];
113

114
    /**
115
     * Tokens which can be the end token of an arrow function.
116
     *
117
     * @since 1.0.0
118
     *
119
     * @var array <int|string> => <true>
120
     */
121
    private static $arrowFunctionEndTokens = [
122
        \T_COLON                => true,
123
        \T_COMMA                => true,
124
        \T_SEMICOLON            => true,
125
        \T_CLOSE_PARENTHESIS    => true,
126
        \T_CLOSE_SQUARE_BRACKET => true,
127
        \T_CLOSE_CURLY_BRACKET  => true,
128
        \T_CLOSE_SHORT_ARRAY    => true,
129
        \T_OPEN_TAG             => true,
130
        \T_CLOSE_TAG            => true,
131
    ];
132

133
    /**
134
     * Returns the declaration name for a function.
135
     *
136
     * Alias for the {@see \PHPCSUtils\Utils\ObjectDeclarations::getName()} method.
137
     *
138
     * @codeCoverageIgnore
139
     *
140
     * @see \PHPCSUtils\BackCompat\BCFile::getDeclarationName() Original function.
141
     * @see \PHPCSUtils\Utils\ObjectDeclarations::getName()     PHPCSUtils native improved version.
142
     *
143
     * @since 1.0.0
144
     *
145
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
146
     * @param int                         $stackPtr  The position of the declaration token
147
     *                                               which declared the function.
148
     *
149
     * @return string|null The name of the function; or NULL if the passed token doesn't exist,
150
     *                     the function is anonymous or in case of a parse error/live coding.
151
     *
152
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type
153
     *                                                      T_FUNCTION, T_CLASS, T_TRAIT, or T_INTERFACE.
154
     */
155
    public static function getName(File $phpcsFile, $stackPtr)
156
    {
157
        return ObjectDeclarations::getName($phpcsFile, $stackPtr);
158
    }
159

160
    /**
161
     * Retrieves the visibility and implementation properties of a method.
162
     *
163
     * The format of the return value is:
164
     * <code>
165
     *   array(
166
     *    'scope'                 => 'public', // Public, private, or protected
167
     *    'scope_specified'       => true,     // TRUE if the scope keyword was found.
168
     *    'return_type'           => '',       // The return type of the method.
169
     *    'return_type_token'     => integer,  // The stack pointer to the start of the return type
170
     *                                         // or FALSE if there is no return type.
171
     *    'return_type_end_token' => integer,  // The stack pointer to the end of the return type
172
     *                                         // or FALSE if there is no return type.
173
     *    'nullable_return_type'  => false,    // TRUE if the return type is nullable.
174
     *    'is_abstract'           => false,    // TRUE if the abstract keyword was found.
175
     *    'is_final'              => false,    // TRUE if the final keyword was found.
176
     *    'is_static'             => false,    // TRUE if the static keyword was found.
177
     *    'has_body'              => false,    // TRUE if the method has a body
178
     *   );
179
     * </code>
180
     *
181
     * Main differences with the PHPCS version:
182
     * - Bugs fixed:
183
     *   - Handling of PHPCS annotations.
184
     *   - `has_body` index could be set to `true` for functions without body in the case of
185
     *      parse errors or live coding.
186
     * - Defensive coding against incorrect calls to this method.
187
     * - More efficient checking whether a function has a body.
188
     * - New `return_type_end_token` (int|false) array index.
189
     * - To allow for backward compatible handling of arrow functions, this method will also accept
190
     *   `T_STRING` tokens and examine them to check if these are arrow functions.
191
     *
192
     * @see \PHP_CodeSniffer\Files\File::getMethodProperties()   Original source.
193
     * @see \PHPCSUtils\BackCompat\BCFile::getMethodProperties() Cross-version compatible version of the original.
194
     *
195
     * @since 1.0.0
196
     * @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
197
     *
198
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
199
     * @param int                         $stackPtr  The position in the stack of the function token to
200
     *                                               acquire the properties for.
201
     *
202
     * @return array
203
     *
204
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a T_FUNCTION
205
     *                                                      or T_CLOSURE token nor an arrow function.
206
     */
207
    public static function getProperties(File $phpcsFile, $stackPtr)
208
    {
209
        $tokens         = $phpcsFile->getTokens();
104×
210
        $arrowOpenClose = self::getArrowFunctionOpenClose($phpcsFile, $stackPtr);
104×
211

212
        if (isset($tokens[$stackPtr]) === false
104×
213
            || ($tokens[$stackPtr]['code'] !== \T_FUNCTION
102×
214
                && $tokens[$stackPtr]['code'] !== \T_CLOSURE
100×
215
                && $arrowOpenClose === false)
102×
216
        ) {
217
            throw new RuntimeException('$stackPtr must be of type T_FUNCTION or T_CLOSURE or an arrow function');
16×
218
        }
219

220
        if ($tokens[$stackPtr]['code'] === \T_FUNCTION) {
88×
221
            $valid = Tokens::$methodPrefixes;
68×
222
        } else {
223
            $valid = [\T_STATIC => \T_STATIC];
20×
224
        }
225

226
        $valid += Tokens::$emptyTokens;
88×
227

228
        $scope          = 'public';
88×
229
        $scopeSpecified = false;
88×
230
        $isAbstract     = false;
88×
231
        $isFinal        = false;
88×
232
        $isStatic       = false;
88×
233

234
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
88×
235
            if (isset($valid[$tokens[$i]['code']]) === false) {
88×
236
                break;
84×
237
            }
238

239
            switch ($tokens[$i]['code']) {
84×
240
                case \T_PUBLIC:
241
                    $scope          = 'public';
20×
242
                    $scopeSpecified = true;
20×
243
                    break;
20×
244
                case \T_PRIVATE:
245
                    $scope          = 'private';
4×
246
                    $scopeSpecified = true;
4×
247
                    break;
4×
248
                case \T_PROTECTED:
249
                    $scope          = 'protected';
8×
250
                    $scopeSpecified = true;
8×
251
                    break;
8×
252
                case \T_ABSTRACT:
253
                    $isAbstract = true;
12×
254
                    break;
12×
255
                case \T_FINAL:
256
                    $isFinal = true;
4×
257
                    break;
4×
258
                case \T_STATIC:
259
                    $isStatic = true;
12×
260
                    break;
12×
261
            }
262
        }
263

264
        $returnType         = '';
88×
265
        $returnTypeToken    = false;
88×
266
        $returnTypeEndToken = false;
88×
267
        $nullableReturnType = false;
88×
268
        $hasBody            = false;
88×
269

270
        $parenthesisCloser = null;
88×
271
        if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
88×
272
            $parenthesisCloser = $tokens[$stackPtr]['parenthesis_closer'];
82×
273
        } elseif ($arrowOpenClose !== false) {
47×
274
            // Arrow function in combination with PHP < 7.4 or PHPCS < 3.5.3.
275
            $parenthesisCloser = $arrowOpenClose['parenthesis_closer'];
6×
276
        }
277

278
        if (isset($parenthesisCloser) === true) {
88×
279
            $scopeOpener = null;
88×
280
            if (isset($tokens[$stackPtr]['scope_opener']) === true) {
88×
281
                $scopeOpener = $tokens[$stackPtr]['scope_opener'];
70×
282
            } elseif ($arrowOpenClose !== false) {
53×
283
                // Arrow function in combination with PHP < 7.4 or PHPCS < 3.5.3.
284
                $scopeOpener = $arrowOpenClose['scope_opener'];
6×
285
            }
286

287
            for ($i = $parenthesisCloser; $i < $phpcsFile->numTokens; $i++) {
88×
288
                if ($i === $scopeOpener) {
88×
289
                    // End of function definition.
290
                    $hasBody = true;
76×
291
                    break;
76×
292
                }
293

294
                if ($scopeOpener === null && $tokens[$i]['code'] === \T_SEMICOLON) {
88×
295
                    // End of abstract/interface function definition.
296
                    break;
12×
297
                }
298

299
                if ($tokens[$i]['type'] === 'T_NULLABLE'
88×
300
                    // Handle nullable tokens in PHPCS < 2.8.0.
301
                    || (\defined('T_NULLABLE') === false && $tokens[$i]['code'] === \T_INLINE_THEN)
88×
302
                    // Handle nullable tokens with arrow functions in PHPCS 2.8.0 - 2.9.0.
303
                    || ($arrowOpenClose !== false && $tokens[$i]['code'] === \T_INLINE_THEN
88×
304
                        && \version_compare(Helper::getVersion(), '2.9.1', '<') === true)
88×
305
                ) {
306
                    $nullableReturnType = true;
16×
307
                }
308

309
                if (isset(Collections::$returnTypeTokens[$tokens[$i]['code']]) === true) {
88×
310
                    if ($returnTypeToken === false) {
56×
311
                        $returnTypeToken = $i;
56×
312
                    }
313

314
                    $returnType        .= $tokens[$i]['content'];
56×
315
                    $returnTypeEndToken = $i;
56×
316
                }
317
            }
318
        }
319

320
        if ($returnType !== '' && $nullableReturnType === true) {
88×
321
            $returnType = '?' . $returnType;
16×
322
        }
323

324
        return [
325
            'scope'                 => $scope,
88×
326
            'scope_specified'       => $scopeSpecified,
88×
327
            'return_type'           => $returnType,
88×
328
            'return_type_token'     => $returnTypeToken,
88×
329
            'return_type_end_token' => $returnTypeEndToken,
88×
330
            'nullable_return_type'  => $nullableReturnType,
88×
331
            'is_abstract'           => $isAbstract,
88×
332
            'is_final'              => $isFinal,
88×
333
            'is_static'             => $isStatic,
88×
334
            'has_body'              => $hasBody,
88×
335
        ];
336
    }
337

338
    /**
339
     * Retrieves the method parameters for the specified function token.
340
     *
341
     * Also supports passing in a USE token for a closure use group.
342
     *
343
     * The returned array will contain the following information for each parameter:
344
     *
345
     * <code>
346
     *   0 => array(
347
     *         'name'                => '$var',  // The variable name.
348
     *         'token'               => integer, // The stack pointer to the variable name.
349
     *         'content'             => string,  // The full content of the variable definition.
350
     *         'pass_by_reference'   => boolean, // Is the variable passed by reference?
351
     *         'reference_token'     => integer, // The stack pointer to the reference operator
352
     *                                           // or FALSE if the param is not passed by reference.
353
     *         'variable_length'     => boolean, // Is the param of variable length through use of `...` ?
354
     *         'variadic_token'      => integer, // The stack pointer to the ... operator
355
     *                                           // or FALSE if the param is not variable length.
356
     *         'type_hint'           => string,  // The type hint for the variable.
357
     *         'type_hint_token'     => integer, // The stack pointer to the start of the type hint
358
     *                                           // or FALSE if there is no type hint.
359
     *         'type_hint_end_token' => integer, // The stack pointer to the end of the type hint
360
     *                                           // or FALSE if there is no type hint.
361
     *         'nullable_type'       => boolean, // TRUE if the var type is nullable.
362
     *         'comma_token'         => integer, // The stack pointer to the comma after the param
363
     *                                           // or FALSE if this is the last param.
364
     *        )
365
     * </code>
366
     *
367
     * Parameters with default values have the following additional array indexes:
368
     *         'default'             => string,  // The full content of the default value.
369
     *         'default_token'       => integer, // The stack pointer to the start of the default value.
370
     *         'default_equal_token' => integer, // The stack pointer to the equals sign.
371
     *
372
     * Main differences with the PHPCS version:
373
     * - Defensive coding against incorrect calls to this method.
374
     * - More efficient and more stable checking whether a T_USE token is a closure use.
375
     * - More efficient and more stable looping of the default value.
376
     * - Clearer exception message when a non-closure use token was passed to the function.
377
     * - To allow for backward compatible handling of arrow functions, this method will also accept
378
     *   `T_STRING` tokens and examine them to check if these are arrow functions.
379
     *
380
     * @see \PHP_CodeSniffer\Files\File::getMethodParameters()   Original source.
381
     * @see \PHPCSUtils\BackCompat\BCFile::getMethodParameters() Cross-version compatible version of the original.
382
     *
383
     * @since 1.0.0
384
     * @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
385
     *
386
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
387
     * @param int                         $stackPtr  The position in the stack of the function token
388
     *                                               to acquire the parameters for.
389
     *
390
     * @return array
391
     *
392
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is not of
393
     *                                                      type T_FUNCTION, T_CLOSURE or T_USE,
394
     *                                                      nor an arrow function.
395
     */
396
    public static function getParameters(File $phpcsFile, $stackPtr)
397
    {
398
        $tokens         = $phpcsFile->getTokens();
144×
399
        $arrowOpenClose = self::getArrowFunctionOpenClose($phpcsFile, $stackPtr);
144×
400

401
        if (isset($tokens[$stackPtr]) === false
144×
402
            || ($tokens[$stackPtr]['code'] !== \T_FUNCTION
142×
403
                && $tokens[$stackPtr]['code'] !== \T_CLOSURE
140×
404
                && $tokens[$stackPtr]['code'] !== \T_USE
140×
405
                && $arrowOpenClose === false)
142×
406
        ) {
407
            throw new RuntimeException('$stackPtr must be of type T_FUNCTION, T_CLOSURE or T_USE or an arrow function');
16×
408
        }
409

410
        if ($tokens[$stackPtr]['code'] === \T_USE) {
128×
411
            $opener = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
24×
412
            if ($opener === false
413
                || $tokens[$opener]['code'] !== \T_OPEN_PARENTHESIS
24×
414
                || UseStatements::isClosureUse($phpcsFile, $stackPtr) === false
24×
415
            ) {
416
                throw new RuntimeException('$stackPtr was not a valid closure T_USE');
20×
417
            }
418
        } elseif ($arrowOpenClose !== false) {
108×
419
            // Arrow function in combination with PHP < 7.4 or PHPCS < 3.5.3/4/5.
420
            $opener = $arrowOpenClose['parenthesis_opener'];
12×
421
        } else {
422
            if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
92×
423
                // Live coding or syntax error, so no params to find.
424
                return [];
4×
425
            }
426

427
            $opener = $tokens[$stackPtr]['parenthesis_opener'];
88×
428
        }
429

430
        if (isset($tokens[$opener]['parenthesis_closer']) === false) {
108×
431
            // Live coding or syntax error, so no params to find.
432
            return [];
4×
433
        }
434

435
        $closer = $tokens[$opener]['parenthesis_closer'];
104×
436

437
        $vars             = [];
104×
438
        $currVar          = null;
104×
439
        $paramStart       = ($opener + 1);
104×
440
        $defaultStart     = null;
104×
441
        $equalToken       = null;
104×
442
        $paramCount       = 0;
104×
443
        $passByReference  = false;
104×
444
        $referenceToken   = false;
104×
445
        $variableLength   = false;
104×
446
        $variadicToken    = false;
104×
447
        $typeHint         = '';
104×
448
        $typeHintToken    = false;
104×
449
        $typeHintEndToken = false;
104×
450
        $nullableType     = false;
104×
451

452
        for ($i = $paramStart; $i <= $closer; $i++) {
104×
453
            // Changed from checking 'code' to 'type' to allow for T_NULLABLE not existing in PHPCS < 2.8.0.
454
            switch ($tokens[$i]['type']) {
104×
455
                case 'T_BITWISE_AND':
104×
456
                    $passByReference = true;
20×
457
                    $referenceToken  = $i;
20×
458
                    break;
20×
459

460
                case 'T_VARIABLE':
104×
461
                    $currVar = $i;
92×
462
                    break;
92×
463

464
                case 'T_ELLIPSIS':
104×
465
                    $variableLength = true;
28×
466
                    $variadicToken  = $i;
28×
467
                    break;
28×
468

469
                case 'T_ARRAY_HINT': // PHPCS < 3.3.0.
104×
470
                case 'T_CALLABLE':
104×
471
                case 'T_SELF':
104×
472
                case 'T_PARENT':
104×
473
                case 'T_STATIC': // Self and parent are valid, static invalid, but was probably intended as type hint.
104×
474
                case 'T_STRING':
104×
475
                case 'T_NS_SEPARATOR':
104×
476
                    if ($typeHintToken === false) {
52×
477
                        $typeHintToken = $i;
52×
478
                    }
479

480
                    $typeHint        .= $tokens[$i]['content'];
52×
481
                    $typeHintEndToken = $i;
52×
482
                    break;
52×
483

484
                case 'T_NULLABLE':
104×
485
                case 'T_INLINE_THEN': // PHPCS < 2.8.0.
104×
486
                    $nullableType     = true;
28×
487
                    $typeHint        .= $tokens[$i]['content'];
28×
488
                    $typeHintEndToken = $i;
28×
489
                    break;
28×
490

491
                case 'T_CLOSE_PARENTHESIS':
104×
492
                case 'T_COMMA':
94×
493
                    // If it's null, then there must be no parameters for this
494
                    // method.
495
                    if ($currVar === null) {
104×
496
                        continue 2;
12×
497
                    }
498

499
                    $vars[$paramCount]            = [];
92×
500
                    $vars[$paramCount]['token']   = $currVar;
92×
501
                    $vars[$paramCount]['name']    = $tokens[$currVar]['content'];
92×
502
                    $vars[$paramCount]['content'] = \trim(
92×
503
                        GetTokensAsString::normal($phpcsFile, $paramStart, ($i - 1))
92×
504
                    );
505

506
                    if ($defaultStart !== null) {
92×
507
                        $vars[$paramCount]['default']             = \trim(
40×
508
                            GetTokensAsString::normal($phpcsFile, $defaultStart, ($i - 1))
40×
509
                        );
510
                        $vars[$paramCount]['default_token']       = $defaultStart;
40×
511
                        $vars[$paramCount]['default_equal_token'] = $equalToken;
40×
512
                    }
513

514
                    $vars[$paramCount]['pass_by_reference']   = $passByReference;
92×
515
                    $vars[$paramCount]['reference_token']     = $referenceToken;
92×
516
                    $vars[$paramCount]['variable_length']     = $variableLength;
92×
517
                    $vars[$paramCount]['variadic_token']      = $variadicToken;
92×
518
                    $vars[$paramCount]['type_hint']           = $typeHint;
92×
519
                    $vars[$paramCount]['type_hint_token']     = $typeHintToken;
92×
520
                    $vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken;
92×
521
                    $vars[$paramCount]['nullable_type']       = $nullableType;
92×
522

523
                    if ($tokens[$i]['code'] === \T_COMMA) {
92×
524
                        $vars[$paramCount]['comma_token'] = $i;
52×
525
                    } else {
526
                        $vars[$paramCount]['comma_token'] = false;
92×
527
                    }
528

529
                    // Reset the vars, as we are about to process the next parameter.
530
                    $currVar          = null;
92×
531
                    $paramStart       = ($i + 1);
92×
532
                    $defaultStart     = null;
92×
533
                    $equalToken       = null;
92×
534
                    $passByReference  = false;
92×
535
                    $referenceToken   = false;
92×
536
                    $variableLength   = false;
92×
537
                    $variadicToken    = false;
92×
538
                    $typeHint         = '';
92×
539
                    $typeHintToken    = false;
92×
540
                    $typeHintEndToken = false;
92×
541
                    $nullableType     = false;
92×
542

543
                    $paramCount++;
92×
544
                    break;
92×
545

546
                case 'T_EQUAL':
84×
547
                    $defaultStart = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
40×
548
                    $equalToken   = $i;
40×
549

550
                    // Skip past everything in the default value before going into the next switch loop.
551
                    for ($j = ($i + 1); $j <= $closer; $j++) {
40×
552
                        // Skip past array()'s et al as default values.
553
                        if (isset($tokens[$j]['parenthesis_opener'], $tokens[$j]['parenthesis_closer'])) {
40×
554
                            $j = $tokens[$j]['parenthesis_closer'];
24×
555

556
                            if ($j === $closer) {
24×
557
                                // Found the end of the parameter.
558
                                break;
24×
559
                            }
560

561
                            continue;
4×
562
                        }
563

564
                        // Skip past short arrays et al as default values.
565
                        if (isset($tokens[$j]['bracket_opener'])) {
40×
566
                            $j = $tokens[$j]['bracket_closer'];
4×
567
                            continue;
4×
568
                        }
569

570
                        if ($tokens[$j]['code'] === \T_COMMA) {
40×
571
                            break;
24×
572
                        }
573
                    }
574

575
                    $i = ($j - 1);
40×
576
                    break;
40×
577
            }
578
        }
579

580
        return $vars;
104×
581
    }
582

583
    /**
584
     * Check if an arbitrary token is the "fn" keyword for a PHP 7.4 arrow function.
585
     *
586
     * Helper function for cross-version compatibility with both PHP as well as PHPCS.
587
     * - PHP 7.4+ will tokenize most tokens with the content "fn" as T_FN, even when it isn't an arrow function.
588
     * - PHPCS < 3.5.3 will tokenize arrow functions keywords as T_STRING.
589
     * - PHPCS 3.5.3/3.5.4 will tokenize the keyword differently depending on which PHP version is used
590
     *   and similar to PHP will tokenize most tokens with the content "fn" as T_FN, even when it's not an
591
     *   arrow function.
592
     *   Note: the tokens tokenized by PHPCS 3.5.3 - 3.5.4 as T_FN are not 100% the same as those tokenized
593
     *   by PHP 7.4+ as T_FN.
594
     *
595
     * Either way, the T_FN token is not a reliable indicator that something is in actual fact an arrow function.
596
     * This function solves that and will give reliable results in the same way as this is now solved in PHPCS 3.5.5.
597
     *
598
     * @see \PHPCSUtils\Utils\FunctionDeclarations::getArrowFunctionOpenClose() Related function.
599
     *
600
     * @since 1.0.0
601
     *
602
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
603
     * @param int                         $stackPtr  The token to check. Typically a T_FN or
604
     *                                               T_STRING token as those are the only two
605
     *                                               tokens which can be the arrow function keyword.
606
     *
607
     * @return bool TRUE is the token is the "fn" keyword for an arrow function. FALSE when not or
608
     *              in case of live coding/parse error.
609
     */
610
    public static function isArrowFunction(File $phpcsFile, $stackPtr)
611
    {
612
        $tokens = $phpcsFile->getTokens();
176×
613
        if (isset($tokens[$stackPtr]) === false) {
176×
614
            return false;
4×
615
        }
616

617
        if ($tokens[$stackPtr]['type'] === 'T_FN'
172×
618
            && isset($tokens[$stackPtr]['scope_closer']) === true
172×
619
        ) {
620
            return true;
54×
621
        }
622

623
        if (isset(Collections::arrowFunctionTokensBC()[$tokens[$stackPtr]['code']]) === false
118×
624
            || \strtolower($tokens[$stackPtr]['content']) !== 'fn'
118×
625
        ) {
626
            return false;
12×
627
        }
628

629
        $openClose = self::getArrowFunctionOpenClose($phpcsFile, $stackPtr);
106×
630
        if ($openClose !== false && isset($openClose['scope_closer'])) {
106×
631
            return true;
54×
632
        }
633

634
        return false;
52×
635
    }
636

637
    /**
638
     * Retrieve the parenthesis opener, parenthesis closer, the scope opener and the scope closer
639
     * for an arrow function.
640
     *
641
     * Helper function for cross-version compatibility with both PHP as well as PHPCS.
642
     * In PHPCS versions prior to PHPCS 3.5.3/3.5.4, the `T_FN` token is not yet backfilled
643
     * and does not have parenthesis opener/closer nor scope opener/closer indexes assigned
644
     * in the `$tokens` array.
645
     *
646
     * @see \PHPCSUtils\Utils\FunctionDeclarations::isArrowFunction() Related function.
647
     *
648
     * @since 1.0.0
649
     *
650
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
651
     * @param int                         $stackPtr  The token to retrieve the openers/closers for.
652
     *                                               Typically a T_FN or T_STRING token as those are the
653
     *                                               only two tokens which can be the arrow function keyword.
654
     *
655
     * @return array|false An array with the token pointers or FALSE if this is not an arrow function.
656
     *                     The format of the return value is:
657
     *                     <code>
658
     *                     array(
659
     *                       'parenthesis_opener' => integer, // Stack pointer to the parenthesis opener.
660
     *                       'parenthesis_closer' => integer, // Stack pointer to the parenthesis closer.
661
     *                       'scope_opener'       => integer, // Stack pointer to the scope opener (arrow).
662
     *                       'scope_closer'       => integer, // Stack pointer to the scope closer.
663
     *                     )
664
     *                     </code>
665
     */
666
    public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr)
667
    {
668
        $tokens = $phpcsFile->getTokens();
282×
669

670
        if (isset($tokens[$stackPtr]) === false
282×
671
            || isset(Collections::arrowFunctionTokensBC()[$tokens[$stackPtr]['code']]) === false
280×
672
            || \strtolower($tokens[$stackPtr]['content']) !== 'fn'
280×
673
        ) {
674
            return false;
18×
675
        }
676

677
        if ($tokens[$stackPtr]['type'] === 'T_FN'
270×
678
            && isset($tokens[$stackPtr]['scope_closer']) === true
270×
679
        ) {
680
            // The keys will either all be set or none will be set, so no additional checks needed.
681
            return [
682
                'parenthesis_opener' => $tokens[$stackPtr]['parenthesis_opener'],
54×
683
                'parenthesis_closer' => $tokens[$stackPtr]['parenthesis_closer'],
54×
684
                'scope_opener'       => $tokens[$stackPtr]['scope_opener'],
54×
685
                'scope_closer'       => $tokens[$stackPtr]['scope_closer'],
54×
686
            ];
687
        }
688

689
        /*
690
         * This is either a T_STRING token pre-PHP 7.4, or T_FN on PHP 7.4 in combination
691
         * with PHPCS < 3.5.3/4/5.
692
         *
693
         * Now see about finding the relevant arrow function tokens.
694
         */
695
        $returnValue = [];
216×
696

697
        $nextNonEmpty = $phpcsFile->findNext(
216×
698
            (Tokens::$emptyTokens + [\T_BITWISE_AND]),
216×
699
            ($stackPtr + 1),
216×
700
            null,
216×
701
            true
702
        );
703
        if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
216×
704
            return false;
48×
705
        }
706

707
        $returnValue['parenthesis_opener'] = $nextNonEmpty;
168×
708
        if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
168×
NEW
709
            return false;
!
710
        }
711

712
        $returnValue['parenthesis_closer'] = $tokens[$nextNonEmpty]['parenthesis_closer'];
168×
713

714
        $ignore                 = Tokens::$emptyTokens;
168×
715
        $ignore                += Collections::$returnTypeTokens;
168×
716
        $ignore[\T_COLON]       = \T_COLON;
168×
717
        $ignore[\T_INLINE_ELSE] = \T_INLINE_ELSE; // Return type colon on PHPCS < 2.9.1.
168×
718
        $ignore[\T_INLINE_THEN] = \T_INLINE_THEN; // Nullable type indicator on PHPCS < 2.9.1.
168×
719

720
        if (\defined('T_NULLABLE') === true) {
168×
721
            $ignore[\T_NULLABLE] = \T_NULLABLE;
99×
722
        }
723

724
        $arrow = $phpcsFile->findNext(
168×
725
            $ignore,
168×
726
            ($tokens[$nextNonEmpty]['parenthesis_closer'] + 1),
168×
727
            null,
168×
728
            true
729
        );
730

731
        if ($arrow === false
732
            || ($tokens[$arrow]['code'] !== \T_DOUBLE_ARROW && $tokens[$arrow]['type'] !== 'T_FN_ARROW')
168×
733
        ) {
734
            return false;
60×
735
        }
736

737
        $returnValue['scope_opener'] = $arrow;
108×
738
        $inTernary                   = false;
108×
739

740
        for ($scopeCloser = ($arrow + 1); $scopeCloser < $phpcsFile->numTokens; $scopeCloser++) {
108×
741
            if (isset(self::$arrowFunctionEndTokens[$tokens[$scopeCloser]['code']]) === true
108×
742
                // BC for misidentified ternary else in some PHPCS versions.
743
                && ($tokens[$scopeCloser]['code'] !== \T_COLON || $inTernary === false)
108×
744
            ) {
745
                break;
108×
746
            }
747

748
            if (isset(Collections::arrowFunctionTokensBC()[$tokens[$scopeCloser]['code']]) === true) {
108×
749
                $nested = self::getArrowFunctionOpenClose($phpcsFile, $scopeCloser);
14×
750
                if ($nested !== false && isset($nested['scope_closer'])) {
14×
751
                    // We minus 1 here in case the closer can be shared with us.
752
                    $scopeCloser = ($nested['scope_closer'] - 1);
8×
753
                    continue;
8×
754
                }
755
            }
756

757
            if (isset($tokens[$scopeCloser]['scope_closer']) === true
108×
758
                && $tokens[$scopeCloser]['code'] !== \T_INLINE_ELSE
108×
759
            ) {
760
                // We minus 1 here in case the closer can be shared with us.
761
                $scopeCloser = ($tokens[$scopeCloser]['scope_closer'] - 1);
!
762
                continue;
!
763
            }
764

765
            if (isset($tokens[$scopeCloser]['parenthesis_closer']) === true) {
108×
766
                $scopeCloser = $tokens[$scopeCloser]['parenthesis_closer'];
16×
767
                continue;
16×
768
            }
769

770
            if (isset($tokens[$scopeCloser]['bracket_closer']) === true) {
108×
771
                $scopeCloser = $tokens[$scopeCloser]['bracket_closer'];
4×
772
                continue;
4×
773
            }
774

775
            if ($tokens[$scopeCloser]['code'] === \T_INLINE_THEN) {
108×
776
                $inTernary = true;
4×
777
                continue;
4×
778
            }
779

780
            if ($tokens[$scopeCloser]['code'] === \T_INLINE_ELSE) {
108×
781
                if ($inTernary === false) {
!
782
                    break;
!
783
                }
784

785
                $inTernary = false;
!
786
            }
787
        }
788

789
        if ($scopeCloser === $phpcsFile->numTokens) {
108×
NEW
790
            return false;
!
791
        }
792

793
        $returnValue['scope_closer'] = $scopeCloser;
108×
794

795
        return $returnValue;
108×
796
    }
797

798
    /**
799
     * Checks if a given function is a PHP magic function.
800
     *
801
     * @todo Add check for the function declaration being namespaced!
802
     *
803
     * @see \PHPCSUtils\Utils\FunctionDeclaration::isMagicFunctionName() For when you already know the name of the
804
     *                                                                   function and scope checking is done in the
805
     *                                                                   sniff.
806
     *
807
     * @since 1.0.0
808
     *
809
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
810
     * @param int                         $stackPtr  The T_FUNCTION token to check.
811
     *
812
     * @return bool
813
     */
814
    public static function isMagicFunction(File $phpcsFile, $stackPtr)
815
    {
816
        $tokens = $phpcsFile->getTokens();
108×
817
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_FUNCTION) {
108×
818
            return false;
8×
819
        }
820

821
        if (Conditions::hasCondition($phpcsFile, $stackPtr, BCTokens::ooScopeTokens()) === true) {
100×
822
            return false;
80×
823
        }
824

825
        $name = self::getName($phpcsFile, $stackPtr);
20×
826
        return self::isMagicFunctionName($name);
20×
827
    }
828

829
    /**
830
     * Verify if a given function name is the name of a PHP magic function.
831
     *
832
     * @since 1.0.0
833
     *
834
     * @param string $name The full function name.
835
     *
836
     * @return bool
837
     */
838
    public static function isMagicFunctionName($name)
839
    {
840
        $name = \strtolower($name);
28×
841
        return (isset(self::$magicFunctions[$name]) === true);
28×
842
    }
843

844
    /**
845
     * Checks if a given function is a PHP magic method.
846
     *
847
     * @see \PHPCSUtils\Utils\FunctionDeclaration::isMagicMethodName() For when you already know the name of the
848
     *                                                                 method and scope checking is done in the
849
     *                                                                 sniff.
850
     *
851
     * @since 1.0.0
852
     *
853
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
854
     * @param int                         $stackPtr  The T_FUNCTION token to check.
855
     *
856
     * @return bool
857
     */
858
    public static function isMagicMethod(File $phpcsFile, $stackPtr)
859
    {
860
        if (Scopes::isOOMethod($phpcsFile, $stackPtr) === false) {
108×
861
            return false;
28×
862
        }
863

864
        $name = self::getName($phpcsFile, $stackPtr);
80×
865
        return self::isMagicMethodName($name);
80×
866
    }
867

868
    /**
869
     * Verify if a given function name is the name of a PHP magic method.
870
     *
871
     * @since 1.0.0
872
     *
873
     * @param string $name The full function name.
874
     *
875
     * @return bool
876
     */
877
    public static function isMagicMethodName($name)
878
    {
879
        $name = \strtolower($name);
152×
880
        return (isset(self::$magicMethods[$name]) === true);
152×
881
    }
882

883
    /**
884
     * Checks if a given function is a PHP native double underscore method.
885
     *
886
     * @see \PHPCSUtils\Utils\FunctionDeclaration::isPHPDoubleUnderscoreMethodName() For when you already know the
887
     *                                                                               name of the method and scope
888
     *                                                                               checking is done in the sniff.
889
     *
890
     * @since 1.0.0
891
     *
892
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
893
     * @param int                         $stackPtr  The T_FUNCTION token to check.
894
     *
895
     * @return bool
896
     */
897
    public static function isPHPDoubleUnderscoreMethod(File $phpcsFile, $stackPtr)
898
    {
899
        $tokens = $phpcsFile->getTokens();
108×
900
        if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_FUNCTION) {
108×
901
            return false;
8×
902
        }
903

904
        $scopePtr = Scopes::validDirectScope($phpcsFile, $stackPtr, BCTokens::ooScopeTokens());
100×
905
        if ($scopePtr === false) {
100×
906
            return false;
20×
907
        }
908

909
        /*
910
         * If this is a class, make sure it extends something, as otherwise, the methods
911
         * still can't be overloads for the SOAPClient methods.
912
         * For a trait/interface we don't know the concrete implementation context, so skip
913
         * this check.
914
         */
915
        if ($tokens[$scopePtr]['code'] === \T_CLASS || $tokens[$scopePtr]['code'] === \T_ANON_CLASS) {
80×
916
            $extends = ObjectDeclarations::findExtendedClassName($phpcsFile, $scopePtr);
48×
917
            if ($extends === false) {
48×
918
                return false;
40×
919
            }
920
        }
921

922
        $name = self::getName($phpcsFile, $stackPtr);
40×
923
        return self::isPHPDoubleUnderscoreMethodName($name);
40×
924
    }
925

926
    /**
927
     * Verify if a given function name is the name of a PHP native double underscore method.
928
     *
929
     * @since 1.0.0
930
     *
931
     * @param string $name The full function name.
932
     *
933
     * @return bool
934
     */
935
    public static function isPHPDoubleUnderscoreMethodName($name)
936
    {
937
        $name = \strtolower($name);
112×
938
        return (isset(self::$methodsDoubleUnderscore[$name]) === true);
112×
939
    }
940

941
    /**
942
     * Checks if a given function is a magic method or a PHP native double underscore method.
943
     *
944
     * @see \PHPCSUtils\Utils\FunctionDeclaration::isSpecialMethodName() For when you already know the name of the
945
     *                                                                   method and scope checking is done in the
946
     *                                                                   sniff.
947
     *
948
     * @since 1.0.0
949
     *
950
     * {@internal Not the most efficient way of checking this, but less efficient ways will get
951
     *            less reliable results or introduce a lot of code duplication.}
952
     *
953
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
954
     * @param int                         $stackPtr  The T_FUNCTION token to check.
955
     *
956
     * @return bool
957
     */
958
    public static function isSpecialMethod(File $phpcsFile, $stackPtr)
959
    {
960
        if (self::isMagicMethod($phpcsFile, $stackPtr) === true) {
108×
961
            return true;
28×
962
        }
963

964
        if (self::isPHPDoubleUnderscoreMethod($phpcsFile, $stackPtr) === true) {
80×
965
            return true;
16×
966
        }
967

968
        return false;
64×
969
    }
970

971
    /**
972
     * Verify if a given function name is the name of a magic method or a PHP native double underscore method.
973
     *
974
     * @since 1.0.0
975
     *
976
     * @param string $name The full function name.
977
     *
978
     * @return bool
979
     */
980
    public static function isSpecialMethodName($name)
981
    {
982
        $name = \strtolower($name);
264×
983
        return (isset(self::$magicMethods[$name]) === true || isset(self::$methodsDoubleUnderscore[$name]) === true);
264×
984
    }
985
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc