Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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 Squiz Pty Ltd (ABN 77 084 670 600)
12
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
13
 * @version   CVS: $Id: ArrayDeclarationSniff.php 287724 2009-08-26 02:56:44Z squiz $
14
 * @link      http://pear.php.net/package/PHP_CodeSniffer
15
 */
16
 
17
/**
18
 * A test to ensure that arrays conform to the array coding standard.
19
 *
20
 * @category  PHP
21
 * @package   PHP_CodeSniffer
22
 * @author    Greg Sherwood <gsherwood@squiz.net>
23
 * @author    Marc McIntyre <mmcintyre@squiz.net>
24
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
25
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
26
 * @version   Release: 1.2.1
27
 * @link      http://pear.php.net/package/PHP_CodeSniffer
28
 */
29
class Squiz_Sniffs_Arrays_ArrayDeclarationSniff implements PHP_CodeSniffer_Sniff
30
{
31
 
32
 
33
    /**
34
     * Returns an array of tokens this test wants to listen for.
35
     *
36
     * @return array
37
     */
38
    public function register()
39
    {
40
        return array(T_ARRAY);
41
 
42
    }//end register()
43
 
44
 
45
    /**
46
     * Processes this sniff, when one of its tokens is encountered.
47
     *
48
     * @param PHP_CodeSniffer_File $phpcsFile The current file being checked.
49
     * @param int                  $stackPtr  The position of the current token in the
50
     *                                        stack passed in $tokens.
51
     *
52
     * @return void
53
     */
54
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
55
    {
56
        $tokens = $phpcsFile->getTokens();
57
 
58
        // Array keyword should be lower case.
59
        if (strtolower($tokens[$stackPtr]['content']) !== $tokens[$stackPtr]['content']) {
60
            $error = 'Array keyword should be lower case; expected "array" but found "'.$tokens[$stackPtr]['content'].'"';
61
            $phpcsFile->addError($error, $stackPtr);
62
        }
63
 
64
        $arrayStart   = $tokens[$stackPtr]['parenthesis_opener'];
65
        $arrayEnd     = $tokens[$arrayStart]['parenthesis_closer'];
66
        $keywordStart = $tokens[$stackPtr]['column'];
67
 
68
        if ($arrayStart != ($stackPtr + 1)) {
69
            $error = 'There must be no space between the Array keyword and the opening parenthesis';
70
            $phpcsFile->addError($error, $stackPtr);
71
        }
72
 
73
        // Check for empty arrays.
74
        $content = $phpcsFile->findNext(array(T_WHITESPACE), ($arrayStart + 1), ($arrayEnd + 1), true);
75
        if ($content === $arrayEnd) {
76
            // Empty array, but if the brackets aren't together, there's a problem.
77
            if (($arrayEnd - $arrayStart) !== 1) {
78
                $error = 'Empty array declaration must have no space between the parentheses';
79
                $phpcsFile->addError($error, $stackPtr);
80
 
81
                // We can return here because there is nothing else to check. All code
82
                // below can assume that the array is not empty.
83
                return;
84
            }
85
        }
86
 
87
        if ($tokens[$arrayStart]['line'] === $tokens[$arrayEnd]['line']) {
88
            // Single line array.
89
            // Check if there are multiple values. If so, then it has to be multiple lines
90
            // unless it is contained inside a function call or condition.
91
            $nextComma  = $arrayStart;
92
            $valueCount = 0;
93
            $commas     = array();
94
            while (($nextComma = $phpcsFile->findNext(array(T_COMMA), ($nextComma + 1), $arrayEnd)) !== false) {
95
                $valueCount++;
96
                $commas[] = $nextComma;
97
            }
98
 
99
            // Now check each of the double arrows (if any).
100
            $nextArrow = $arrayStart;
101
            while (($nextArrow = $phpcsFile->findNext(T_DOUBLE_ARROW, ($nextArrow + 1), $arrayEnd)) !== false) {
102
                if ($tokens[($nextArrow - 1)]['code'] !== T_WHITESPACE) {
103
                    $content = $tokens[($nextArrow - 1)]['content'];
104
                    $error   = "Expected 1 space between \"$content\" and double arrow; 0 found";
105
                    $phpcsFile->addError($error, $nextArrow);
106
                } else {
107
                    $spaceLength = strlen($tokens[($nextArrow - 1)]['content']);
108
                    if ($spaceLength !== 1) {
109
                        $content = $tokens[($nextArrow - 2)]['content'];
110
                        $error   = "Expected 1 space between \"$content\" and double arrow; $spaceLength found";
111
                        $phpcsFile->addError($error, $nextArrow);
112
                    }
113
                }
114
 
115
                if ($tokens[($nextArrow + 1)]['code'] !== T_WHITESPACE) {
116
                    $content = $tokens[($nextArrow + 1)]['content'];
117
                    $error   = "Expected 1 space between double arrow and \"$content\"; 0 found";
118
                    $phpcsFile->addError($error, $nextArrow);
119
                } else {
120
                    $spaceLength = strlen($tokens[($nextArrow + 1)]['content']);
121
                    if ($spaceLength !== 1) {
122
                        $content = $tokens[($nextArrow + 2)]['content'];
123
                        $error   = "Expected 1 space between double arrow and \"$content\"; $spaceLength found";
124
                        $phpcsFile->addError($error, $nextArrow);
125
                    }
126
                }
127
            }//end while
128
 
129
            if ($valueCount > 0) {
130
                $conditionCheck = $phpcsFile->findPrevious(array(T_OPEN_PARENTHESIS, T_SEMICOLON), ($stackPtr - 1), null, false);
131
 
132
                if (($conditionCheck === false) || ($tokens[$conditionCheck]['line'] !== $tokens[$stackPtr]['line'])) {
133
                    $error = 'Array with multiple values cannot be declared on a single line';
134
                    $phpcsFile->addError($error, $stackPtr);
135
                    return;
136
                }
137
 
138
                // We have a multiple value array that is inside a condition or
139
                // function. Check its spacing is correct.
140
                foreach ($commas as $comma) {
141
                    if ($tokens[($comma + 1)]['code'] !== T_WHITESPACE) {
142
                        $content = $tokens[($comma + 1)]['content'];
143
                        $error   = "Expected 1 space between comma and \"$content\"; 0 found";
144
                        $phpcsFile->addError($error, $comma);
145
                    } else {
146
                        $spaceLength = strlen($tokens[($comma + 1)]['content']);
147
                        if ($spaceLength !== 1) {
148
                            $content = $tokens[($comma + 2)]['content'];
149
                            $error   = "Expected 1 space between comma and \"$content\"; $spaceLength found";
150
                            $phpcsFile->addError($error, $comma);
151
                        }
152
                    }
153
 
154
                    if ($tokens[($comma - 1)]['code'] === T_WHITESPACE) {
155
                        $content     = $tokens[($comma - 2)]['content'];
156
                        $spaceLength = strlen($tokens[($comma - 1)]['content']);
157
                        $error       = "Expected 0 spaces between \"$content\" and comma; $spaceLength found";
158
                        $phpcsFile->addError($error, $comma);
159
                    }
160
                }//end foreach
161
            }//end if
162
 
163
            return;
164
        }//end if
165
 
166
        // Check the closing bracket is on a new line.
167
        $lastContent = $phpcsFile->findPrevious(array(T_WHITESPACE), ($arrayEnd - 1), $arrayStart, true);
168
        if ($tokens[$lastContent]['line'] !== ($tokens[$arrayEnd]['line'] - 1)) {
169
            $error = 'Closing parenthesis of array declaration must be on a new line';
170
            $phpcsFile->addError($error, $arrayEnd);
171
        } else if ($tokens[$arrayEnd]['column'] !== $keywordStart) {
172
            // Check the closing bracket is lined up under the a in array.
173
            $expected  = $keywordStart;
174
            $expected .= ($keywordStart === 0) ? ' space' : ' spaces';
175
            $found     = $tokens[$arrayEnd]['column'];
176
            $found    .= ($found === 0) ? ' space' : ' spaces';
177
            $phpcsFile->addError("Closing parenthesis not aligned correctly; expected $expected but found $found", $arrayEnd);
178
        }
179
 
180
        $nextToken  = $stackPtr;
181
        $lastComma  = $stackPtr;
182
        $keyUsed    = false;
183
        $singleUsed = false;
184
        $lastToken  = '';
185
        $indices    = array();
186
        $maxLength  = 0;
187
 
188
        // Find all the double arrows that reside in this scope.
189
        while (($nextToken = $phpcsFile->findNext(array(T_DOUBLE_ARROW, T_COMMA, T_ARRAY), ($nextToken + 1), $arrayEnd)) !== false) {
190
            $currentEntry = array();
191
 
192
            if ($tokens[$nextToken]['code'] === T_ARRAY) {
193
                // Let subsequent calls of this test handle nested arrays.
194
                $indices[] = array(
195
                              'value' => $nextToken,
196
                             );
197
                $nextToken = $tokens[$tokens[$nextToken]['parenthesis_opener']]['parenthesis_closer'];
198
                continue;
199
            }
200
 
201
            if ($tokens[$nextToken]['code'] === T_COMMA) {
202
                $stackPtrCount = 0;
203
                if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
204
                    $stackPtrCount = count($tokens[$stackPtr]['nested_parenthesis']);
205
                }
206
 
207
                if (count($tokens[$nextToken]['nested_parenthesis']) > ($stackPtrCount + 1)) {
208
                    // This comma is inside more parenthesis than the ARRAY keyword,
209
                    // then there it is actually a comma used to seperate arguments
210
                    // in a function call.
211
                    continue;
212
                }
213
 
214
                if ($keyUsed === true && $lastToken === T_COMMA) {
215
                    $error = 'No key specified for array entry; first entry specifies key';
216
                    $phpcsFile->addError($error, $nextToken);
217
                    return;
218
                }
219
 
220
                if ($keyUsed === false) {
221
                    if ($tokens[($nextToken - 1)]['code'] === T_WHITESPACE) {
222
                        $content     = $tokens[($nextToken - 2)]['content'];
223
                        $spaceLength = strlen($tokens[($nextToken - 1)]['content']);
224
                        $error       = "Expected 0 spaces between \"$content\" and comma; $spaceLength found";
225
                        $phpcsFile->addError($error, $nextToken);
226
                    }
227
 
228
                    // Find the value, which will be the first token on the line,
229
                    // excluding the leading whitespace.
230
                    $valueContent = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($nextToken - 1), null, true);
231
                    while ($tokens[$valueContent]['line'] === $tokens[$nextToken]['line']) {
232
                        if ($valueContent === $arrayStart) {
233
                            // Value must have been on the same line as the array
234
                            // parenthesis, so we have reached the start of the value.
235
                            break;
236
                        }
237
 
238
                        $valueContent--;
239
                    }
240
 
241
                    $valueContent = $phpcsFile->findNext(T_WHITESPACE, ($valueContent + 1), $nextToken, true);
242
                    $indices[]    = array('value' => $valueContent);
243
                    $singleUsed   = true;
244
                }//end if
245
 
246
                $lastToken = T_COMMA;
247
                continue;
248
            }//end if
249
 
250
            if ($tokens[$nextToken]['code'] === T_DOUBLE_ARROW) {
251
                if ($singleUsed === true) {
252
                    $error = 'Key specified for array entry; first entry has no key';
253
                    $phpcsFile->addError($error, $nextToken);
254
                    return;
255
                }
256
 
257
                $currentEntry['arrow'] = $nextToken;
258
                $keyUsed               = true;
259
 
260
                // Find the start of index that uses this double arrow.
261
                $indexEnd   = $phpcsFile->findPrevious(T_WHITESPACE, ($nextToken - 1), $arrayStart, true);
262
                $indexStart = $phpcsFile->findPrevious(T_WHITESPACE, $indexEnd, $arrayStart);
263
 
264
                if ($indexStart === false) {
265
                    $index = $indexEnd;
266
                } else {
267
                    $index = ($indexStart + 1);
268
                }
269
 
270
                $currentEntry['index']         = $index;
271
                $currentEntry['index_content'] = $phpcsFile->getTokensAsString($index, ($indexEnd - $index + 1));
272
 
273
                $indexLength = strlen($currentEntry['index_content']);
274
                if ($maxLength < $indexLength) {
275
                    $maxLength = $indexLength;
276
                }
277
 
278
                // Find the value of this index.
279
                $nextContent           = $phpcsFile->findNext(array(T_WHITESPACE), ($nextToken + 1), $arrayEnd, true);
280
                $currentEntry['value'] = $nextContent;
281
                $indices[]             = $currentEntry;
282
                $lastToken             = T_DOUBLE_ARROW;
283
            }//end if
284
        }//end while
