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

PHPCSStandards / PHP_CodeSniffer / 12120287982

02 Dec 2024 01:23PM UTC coverage: 77.811% (+0.1%) from 77.716%
12120287982

Pull #748

github

web-flow
Merge c149b391d into daf63d979
Pull Request #748: Squiz/ArrayDeclaration: fixes false positive when handling short lists inside `foreach`

15 of 15 new or added lines in 1 file covered. (100.0%)

3 existing lines in 1 file now uncovered.

24288 of 31214 relevant lines covered (77.81%)

65.16 hits per line

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

99.54
/src/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php
1
<?php
2
/**
3
 * Ensures that arrays conform to the array coding standard.
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/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Standards\Squiz\Sniffs\Arrays;
11

12
use PHP_CodeSniffer\Files\File;
13
use PHP_CodeSniffer\Sniffs\Sniff;
14
use PHP_CodeSniffer\Util\Tokens;
15

16
class ArrayDeclarationSniff implements Sniff
17
{
18

19

20
    /**
21
     * Returns an array of tokens this test wants to listen for.
22
     *
23
     * @return array<int|string>
24
     */
25
    public function register()
3✔
26
    {
27
        return [
1✔
28
            T_ARRAY,
3✔
29
            T_OPEN_SHORT_ARRAY,
3✔
30
        ];
2✔
31

32
    }//end register()
33

34

35
    /**
36
     * Processes this sniff, when one of its tokens is encountered.
37
     *
38
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked.
39
     * @param int                         $stackPtr  The position of the current token in
40
     *                                               the stack passed in $tokens.
41
     *
42
     * @return void
43
     */
44
    public function process(File $phpcsFile, $stackPtr)
3✔
45
    {
46
        $tokens = $phpcsFile->getTokens();
3✔
47

48
        // Prevent acting on short lists inside a foreach (see
49
        // https://github.com/PHPCSStandards/PHP_CodeSniffer/issues/527).
50
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY
3✔
51
            && isset($tokens[$stackPtr]['nested_parenthesis']) === true
3✔
52
        ) {
1✔
53
            $nestedParens          = $tokens[$stackPtr]['nested_parenthesis'];
3✔
54
            $lastParenthesisCloser = end($nestedParens);
3✔
55
            $lastParenthesisOpener = key($tokens[$stackPtr]['nested_parenthesis']);
3✔
56
            reset($tokens[$stackPtr]['nested_parenthesis']);
3✔
57

58
            if (isset($tokens[$lastParenthesisCloser]['parenthesis_owner']) === true
3✔
59
                && $tokens[$tokens[$lastParenthesisCloser]['parenthesis_owner']]['code'] === T_FOREACH
3✔
60
            ) {
1✔
61
                $asKeyword = $phpcsFile->findNext(T_AS, ($lastParenthesisOpener + 1), $lastParenthesisCloser);
3✔
62

63
                if ($asKeyword !== false && $asKeyword < $stackPtr) {
3✔
64
                    return;
3✔
65
                }
66
            }
1✔
67
        }
1✔
68

69
        if ($tokens[$stackPtr]['code'] === T_ARRAY) {
3✔
70
            $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'no');
3✔
71

72
            // Array keyword should be lower case.
73
            if ($tokens[$stackPtr]['content'] !== strtolower($tokens[$stackPtr]['content'])) {
3✔
74
                if ($tokens[$stackPtr]['content'] === strtoupper($tokens[$stackPtr]['content'])) {
3✔
75
                    $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'upper');
3✔
76
                } else {
1✔
77
                    $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'mixed');
3✔
78
                }
79

80
                $error = 'Array keyword should be lower case; expected "array" but found "%s"';
3✔
81
                $data  = [$tokens[$stackPtr]['content']];
3✔
82
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NotLowerCase', $data);
3✔
83
                if ($fix === true) {
3✔
84
                    $phpcsFile->fixer->replaceToken($stackPtr, 'array');
3✔
85
                }
1✔
86
            } else {
1✔
87
                $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'lower');
3✔
88
            }
89

90
            $arrayStart = $tokens[$stackPtr]['parenthesis_opener'];
3✔
91
            if (isset($tokens[$arrayStart]['parenthesis_closer']) === false) {
3✔
92
                return;
3✔
93
            }
94

95
            $arrayEnd = $tokens[$arrayStart]['parenthesis_closer'];
3✔
96

97
            if ($arrayStart !== ($stackPtr + 1)) {
3✔
98
                $error = 'There must be no space between the "array" keyword and the opening parenthesis';
3✔
99

100
                $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), $arrayStart, true);
3✔
101
                if (isset(Tokens::$commentTokens[$tokens[$next]['code']]) === true) {
3✔
102
                    // We don't have anywhere to put the comment, so don't attempt to fix it.
103
                    $phpcsFile->addError($error, $stackPtr, 'SpaceAfterKeyword');
3✔
104
                } else {
1✔
105
                    $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword');
3✔
106
                    if ($fix === true) {
3✔
107
                        $phpcsFile->fixer->beginChangeset();
3✔
108
                        for ($i = ($stackPtr + 1); $i < $arrayStart; $i++) {
3✔
109
                            $phpcsFile->fixer->replaceToken($i, '');
3✔
110
                        }
1✔
111

112
                        $phpcsFile->fixer->endChangeset();
3✔
113
                    }
1✔
114
                }
115
            }
1✔
116
        } else {
1✔
117
            $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'yes');
3✔
118
            $arrayStart = $stackPtr;
3✔
119
            $arrayEnd   = $tokens[$stackPtr]['bracket_closer'];
3✔
120
        }//end if
121

122
        // Check for empty arrays.
123
        $content = $phpcsFile->findNext(T_WHITESPACE, ($arrayStart + 1), ($arrayEnd + 1), true);
