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

PHPCSStandards / PHPCSUtils / 360

21 Feb 2020 - 23:25 coverage: 91.695% (-6.05%) from 97.743%
360

Pull #100

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
PHPUnit: be strict about covers annotations
Pull Request #100: PHPUnit: be strict about covers annotations

2153 of 2348 relevant lines covered (91.7%)

37.38 hits per line

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

92.77
/PHPCSUtils/Utils/Arrays.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\Helper;
17
use PHPCSUtils\Tokens\Collections;
18
use PHPCSUtils\Utils\FunctionDeclarations;
19
use PHPCSUtils\Utils\Lists;
20

21
/**
22
 * Utility functions for use when examining arrays.
23
 *
24
 * @since 1.0.0
25
 */
26
class Arrays
27
{
28

29
    /**
30
     * The tokens to target to find the double arrow in an array item.
31
     *
32
     * Note: this array does not contain the `T_FN` token as it may or may not exist.
33
     * If it exists, it will be added in the `getDoubleArrowPtr()` function.
34
     *
35
     * @since 1.0.0
36
     *
37
     * @var array <int|string> => <int|string>
38
     */
39
    private static $doubleArrowTargets = [
40
        \T_DOUBLE_ARROW     => \T_DOUBLE_ARROW,
41
        \T_ARRAY            => \T_ARRAY,
42
        \T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
43
        \T_STRING           => \T_STRING, // BC for T_FN token in PHPCS < 3.5.3 icw PHP < 7.4.
44
    ];
45

46
    /**
47
     * Determine whether a `T_OPEN/CLOSE_SHORT_ARRAY` token is a short array() construct
48
     * and not a short list.
49
     *
50
     * This method also accepts `T_OPEN/CLOSE_SQUARE_BRACKET` tokens to allow it to be
51
     * PHPCS cross-version compatible as the short array tokenizing has been plagued by
52
     * a number of bugs over time.
53
     *
54
     * @since 1.0.0
55
     *
56
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
57
     * @param int                         $stackPtr  The position of the short array bracket token.
58
     *
59
     * @return bool True if the token passed is the open/close bracket of a short array.
60
     *              False if the token is a short list bracket, a plain square bracket
61
     *              or not one of the accepted tokens.
62
     */
63
    public static function isShortArray(File $phpcsFile, $stackPtr)
64
    {
65
        $tokens = $phpcsFile->getTokens();
82×
66

67
        // Is this one of the tokens this function handles ?
68
        if (isset($tokens[$stackPtr]) === false
82×
69
            || isset(Collections::$shortArrayTokensBC[$tokens[$stackPtr]['code']]) === false
82×
70
        ) {
71
            return false;
6×
72
        }
73

74
        // All known tokenizer bugs are in PHPCS versions before 3.3.0.
75
        $phpcsVersion = Helper::getVersion();
76×
76

77
        /*
78
         * Deal with square brackets which may be incorrectly tokenized short arrays.
79
         */
80
        if (isset(Collections::$shortArrayTokens[$tokens[$stackPtr]['code']]) === false) {
76×
81
            if (\version_compare($phpcsVersion, '3.3.0', '>=')) {
35×
82
                // These will just be properly tokenized, plain square brackets. No need for further checks.
83
                return false;
17×
84
            }
85

86
            $opener = $stackPtr;
18×
87
            if ($tokens[$stackPtr]['code'] === \T_CLOSE_SQUARE_BRACKET) {
18×
88
                $opener = $tokens[$stackPtr]['bracket_opener'];
1×
89
            }
90

91
            if (isset($tokens[$opener]['bracket_closer']) === false) {
18×
92
                return false;
1×
93
            }
94

95
            $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true);
17×
96

97
            if (\version_compare($phpcsVersion, '2.8.0', '>=')) {
17×
98
                /*
99
                 * BC: Work around a bug in the tokenizer of PHPCS 2.8.0 - 3.2.3 where a `[` would be
100
                 * tokenized as T_OPEN_SQUARE_BRACKET instead of T_OPEN_SHORT_ARRAY if it was
101
                 * preceded by a PHP open tag at the very start of the file.
102
                 *
103
                 * If we have square brackets which are not that specific situation, they are just plain
104
                 * square brackets.
105
                 *
106
                 * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1971
107
                 */
UNCOV
108
                if ($prevNonEmpty !== 0 || $tokens[$prevNonEmpty]['code'] !== \T_OPEN_TAG) {
!
UNCOV
109
                    return false;
!
110
                }
111
            }
112

113
            if (\version_compare($phpcsVersion, '2.8.0', '<')) {
17×
114
                /*
115
                 * BC: Work around a bug in the tokenizer of PHPCS < 2.8.0 where a `[` would be
116
                 * tokenized as T_OPEN_SQUARE_BRACKET instead of T_OPEN_SHORT_ARRAY if it was
117
                 * preceded by a close curly of a control structure.
118
                 *
119
                 * If we have square brackets which are not that specific situation, they are just plain
120
                 * square brackets.
121
                 *
122
                 * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1284
123
                 */
124
                if ($tokens[$prevNonEmpty]['code'] !== \T_CLOSE_CURLY_BRACKET
17×
125
                    || isset($tokens[$prevNonEmpty]['scope_condition']) === false
17×
126
                ) {
127
                    return false;
15×
128
                }
129
            }
130
        } else {
131
            /*
132
             * Deal with short array brackets which may be incorrectly tokenized plain square brackets.
133
             */
134
            if (\version_compare($phpcsVersion, '2.9.0', '<')) {
41×
135
                $opener = $stackPtr;
20×
136
                if ($tokens[$stackPtr]['code'] === \T_CLOSE_SHORT_ARRAY) {
20×
137
                    $opener = $tokens[$stackPtr]['bracket_opener'];
1×
138
                }
139

140
                /*
141
                 * BC: Work around a bug in the tokenizer of PHPCS < 2.9.0 where array dereferencing
142
                 * of short array and string literals would be incorrectly tokenized as short array.
143
                 * I.e. the square brackets in `'PHP'[0]` would be tokenized as short array.
144
                 *
145
                 * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1381
146
                 */
147
                $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true);
20×
148
                if ($tokens[$prevNonEmpty]['code'] === \T_CLOSE_SHORT_ARRAY
20×
149
                    || $tokens[$prevNonEmpty]['code'] === \T_CONSTANT_ENCAPSED_STRING
20×
150
                ) {
151
                    return false;
1×
152
                }
153

154
                /*
155
                 * BC: Work around a bug in the tokenizer of PHPCS 2.8.0 and 2.8.1 where array dereferencing
156
                 * of a variable variable would be incorrectly tokenized as short array.
157
                 *
158
                 * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1284
159
                 */
160
                if (\version_compare($phpcsVersion, '2.8.0', '>=')
19×
161
                    && $tokens[$prevNonEmpty]['code'] === \T_CLOSE_CURLY_BRACKET
19×
162
                ) {
163
                    $openCurly     = $tokens[$prevNonEmpty]['bracket_opener'];
!
164
                    $beforeCurlies = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($openCurly - 1), null, true);
!
165
                    if ($tokens[$beforeCurlies]['code'] === \T_DOLLAR) {
!
166
                        return false;
!
167
                    }
168
                }
169
            }
