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