3✔
124
        if ($content === $arrayEnd) {
3✔
125
            // Empty array, but if the brackets aren't together, there's a problem.
126
            if (($arrayEnd - $arrayStart) !== 1) {
3✔
127
                $error = 'Empty array declaration must have no space between the parentheses';
3✔
128
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceInEmptyArray');
3✔
129

130
                if ($fix === true) {
3✔
131
                    $phpcsFile->fixer->beginChangeset();
3✔
132
                    for ($i = ($arrayStart + 1); $i < $arrayEnd; $i++) {
3✔
133
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
134
                    }
1✔
135

136
                    $phpcsFile->fixer->endChangeset();
3✔
137
                }
1✔
138
            }
1✔
139

140
            // We can return here because there is nothing else to check. All code
141
            // below can assume that the array is not empty.
142
            return;
3✔
143
        }
144

145
        if ($tokens[$arrayStart]['line'] === $tokens[$arrayEnd]['line']) {
3✔
146
            $this->processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd);
3✔
147
        } else {
1✔
148
            $this->processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd);
3✔
149
        }
150

151
    }//end process()
2✔
152

153

154
    /**
155
     * Processes a single-line array definition.
156
     *
157
     * @param \PHP_CodeSniffer\Files\File $phpcsFile  The current file being checked.
158
     * @param int                         $stackPtr   The position of the current token
159
     *                                                in the stack passed in $tokens.
160
     * @param int                         $arrayStart The token that starts the array definition.
161
     * @param int                         $arrayEnd   The token that ends the array definition.
162
     *
163
     * @return void
164
     */
165
    public function processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd)
3✔
166
    {
167
        $tokens = $phpcsFile->getTokens();
3✔
168

169
        // Check if there are multiple values. If so, then it has to be multiple lines
170
        // unless it is contained inside a function call or condition.
171
        $valueCount = 0;
3✔
172
        $commas     = [];
3✔
173
        for ($i = ($arrayStart + 1); $i < $arrayEnd; $i++) {
3✔
174
            // Skip bracketed statements, like function calls.
175
            if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
3✔
176
                $i = $tokens[$i]['parenthesis_closer'];
3✔
177
                continue;
3✔
178
            }
179

180
            if ($tokens[$i]['code'] === T_COMMA) {
3✔
181
                // Before counting this comma, make sure we are not
182
                // at the end of the array.
183
                $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), $arrayEnd, true);
3✔
184
                if ($next !== false) {
3✔
185
                    $valueCount++;
3✔
186
                    $commas[] = $i;
3✔
187
                } else {
1✔
188
                    // There is a comma at the end of a single line array.
189
                    $error = 'Comma not allowed after last value in single-line array declaration';
3✔
190
                    $fix   = $phpcsFile->addFixableError($error, $i, 'CommaAfterLast');
3✔
191
                    if ($fix === true) {
3✔
192
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
193
                    }
1✔
194
                }
195
            }
1✔
196
        }//end for
1✔
197

198
        // Now check each of the double arrows (if any).
199
        $nextArrow = $arrayStart;
3✔
200
        while (($nextArrow = $phpcsFile->findNext(T_DOUBLE_ARROW, ($nextArrow + 1), $arrayEnd)) !== false) {
3✔
201
            if ($tokens[($nextArrow - 1)]['code'] !== T_WHITESPACE) {
3✔
202
                $content = $tokens[($nextArrow - 1)]['content'];
3✔
203
                $error   = 'Expected 1 space between "%s" and double arrow; 0 found';
3✔
204
                $data    = [$content];
3✔
205
                $fix     = $phpcsFile->addFixableError($error, $nextArrow, 'NoSpaceBeforeDoubleArrow', $data);
3✔
206
                if ($fix === true) {
3✔
207
                    $phpcsFile->fixer->addContentBefore($nextArrow, ' ');
3✔
208
                }
1✔
209
            } else {
1✔
210
                $spaceLength = $tokens[($nextArrow - 1)]['length'];
3✔
211
                if ($spaceLength !== 1) {
3✔
212
                    $content = $tokens[($nextArrow - 2)]['content'];
3✔
213
                    $error   = 'Expected 1 space between "%s" and double arrow; %s found';
3✔
214
                    $data    = [
1✔
215
                        $content,
3✔
216
                        $spaceLength,
3✔
217
                    ];
2✔
218

219
                    $fix = $phpcsFile->addFixableError($error, $nextArrow, 'SpaceBeforeDoubleArrow', $data);
3✔
220
                    if ($fix === true) {
3✔
221
                        $phpcsFile->fixer->replaceToken(($nextArrow - 1), ' ');
3✔
222
                    }
1✔
223
                }
1✔
224
            }//end if
225

226
            if ($tokens[($nextArrow + 1)]['code'] !== T_WHITESPACE) {
3✔
227
                $content = $tokens[($nextArrow + 1)]['content'];
3✔
228
                $error   = 'Expected 1 space between double arrow and "%s"; 0 found';
3✔
229
                $data    = [$content];
3✔
230
                $fix     = $phpcsFile->addFixableError($error, $nextArrow, 'NoSpaceAfterDoubleArrow', $data);
3✔
231
                if ($fix === true) {
3✔
232
                    $phpcsFile->fixer->addContent($nextArrow, ' ');
3✔
233
                }
1✔
234
            } else {
1✔
235
                $spaceLength = $tokens[($nextArrow + 1)]['length'];
3✔
236
                if ($spaceLength !== 1) {
3✔
237
                    $content = $tokens[($nextArrow + 2)]['content'];
3✔
238
                    $error   = 'Expected 1 space between double arrow and "%s"; %s found';
3✔
239
                    $data    = [
1✔
240
                        $content,
3✔
241
                        $spaceLength,
3✔
242
                    ];
2✔
243

244
                    $fix = $phpcsFile->addFixableError($error, $nextArrow, 'SpaceAfterDoubleArrow', $data);
3✔
245
                    if ($fix === true) {
3✔
246
                        $phpcsFile->fixer->replaceToken(($nextArrow + 1), ' ');
3✔
247
                    }
1✔
248
                }
1✔
249
            }//end if
250
        }//end while
