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

PHPCSStandards / PHPCSUtils / 285

11 Feb 2020 - 6:43 coverage: 97.696% (-0.002%) from 97.698%
285

Pull #80

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Documentation: various minor fixes
Pull Request #80: Documentation: various minor fixes

2290 of 2344 relevant lines covered (97.7%)

69.97 hits per line

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

97.47
/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.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\AbstractSniffs;
12

13
use PHP_CodeSniffer\Exceptions\RuntimeException;
14
use PHP_CodeSniffer\Files\File;
15
use PHP_CodeSniffer\Sniffs\Sniff;
16
use PHP_CodeSniffer\Util\Tokens;
17
use PHPCSUtils\BackCompat\BCTokens;
18
use PHPCSUtils\Utils\Arrays;
19
use PHPCSUtils\Utils\Numbers;
20
use PHPCSUtils\Utils\PassedParameters;
21
use PHPCSUtils\Utils\TextStrings;
22

23
/**
24
 * Abstract sniff to easily examine all parts of an array declaration.
25
 *
26
 * @since 1.0.0
27
 */
28
abstract class AbstractArrayDeclarationSniff implements Sniff
29
{
30

31
    /**
32
     * The stack pointer to the array keyword or the short array open token.
33
     *
34
     * @since 1.0.0
35
     *
36
     * @var int
37
     */
38
    protected $stackPtr;
39

40
    /**
41
     * The token stack for the current file being examined.
42
     *
43
     * @since 1.0.0
44
     *
45
     * @var array
46
     */
47
    protected $tokens;
48

49
    /**
50
     * The stack pointer to the array opener.
51
     *
52
     * @since 1.0.0
53
     *
54
     * @var int
55
     */
56
    protected $arrayOpener;
57

58
    /**
59
     * The stack pointer to the array closer.
60
     *
61
     * @since 1.0.0
62
     *
63
     * @var int
64
     */
65
    protected $arrayCloser;
66

67
    /**
68
     * A multi-dimentional array with information on each array item.
69
     *
70
     * The array index is 1-based and contains the following information on each array item:
71
     * - 'start' : The stack pointer to the first token in the array item.
72
     * - 'end'   : The stack pointer to the first token in the array item.
73
     * - 'raw'   : A string with the contents of all tokens between `start` and `end`.
74
     * - 'clean' : Same as `raw`, but all comment tokens have been stripped out.
75
     *
76
     * @since 1.0.0
77
     *
78
     * @var array
79
     */
80
    protected $arrayItems;
81

82
    /**
83
     * How many items are in the array.
84
     *
85
     * @since 1.0.0
86
     *
87
     * @var int
88
     */
89
    protected $itemCount = 0;
90

91
    /**
92
     * Whether or not the array is single line.
93
     *
94
     * @since 1.0.0
95
     *
96
     * @var bool
97
     */
98
    protected $singleLine;
99

100
    /**
101
     * List of tokens which can safely be used with an eval() expression.
102
     *
103
     * @since 1.0.0
104
     *
105
     * @var array
106
     */
107
    private $acceptedTokens = [
108
        \T_NULL                     => \T_NULL,
109
        \T_TRUE                     => \T_TRUE,
110
        \T_FALSE                    => \T_FALSE,
111
        \T_LNUMBER                  => \T_LNUMBER,
112
        \T_DNUMBER                  => \T_DNUMBER,
113
        \T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
114
        \T_STRING_CONCAT            => \T_STRING_CONCAT,
115
        \T_INLINE_THEN              => \T_INLINE_THEN,
116
        \T_INLINE_ELSE              => \T_INLINE_ELSE,
117
        \T_BOOLEAN_NOT              => \T_BOOLEAN_NOT,
118
    ];
119

120
    /**
121
     * Set up this class.
122
     *
123
     * @since 1.0.0
124
     *
125
     * @codeCoverageIgnore
126
     *
127
     * @return void
128
     */
129
    final public function __construct()
130
    {
131
        // Enhance the list of accepted tokens.
132
        $this->acceptedTokens += BCTokens::assignmentTokens();
133
        $this->acceptedTokens += BCTokens::comparisonTokens();
134
        $this->acceptedTokens += BCTokens::arithmeticTokens();
135
        $this->acceptedTokens += BCTokens::operators();
136
        $this->acceptedTokens += BCTokens::booleanOperators();
137
        $this->acceptedTokens += BCTokens::castTokens();
138
        $this->acceptedTokens += BCTokens::bracketTokens();
139
        $this->acceptedTokens += BCTokens::heredocTokens();
140
    }
141

142
    /**
143
     * Returns an array of tokens this test wants to listen for.
144
     *
145
     * @since 1.0.0
146
     *
147
     * @codeCoverageIgnore
148
     *
149
     * @return array
150
     */
151
    public function register()
152
    {
153
        return [
154
            \T_ARRAY,
155
            \T_OPEN_SHORT_ARRAY,
156
            \T_OPEN_SQUARE_BRACKET,
157
        ];
158
    }
159

160
    /**
161
     * Processes this test when one of its tokens is encountered.
162
     *
163
     * This method fills the properties with relevant information for examining the array
164
     * and then passes off to the `processArray()` method.
165
     *
166
     * @since 1.0.0
167
     *
168
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
169
     *                                               token was found.
170
     * @param int                         $stackPtr  The position in the PHP_CodeSniffer
171
     *                                               file's token stack where the token
172
     *                                               was found.
173
     *
174
     * @return void
175
     */
176
    final public function process(File $phpcsFile, $stackPtr)
177
    {
178
        try {
179
            $this->arrayItems = PassedParameters::getParameters($phpcsFile, $stackPtr);
44×
180
        } catch (RuntimeException $e) {
24×
181
            // Parse error, short list, real square open bracket or incorrectly tokenized short array token.
182
            return;
4×
183
        }
184

185
        $this->stackPtr    = $stackPtr;
40×
186
        $this->tokens      = $phpcsFile->getTokens();
40×
187
        $openClose         = Arrays::getOpenClose($phpcsFile, $stackPtr, true);
40×
188
        $this->arrayOpener = $openClose['opener'];
40×
189
        $this->arrayCloser = $openClose['closer'];
40×
190
        $this->itemCount   = \count($this->arrayItems);
40×
191

192
        $this->singleLine = true;
40×
193
        if ($this->tokens[$openClose['opener']]['line'] !== $this->tokens[$openClose['closer']]['line']) {
40×
194
            $this->singleLine = false;
8×
195
        }
196

197
        $this->processArray($phpcsFile);
40×
198

199
        // Reset select properties between calls to this sniff to lower memory usage.
200
        unset($this->tokens, $this->arrayItems);
40×
201
    }
40×
202

203
    /**
204
     * Process every part of the array declaration.
205
     *
206
     * This contains the default logic for the sniff, but can be overloaded in a concrete child class
207
     * if needed.
208
     *
209
     * @since 1.0.0
210
     *
211
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
212
     *                                               token was found.
213
     *
214
     * @return void
215
     */
216
    public function processArray(File $phpcsFile)
217
    {
218
        if ($this->processOpenClose($phpcsFile, $this->arrayOpener, $this->arrayCloser) === true) {
40×
219
            return;
4×
220
        }
221

222
        if ($this->itemCount === 0) {
36×
223
            return;
4×
224
        }
225

226
        foreach ($this->arrayItems as $itemNr => $arrayItem) {
32×
227
            $arrowPtr = Arrays::getDoubleArrowPtr($phpcsFile, $arrayItem['start'], $arrayItem['end']);
32×
228

229
            if ($arrowPtr !== false) {
32×
230
                if ($this->processKey($phpcsFile, $arrayItem['start'], ($arrowPtr - 1), $itemNr) === true) {
16×
231
                    return;
4×
232
                }
233

234
                if ($this->processArrow($phpcsFile, $arrowPtr, $itemNr) === true) {
12×
235
                    return;
4×
236
                }
237

238
                if ($this->processValue($phpcsFile, ($arrowPtr + 1), $arrayItem['end'], $itemNr) === true) {
8×
239
                    return;
6×
240
                }
241
            } else {
242
                if ($this->processNoKey($phpcsFile, $arrayItem['start'], $itemNr) === true) {
28×
243
                    return;
4×
244
                }
245

246
                if ($this->processValue($phpcsFile, $arrayItem['start'], $arrayItem['end'], $itemNr) === true) {
24×
247
                    return;
4×
248
                }
249
            }
250

251
            $commaPtr = ($arrayItem['end'] + 1);
24×
252
            if ($itemNr < $this->itemCount || $this->tokens[$commaPtr]['code'] === \T_COMMA) {
24×
253
                if ($this->processComma($phpcsFile, $commaPtr, $itemNr) === true) {
24×
254
                    return;
4×
255
                }
256
            }
257
        }
258
    }
8×
259

260
    /**
261
     * Process the array opener and closer.
262
     *
263
     * Optional method to be implemented in concrete child classes.
264
     *
265
     * @since 1.0.0
266
     *
267
     * @codeCoverageIgnore
268
     *
269
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
270
     *                                               token was found.
271
     * @param int                         $openPtr   The position of the array opener token in the token stack.
272
     * @param int                         $closePtr  The position of the array closer token in the token stack.
273
     *
274
     * @return true|void Returning `true` will short-circuit the sniff and stop processing.
275
     */
276
    public function processOpenClose(File $phpcsFile, $openPtr, $closePtr)
277
    {
278
    }
279

280
    /**
281
     * Process the tokens in an array key.
282
     *
283
     * Optional method to be implemented in concrete child classes.
284
     *
285
     * The $startPtr and $endPtr do not discount whitespace or comments, but are all inclusive to
286
     * allow examining all tokens in an array key.
287
     *
288
     * @since 1.0.0
289
     *
290
     * @codeCoverageIgnore
291
     *
292
     * @see \PHPCSUtils\AbstractSniffs\AbstractArrayDeclarationSniff::getActualArrayKey() Optional helper function.
293
     *
294
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
295
     *                                               token was found.
296
     * @param int                         $startPtr  The stack pointer to the first token in the "key" part of
297
     *                                               an array item.
298
     * @param int                         $endPtr    The stack pointer to the last token in the "key" part of
299
     *                                               an array item.
300
     * @param int                         $itemNr    Which item in the array is being handled.
301
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
302
     *
303
     * @return true|void Returning `true` will short-circuit the array item loop and stop processing.
304
     */
305
    public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr)