170
        }
171

172
        // In all other circumstances, make sure this isn't a short list instead of a short array.
173
        return (Lists::isShortList($phpcsFile, $stackPtr) === false);
42×
174
    }
175

176
    /**
177
     * Find the array opener & closer based on a T_ARRAY or T_OPEN_SHORT_ARRAY token.
178
     *
179
     * This method also accepts `T_OPEN_SQUARE_BRACKET` tokens to allow it to be
180
     * PHPCS cross-version compatible as the short array tokenizing has been plagued by
181
     * a number of bugs over time, which affects the short array determination.
182
     *
183
     * @since 1.0.0
184
     *
185
     * @param \PHP_CodeSniffer\Files\File $phpcsFile    The file being scanned.
186
     * @param int                         $stackPtr     The position of the T_ARRAY or T_OPEN_SHORT_ARRAY
187
     *                                                  token in the stack.
188
     * @param true|null                   $isShortArray Short-circuit the short array check for T_OPEN_SHORT_ARRAY
189
     *                                                  tokens if it isn't necessary.
190
     *                                                  Efficiency tweak for when this has already been established,
191
     *                                                  i.e. when encountering a nested array while walking the
192
     *                                                  tokens in an array.
193
     *                                                  Use with care.
194
     *
195
     * @return array|false Array with two keys `opener`, `closer` or false if
196
     *                     not a (short) array token or if the opener/closer
197
     *                     could not be determined.
198
     */
199
    public static function getOpenClose(File $phpcsFile, $stackPtr, $isShortArray = null)
