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
|