285
 
286
        // Check for mutli-line arrays that should be single-line.
287
        $singleValue = false;
288
 
289
        if (empty($indices) === true) {
290
            $singleValue = true;
291
        } else if (count($indices) === 1) {
292
            if ($lastToken === T_COMMA) {
293
                // There may be another array value without a comma.
294
                $exclude     = PHP_CodeSniffer_Tokens::$emptyTokens;
295
                $exclude[]   = T_COMMA;
296
                $nextContent = $phpcsFile->findNext($exclude, ($indices[0]['value'] + 1), $arrayEnd, true);
297
                if ($nextContent === false) {
298
                    $singleValue = true;
299
                }
300
            }
301
 
302
            if ($singleValue === false && isset($indices[0]['arrow']) === false) {
303
                // A single nested array as a value is fine.
304
                if ($tokens[$indices[0]['value']]['code'] !== T_ARRAY) {
305
                    $singleValue === true;
306
                }
307
            }
308
        }
309
 
310
        if ($singleValue === true) {
311
            // Array cannot be empty, so this is a multi-line array with
312
            // a single value. It should be defined on single line.
313
            $error = 'Multi-line array contains a single value; use single-line array instead';
314
            $phpcsFile->addError($error, $stackPtr);
315
            return;
316
        }
317
 