1✔
251

252
        if ($valueCount > 0) {
3✔
253
            $nestedParenthesis = false;
3✔
254
            if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
3✔
255
                $nested            = $tokens[$stackPtr]['nested_parenthesis'];
3✔
256
                $nestedParenthesis = array_pop($nested);
3✔
257
            }
1✔
258

259
            if ($nestedParenthesis === false
2✔
260
                || $tokens[$nestedParenthesis]['line'] !== $tokens[$stackPtr]['line']
3✔
261
            ) {
1✔
262
                $error = 'Array with multiple values cannot be declared on a single line';
3✔
263
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SingleLineNotAllowed');
3✔
264
                if ($fix === true) {
3✔
265
                    $phpcsFile->fixer->beginChangeset();
3✔
266
                    $phpcsFile->fixer->addNewline($arrayStart);
3✔
267

268
                    if ($tokens[($arrayEnd - 1)]['code'] === T_WHITESPACE) {
3✔
269
                        $phpcsFile->fixer->replaceToken(($arrayEnd - 1), $phpcsFile->eolChar);
3✔
270
                    } else {
1✔
271
                        $phpcsFile->fixer->addNewlineBefore($arrayEnd);
3✔
272
                    }
273

274
                    $phpcsFile->fixer->endChangeset();
3✔
275
                }
1✔
276

277
                return;
3✔
278
            }
279

280
            // We have a multiple value array that is inside a condition or
281
            // function. Check its spacing is correct.
282
            foreach ($commas as $comma) {
3✔
283
                if ($tokens[($comma + 1)]['code'] !== T_WHITESPACE) {
3✔
284
                    $content = $tokens[($comma + 1)]['content'];
3✔
285
                    $error   = 'Expected 1 space between comma and "%s"; 0 found';
3✔
286
                    $data    = [$content];
3✔
287
                    $fix     = $phpcsFile->addFixableError($error, $comma, 'NoSpaceAfterComma', $data);
3✔
288
                    if ($fix === true) {
3✔
289
                        $phpcsFile->fixer->addContent($comma, ' ');
3✔
290
                    }
1✔
291
                } else {
1✔
292
                    $spaceLength = $tokens[($comma + 1)]['length'];
3✔
293
                    if ($spaceLength !== 1) {
3✔
294
                        $content = $tokens[($comma + 2)]['content'];
3✔
295
                        $error   = 'Expected 1 space between comma and "%s"; %s found';
3✔
296
                        $data    = [
1✔
297
                            $content,
3✔
298
                            $spaceLength,
3✔
299
                        ];
2✔
300

301
                        $fix = $phpcsFile->addFixableError($error, $comma, 'SpaceAfterComma', $data);
3✔
302
                        if ($fix === true) {
3✔
303
                            $phpcsFile->fixer->replaceToken(($comma + 1), ' ');
3✔
304
                        }
1✔
305
                    }
1✔
306
                }//end if
307

308
                if ($tokens[($comma - 1)]['code'] === T_WHITESPACE) {
3✔
309
                    $content     = $tokens[($comma - 2)]['content'];
3✔
310
                    $spaceLength = $tokens[($comma - 1)]['length'];
3✔
311
                    $error       = 'Expected 0 spaces between "%s" and comma; %s found';
3✔
312
                    $data        = [
1✔
313
                        $content,
3✔
314
                        $spaceLength,
3✔
315
                    ];
2✔
316

317
                    $fix = $phpcsFile->addFixableError($error, $comma, 'SpaceBeforeComma', $data);
3✔
318
                    if ($fix === true) {
3✔
319
                        $phpcsFile->fixer->replaceToken(($comma - 1), '');
3✔
320
                    }
1✔
321
                }
1✔
322
            }//end foreach
1✔
323
        }//end if
1✔
324

325
    }//end processSingleLineArray()
2✔
326

327

328
    /**
329
     * Processes a multi-line array definition.
330
     *
331
     * @param \PHP_CodeSniffer\Files\File $phpcsFile  The current file being checked.
332
     * @param int                         $stackPtr   The position of the current token
333
     *                                                in the stack passed in $tokens.
334
     * @param int                         $arrayStart The token that starts the array definition.
335
     * @param int                         $arrayEnd   The token that ends the array definition.
336
     *
337
     * @return void
338
     */
339
    public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd)
3✔
340
    {
341
        $tokens       = $phpcsFile->getTokens();
3✔
342
        $keywordStart = $tokens[$stackPtr]['column'];
3✔
343

344
        // Check the closing bracket is on a new line.
345
        $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($arrayEnd - 1), $arrayStart, true);
3✔
346
        if ($tokens[$lastContent]['line'] === $tokens[$arrayEnd]['line']) {
3✔
347
            $error = 'Closing parenthesis of array declaration must be on a new line';
3✔
348
            $fix   = $phpcsFile->addFixableError($error, $arrayEnd, 'CloseBraceNewLine');
3✔
349
            if ($fix === true) {
3✔
350
                $phpcsFile->fixer->addNewlineBefore($arrayEnd);
3✔
351
            }
1✔
352
        } else if ($tokens[$arrayEnd]['column'] !== $keywordStart) {
3✔
353
            // Check the closing bracket is lined up under the "a" in array.
354
            $expected       = ($keywordStart - 1);
3✔
355
            $found          = ($tokens[$arrayEnd]['column'] - 1);
3✔
356
            $pluralizeSpace = 's';
3✔
357
            if ($expected === 1) {
3✔
358
                $pluralizeSpace = '';
3✔
359
            }
1✔
360

361
            $error = 'Closing parenthesis not aligned correctly; expected %s space%s but found %s';
3✔
362
            $data  = [
1✔
363
                $expected,
3✔
364
                $pluralizeSpace,
3✔
365
                $found,
3✔
366
            ];
2✔
367

368
            $fix = $phpcsFile->addFixableError($error, $arrayEnd, 'CloseBraceNotAligned', $data);
3✔
369
            if ($fix === true) {
3✔
370
                if ($found === 0) {
3✔
371
                    $phpcsFile->fixer->addContent(($arrayEnd - 1), str_repeat(' ', $expected));
3✔
372
                } else {
1✔
373
                    $phpcsFile->fixer->replaceToken(($arrayEnd - 1), str_repeat(' ', $expected));
3✔
374
                }
375
            }
1✔
376
        }//end if
