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