306
    {
307
    }
308

309
    /**
310
     * Process an array item without an array key.
311
     *
312
     * Optional method to be implemented in concrete child classes.
313
     *
314
     * @since 1.0.0
315
     *
316
     * @codeCoverageIgnore
317
     *
318
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
319
     *                                               token was found.
320
     * @param int                         $startPtr  The stack pointer to the first token in the array item,
321
     *                                               which in this case will be the first token of the array
322
     *                                               value part of the array item.
323
     * @param int                         $itemNr    Which item in the array is being handled.
324
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
325
     *
326
     * @return true|void Returning `true` will short-circuit the array item loop and stop processing.
327
     */
328
    public function processNoKey(File $phpcsFile, $startPtr, $itemNr)
329
    {
330
    }
331

332
    /**
333
     * Process the double arrow.
334
     *
335
     * Optional method to be implemented in concrete child classes.
336
     *
337
     * @since 1.0.0
338
     *
339
     * @codeCoverageIgnore
340
     *
341
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
342
     *                                               token was found.
343
     * @param int                         $arrowPtr  The stack pointer to the double arrow for the array item.
344
     * @param int                         $itemNr    Which item in the array is being handled.
345
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
346
     *
347
     * @return true|void Returning `true` will short-circuit the array item loop and stop processing.
348
     */
