annotate vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Arrays/ArraySniff.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@17 1 <?php
Chris@17 2 /**
Chris@17 3 * \Drupal\Sniffs\Arrays\ArraySniff.
Chris@17 4 *
Chris@17 5 * @category PHP
Chris@17 6 * @package PHP_CodeSniffer
Chris@17 7 * @link http://pear.php.net/package/PHP_CodeSniffer
Chris@17 8 */
Chris@17 9
Chris@17 10 namespace Drupal\Sniffs\Arrays;
Chris@17 11
Chris@17 12 use PHP_CodeSniffer\Files\File;
Chris@17 13 use PHP_CodeSniffer\Sniffs\Sniff;
Chris@17 14 use PHP_CodeSniffer\Util\Tokens;
Chris@17 15
Chris@17 16 /**
Chris@17 17 * ArraySniff.
Chris@17 18 *
Chris@17 19 * Checks if the array's are styled in the Drupal way.
Chris@17 20 * - Comma after the last array element
Chris@17 21 * - Indentation is 2 spaces for multi line array definitions
Chris@17 22 *
Chris@17 23 * @category PHP
Chris@17 24 * @package PHP_CodeSniffer
Chris@17 25 * @link http://pear.php.net/package/PHP_CodeSniffer
Chris@17 26 */
Chris@17 27 class ArraySniff implements Sniff
Chris@17 28 {
Chris@17 29
Chris@17 30
Chris@17 31 /**
Chris@17 32 * Returns an array of tokens this test wants to listen for.
Chris@17 33 *
Chris@17 34 * @return array
Chris@17 35 */
Chris@17 36 public function register()
Chris@17 37 {
Chris@17 38 return array(
Chris@17 39 T_ARRAY,
Chris@17 40 T_OPEN_SHORT_ARRAY,
Chris@17 41 );
Chris@17 42
Chris@17 43 }//end register()
Chris@17 44
Chris@17 45
Chris@17 46 /**
Chris@17 47 * Processes this test, when one of its tokens is encountered.
Chris@17 48 *
Chris@17 49 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
Chris@17 50 * @param int $stackPtr The position of the current token in
Chris@17 51 * the stack passed in $tokens.
Chris@17 52 *
Chris@17 53 * @return void
Chris@17 54 */
Chris@17 55 public function process(File $phpcsFile, $stackPtr)
Chris@17 56 {
Chris@17 57 $tokens = $phpcsFile->getTokens();
Chris@17 58
Chris@17 59 // Support long and short syntax.
Chris@17 60 $parenthesis_opener = 'parenthesis_opener';
Chris@17 61 $parenthesis_closer = 'parenthesis_closer';
Chris@17 62 if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY) {
Chris@17 63 $parenthesis_opener = 'bracket_opener';
Chris@17 64 $parenthesis_closer = 'bracket_closer';
Chris@17 65 }
Chris@17 66
Chris@17 67 // Sanity check: this can sometimes be NULL if the array was not correctly
Chris@17 68 // parsed.
Chris@17 69 if ($tokens[$stackPtr][$parenthesis_closer] === null) {
Chris@17 70 return;
Chris@17 71 }
Chris@17 72
Chris@17 73 $lastItem = $phpcsFile->findPrevious(
Chris@17 74 Tokens::$emptyTokens,
Chris@17 75 ($tokens[$stackPtr][$parenthesis_closer] - 1),
Chris@17 76 $stackPtr,
Chris@17 77 true
Chris@17 78 );
Chris@17 79
Chris@17 80 // Empty array.
Chris@17 81 if ($lastItem === $tokens[$stackPtr][$parenthesis_opener]) {
Chris@17 82 return;
Chris@17 83 }
Chris@17 84
Chris@17 85 // Inline array.
Chris@17 86 $isInlineArray = $tokens[$tokens[$stackPtr][$parenthesis_opener]]['line'] === $tokens[$tokens[$stackPtr][$parenthesis_closer]]['line'];
Chris@17 87
Chris@17 88 // Check if the last item in a multiline array has a "closing" comma.
Chris@17 89 if ($tokens[$lastItem]['code'] !== T_COMMA && $isInlineArray === false
Chris@17 90 && $tokens[($lastItem + 1)]['code'] !== T_CLOSE_PARENTHESIS
Chris@17 91 && $tokens[($lastItem + 1)]['code'] !== T_CLOSE_SHORT_ARRAY
Chris@17 92 && isset(Tokens::$heredocTokens[$tokens[$lastItem]['code']]) === false
Chris@17 93 ) {
Chris@17 94 $data = array($tokens[$lastItem]['content']);
Chris@17 95 $fix = $phpcsFile->addFixableWarning('A comma should follow the last multiline array item. Found: %s', $lastItem, 'CommaLastItem', $data);
Chris@17 96 if ($fix === true) {
Chris@17 97 $phpcsFile->fixer->addContent($lastItem, ',');
Chris@17 98 }
Chris@17 99
Chris@17 100 return;
Chris@17 101 }
Chris@17 102
Chris@17 103 if ($isInlineArray === true) {
Chris@17 104 // Check if this array contains at least 3 elements and exceeds the 80
Chris@17 105 // character line length.
Chris@17 106 if ($tokens[$tokens[$stackPtr][$parenthesis_closer]]['column'] > 80) {
Chris@17 107 $comma1 = $phpcsFile->findNext(T_COMMA, ($stackPtr + 1), $tokens[$stackPtr][$parenthesis_closer]);
Chris@17 108 if ($comma1 !== false) {
Chris@17 109 $comma2 = $phpcsFile->findNext(T_COMMA, ($comma1 + 1), $tokens[$stackPtr][$parenthesis_closer]);
Chris@17 110 if ($comma2 !== false) {
Chris@17 111 $error = 'If the line declaring an array spans longer than 80 characters, each element should be broken into its own line';
Chris@17 112 $phpcsFile->addError($error, $stackPtr, 'LongLineDeclaration');
Chris@17 113 }
Chris@17 114 }
Chris@17 115 }
Chris@17 116
Chris@17 117 // Only continue for multi line arrays.
Chris@17 118 return;
Chris@17 119 }
Chris@17 120
Chris@17 121 // Find the first token on this line.
Chris@17 122 $firstLineColumn = $tokens[$stackPtr]['column'];
Chris@17 123 for ($i = $stackPtr; $i >= 0; $i--) {
Chris@17 124 // If there is a PHP open tag then this must be a template file where we
Chris@17 125 // don't check indentation.
Chris@17 126 if ($tokens[$i]['code'] === T_OPEN_TAG) {
Chris@17 127 return;
Chris@17 128 }
Chris@17 129
Chris@17 130 // Record the first code token on the line.
Chris@17 131 if ($tokens[$i]['code'] !== T_WHITESPACE) {
Chris@17 132 $firstLineColumn = $tokens[$i]['column'];
Chris@17 133 // This could be a multi line string or comment beginning with white
Chris@17 134 // spaces.
Chris@17 135 $trimmed = ltrim($tokens[$i]['content']);
Chris@17 136 if ($trimmed !== $tokens[$i]['content']) {
Chris@17 137 $firstLineColumn = ($firstLineColumn + strpos($tokens[$i]['content'], $trimmed));
Chris@17 138 }
Chris@17 139 }
Chris@17 140
Chris@17 141 // It's the start of the line, so we've found our first php token.
Chris@17 142 if ($tokens[$i]['column'] === 1) {
Chris@17 143 break;
Chris@17 144 }
Chris@17 145 }//end for
Chris@17 146
Chris@17 147 $lineStart = $stackPtr;
Chris@17 148 // Iterate over all lines of this array.
Chris@17 149 while ($lineStart < $tokens[$stackPtr][$parenthesis_closer]) {
Chris@17 150 // Find next line start.
Chris@17 151 $newLineStart = $lineStart;
Chris@17 152 $current_line = $tokens[$newLineStart]['line'];
Chris@17 153 while ($current_line >= $tokens[$newLineStart]['line']) {
Chris@17 154 $newLineStart = $phpcsFile->findNext(
Chris@17 155 Tokens::$emptyTokens,
Chris@17 156 ($newLineStart + 1),
Chris@17 157 ($tokens[$stackPtr][$parenthesis_closer] + 1),
Chris@17 158 true
Chris@17 159 );
Chris@17 160
Chris@17 161 if ($newLineStart === false) {
Chris@17 162 break 2;
Chris@17 163 }
Chris@17 164
Chris@17 165 // Long array syntax: Skip nested arrays, they are checked in a next
Chris@17 166 // run.
Chris@17 167 if ($tokens[$newLineStart]['code'] === T_ARRAY) {
Chris@17 168 $newLineStart = $tokens[$newLineStart]['parenthesis_closer'];
Chris@17 169 $current_line = $tokens[$newLineStart]['line'];
Chris@17 170 }
Chris@17 171
Chris@17 172 // Short array syntax: Skip nested arrays, they are checked in a next
Chris@17 173 // run.
Chris@17 174 if ($tokens[$newLineStart]['code'] === T_OPEN_SHORT_ARRAY) {
Chris@17 175 $newLineStart = $tokens[$newLineStart]['bracket_closer'];
Chris@17 176 $current_line = $tokens[$newLineStart]['line'];
Chris@17 177 }
Chris@17 178
Chris@17 179 // Nested structures such as closures: skip those, they are checked
Chris@17 180 // in other sniffs. If the conditions of a token are different it
Chris@17 181 // means that it is in a different nesting level.
Chris@17 182 if ($tokens[$newLineStart]['conditions'] !== $tokens[$stackPtr]['conditions']) {
Chris@17 183 $current_line++;
Chris@17 184 }
Chris@17 185 }//end while
Chris@17 186
Chris@17 187 if ($newLineStart === $tokens[$stackPtr][$parenthesis_closer]) {
Chris@17 188 // End of the array reached.
Chris@17 189 if ($tokens[$newLineStart]['column'] !== $firstLineColumn) {
Chris@17 190 $error = 'Array closing indentation error, expected %s spaces but found %s';
Chris@17 191 $data = array(
Chris@17 192 $firstLineColumn - 1,
Chris@17 193 $tokens[$newLineStart]['column'] - 1,
Chris@17 194 );
Chris@17 195 $fix = $phpcsFile->addFixableError($error, $newLineStart, 'ArrayClosingIndentation', $data);
Chris@17 196 if ($fix === true) {
Chris@17 197 if ($tokens[$newLineStart]['column'] === 1) {
Chris@17 198 $phpcsFile->fixer->addContentBefore($newLineStart, str_repeat(' ', ($firstLineColumn - 1)));
Chris@17 199 } else {
Chris@17 200 $phpcsFile->fixer->replaceToken(($newLineStart - 1), str_repeat(' ', ($firstLineColumn - 1)));
Chris@17 201 }
Chris@17 202 }
Chris@17 203 }
Chris@17 204
Chris@17 205 break;
Chris@17 206 }
Chris@17 207
Chris@17 208 $expectedColumn = ($firstLineColumn + 2);
Chris@17 209 // If the line starts with "->" then we assume an additional level of
Chris@17 210 // indentation.
Chris@17 211 if ($tokens[$newLineStart]['code'] === T_OBJECT_OPERATOR) {
Chris@17 212 $expectedColumn += 2;
Chris@17 213 }
Chris@17 214
Chris@17 215 if ($tokens[$newLineStart]['column'] !== $expectedColumn) {
Chris@17 216 // Skip lines in nested structures such as a function call within an
Chris@17 217 // array, no defined coding standard for those.
Chris@17 218 $innerNesting = empty($tokens[$newLineStart]['nested_parenthesis']) === false
Chris@17 219 && end($tokens[$newLineStart]['nested_parenthesis']) < $tokens[$stackPtr][$parenthesis_closer];
Chris@17 220 // Skip lines that are part of a multi-line string.
Chris@17 221 $isMultiLineString = $tokens[($newLineStart - 1)]['code'] === T_CONSTANT_ENCAPSED_STRING
Chris@17 222 && substr($tokens[($newLineStart - 1)]['content'], -1) === $phpcsFile->eolChar;
Chris@17 223 // Skip NOWDOC or HEREDOC lines.
Chris@17 224 $nowDoc = isset(Tokens::$heredocTokens[$tokens[$newLineStart]['code']]);
Chris@17 225 if ($innerNesting === false && $isMultiLineString === false && $nowDoc === false) {
Chris@17 226 $error = 'Array indentation error, expected %s spaces but found %s';
Chris@17 227 $data = array(
Chris@17 228 $expectedColumn - 1,
Chris@17 229 $tokens[$newLineStart]['column'] - 1,
Chris@17 230 );
Chris@17 231 $fix = $phpcsFile->addFixableError($error, $newLineStart, 'ArrayIndentation', $data);
Chris@17 232 if ($fix === true) {
Chris@17 233 if ($tokens[$newLineStart]['column'] === 1) {
Chris@17 234 $phpcsFile->fixer->addContentBefore($newLineStart, str_repeat(' ', ($expectedColumn - 1)));
Chris@17 235 } else {
Chris@17 236 $phpcsFile->fixer->replaceToken(($newLineStart - 1), str_repeat(' ', ($expectedColumn - 1)));
Chris@17 237 }
Chris@17 238 }
Chris@17 239 }
Chris@17 240 }//end if
Chris@17 241
Chris@17 242 $lineStart = $newLineStart;
Chris@17 243 }//end while
Chris@17 244
Chris@17 245 }//end process()
Chris@17 246
Chris@17 247
Chris@17 248 }//end class