1✔
377

378
        $keyUsed    = false;
3✔
379
        $singleUsed = false;
3✔
380
        $indices    = [];
3✔
381
        $maxLength  = 0;
3✔
382

383
        if ($tokens[$stackPtr]['code'] === T_ARRAY) {
3✔
384
            $lastToken = $tokens[$stackPtr]['parenthesis_opener'];
3✔
385
        } else {
1✔
386
            $lastToken = $stackPtr;
3✔
387
        }
388

389
        // Find all the double arrows that reside in this scope.
390
        for ($nextToken = ($stackPtr + 1); $nextToken < $arrayEnd; $nextToken++) {
3✔
391
            // Skip bracketed statements, like function calls.
392
            if ($tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS
3✔
393
                && (isset($tokens[$nextToken]['parenthesis_owner']) === false
3✔
394
                || $tokens[$nextToken]['parenthesis_owner'] !== $stackPtr)
3✔
395
            ) {
1✔
396
                $nextToken = $tokens[$nextToken]['parenthesis_closer'];
3✔
397
                continue;
3✔
398
            }
399

400
            if ($tokens[$nextToken]['code'] === T_ARRAY
3✔
401
                || $tokens[$nextToken]['code'] === T_OPEN_SHORT_ARRAY
3✔
402
                || $tokens[$nextToken]['code'] === T_CLOSURE
3✔
403
                || $tokens[$nextToken]['code'] === T_FN
3✔
404
                || $tokens[$nextToken]['code'] === T_MATCH
3✔
405
            ) {
1✔
406
                // Let subsequent calls of this test handle nested arrays.
407
                if ($tokens[$lastToken]['code'] !== T_DOUBLE_ARROW) {
3✔
408
                    $indices[] = ['value' => $nextToken];
3✔
409
                    $lastToken = $nextToken;
3✔
410
                }
1✔
411

412
                if ($tokens[$nextToken]['code'] === T_ARRAY) {
3✔
413
                    $nextToken = $tokens[$tokens[$nextToken]['parenthesis_opener']]['parenthesis_closer'];
3✔
414
                } else if ($tokens[$nextToken]['code'] === T_OPEN_SHORT_ARRAY) {
3✔
415
                    $nextToken = $tokens[$nextToken]['bracket_closer'];
3✔
416
                } else {
1✔
417
                    // T_CLOSURE.
418
                    $nextToken = $tokens[$nextToken]['scope_closer'];
3✔
419
                }
420

421
                $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($nextToken + 1), null, true);
3✔
422
                if ($tokens[$nextToken]['code'] !== T_COMMA) {
3✔
423
                    $nextToken--;
3✔
424
                } else {
1✔
425
                    $lastToken = $nextToken;
3✔
426
                }
427

428
                continue;
3✔
429
            }//end if
430

431
            if ($tokens[$nextToken]['code'] !== T_DOUBLE_ARROW && $tokens[$nextToken]['code'] !== T_COMMA) {
3✔
432
                continue;
3✔
433
            }
434

435
            $currentEntry = [];
3✔
436

437
            if ($tokens[$nextToken]['code'] === T_COMMA) {
3✔
438
                $stackPtrCount = 0;
3✔
439
                if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
3✔
440
                    $stackPtrCount = count($tokens[$stackPtr]['nested_parenthesis']);
3✔
441
                }
1✔
442

443
                $commaCount = 0;
3✔
444
                if (isset($tokens[$nextToken]['nested_parenthesis']) === true) {
3✔
445
                    $commaCount = count($tokens[$nextToken]['nested_parenthesis']);
3✔
446
                    if ($tokens[$stackPtr]['code'] === T_ARRAY) {
3✔
447
                        // Remove parenthesis that are used to define the array.
448
                        $commaCount--;
3✔
449
                    }
1✔
450
                }
1✔
451

452
                if ($commaCount > $stackPtrCount) {
3✔
453
                    // This comma is inside more parenthesis than the ARRAY keyword,
454
                    // then there it is actually a comma used to separate arguments
455
                    // in a function call.
UNCOV
456
                    continue;
×
457
                }
458

459
                if ($keyUsed === true && $tokens[$lastToken]['code'] === T_COMMA) {
3✔
460
                    $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($lastToken + 1), null, true);
3✔
461
                    // Allow for PHP 7.4+ array unpacking within an array declaration.
462
                    if ($tokens[$nextToken]['code'] !== T_ELLIPSIS) {
3✔
463
                        $error = 'No key specified for array entry; first entry specifies key';
3✔
464
                        $phpcsFile->addError($error, $nextToken, 'NoKeySpecified');
3✔
465
                        return;
3✔
466
                    }
467
                }
1✔
468