349
    public function processArrow(File $phpcsFile, $arrowPtr, $itemNr)
350
    {
351
    }
352

353
    /**
354
     * Process the tokens in an array value.
355
     *
356
     * Optional method to be implemented in concrete child classes.
357
     *
358
     * The $startPtr and $endPtr do not discount whitespace or comments, but are all inclusive to
359
     * allow examining all tokens in an array value.
360
     *
361
     * @since 1.0.0
362
     *
363
     * @codeCoverageIgnore
364
     *
365
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
366
     *                                               token was found.
367
     * @param int                         $startPtr  The stack pointer to the first token in the "value" part of
368
     *                                               an array item.
369
     * @param int                         $endPtr    The stack pointer to the last token in the "value" part of
370
     *                                               an array item.
371
     * @param int                         $itemNr    Which item in the array is being handled.
372
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
373
     *
374
     * @return true|void Returning `true` will short-circuit the array item loop and stop processing.
375
     */
376
    public function processValue(File $phpcsFile, $startPtr, $endPtr, $itemNr)
377
    {
378
    }
379

380
    /**
381
     * Process the comma after an array item.
382
     *
383
     * Optional method to be implemented in concrete child classes.
384
     *
385
     * @since 1.0.0
386
     *
387
     * @codeCoverageIgnore
388
     *
389
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
390
     *                                               token was found.
391
     * @param int                         $commaPtr  The stack pointer to the comma.
392
     * @param int                         $itemNr    Which item in the array is being handled.
393
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
394
     *
395
     * @return true|void Returning `true` will short-circuit the array item loop and stop processing.
396
     */
397
    public function processComma(File $phpcsFile, $commaPtr, $itemNr)