318
        /*
319
            This section checks for arrays that don't specify keys.
320
 
321
            Arrays such as:
322
               array(
323
                'aaa',
324
                'bbb',
325
                'd',
326
               );
327
        */
328
 
329
        if ($keyUsed === false && empty($indices) === false) {
330
            $count     = count($indices);
331
            $lastIndex = $indices[($count - 1)]['value'];
332
 
333
            $trailingContent = $phpcsFile->findPrevious(T_WHITESPACE, ($arrayEnd - 1), $lastIndex, true);
334
            if ($tokens[$trailingContent]['code'] !== T_COMMA) {
335
                $error = 'Comma required after last value in array declaration';
336
                $phpcsFile->addError($error, $trailingContent);
337
            }
338
 
339
            foreach ($indices as $value) {
340
                if (empty($value['value']) === true) {
341
                    // Array was malformed and we couldn't figure out
342
                    // the array value correctly, so we have to ignore it.
343
                    // Other parts of this sniff will correct the error.
344
                    continue;
345
                }
346
 
347
                if ($tokens[($value['value'] - 1)]['code'] === T_WHITESPACE) {
348
                    // A whitespace token before this value means that the value
349
                    // was indented and not flush with the opening parenthesis.
350
                    if ($tokens[$value['value']]['column'] !== ($keywordStart + 1)) {
351
                        $error = 'Array value not aligned correctly; expected '.($keywordStart + 1).' spaces but found '.$tokens[$value['value']]['column'];
352
                        $phpcsFile->addError($error, $value['value']);
353
                    }
354
                }
355
            }
356
        }//end if