200
    {
201
        $tokens = $phpcsFile->getTokens();
22×
202

203
        // Is this one of the tokens this function handles ?
204
        if (isset($tokens[$stackPtr]) === false
22×
205
            || isset(Collections::$arrayTokensBC[$tokens[$stackPtr]['code']]) === false
22×
206
        ) {
207
            return false;
4×
208
        }
209

210
        switch ($tokens[$stackPtr]['code']) {
18×
211
            case \T_ARRAY:
212
                if (isset($tokens[$stackPtr]['parenthesis_opener'])) {
8×
213
                    $opener = $tokens[$stackPtr]['parenthesis_opener'];
8×
214

215
                    if (isset($tokens[$opener]['parenthesis_closer'])) {
8×
216
                        $closer = $tokens[$opener]['parenthesis_closer'];
6×
217
                    }
218
                }
219
                break;
8×
220

221
            case \T_OPEN_SHORT_ARRAY:
222
            case \T_OPEN_SQUARE_BRACKET:
10×
223
                if ($isShortArray === true || self::isShortArray($phpcsFile, $stackPtr) === true) {
10×
224
                    $opener = $stackPtr;
6×
225
                    $closer = $tokens[$stackPtr]['bracket_closer'];
6×
226
                }
227
                break;
10×
228
        }
229

230
        if (isset($opener, $closer)) {
18×
231
            return [
232
                'opener' => $opener,
12×
233
                'closer' => $closer,
12×
234
            ];
235
        }
236

237
        return false;
6×
238
    }
239

240
    /**
241
     * Get the stack pointer position of the double arrow within an array item.
242
     *
243
     * Expects to be passed the array item start and end tokens as retrieved via
244
     * {@see \PHPCSUtils\Utils\PassedParameters::getParameters()}.
245
     *
246
     * @since 1.0.0
247
     * @since 1.0.0-alpha2 Now allows for arrow functions in arrays.
248
     *
249
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being examined.
250
     * @param int                         $start     Stack pointer to the start of the array item.
251
     * @param int                         $end       Stack pointer to the end of the array item (inclusive).
252
     *
253
     * @return int|false Stack pointer to the double arrow if this array item has a key or false otherwise.
254
     *
255
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the start or end positions are invalid.
256
     */
257
    public static function getDoubleArrowPtr(File $phpcsFile, $start, $end)
258
    {
259
        $tokens = $phpcsFile->getTokens();
46×
260

261
        if (isset($tokens[$start], $tokens[$end]) === false || $start > $end) {
46×
262
            throw new RuntimeException(
6×
263
                'Invalid start and/or end position passed to getDoubleArrowPtr().'
264
                . ' Received: $start ' . $start . ', $end ' . $end
6×
265
            );
266
        }
267

268
        $targets  = self::$doubleArrowTargets;
40×
269
        $targets += Collections::$closedScopes;
40×
270
        $targets += Collections::arrowFunctionTokensBC();
40×
271

272
        $doubleArrow = ($start - 1);
40×
273
        ++$end;
40×
274
        do {
275
            $doubleArrow = $phpcsFile->findNext(
40×
276
                $targets,
40×
277
                ($doubleArrow + 1),
40×
278
                $end
279
            );
280

281
            if ($doubleArrow === false) {
40×
282
                break;
10×
283
            }
284

285
            if ($tokens[$doubleArrow]['code'] === \T_DOUBLE_ARROW) {
36×
286
                return $doubleArrow;
21×
287
            }
288

289
            /*
290
             * BC: work-around a bug in PHPCS 3.5.4 where the double arrow is incorrectly tokenized as T_STRING.
291
             * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/2865
292
             */
293
            if ($tokens[$doubleArrow]['code'] === \T_STRING && $tokens[$doubleArrow]['content'] === '=>') {
22×
294
                return $doubleArrow;
1×
295
            }
296

297
            // Skip over closed scopes which may contain foreach structures or generators.
298
            if (isset(Collections::$closedScopes[$tokens[$doubleArrow]['code']]) === true
22×
299
                && isset($tokens[$doubleArrow]['scope_closer']) === true
22×
300
            ) {
301
                $doubleArrow = $tokens[$doubleArrow]['scope_closer'];
8×
302
                continue;
8×
303
            }
304

305
            // BC for PHP 7.4 arrow functions with PHPCS < 3.5.3.
306
            if (isset(Collections::arrowFunctionTokensBC()[$tokens[$doubleArrow]['code']]) === true
14×
307
                && FunctionDeclarations::isArrowFunction($phpcsFile, $doubleArrow) === false
14×
308
            ) {
309
                // Not an arrow function, continue looking.
310
                continue;
6×
311
            }
312

313
            // Start of nested long/short array or arrow function.
314
            break;
8×
315
        } while ($doubleArrow < $end);
14×
316

317
        return false;
18×
318
    }
319
}
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