469
                if ($keyUsed === false) {
3✔
470
                    if ($tokens[($nextToken - 1)]['code'] === T_WHITESPACE) {
3✔
471
                        $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($nextToken - 1), null, true);
3✔
472
                        if (($tokens[$prev]['code'] !== T_END_HEREDOC
3✔
473
                            && $tokens[$prev]['code'] !== T_END_NOWDOC)
3✔
474
                            || $tokens[($nextToken - 1)]['line'] === $tokens[$nextToken]['line']
3✔
475
                        ) {
1✔
476
                            if ($tokens[($nextToken - 1)]['content'] === $phpcsFile->eolChar) {
3✔
477
                                $spaceLength = 'newline';
3✔
478
                            } else {
1✔
479
                                $spaceLength = $tokens[($nextToken - 1)]['length'];
3✔
480
                            }
481

482
                            $error = 'Expected 0 spaces before comma; %s found';
3✔
483
                            $data  = [$spaceLength];
3✔
484

485
                            // The error is only fixable if there is only whitespace between the tokens.
486
                            if ($prev === $phpcsFile->findPrevious(T_WHITESPACE, ($nextToken - 1), null, true)) {
3✔
487
                                $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpaceBeforeComma', $data);
3✔
488
                                if ($fix === true) {
3✔
489
                                    $phpcsFile->fixer->replaceToken(($nextToken - 1), '');
3✔
490
                                }
1✔
491
                            } else {
1✔
492
                                $phpcsFile->addError($error, $nextToken, 'SpaceBeforeComma', $data);
3✔
493
                            }
494
                        }
1✔
495
                    }//end if
1✔
496

497
                    $valueContent = $phpcsFile->findNext(
3✔
498
                        Tokens::$emptyTokens,
3✔
499
                        ($lastToken + 1),
3✔
500
                        $nextToken,
3✔
501
                        true
2✔
502
                    );
2✔
503

504
                    $indices[]          = ['value' => $valueContent];
3✔
505
                    $usesArrayUnpacking = $phpcsFile->findPrevious(
3✔
506
                        Tokens::$emptyTokens,
3✔
507
                        ($nextToken - 2),
3✔
508
                        null,
3✔
509
                        true
2✔
510
                    );
2✔
511
                    if ($tokens[$usesArrayUnpacking]['code'] !== T_ELLIPSIS) {
3✔
512
                        // Don't decide if an array is key => value indexed or not when PHP 7.4+ array unpacking is used.
513
                        $singleUsed = true;
3✔
514
                    }
1✔
515
                }//end if
1✔
516

517
                $lastToken = $nextToken;
3✔
518
                continue;
3✔
519
            }//end if
520

521
            if ($tokens[$nextToken]['code'] === T_DOUBLE_ARROW) {
3✔
522
                if ($singleUsed === true) {
3✔
523
                    $error = 'Key specified for array entry; first entry has no key';
3✔
524
                    $phpcsFile->addError($error, $nextToken, 'KeySpecified');
3✔
525
                    return;
3✔
526
                }
527

528
                $currentEntry['arrow'] = $nextToken;
3✔
529
                $keyUsed = true;
3✔
530

531
                // Find the start of index that uses this double arrow.
532
                $indexEnd   = $phpcsFile->findPrevious(T_WHITESPACE, ($nextToken - 1), $arrayStart, true);
3✔
533
                $indexStart = $phpcsFile->findStartOfStatement($indexEnd);
3✔
534

535
                if ($indexStart === $indexEnd) {
3✔
536
                    $currentEntry['index']         = $indexEnd;
3✔
537
                    $currentEntry['index_content'] = $tokens[$indexEnd]['content'];
3✔
538
                    $currentEntry['index_length']  = $tokens[$indexEnd]['length'];
3✔
539
                } else {
1✔
540
                    $currentEntry['index']         = $indexStart;
3✔
541
                    $currentEntry['index_content'] = '';
3✔
542
                    $currentEntry['index_length']  = 0;
3✔
543
                    for ($i = $indexStart; $i <= $indexEnd; $i++) {
3✔
544
                        $currentEntry['index_content'] .= $tokens[$i]['content'];
3✔
545
                        $currentEntry['index_length']  += $tokens[$i]['length'];
3✔
546
                    }
1✔
547
                }
548

549
                if ($maxLength < $currentEntry['index_length']) {
3✔
550
                    $maxLength = $currentEntry['index_length'];
3✔
551
                }
1✔
552

553
                // Find the value of this index.
554
                $nextContent = $phpcsFile->findNext(
3✔
555
                    Tokens::$emptyTokens,
3✔
556
                    ($nextToken + 1),
3✔
557
                    $arrayEnd,
3✔
558
                    true
2✔
559
                );
2✔
560

561
                $currentEntry['value'] = $nextContent;
3✔
562
                $indices[] = $currentEntry;
3✔
563
                $lastToken = $nextToken;
3✔
564
            }//end if
1✔
565
        }//end for
1✔
566

567
        // Check for multi-line arrays that should be single-line.
568
        $singleValue = false;
3✔
569

570
        if (empty($indices) === true) {
3✔
571
            $singleValue = true;
3✔
572
        } else if (count($indices) === 1 && $tokens[$lastToken]['code'] === T_COMMA) {
3✔
573
            // There may be another array value without a comma.
574
            $exclude     = Tokens::$emptyTokens;
3✔
575
            $exclude[]   = T_COMMA;
3✔
576
            $nextContent = $phpcsFile->findNext($exclude, ($indices[0]['value'] + 1), $arrayEnd, true);
3✔
577
            if ($nextContent === false) {
3✔
578
                $singleValue = true;
3✔
579
            }
1✔
580
        }
1✔
581