357
 
358
        /*
359
            Below the actual indentation of the array is checked.
360
            Errors will be thrown when a key is not aligned, when
361
            a double arrow is not aligned, and when a value is not
362
            aligned correctly.
363
            If an error is found in one of the above areas, then errors
364
            are not reported for the rest of the line to avoid reporting
365
            spaces and columns incorrectly. Often fixing the first
366
            problem will fix the other 2 anyway.
367
 
368
            For example:
369
 
370
            $a = array(
371
                  'index'  => '2',
372
                 );
373
 
374
            In this array, the double arrow is indented too far, but this
375
            will also cause an error in the value's alignment. If the arrow were
376
            to be moved back one space however, then both errors would be fixed.
377
        */
378
 
379
        $numValues = count($indices);
380
 
381
        $indicesStart = ($keywordStart + 1);
382
        $arrowStart   = ($indicesStart + $maxLength + 1);
383
        $valueStart   = ($arrowStart + 3);
384
        foreach ($indices as $index) {
385
            if (isset($index['index']) === false) {
386
                // Array value only.
387
                if (($tokens[$index['value']]['line'] === $tokens[$stackPtr]['line']) && ($numValues > 1)) {
388
                    $phpcsFile->addError('The first value in a multi-value array must be on a new line', $stackPtr);
389
                }
390
 
391
                continue;
392
            }
393
 
394
            if (($tokens[$index['index']]['line'] === $tokens[$stackPtr]['line'])) {
395
                $phpcsFile->addError('The first index in a multi-value array must be on a new line', $stackPtr);
396
                continue;
397
            }
398
 
399
            if ($tokens[$index['index']]['column'] !== $indicesStart) {
400
                $phpcsFile->addError('Array key not aligned correctly; expected '.$indicesStart.' spaces but found '.$tokens[$index['index']]['column'], $index['index']);
401
                continue;
402
            }
403
 
404
            if ($tokens[$index['arrow']]['column'] !== $arrowStart) {
405
                $expected  = ($arrowStart - (strlen($index['index_content']) + $tokens[$index['index']]['column']));
406
                $expected .= ($expected === 1) ? ' space' : ' spaces';
407
                $found     = ($tokens[$index['arrow']]['column'] - (strlen($index['index_content']) + $tokens[$index['index']]['column']));
408
                $phpcsFile->addError("Array double arrow not aligned correctly; expected $expected but found $found", $index['arrow']);
409
                continue;
410
            }
411
 
412
            if ($tokens[$index['value']]['column'] !== $valueStart) {
413
                $expected  = ($valueStart - (strlen($tokens[$index['arrow']]['content']) + $tokens[$index['arrow']]['column']));
414
                $expected .= ($expected === 1) ? ' space' : ' spaces';
415
                $found     = ($tokens[$index['value']]['column'] - (strlen($tokens[$index['arrow']]['content']) + $tokens[$index['arrow']]['column']));
416
                $phpcsFile->addError("Array value not aligned correctly; expected $expected but found $found", $index['arrow']);
417
            }
418
 
419
            // Check each line ends in a comma.
420
            if ($tokens[$index['value']]['code'] !== T_ARRAY) {
421
                $nextComma = $phpcsFile->findNext(array(T_COMMA), ($index['value'] + 1));
422
                if (($nextComma === false) || ($tokens[$nextComma]['line'] !== $tokens[$index['value']]['line'])) {
423
                    $error = 'Each line in an array declaration must end in a comma';
424
                    $phpcsFile->addError($error, $index['value']);
425
                }
426
 
427
                // Check that there is no space before the comma.
428
                if ($nextComma !== false && $tokens[($nextComma - 1)]['code'] === T_WHITESPACE) {
429
                    $content     = $tokens[($nextComma - 2)]['content'];
430
                    $spaceLength = strlen($tokens[($nextComma - 1)]['content']);
431
                    $error       = "Expected 0 spaces between \"$content\" and comma; $spaceLength found";
432
                    $phpcsFile->addError($error, $nextComma);
433
                }
434
            }
435
        }//end foreach
436
 
437
    }//end process()
438
 
439
 
440
}//end class
441
 
442
?>