1<?php
2/**
3 * A test to ensure that arrays conform to the array coding standard.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @author    Marc McIntyre <mmcintyre@squiz.net>
11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 * @link      http://pear.php.net/package/PHP_CodeSniffer
14 */
15
16/**
17 * A test to ensure that arrays conform to the array coding standard.
18 *
19 * @category  PHP
20 * @package   PHP_CodeSniffer
21 * @author    Greg Sherwood <gsherwood@squiz.net>
22 * @author    Marc McIntyre <mmcintyre@squiz.net>
23 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
24 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
25 * @version   Release: @package_version@
26 * @link      http://pear.php.net/package/PHP_CodeSniffer
27 */
28class Squiz_Sniffs_Arrays_ArrayDeclarationSniff implements PHP_CodeSniffer_Sniff
29{
30
31
32    /**
33     * Returns an array of tokens this test wants to listen for.
34     *
35     * @return array
36     */
37    public function register()
38    {
39        return array(
40                T_ARRAY,
41                T_OPEN_SHORT_ARRAY,
42               );
43
44    }//end register()
45
46
47    /**
48     * Processes this sniff, when one of its tokens is encountered.
49     *
50     * @param PHP_CodeSniffer_File $phpcsFile The current file being checked.
51     * @param int                  $stackPtr  The position of the current token in
52     *                                        the stack passed in $tokens.
53     *
54     * @return void
55     */
56    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
57    {
58        $tokens = $phpcsFile->getTokens();
59
60        if ($tokens[$stackPtr]['code'] === T_ARRAY) {
61            $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'no');
62
63            // Array keyword should be lower case.
64            if ($tokens[$stackPtr]['content'] !== strtolower($tokens[$stackPtr]['content'])) {
65                if ($tokens[$stackPtr]['content'] === strtoupper($tokens[$stackPtr]['content'])) {
66                    $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'upper');
67                } else {
68                    $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'mixed');
69                }
70
71                $error = 'Array keyword should be lower case; expected "array" but found "%s"';
72                $data  = array($tokens[$stackPtr]['content']);
73                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NotLowerCase', $data);
74                if ($fix === true) {
75                    $phpcsFile->fixer->replaceToken($stackPtr, 'array');
76                }
77            } else {
78                $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'lower');
79            }
80
81            $arrayStart = $tokens[$stackPtr]['parenthesis_opener'];
82            if (isset($tokens[$arrayStart]['parenthesis_closer']) === false) {
83                return;
84            }
85
86            $arrayEnd = $tokens[$arrayStart]['parenthesis_closer'];
87
88            if ($arrayStart !== ($stackPtr + 1)) {
89                $error = 'There must be no space between the "array" keyword and the opening parenthesis';
90                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword');
91
92                if ($fix === true) {
93                    $phpcsFile->fixer->beginChangeset();
94                    for ($i = ($stackPtr + 1); $i < $arrayStart; $i++) {
95                        $phpcsFile->fixer->replaceToken($i, '');
96                    }
97
98                    $phpcsFile->fixer->endChangeset();
99                }
100            }
101        } else {
102            $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'yes');
103            $arrayStart = $stackPtr;
104            $arrayEnd   = $tokens[$stackPtr]['bracket_closer'];
105        }//end if
106
107        // Check for empty arrays.
108        $content = $phpcsFile->findNext(T_WHITESPACE, ($arrayStart + 1), ($arrayEnd + 1), true);
109        if ($content === $arrayEnd) {
110            // Empty array, but if the brackets aren't together, there's a problem.
111            if (($arrayEnd - $arrayStart) !== 1) {
112                $error = 'Empty array declaration must have no space between the parentheses';
113                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceInEmptyArray');
114
115                if ($fix === true) {
116                    $phpcsFile->fixer->beginChangeset();
117                    for ($i = ($arrayStart + 1); $i < $arrayEnd; $i++) {
118                        $phpcsFile->fixer->replaceToken($i, '');
119                    }
120
121                    $phpcsFile->fixer->endChangeset();
122                }
123            }
124
125            // We can return here because there is nothing else to check. All code
126            // below can assume that the array is not empty.
127            return;
128        }
129
130        if ($tokens[$arrayStart]['line'] === $tokens[$arrayEnd]['line']) {
131            $this->processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd);
132        } else {
133            $this->processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd);
134        }
135
136    }//end process()
137
138
139    /**
140     * Processes a single-line array definition.
141     *
142     * @param PHP_CodeSniffer_File $phpcsFile  The current file being checked.
143     * @param int                  $stackPtr   The position of the current token
144     *                                         in the stack passed in $tokens.
145     * @param int                  $arrayStart The token that starts the array definition.
146     * @param int                  $arrayEnd   The token that ends the array definition.
147     *
148     * @return void
149     */
150    public function processSingleLineArray(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $arrayStart, $arrayEnd)
151    {
152        $tokens = $phpcsFile->getTokens();
153
154        // Check if there are multiple values. If so, then it has to be multiple lines
155        // unless it is contained inside a function call or condition.
156        $valueCount = 0;
157        $commas     = array();
158        for ($i = ($arrayStart + 1); $i < $arrayEnd; $i++) {
159            // Skip bracketed statements, like function calls.
160            if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
161                $i = $tokens[$i]['parenthesis_closer'];
162                continue;
163            }
164
165            if ($tokens[$i]['code'] === T_COMMA) {
166                // Before counting this comma, make sure we are not
167                // at the end of the array.
168                $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), $arrayEnd, true);
169                if ($next !== false) {
170                    $valueCount++;
171                    $commas[] = $i;
172                } else {
173                    // There is a comma at the end of a single line array.
174                    $error = 'Comma not allowed after last value in single-line array declaration';
175                    $fix   = $phpcsFile->addFixableError($error, $i, 'CommaAfterLast');
176                    if ($fix === true) {
177                        $phpcsFile->fixer->replaceToken($i, '');
178                    }
179                }
180            }
181        }//end for
182
183        // Now check each of the double arrows (if any).
184        $nextArrow = $arrayStart;
185        while (($nextArrow = $phpcsFile->findNext(T_DOUBLE_ARROW, ($nextArrow + 1), $arrayEnd)) !== false) {
186            if ($tokens[($nextArrow - 1)]['code'] !== T_WHITESPACE) {
187                $content = $tokens[($nextArrow - 1)]['content'];
188                $error   = 'Expected 1 space between "%s" and double arrow; 0 found';
189                $data    = array($content);
190                $fix     = $phpcsFile->addFixableError($error, $nextArrow, 'NoSpaceBeforeDoubleArrow', $data);
191                if ($fix === true) {
192                    $phpcsFile->fixer->addContentBefore($nextArrow, ' ');
193                }
194            } else {
195                $spaceLength = $tokens[($nextArrow - 1)]['length'];
196                if ($spaceLength !== 1) {
197                    $content = $tokens[($nextArrow - 2)]['content'];
198                    $error   = 'Expected 1 space between "%s" and double arrow; %s found';
199                    $data    = array(
200                                $content,
201                                $spaceLength,
202                               );
203
204                    $fix = $phpcsFile->addFixableError($error, $nextArrow, 'SpaceBeforeDoubleArrow', $data);
205                    if ($fix === true) {
206                        $phpcsFile->fixer->replaceToken(($nextArrow - 1), ' ');
207                    }
208                }
209            }//end if
210
211            if ($tokens[($nextArrow + 1)]['code'] !== T_WHITESPACE) {
212                $content = $tokens[($nextArrow + 1)]['content'];
213                $error   = 'Expected 1 space between double arrow and "%s"; 0 found';
214                $data    = array($content);
215                $fix     = $phpcsFile->addFixableError($error, $nextArrow, 'NoSpaceAfterDoubleArrow', $data);
216                if ($fix === true) {
217                    $phpcsFile->fixer->addContent($nextArrow, ' ');
218                }
219            } else {
220                $spaceLength = $tokens[($nextArrow + 1)]['length'];
221                if ($spaceLength !== 1) {
222                    $content = $tokens[($nextArrow + 2)]['content'];
223                    $error   = 'Expected 1 space between double arrow and "%s"; %s found';
224                    $data    = array(
225                                $content,
226                                $spaceLength,
227                               );
228
229                    $fix = $phpcsFile->addFixableError($error, $nextArrow, 'SpaceAfterDoubleArrow', $data);
230                    if ($fix === true) {
231                        $phpcsFile->fixer->replaceToken(($nextArrow + 1), ' ');
232                    }
233                }
234            }//end if
235        }//end while
236
237        if ($valueCount > 0) {
238            $conditionCheck = $phpcsFile->findPrevious(array(T_OPEN_PARENTHESIS, T_SEMICOLON), ($stackPtr - 1), null, false);
239
240            if ($conditionCheck === false
241                || $tokens[$conditionCheck]['line'] !== $tokens[$stackPtr]['line']
242            ) {
243                $error = 'Array with multiple values cannot be declared on a single line';
244                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SingleLineNotAllowed');
245                if ($fix === true) {
246                    $phpcsFile->fixer->beginChangeset();
247                    $phpcsFile->fixer->addNewline($arrayStart);
248                    $phpcsFile->fixer->addNewlineBefore($arrayEnd);
249                    $phpcsFile->fixer->endChangeset();
250                }
251
252                return;
253            }
254
255            // We have a multiple value array that is inside a condition or
256            // function. Check its spacing is correct.
257            foreach ($commas as $comma) {
258                if ($tokens[($comma + 1)]['code'] !== T_WHITESPACE) {
259                    $content = $tokens[($comma + 1)]['content'];
260                    $error   = 'Expected 1 space between comma and "%s"; 0 found';
261                    $data    = array($content);
262                    $fix     = $phpcsFile->addFixableError($error, $comma, 'NoSpaceAfterComma', $data);
263                    if ($fix === true) {
264                        $phpcsFile->fixer->addContent($comma, ' ');
265                    }
266                } else {
267                    $spaceLength = $tokens[($comma + 1)]['length'];
268                    if ($spaceLength !== 1) {
269                        $content = $tokens[($comma + 2)]['content'];
270                        $error   = 'Expected 1 space between comma and "%s"; %s found';
271                        $data    = array(
272                                    $content,
273                                    $spaceLength,
274                                   );
275
276                        $fix = $phpcsFile->addFixableError($error, $comma, 'SpaceAfterComma', $data);
277                        if ($fix === true) {
278                            $phpcsFile->fixer->replaceToken(($comma + 1), ' ');
279                        }
280                    }
281                }//end if
282
283                if ($tokens[($comma - 1)]['code'] === T_WHITESPACE) {
284                    $content     = $tokens[($comma - 2)]['content'];
285                    $spaceLength = $tokens[($comma - 1)]['length'];
286                    $error       = 'Expected 0 spaces between "%s" and comma; %s found';
287                    $data        = array(
288                                    $content,
289                                    $spaceLength,
290                                   );
291
292                    $fix = $phpcsFile->addFixableError($error, $comma, 'SpaceBeforeComma', $data);
293                    if ($fix === true) {
294                        $phpcsFile->fixer->replaceToken(($comma - 1), '');
295                    }
296                }
297            }//end foreach
298        }//end if
299
300    }//end processSingleLineArray()
301
302
303    /**
304     * Processes a multi-line array definition.
305     *
306     * @param PHP_CodeSniffer_File $phpcsFile  The current file being checked.
307     * @param int                  $stackPtr   The position of the current token
308     *                                         in the stack passed in $tokens.
309     * @param int                  $arrayStart The token that starts the array definition.
310     * @param int                  $arrayEnd   The token that ends the array definition.
311     *
312     * @return void
313     */
314    public function processMultiLineArray(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $arrayStart, $arrayEnd)
315    {
316        $tokens       = $phpcsFile->getTokens();
317        $keywordStart = $tokens[$stackPtr]['column'];
318
319        // Check the closing bracket is on a new line.
320        $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($arrayEnd - 1), $arrayStart, true);
321        if ($tokens[$lastContent]['line'] === $tokens[$arrayEnd]['line']) {
322            $error = 'Closing parenthesis of array declaration must be on a new line';
323            $fix   = $phpcsFile->addFixableError($error, $arrayEnd, 'CloseBraceNewLine');
324            if ($fix === true) {
325                $phpcsFile->fixer->addNewlineBefore($arrayEnd);
326            }
327        } else if ($tokens[$arrayEnd]['column'] !== $keywordStart) {
328            // Check the closing bracket is lined up under the "a" in array.
329            $expected = ($keywordStart - 1);
330            $found    = ($tokens[$arrayEnd]['column'] - 1);
331            $error    = 'Closing parenthesis not aligned correctly; expected %s space(s) but found %s';
332            $data     = array(
333                         $expected,
334                         $found,
335                        );
336
337            $fix = $phpcsFile->addFixableError($error, $arrayEnd, 'CloseBraceNotAligned', $data);
338            if ($fix === true) {
339                if ($found === 0) {
340                    $phpcsFile->fixer->addContent(($arrayEnd - 1), str_repeat(' ', $expected));
341                } else {
342                    $phpcsFile->fixer->replaceToken(($arrayEnd - 1), str_repeat(' ', $expected));
343                }
344            }
345        }//end if
346
347        $keyUsed    = false;
348        $singleUsed = false;
349        $indices    = array();
350        $maxLength  = 0;
351
352        if ($tokens[$stackPtr]['code'] === T_ARRAY) {
353            $lastToken = $tokens[$stackPtr]['parenthesis_opener'];
354        } else {
355            $lastToken = $stackPtr;
356        }
357
358        // Find all the double arrows that reside in this scope.
359        for ($nextToken = ($stackPtr + 1); $nextToken < $arrayEnd; $nextToken++) {
360            // Skip bracketed statements, like function calls.
361            if ($tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS
362                && (isset($tokens[$nextToken]['parenthesis_owner']) === false
363                || $tokens[$nextToken]['parenthesis_owner'] !== $stackPtr)
364            ) {
365                $nextToken = $tokens[$nextToken]['parenthesis_closer'];
366                continue;
367            }
368
369            if ($tokens[$nextToken]['code'] === T_ARRAY
370                || $tokens[$nextToken]['code'] === T_OPEN_SHORT_ARRAY
371                || $tokens[$nextToken]['code'] === T_CLOSURE
372            ) {
373                // Let subsequent calls of this test handle nested arrays.
374                if ($tokens[$lastToken]['code'] !== T_DOUBLE_ARROW) {
375                    $indices[] = array('value' => $nextToken);
376                    $lastToken = $nextToken;
377                }
378
379                if ($tokens[$nextToken]['code'] === T_ARRAY) {
380                    $nextToken = $tokens[$tokens[$nextToken]['parenthesis_opener']]['parenthesis_closer'];
381                } else if ($tokens[$nextToken]['code'] === T_OPEN_SHORT_ARRAY) {
382                    $nextToken = $tokens[$nextToken]['bracket_closer'];
383                } else {
384                    // T_CLOSURE.
385                    $nextToken = $tokens[$nextToken]['scope_closer'];
386                }
387
388                $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($nextToken + 1), null, true);
389                if ($tokens[$nextToken]['code'] !== T_COMMA) {
390                    $nextToken--;
391                } else {
392                    $lastToken = $nextToken;
393                }
394
395                continue;
396            }//end if
397
398            if ($tokens[$nextToken]['code'] !== T_DOUBLE_ARROW
399                && $tokens[$nextToken]['code'] !== T_COMMA
400            ) {
401                continue;
402            }
403
404            $currentEntry = array();
405
406            if ($tokens[$nextToken]['code'] === T_COMMA) {
407                $stackPtrCount = 0;
408                if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
409                    $stackPtrCount = count($tokens[$stackPtr]['nested_parenthesis']);
410                }
411
412                $commaCount = 0;
413                if (isset($tokens[$nextToken]['nested_parenthesis']) === true) {
414                    $commaCount = count($tokens[$nextToken]['nested_parenthesis']);
415                    if ($tokens[$stackPtr]['code'] === T_ARRAY) {
416                        // Remove parenthesis that are used to define the array.
417                        $commaCount--;
418                    }
419                }
420
421                if ($commaCount > $stackPtrCount) {
422                    // This comma is inside more parenthesis than the ARRAY keyword,
423                    // then there it is actually a comma used to separate arguments
424                    // in a function call.
425                    continue;
426                }
427
428                if ($keyUsed === true && $tokens[$lastToken]['code'] === T_COMMA) {
429                    $error = 'No key specified for array entry; first entry specifies key';
430                    $phpcsFile->addError($error, $nextToken, 'NoKeySpecified');
431                    return;
432                }
433
434                if ($keyUsed === false) {
435                    if ($tokens[($nextToken - 1)]['code'] === T_WHITESPACE) {
436                        $content = $tokens[($nextToken - 2)]['content'];
437                        if ($tokens[($nextToken - 1)]['content'] === $phpcsFile->eolChar) {
438                            $spaceLength = 'newline';
439                        } else {
440                            $spaceLength = $tokens[($nextToken - 1)]['length'];
441                        }
442
443                        $error = 'Expected 0 spaces between "%s" and comma; %s found';
444                        $data  = array(
445                                  $content,
446                                  $spaceLength,
447                                 );
448
449                        $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpaceBeforeComma', $data);
450                        if ($fix === true) {
451                            $phpcsFile->fixer->replaceToken(($nextToken - 1), '');
452                        }
453                    }
454
455                    $valueContent = $phpcsFile->findNext(
456                        PHP_CodeSniffer_Tokens::$emptyTokens,
457                        ($lastToken + 1),
458                        $nextToken,
459                        true
460                    );
461
462                    $indices[]  = array('value' => $valueContent);
463                    $singleUsed = true;
464                }//end if
465
466                $lastToken = $nextToken;
467                continue;
468            }//end if
469
470            if ($tokens[$nextToken]['code'] === T_DOUBLE_ARROW) {
471                if ($singleUsed === true) {
472                    $error = 'Key specified for array entry; first entry has no key';
473                    $phpcsFile->addError($error, $nextToken, 'KeySpecified');
474                    return;
475                }
476
477                $currentEntry['arrow'] = $nextToken;
478                $keyUsed = true;
479
480                // Find the start of index that uses this double arrow.
481                $indexEnd   = $phpcsFile->findPrevious(T_WHITESPACE, ($nextToken - 1), $arrayStart, true);
482                $indexStart = $phpcsFile->findStartOfStatement($indexEnd);
483
484                if ($indexStart === $indexEnd) {
485                    $currentEntry['index']         = $indexEnd;
486                    $currentEntry['index_content'] = $tokens[$indexEnd]['content'];
487                } else {
488                    $currentEntry['index']         = $indexStart;
489                    $currentEntry['index_content'] = $phpcsFile->getTokensAsString($indexStart, ($indexEnd - $indexStart + 1));
490                }
491
492                $indexLength = strlen($currentEntry['index_content']);
493                if ($maxLength < $indexLength) {
494                    $maxLength = $indexLength;
495                }
496
497                // Find the value of this index.
498                $nextContent = $phpcsFile->findNext(
499                    PHP_CodeSniffer_Tokens::$emptyTokens,
500                    ($nextToken + 1),
501                    $arrayEnd,
502                    true
503                );
504
505                $currentEntry['value'] = $nextContent;
506                $indices[] = $currentEntry;
507                $lastToken = $nextToken;
508            }//end if
509        }//end for
510
511        // Check for mutli-line arrays that should be single-line.
512        $singleValue = false;
513
514        if (empty($indices) === true) {
515            $singleValue = true;
516        } else if (count($indices) === 1 && $tokens[$lastToken]['code'] === T_COMMA) {
517            // There may be another array value without a comma.
518            $exclude     = PHP_CodeSniffer_Tokens::$emptyTokens;
519            $exclude[]   = T_COMMA;
520            $nextContent = $phpcsFile->findNext($exclude, ($indices[0]['value'] + 1), $arrayEnd, true);
521            if ($nextContent === false) {
522                $singleValue = true;
523            }
524        }
525
526        if ($singleValue === true) {
527            // Array cannot be empty, so this is a multi-line array with
528            // a single value. It should be defined on single line.
529            $error = 'Multi-line array contains a single value; use single-line array instead';
530            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'MultiLineNotAllowed');
531
532            if ($fix === true) {
533                $phpcsFile->fixer->beginChangeset();
534                for ($i = ($arrayStart + 1); $i < $arrayEnd; $i++) {
535                    if ($tokens[$i]['code'] !== T_WHITESPACE) {
536                        break;
537                    }
538
539                    $phpcsFile->fixer->replaceToken($i, '');
540                }
541
542                for ($i = ($arrayEnd - 1); $i > $arrayStart; $i--) {
543                    if ($tokens[$i]['code'] !== T_WHITESPACE) {
544                        break;
545                    }
546
547                    $phpcsFile->fixer->replaceToken($i, '');
548                }
549
550                $phpcsFile->fixer->endChangeset();
551            }
552
553            return;
554        }//end if
555
556        /*
557            This section checks for arrays that don't specify keys.
558
559            Arrays such as:
560               array(
561                'aaa',
562                'bbb',
563                'd',
564               );
565        */
566
567        if ($keyUsed === false && empty($indices) === false) {
568            $count     = count($indices);
569            $lastIndex = $indices[($count - 1)]['value'];
570
571            $trailingContent = $phpcsFile->findPrevious(
572                PHP_CodeSniffer_Tokens::$emptyTokens,
573                ($arrayEnd - 1),
574                $lastIndex,
575                true
576            );
577
578            if ($tokens[$trailingContent]['code'] !== T_COMMA) {
579                $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'no');
580                $error = 'Comma required after last value in array declaration';
581                $fix   = $phpcsFile->addFixableError($error, $trailingContent, 'NoCommaAfterLast');
582                if ($fix === true) {
583                    $phpcsFile->fixer->addContent($trailingContent, ',');
584                }
585            } else {
586                $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'yes');
587            }
588
589            $lastValueLine = false;
590            foreach ($indices as $value) {
591                if (empty($value['value']) === true) {
592                    // Array was malformed and we couldn't figure out
593                    // the array value correctly, so we have to ignore it.
594                    // Other parts of this sniff will correct the error.
595                    continue;
596                }
597
598                if ($lastValueLine !== false && $tokens[$value['value']]['line'] === $lastValueLine) {
599                    $error = 'Each value in a multi-line array must be on a new line';
600                    $fix   = $phpcsFile->addFixableError($error, $value['value'], 'ValueNoNewline');
601                    if ($fix === true) {
602                        if ($tokens[($value['value'] - 1)]['code'] === T_WHITESPACE) {
603                            $phpcsFile->fixer->replaceToken(($value['value'] - 1), '');
604                        }
605
606                        $phpcsFile->fixer->addNewlineBefore($value['value']);
607                    }
608                } else if ($tokens[($value['value'] - 1)]['code'] === T_WHITESPACE) {
609                    $expected = $keywordStart;
610
611                    $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $value['value'], true);
612                    $found = ($tokens[$first]['column'] - 1);
613                    if ($found !== $expected) {
614                        $error = 'Array value not aligned correctly; expected %s spaces but found %s';
615                        $data  = array(
616                                  $expected,
617                                  $found,
618                                 );
619
620                        $fix = $phpcsFile->addFixableError($error, $value['value'], 'ValueNotAligned', $data);
621                        if ($fix === true) {
622                            if ($found === 0) {
623                                $phpcsFile->fixer->addContent(($value['value'] - 1), str_repeat(' ', $expected));
624                            } else {
625                                $phpcsFile->fixer->replaceToken(($value['value'] - 1), str_repeat(' ', $expected));
626                            }
627                        }
628                    }
629                }//end if
630
631                $lastValueLine = $tokens[$value['value']]['line'];
632            }//end foreach
633        }//end if
634
635        /*
636            Below the actual indentation of the array is checked.
637            Errors will be thrown when a key is not aligned, when
638            a double arrow is not aligned, and when a value is not
639            aligned correctly.
640            If an error is found in one of the above areas, then errors
641            are not reported for the rest of the line to avoid reporting
642            spaces and columns incorrectly. Often fixing the first
643            problem will fix the other 2 anyway.
644
645            For example:
646
647            $a = array(
648                  'index'  => '2',
649                 );
650
651            or
652
653            $a = [
654                  'index'  => '2',
655                 ];
656
657            In this array, the double arrow is indented too far, but this
658            will also cause an error in the value's alignment. If the arrow were
659            to be moved back one space however, then both errors would be fixed.
660        */
661
662        $numValues = count($indices);
663
664        $indicesStart  = ($keywordStart + 1);
665        $arrowStart    = ($indicesStart + $maxLength + 1);
666        $valueStart    = ($arrowStart + 3);
667        $indexLine     = $tokens[$stackPtr]['line'];
668        $lastIndexLine = null;
669        foreach ($indices as $index) {
670            if (isset($index['index']) === false) {
671                // Array value only.
672                if ($tokens[$index['value']]['line'] === $tokens[$stackPtr]['line'] && $numValues > 1) {
673                    $error = 'The first value in a multi-value array must be on a new line';
674                    $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'FirstValueNoNewline');
675                    if ($fix === true) {
676                        $phpcsFile->fixer->addNewlineBefore($index['value']);
677                    }
678                }
679
680                continue;
681            }
682
683            $lastIndexLine = $indexLine;
684            $indexLine     = $tokens[$index['index']]['line'];
685
686            if ($indexLine === $tokens[$stackPtr]['line']) {
687                $error = 'The first index in a multi-value array must be on a new line';
688                $fix   = $phpcsFile->addFixableError($error, $index['index'], 'FirstIndexNoNewline');
689                if ($fix === true) {
690                    $phpcsFile->fixer->addNewlineBefore($index['index']);
691                }
692
693                continue;
694            }
695
696            if ($indexLine === $lastIndexLine) {
697                $error = 'Each index in a multi-line array must be on a new line';
698                $fix   = $phpcsFile->addFixableError($error, $index['index'], 'IndexNoNewline');
699                if ($fix === true) {
700                    if ($tokens[($index['index'] - 1)]['code'] === T_WHITESPACE) {
701                        $phpcsFile->fixer->replaceToken(($index['index'] - 1), '');
702                    }
703
704                    $phpcsFile->fixer->addNewlineBefore($index['index']);
705                }
706
707                continue;
708            }
709
710            if ($tokens[$index['index']]['column'] !== $indicesStart) {
711                $expected = ($indicesStart - 1);
712                $found    = ($tokens[$index['index']]['column'] - 1);
713                $error    = 'Array key not aligned correctly; expected %s spaces but found %s';
714                $data     = array(
715                             $expected,
716                             $found,
717                            );
718
719                $fix = $phpcsFile->addFixableError($error, $index['index'], 'KeyNotAligned', $data);
720                if ($fix === true) {
721                    if ($found === 0) {
722                        $phpcsFile->fixer->addContent(($index['index'] - 1), str_repeat(' ', $expected));
723                    } else {
724                        $phpcsFile->fixer->replaceToken(($index['index'] - 1), str_repeat(' ', $expected));
725                    }
726                }
727
728                continue;
729            }
730
731            if ($tokens[$index['arrow']]['column'] !== $arrowStart) {
732                $expected = ($arrowStart - (strlen($index['index_content']) + $tokens[$index['index']]['column']));
733                $found    = ($tokens[$index['arrow']]['column'] - (strlen($index['index_content']) + $tokens[$index['index']]['column']));
734                $error    = 'Array double arrow not aligned correctly; expected %s space(s) but found %s';
735                $data     = array(
736                             $expected,
737                             $found,
738                            );
739
740                $fix = $phpcsFile->addFixableError($error, $index['arrow'], 'DoubleArrowNotAligned', $data);
741                if ($fix === true) {
742                    if ($found === 0) {
743                        $phpcsFile->fixer->addContent(($index['arrow'] - 1), str_repeat(' ', $expected));
744                    } else {
745                        $phpcsFile->fixer->replaceToken(($index['arrow'] - 1), str_repeat(' ', $expected));
746                    }
747                }
748
749                continue;
750            }
751
752            if ($tokens[$index['value']]['column'] !== $valueStart) {
753                $expected = ($valueStart - ($tokens[$index['arrow']]['length'] + $tokens[$index['arrow']]['column']));
754                $found    = ($tokens[$index['value']]['column'] - ($tokens[$index['arrow']]['length'] + $tokens[$index['arrow']]['column']));
755                if ($found < 0) {
756                    $found = 'newline';
757                }
758
759                $error = 'Array value not aligned correctly; expected %s space(s) but found %s';
760                $data  = array(
761                          $expected,
762                          $found,
763                         );
764
765                $fix = $phpcsFile->addFixableError($error, $index['arrow'], 'ValueNotAligned', $data);
766                if ($fix === true) {
767                    if ($found === 'newline') {
768                        $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($index['value'] - 1), null, true);
769                        $phpcsFile->fixer->beginChangeset();
770                        for ($i = ($prev + 1); $i < $index['value']; $i++) {
771                            $phpcsFile->fixer->replaceToken($i, '');
772                        }
773
774                        $phpcsFile->fixer->replaceToken(($index['value'] - 1), str_repeat(' ', $expected));
775                        $phpcsFile->fixer->endChangeset();
776                    } else if ($found === 0) {
777                        $phpcsFile->fixer->addContent(($index['value'] - 1), str_repeat(' ', $expected));
778                    } else {
779                        $phpcsFile->fixer->replaceToken(($index['value'] - 1), str_repeat(' ', $expected));
780                    }
781                }
782            }//end if
783
784            // Check each line ends in a comma.
785            $valueLine = $tokens[$index['value']]['line'];
786            $nextComma = false;
787            for ($i = $index['value']; $i < $arrayEnd; $i++) {
788                // Skip bracketed statements, like function calls.
789                if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
790                    $i         = $tokens[$i]['parenthesis_closer'];
791                    $valueLine = $tokens[$i]['line'];
792                    continue;
793                }
794
795                if ($tokens[$i]['code'] === T_ARRAY) {
796                    $i         = $tokens[$tokens[$i]['parenthesis_opener']]['parenthesis_closer'];
797                    $valueLine = $tokens[$i]['line'];
798                    continue;
799                }
800
801                // Skip to the end of multi-line strings.
802                if (isset(PHP_CodeSniffer_Tokens::$stringTokens[$tokens[$i]['code']]) === true) {
803                    $i = $phpcsFile->findNext($tokens[$i]['code'], ($i + 1), null, true);
804                    $i--;
805                    $valueLine = $tokens[$i]['line'];
806                    continue;
807                }
808
809                if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
810                    $i         = $tokens[$i]['bracket_closer'];
811                    $valueLine = $tokens[$i]['line'];
812                    continue;
813                }
814
815                if ($tokens[$i]['code'] === T_CLOSURE) {
816                    $i         = $tokens[$i]['scope_closer'];
817                    $valueLine = $tokens[$i]['line'];
818                    continue;
819                }
820
821                if ($tokens[$i]['code'] === T_COMMA) {
822                    $nextComma = $i;
823                    break;
824                }
825            }//end for
826
827            if ($nextComma === false || ($tokens[$nextComma]['line'] !== $valueLine)) {
828                $error = 'Each line in an array declaration must end in a comma';
829                $fix   = $phpcsFile->addFixableError($error, $index['value'], 'NoComma');
830
831                if ($fix === true) {
832                    // Find the end of the line and put a comma there.
833                    for ($i = ($index['value'] + 1); $i < $arrayEnd; $i++) {
834                        if ($tokens[$i]['line'] > $valueLine) {
835                            break;
836                        }
837                    }
838
839                    $phpcsFile->fixer->addContentBefore(($i - 1), ',');
840                }
841            }
842
843            // Check that there is no space before the comma.
844            if ($nextComma !== false && $tokens[($nextComma - 1)]['code'] === T_WHITESPACE) {
845                $content     = $tokens[($nextComma - 2)]['content'];
846                $spaceLength = $tokens[($nextComma - 1)]['length'];
847                $error       = 'Expected 0 spaces between "%s" and comma; %s found';
848                $data        = array(
849                                $content,
850                                $spaceLength,
851                               );
852
853                $fix = $phpcsFile->addFixableError($error, $nextComma, 'SpaceBeforeComma', $data);
854                if ($fix === true) {
855                    $phpcsFile->fixer->replaceToken(($nextComma - 1), '');
856                }
857            }
858        }//end foreach
859
860    }//end processMultiLineArray()
861
862
863}//end class
864