582
        if ($singleValue === true) {
3✔
583
            // Before we complain, make sure the single value isn't a here/nowdoc.
584
            $next = $phpcsFile->findNext(Tokens::$heredocTokens, ($arrayStart + 1), ($arrayEnd - 1));
3✔
585
            if ($next === false) {
3✔
586
                // Array cannot be empty, so this is a multi-line array with
587
                // a single value. It should be defined on single line.
588
                $error     = 'Multi-line array contains a single value; use single-line array instead';
3✔
589
                $errorCode = 'MultiLineNotAllowed';
3✔
590

591
                $find    = Tokens::$phpcsCommentTokens;
3✔
592
                $find[]  = T_COMMENT;
3✔
593
                $comment = $phpcsFile->findNext($find, ($arrayStart + 1), $arrayEnd);
3✔
594
                if ($comment === false) {
3✔
595
                    $fix = $phpcsFile->addFixableError($error, $stackPtr, $errorCode);
3✔
596
                } else {
1✔
597
                    $fix = false;
3✔
598
                    $phpcsFile->addError($error, $stackPtr, $errorCode);
3✔
599
                }
600

601
                if ($fix === true) {
3✔
602
                    $phpcsFile->fixer->beginChangeset();
3✔
603
                    for ($i = ($arrayStart + 1); $i < $arrayEnd; $i++) {
3✔
604
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
605
                            break;
3✔
606
                        }
607

608
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
609
                    }
1✔
610

611
                    for ($i = ($arrayEnd - 1); $i > $arrayStart; $i--) {
3✔
612
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
613
                            break;
3✔
614
                        }
615

616
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
617
                    }
1✔
618

619
                    $phpcsFile->fixer->endChangeset();
3✔
620
                }
1✔
621

622
                return;
3✔
623
            }//end if
624
        }//end if
1✔
625

626
        /*
627
            This section checks for arrays that don't specify keys.
628

629
            Arrays such as:
630
               array(
631
                'aaa',
632
                'bbb',
633
                'd',
634
               );
635
        */
636

637
        if ($keyUsed === false && empty($indices) === false) {
3✔
638
            $count     = count($indices);
3✔
639
            $lastIndex = $indices[($count - 1)]['value'];
3✔
640

641
            $trailingContent = $phpcsFile->findPrevious(
3✔
642
                Tokens::$emptyTokens,
3✔
643
                ($arrayEnd - 1),
3✔
644
                $lastIndex,
3✔
645
                true
2✔
646
            );
2✔
647

648
            if ($tokens[$trailingContent]['code'] !== T_COMMA) {
3✔
649
                $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'no');
3✔
650
                $error = 'Comma required after last value in array declaration';
3✔
651
                $fix   = $phpcsFile->addFixableError($error, $trailingContent, 'NoCommaAfterLast');
3✔
652
                if ($fix === true) {
3✔
653
                    $phpcsFile->fixer->addContent($trailingContent, ',');
3✔
654
                }
1✔
655
            } else {
1✔
656
                $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'yes');
3✔
657
            }
658

659
            foreach ($indices as $valuePosition => $value) {
3✔
660
                if (empty($value['value']) === true) {
3✔
661
                    // Array was malformed and we couldn't figure out
662
                    // the array value correctly, so we have to ignore it.
663
                    // Other parts of this sniff will correct the error.
UNCOV
664
                    continue;
×
665
                }
666

667
                $valuePointer = $value['value'];
3✔
668

669
                $ignoreTokens  = [
1✔
670
                    T_WHITESPACE => T_WHITESPACE,
3✔
671
                    T_COMMA      => T_COMMA,
3✔
672
                ];
2✔
673
                $ignoreTokens += Tokens::$castTokens;
3✔
674

675
                if ($tokens[$valuePointer]['code'] === T_CLOSURE
3✔
676
                    || $tokens[$valuePointer]['code'] === T_FN
3✔
677
                ) {
1✔
678
                    // Check if the closure is static, if it is, override the value pointer as indices before skip static.
679
                    $staticPointer = $phpcsFile->findPrevious($ignoreTokens, ($valuePointer - 1), ($arrayStart + 1), true);
3✔
680
                    if ($staticPointer !== false && $tokens[$staticPointer]['code'] === T_STATIC) {
3✔
681
                        $valuePointer = $staticPointer;
3✔
682
                    }
1✔
683
                }
1✔
684

685
                $previous = $phpcsFile->findPrevious($ignoreTokens, ($valuePointer - 1), ($arrayStart + 1), true);
3✔
686
                if ($previous === false) {
3✔
687
                    $previous = $stackPtr;
3✔
688
                }
1✔
689

690
                $previousIsWhitespace = $tokens[($valuePointer - 1)]['code'] === T_WHITESPACE;
3✔
691
                if ($tokens[$previous]['line'] === $tokens[$valuePointer]['line']) {
3✔
692
                    $error = 'Each value in a multi-line array must be on a new line';
3✔
693
                    if ($valuePosition === 0) {
3✔
694
                        $error = 'The first value in a multi-value array must be on a new line';
3✔
695
                    }
1✔
696

697
                    $fix = $phpcsFile->addFixableError($error, $valuePointer, 'ValueNoNewline');
3✔
698
                    if ($fix === true) {
3✔
699
                        if ($previousIsWhitespace === true) {
3✔
700
                            $phpcsFile->fixer->replaceToken(($valuePointer - 1), $phpcsFile->eolChar);
3✔
701
                        } else {
1✔
702
                            $phpcsFile->fixer->addNewlineBefore($valuePointer);
3✔
703
                        }
704
                    }
1✔
705
                } else if ($previousIsWhitespace === true) {
3✔
706
                    $expected = $keywordStart;
3✔
707

708
                    $first          = $phpcsFile->findFirstOnLine(T_WHITESPACE, $valuePointer, true);
3✔
709
                    $found          = ($tokens[$first]['column'] - 1);
3✔
710
                    $pluralizeSpace = 's';
3✔
711
                    if ($expected === 1) {
3✔
712
                        $pluralizeSpace = '';
3✔
713
                    }
1✔
714

715
                    if ($found !== $expected) {
3✔
716
                        $error = 'Array value not aligned correctly; expected %s space%s but found %s';
3✔
717
                        $data  = [
1✔
718
                            $expected,
3✔
719
                            $pluralizeSpace,
3✔
720
                            $found,
3✔
721
                        ];
2✔
722

723
                        $fix = $phpcsFile->addFixableError($error, $first, 'ValueNotAligned', $data);
3✔
724
                        if ($fix === true) {
3✔
725
                            if ($found === 0) {
3✔
726
                                $phpcsFile->fixer->addContent(($first - 1), str_repeat(' ', $expected));
3✔
727
                            } else {
1✔
728
                                $phpcsFile->fixer->replaceToken(($first - 1), str_repeat(' ', $expected));
3✔
729
                            }
730
                        }
1✔
731
                    }
1✔
732
                }//end if
1✔
733
            }//end foreach