398
    {
399
    }
400

401
    /**
402
     * Determine what the actual array key would be.
403
     *
404
     * Optional helper function for processsing array keys in the processKey() function.
405
     *
406
     * @since 1.0.0
407
     *
408
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
409
     *                                               token was found.
410
     * @param int                         $startPtr  The stack pointer to the first token in the "key" part of
411
     *                                               an array item.
412
     * @param int                         $endPtr    The stack pointer to the last token in the "key" part of
413
     *                                               an array item.
414
     *
415
     * @return string|int|void The string or integer array key or void if the array key could not
416
     *                         reliably be determined.
417
     */
418
    public function getActualArrayKey(File $phpcsFile, $startPtr, $endPtr)
419
    {
420
        /*
421
         * Determine the value of the key.
422
         */
423
        $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true);
24×
424
        $lastNonEmpty  = $phpcsFile->findPrevious(Tokens::$emptyTokens, $endPtr, null, true);
24×
425

426
        $content = '';
24×
427

428
        for ($i = $firstNonEmpty; $i <= $lastNonEmpty; $i++) {
24×
429
            if (isset(Tokens::$commentTokens[$this->tokens[$i]['code']]) === true) {
24×
430
                continue;
4×
431
            }
432

433
            if ($this->tokens[$i]['code'] === \T_WHITESPACE) {
24×
434
                $content .= ' ';
20×
435
                continue;
20×
436
            }
437

438
            if (isset($this->acceptedTokens[$this->tokens[$i]['code']]) === false) {
24×
439
                // This is not a key we can evaluate. Might be a variable or constant.
440
                return;
4×
441
            }
442

443
            // Take PHP 7.4 numeric literal separators into account.
444
            if ($this->tokens[$i]['code'] === \T_LNUMBER || $this->tokens[$i]['code'] === \T_DNUMBER) {
24×
445
                try {
446
                    $number   = Numbers::getCompleteNumber($phpcsFile, $i);
12×
447
                    $content .= $number['content'];
12×
448
                    $i        = $number['last_token'];
12×
449
                } catch (RuntimeException $e) {
6×
450
                    // This must be PHP 3.5.3 with the broken backfill. Let's presume it's a ordinary number.
451
                    // If it's not, the sniff will bow out on the following T_STRING anyway if the
452
                    // backfill was broken.
453
                    $content .= \str_replace('_', '', $this->tokens[$i]['content']);
!
454
                }
455
                continue;
12×
456
            }
457

458
            // Account for heredoc with vars.
459
            if ($this->tokens[$i]['code'] === \T_START_HEREDOC) {
24×
460
                $text = TextStrings::getCompleteTextString($phpcsFile, $i);
8×
461

462
                // Check if there's a variable in the heredoc.
463
                if (\preg_match('`(?<![\\\\])\$`', $text) === 1) {
8×
464
                    return;
4×
465
                }
466

467
                for ($j = $i; $j <= $this->tokens[$i]['scope_closer']; $j++) {
4×
468
                    $content .= $this->tokens[$j]['content'];
4×
469
                }
470

471
                $i = $this->tokens[$i]['scope_closer'];
4×
472
                continue;
4×
473
            }
474

475
            $content .= $this->tokens[$i]['content'];
20×
476
        }
477

478
        // The PHP_EOL is to prevent getting parse errors when the key is a heredoc/nowdoc.
479
        $key = eval('return ' . $content . ';' . \PHP_EOL);
20×
480

481
        /*
482
         * Ok, so now we know the base value of the key, let's determine whether it is
483
         * an acceptable index key for an array and if not, what it would turn into.
484
         */
485

486
        $integerKey = false;
20×
487

20×
488
        switch (\gettype($key)) {
489
            case 'NULL':
4×
490
                // An array key of `null` will become an empty string.
491
                return '';
20×
492

8×
493
            case 'boolean':
494
                return ($key === true) ? 1 : 0;
20×
495

12×
496
            case 'integer':
497
                return $key;
20×
498

12×
499
            case 'double':
500
                return (int) $key; // Will automatically cut off the decimal part.
20×
501

20×
502
            case 'string':
12×
503
                if (Numbers::isDecimalInt($key) === true) {
504
                    return (int) $key;
505
                }
8×
506

507
                return $key;
508

509
            default:
510
                /*
511
                 * Shouldn't be possible. Either way, if it's not one of the above types,
UNCOV
512
                 * this is not a key we can handle.
!
513
                 */
514
                return;
515
        }
516
    }
517
}
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