comparison vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Arrays/ArraySniff.php @ 17:129ea1e6d783

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