1✔
734
        }//end if
1✔
735

736
        /*
737
            Below the actual indentation of the array is checked.
738
            Errors will be thrown when a key is not aligned, when
739
            a double arrow is not aligned, and when a value is not
740
            aligned correctly.
741
            If an error is found in one of the above areas, then errors
742
            are not reported for the rest of the line to avoid reporting
743
            spaces and columns incorrectly. Often fixing the first
744
            problem will fix the other 2 anyway.
745

746
            For example:
747

748
            $a = array(
749
                  'index'  => '2',
750
                 );
751

752
            or
753

754
            $a = [
755
                  'index'  => '2',
756
                 ];
757

758
            In this array, the double arrow is indented too far, but this
759
            will also cause an error in the value's alignment. If the arrow were
760
            to be moved back one space however, then both errors would be fixed.
761
        */
762

763
        $indicesStart = ($keywordStart + 1);
3✔
764
        foreach ($indices as $valuePosition => $index) {
3✔
765
            $valuePointer = $index['value'];
3✔
766
            if ($valuePointer === false) {
3✔
767
                // Syntax error or live coding.
768
                continue;
3✔
769
            }
770

771
            if (isset($index['index']) === false) {
3✔
772
                // Array value only.
773
                continue;
3✔
774
            }
775

776
            $indexPointer = $index['index'];
3✔
777
            $indexLine    = $tokens[$indexPointer]['line'];
3✔
778

779
            $previous = $phpcsFile->findPrevious([T_WHITESPACE, T_COMMA], ($indexPointer - 1), ($arrayStart + 1), true);
3✔
780
            if ($previous === false) {
3✔
781
                $previous = $stackPtr;
3✔
782
            }
1✔
783

784
            if ($tokens[$previous]['line'] === $indexLine) {
3✔
785
                $error = 'Each index in a multi-line array must be on a new line';
3✔
786
                if ($valuePosition === 0) {
3✔
787
                    $error = 'The first index in a multi-value array must be on a new line';
3✔
788
                }
1✔
789

790
                $fix = $phpcsFile->addFixableError($error, $indexPointer, 'IndexNoNewline');
3✔
791
                if ($fix === true) {
3✔
792
                    if ($tokens[($indexPointer - 1)]['code'] === T_WHITESPACE) {
3✔
793
                        $phpcsFile->fixer->replaceToken(($indexPointer - 1), $phpcsFile->eolChar);
3✔
794
                    } else {
1✔
795
                        $phpcsFile->fixer->addNewlineBefore($indexPointer);
3✔
796
                    }
797
                }
1✔
798

799
                continue;
3✔
800
            }
801

802
            if ($tokens[$indexPointer]['column'] !== $indicesStart && ($indexPointer - 1) !== $arrayStart) {
3✔
803
                $expected       = ($indicesStart - 1);
3✔
804
                $found          = ($tokens[$indexPointer]['column'] - 1);
3✔
805
                $pluralizeSpace = 's';
3✔
806
                if ($expected === 1) {
3✔
807
                    $pluralizeSpace = '';
3✔
808
                }
1✔
809

810
                $error = 'Array key not aligned correctly; expected %s space%s but found %s';
3✔
811
                $data  = [
1✔
812
                    $expected,
3✔
813
                    $pluralizeSpace,
3✔
814
                    $found,
3✔
815
                ];
2✔
816

817
                $fix = $phpcsFile->addFixableError($error, $indexPointer, 'KeyNotAligned', $data);
3✔
818
                if ($fix === true) {
3✔
819
                    if ($found === 0 || $tokens[($indexPointer - 1)]['code'] !== T_WHITESPACE) {
3✔
820
                        $phpcsFile->fixer->addContent(($indexPointer - 1), str_repeat(' ', $expected));
3✔
821
                    } else {
1✔
822
                        $phpcsFile->fixer->replaceToken(($indexPointer - 1), str_repeat(' ', $expected));
3✔
823
                    }
824
                }
1✔
825
            }//end if
1✔
826

827
            $arrowStart = ($tokens[$indexPointer]['column'] + $maxLength + 1);
3✔
828
            if ($tokens[$index['arrow']]['column'] !== $arrowStart) {
3✔
829
                $expected       = ($arrowStart - ($index['index_length'] + $tokens[$indexPointer]['column']));
3✔
830
                $found          = ($tokens[$index['arrow']]['column'] - ($index['index_length'] + $tokens[$indexPointer]['column']));
3✔
831
                $pluralizeSpace = 's';
3✔
832
                if ($expected === 1) {
3✔
833
                    $pluralizeSpace = '';
3✔
834
                }
1✔
835

836
                $error = 'Array double arrow not aligned correctly; expected %s space%s but found %s';
3✔
837
                $data  = [
1✔
838
                    $expected,
3✔
839
                    $pluralizeSpace,
3✔
840
                    $found,
3✔
841
                ];
2✔
842

843
                $fix = $phpcsFile->addFixableError($error, $index['arrow'], 'DoubleArrowNotAligned', $data);
3✔
844
                if ($fix === true) {
3✔
845
                    if ($found === 0) {
3✔
846
                        $phpcsFile->fixer->addContent(($index['arrow'] - 1), str_repeat(' ', $expected));
3✔
847
                    } else {
1✔
848
                        $phpcsFile->fixer->replaceToken(($index['arrow'] - 1), str_repeat(' ', $expected));
3✔
849
                    }
850
                }
1✔
851

852
                continue;
3✔
853
            }//end if
854

855
            $valueStart = ($arrowStart + 3);
3✔
856
            if ($tokens[$valuePointer]['column'] !== $valueStart) {
3✔
857
                $expected = ($valueStart - ($tokens[$index['arrow']]['length'] + $tokens[$index['arrow']]['column']));
3✔
858
                $found    = ($tokens[$valuePointer]['column'] - ($tokens[$index['arrow']]['length'] + $tokens[$index['arrow']]['column']));
3✔
859
                if ($found < 0) {
3✔
860
                    $found = 'newline';
3✔
861
                }
1✔
862

863
                $pluralizeSpace = 's';
3✔
864
                if ($expected === 1) {
3✔
865
                    $pluralizeSpace = '';
3✔
866
                }
1✔
867

868
                $error = 'Array value not aligned correctly; expected %s space%s but found %s';
3✔
869
                $data  = [
1✔
870
                    $expected,
3✔
871
                    $pluralizeSpace,
3✔
872
                    $found,
3✔
873
                ];
2✔
874

875
                $fix = $phpcsFile->addFixableError($error, $index['arrow'], 'ValueNotAligned', $data);
3✔
876
                if ($fix === true) {
3✔
877
                    if ($found === 'newline') {
3✔
878
                        $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($valuePointer - 1), null, true);
3✔
879
                        $phpcsFile->fixer->beginChangeset();
3✔
880
                        for ($i = ($prev + 1); $i < $valuePointer; $i++) {
3✔
881
                            $phpcsFile->fixer->replaceToken($i, '');
3✔
882
                        }
1✔
883

884
                        $phpcsFile->fixer->replaceToken(($valuePointer - 1), str_repeat(' ', $expected));
3✔
885
                        $phpcsFile->fixer->endChangeset();
3✔
886
                    } else if ($found === 0) {
3✔
887
                        $phpcsFile->fixer->addContent(($valuePointer - 1), str_repeat(' ', $expected));
3✔
888
                    } else {
1✔
889
                        $phpcsFile->fixer->replaceToken(($valuePointer - 1), str_repeat(' ', $expected));
3✔
890
                    }
891
                }
1✔
892
            }//end if
1✔
893

894
            // Check each line ends in a comma.
895
            $valueStart = $valuePointer;
3✔
896
            $nextComma  = false;
3✔
897

898
            $end = $phpcsFile->findEndOfStatement($valueStart);
3✔
899
            if ($end === false) {
3✔
UNCOV
900
                $valueEnd = $valueStart;
×
901
            } else if ($tokens[$end]['code'] === T_COMMA) {
3✔
902
                $valueEnd  = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($end - 1), $valueStart, true);
3✔
903
                $nextComma = $end;
3✔
904
            } else {
1✔
905
                $valueEnd = $end;
3✔
906
                $next     = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), $arrayEnd, true);
3✔
907
                if ($next !== false && $tokens[$next]['code'] === T_COMMA) {
3✔
908
                    $nextComma = $next;
3✔
909
                }
1✔
910
            }
911

912
            $valueLine = $tokens[$valueEnd]['line'];
3✔
913
            if ($tokens[$valueEnd]['code'] === T_END_HEREDOC || $tokens[$valueEnd]['code'] === T_END_NOWDOC) {
3✔
914
                $valueLine++;
3✔
915
            }
1✔
916

917
            if ($nextComma === false || ($tokens[$nextComma]['line'] !== $valueLine)) {
3✔
918
                $error = 'Each line in an array declaration must end in a comma';
3✔
919
                $fix   = $phpcsFile->addFixableError($error, $valuePointer, 'NoComma');
3✔
920

921
                if ($fix === true) {
3✔
922
                    // Find the end of the line and put a comma there.
923
                    for ($i = ($valuePointer + 1); $i <= $arrayEnd; $i++) {
3✔
924
                        if ($tokens[$i]['line'] > $valueLine) {
3✔
925
                            break;
3✔
926
                        }
927
                    }
1✔
928

929
                    $phpcsFile->fixer->beginChangeset();
3✔
930
                    $phpcsFile->fixer->addContentBefore(($i - 1), ',');
3✔
931
                    if ($nextComma !== false) {
3✔
932
                        $phpcsFile->fixer->replaceToken($nextComma, '');
3✔
933
                    }
1✔
934

935
                    $phpcsFile->fixer->endChangeset();
3✔
936
                }
1✔
937
            }//end if
1✔
938

939
            // Check that there is no space before the comma.
940
            if ($nextComma !== false && $tokens[($nextComma - 1)]['code'] === T_WHITESPACE) {
3✔
941
                // Here/nowdoc closing tags must have the comma on the next line.
942
                $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($nextComma - 1), null, true);
3✔
943
                if ($tokens[$prev]['code'] !== T_END_HEREDOC && $tokens[$prev]['code'] !== T_END_NOWDOC) {
3✔
944
                    $content     = $tokens[($nextComma - 2)]['content'];
3✔
945
                    $spaceLength = $tokens[($nextComma - 1)]['length'];
3✔
946
                    $error       = 'Expected 0 spaces between "%s" and comma; %s found';
3✔
947
                    $data        = [
1✔
948
                        $content,
3✔
949
                        $spaceLength,
3✔
950
                    ];
2✔
951

952
                    $fix = $phpcsFile->addFixableError($error, $nextComma, 'SpaceBeforeComma', $data);
3✔
953
                    if ($fix === true) {
3✔
954
                        $phpcsFile->fixer->replaceToken(($nextComma - 1), '');
3✔
955
                    }
1✔
956
                }
1✔
957
            }
1✔
958
        }//end foreach
1✔
959

960
    }//end processMultiLineArray()
2✔
961

962

963
}//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

© 2026 Coveralls, Inc