~ubuntu-branches/ubuntu/vivid/php-codesniffer/vivid

« back to all changes in this revision

Viewing changes to PHP_CodeSniffer-1.5.4/CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php

  • Committer: Package Import Robot
  • Author(s): David Prévot, Greg Sherwood, Alexey, Emily, David Prévot
  • Date: 2014-09-26 13:44:35 UTC
  • mfrom: (1.1.6)
  • Revision ID: package-import@ubuntu.com-20140926134435-wvjq16miqq4d60y0
Tags: 1.5.5-1
[ Greg Sherwood ]
* Improved closure support in Generic ScopeIndentSniff
* Improved indented PHP tag support in Generic ScopeIndentSniff
* Standards can now be located within hidden directories
 (further fix for bug #20323)
* Fixed bug #20373 : Inline comment sniff tab handling way
* Fixed bug #20378 : Report appended to existing file if no errors
  found in run
* Fixed bug #20381 : Invalid "Comment closer must be on a new line"
* PHP tokenizer no longer converts class/function names to special
  tokens types
* Fixed bug #20386 : Squiz.Commenting.ClassComment.SpacingBefore
  thrown if first block comment
* Squiz and PEAR FunctionCommentSnif now support _()
* PEAR ValidFunctionNameSniff no longer throws an error for _()
* Fixed bug #248 : FunctionCommentSniff expects ampersand on param name
* Fixed bug #248 in Squiz sniff as well
* Fixed bug #265 : False positives with type hints in ForbiddenFunctionsSniff
* Prepare for 1.5.5 release

[ Alexey ]
* Allowed single undersored methods and functions

[ Emily ]
* Added var_dump to discouraged functions sniff

[ David Prévot ]
* Revert "Add XS-Testsuite still needed for ci.d.n"
* Add self to uploaders
* Bump standards version to 3.9.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
<?php
2
 
/**
3
 
 * Generic_Sniffs_Whitespace_ScopeIndentSniff.
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-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12
 
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13
 
 * @link      http://pear.php.net/package/PHP_CodeSniffer
14
 
 */
15
 
 
16
 
/**
17
 
 * Generic_Sniffs_Whitespace_ScopeIndentSniff.
18
 
 *
19
 
 * Checks that control structures are structured correctly, and their content
20
 
 * is indented correctly. This sniff will throw errors if tabs are used
21
 
 * for indentation rather than spaces.
22
 
 *
23
 
 * @category  PHP
24
 
 * @package   PHP_CodeSniffer
25
 
 * @author    Greg Sherwood <gsherwood@squiz.net>
26
 
 * @author    Marc McIntyre <mmcintyre@squiz.net>
27
 
 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
28
 
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
29
 
 * @version   Release: 1.5.4
30
 
 * @link      http://pear.php.net/package/PHP_CodeSniffer
31
 
 */
32
 
class Generic_Sniffs_WhiteSpace_ScopeIndentSniff implements PHP_CodeSniffer_Sniff
33
 
{
34
 
 
35
 
    /**
36
 
     * The number of spaces code should be indented.
37
 
     *
38
 
     * @var int
39
 
     */
40
 
    public $indent = 4;
41
 
 
42
 
    /**
43
 
     * Does the indent need to be exactly right.
44
 
     *
45
 
     * If TRUE, indent needs to be exactly $indent spaces. If FALSE,
46
 
     * indent needs to be at least $indent spaces (but can be more).
47
 
     *
48
 
     * @var bool
49
 
     */
50
 
    public $exact = false;
51
 
 
52
 
    /**
53
 
     * List of tokens not needing to be checked for indentation.
54
 
     *
55
 
     * Useful to allow Sniffs based on this to easily ignore/skip some
56
 
     * tokens from verification. For example, inline html sections
57
 
     * or php open/close tags can escape from here and have their own
58
 
     * rules elsewhere.
59
 
     *
60
 
     * @var array
61
 
     */
62
 
    public $ignoreIndentationTokens = array();
63
 
 
64
 
    /**
65
 
     * Any scope openers that should not cause an indent.
66
 
     *
67
 
     * @var array(int)
68
 
     */
69
 
    protected $nonIndentingScopes = array();
70
 
 
71
 
    /**
72
 
     * Stores the indent of the PHP open tags we found.
73
 
     *
74
 
     * This value is used to calculate the expected indent of top level structures
75
 
     * so we don't assume they are always at column 1. If PHP code is embedded inside
76
 
     * HTML (etc.) code, then the starting column for that code may not be column 1.
77
 
     *
78
 
     * @var int[]
79
 
     */
80
 
    private $_openTagIndents = array();
81
 
 
82
 
    /**
83
 
     * Stores the indent of the PHP close tags we found.
84
 
     *
85
 
     * This value is used to calculate the expected indent of top level structures
86
 
     * so we don't assume they are always at column 1. If PHP code is embedded inside
87
 
     * HTML (etc.) code, then the starting column for that code may not be column 1.
88
 
     *
89
 
     * @var int[]
90
 
     */
91
 
    private $_closeTagIndents = array();
92
 
 
93
 
 
94
 
    /**
95
 
     * Returns an array of tokens this test wants to listen for.
96
 
     *
97
 
     * @return array
98
 
     */
99
 
    public function register()
100
 
    {
101
 
        $tokens   = PHP_CodeSniffer_Tokens::$scopeOpeners;
102
 
        $tokens[] = T_OPEN_TAG;
103
 
        $tokens[] = T_CLOSE_TAG;
104
 
        return $tokens;
105
 
 
106
 
    }//end register()
107
 
 
108
 
 
109
 
    /**
110
 
     * Processes this test, when one of its tokens is encountered.
111
 
     *
112
 
     * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document.
113
 
     * @param int                  $stackPtr  The position of the current token
114
 
     *                                        in the stack passed in $tokens.
115
 
     *
116
 
     * @return void
117
 
     */
118
 
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
119
 
    {
120
 
        $tokens = $phpcsFile->getTokens();
121
 
 
122
 
        // We only want to record the indent of open tags, not process them.
123
 
        if ($tokens[$stackPtr]['code'] == T_OPEN_TAG) {
124
 
            $indent = ($tokens[$stackPtr]['column'] - 1);
125
 
            if (empty($this->_closeTagIndents) === false
126
 
                && $indent === $this->_closeTagIndents[0]
127
 
            ) {
128
 
                array_shift($this->_closeTagIndents);
129
 
            } else {
130
 
                array_unshift($this->_openTagIndents, $indent);
131
 
            }
132
 
 
133
 
            return;
134
 
        }
135
 
 
136
 
        if ($tokens[$stackPtr]['code'] == T_CLOSE_TAG) {
137
 
            $indent = ($tokens[$stackPtr]['column'] - 1);
138
 
            if ($indent === $this->_openTagIndents[0]) {
139
 
                array_shift($this->_openTagIndents);
140
 
            } else {
141
 
                array_unshift($this->_closeTagIndents, $indent);
142
 
            }
143
 
 
144
 
            return;
145
 
        }
146
 
 
147
 
        // If this is an inline condition (ie. there is no scope opener), then
148
 
        // return, as this is not a new scope.
149
 
        if (isset($tokens[$stackPtr]['scope_opener']) === false) {
150
 
            return;
151
 
        }
152
 
 
153
 
        if ($tokens[$stackPtr]['code'] === T_ELSE) {
154
 
            $next = $phpcsFile->findNext(
155
 
                PHP_CodeSniffer_Tokens::$emptyTokens,
156
 
                ($stackPtr + 1),
157
 
                null,
158
 
                true
159
 
            );
160
 
 
161
 
            // We will handle the T_IF token in another call to process.
162
 
            if ($tokens[$next]['code'] === T_IF) {
163
 
                return;
164
 
            }
165
 
        }
166
 
 
167
 
        // Find the first token on this line.
168
 
        $firstToken = $stackPtr;
169
 
        for ($i = $stackPtr; $i >= 0; $i--) {
170
 
            // Record the first code token on the line.
171
 
            if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) {
172
 
                $firstToken = $i;
173
 
            }
174
 
 
175
 
            // It's the start of the line, so we've found our first php token.
176
 
            if ($tokens[$i]['column'] === 1) {
177
 
                break;
178
 
            }
179
 
        }
180
 
 
181
 
        // Based on the conditions that surround this token, determine the
182
 
        // indent that we expect this current content to be.
183
 
        $expectedIndent = $this->calculateExpectedIndent($tokens, $firstToken);
184
 
 
185
 
        // Don't process the first token if it is a closure because they have
186
 
        // different indentation rules as they are often used as function arguments
187
 
        // for multi-line function calls. But continue to process the content of the
188
 
        // closure because it should be indented as normal.
189
 
        if ($tokens[$firstToken]['code'] !== T_CLOSURE
190
 
            && $tokens[$firstToken]['column'] !== $expectedIndent
191
 
        ) {
192
 
            // If the scope opener is a closure but it is not the first token on the
193
 
            // line, then the first token may be a variable or array index as so
194
 
            // should not require exact indentation unless the exact member var
195
 
            // is set to TRUE.
196
 
            $exact = true;
197
 
            if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
198
 
                $exact = $this->exact;
199
 
            }
200
 
 
201
 
            if ($exact === true || $tokens[$firstToken]['column'] < $expectedIndent) {
202
 
                $error = 'Line indented incorrectly; expected %s spaces, found %s';
203
 
                $data  = array(
204
 
                          ($expectedIndent - 1),
205
 
                          ($tokens[$firstToken]['column'] - 1),
206
 
                         );
207
 
                $phpcsFile->addError($error, $stackPtr, 'Incorrect', $data);
208
 
            }
209
 
        }//end if
210
 
 
211
 
        $scopeOpener = $tokens[$stackPtr]['scope_opener'];
212
 
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
213
 
 
214
 
        // Some scopes are expected not to have indents.
215
 
        if (in_array($tokens[$firstToken]['code'], $this->nonIndentingScopes) === false) {
216
 
            $indent = ($expectedIndent + $this->indent);
217
 
        } else {
218
 
            $indent = $expectedIndent;
219
 
        }
220
 
 
221
 
        $newline     = false;
222
 
        $commentOpen = false;
223
 
        $inHereDoc   = false;
224
 
 
225
 
        // Only loop over the content between the opening and closing brace, not
226
 
        // the braces themselves.
227
 
        for ($i = ($scopeOpener + 1); $i < $scopeCloser; $i++) {
228
 
 
229
 
            // If this token is another scope, skip it as it will be handled by
230
 
            // another call to this sniff.
231
 
            if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$scopeOpeners) === true) {
232
 
                if (isset($tokens[$i]['scope_opener']) === true) {
233
 
                    $i = $tokens[$i]['scope_closer'];
234
 
 
235
 
                    // If the scope closer is followed by a semi-colon, the semi-colon is part
236
 
                    // of the closer and should also be ignored. This most commonly happens with
237
 
                    // CASE statements that end with "break;", where we don't want to stop
238
 
                    // ignoring at the break, but rather at the semi-colon.
239
 
                    $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($i + 1), null, true);
240
 
                    if ($tokens[$nextToken]['code'] === T_SEMICOLON) {
241
 
                        $i = $nextToken;
242
 
                    }
243
 
                } else {
244
 
                    // If this token does not have a scope_opener indice, then
245
 
                    // it's probably an inline scope, so let's skip to the next
246
 
                    // semicolon. Inline scopes include inline if's, abstract
247
 
                    // methods etc.
248
 
                    $nextToken = $phpcsFile->findNext(T_SEMICOLON, $i, $scopeCloser);
249
 
                    if ($nextToken !== false) {
250
 
                        $i = $nextToken;
251
 
                    }
252
 
                }
253
 
 
254
 
                continue;
255
 
            }//end if
256
 
 
257
 
            // If this is a HEREDOC then we need to ignore it as the
258
 
            // whitespace before the contents within the HEREDOC are
259
 
            // considered part of the content.
260
 
            if ($tokens[$i]['code'] === T_START_HEREDOC
261
 
                || $tokens[$i]['code'] === T_START_NOWDOC
262
 
            ) {
263
 
                $inHereDoc = true;
264
 
                continue;
265
 
            } else if ($inHereDoc === true) {
266
 
                if ($tokens[$i]['code'] === T_END_HEREDOC
267
 
                    || $tokens[$i]['code'] === T_END_NOWDOC
268
 
                ) {
269
 
                    $inHereDoc = false;
270
 
                }
271
 
 
272
 
                continue;
273
 
            }
274
 
 
275
 
            if ($tokens[$i]['column'] === 1) {
276
 
                // We started a newline.
277
 
                $newline = true;
278
 
            }
279
 
 
280
 
            if ($newline === true && $tokens[$i]['code'] !== T_WHITESPACE) {
281
 
                // If we started a newline and we find a token that is not
282
 
                // whitespace, then this must be the first token on the line that
283
 
                // must be indented.
284
 
                $newline    = false;
285
 
                $firstToken = $i;
286
 
 
287
 
                $column = $tokens[$firstToken]['column'];
288
 
 
289
 
                // Ignore the token for indentation if it's in the ignore list.
290
 
                if (in_array($tokens[$firstToken]['code'], $this->ignoreIndentationTokens)
291
 
                    || in_array($tokens[$firstToken]['type'], $this->ignoreIndentationTokens)
292
 
                ) {
293
 
                    continue;
294
 
                }
295
 
 
296
 
                // Special case for non-PHP code.
297
 
                if ($tokens[$firstToken]['code'] === T_INLINE_HTML) {
298
 
                    $trimmedContentLength
299
 
                        = strlen(ltrim($tokens[$firstToken]['content']));
300
 
                    if ($trimmedContentLength === 0) {
301
 
                        continue;
302
 
                    }
303
 
 
304
 
                    $contentLength = strlen($tokens[$firstToken]['content']);
305
 
                    $column        = ($contentLength - $trimmedContentLength + 1);
306
 
                }
307
 
 
308
 
                // Check to see if this constant string spans multiple lines.
309
 
                // If so, then make sure that the strings on lines other than the
310
 
                // first line are indented appropriately, based on their whitespace.
311
 
                if (in_array($tokens[$firstToken]['code'], PHP_CodeSniffer_Tokens::$stringTokens) === true) {
312
 
                    if (in_array($tokens[($firstToken - 1)]['code'], PHP_CodeSniffer_Tokens::$stringTokens) === true) {
313
 
                        // If we find a string that directly follows another string
314
 
                        // then its just a string that spans multiple lines, so we
315
 
                        // don't need to check for indenting.
316
 
                        continue;
317
 
                    }
318
 
                }
319
 
 
320
 
                // This is a special condition for T_DOC_COMMENT and C-style
321
 
                // comments, which contain whitespace between each line.
322
 
                if (in_array($tokens[$firstToken]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) {
323
 
                    $content = trim($tokens[$firstToken]['content']);
324
 
                    if (preg_match('|^/\*|', $content) !== 0) {
325
 
                        // Check to see if the end of the comment is on the same line
326
 
                        // as the start of the comment. If it is, then we don't
327
 
                        // have to worry about opening a comment.
328
 
                        if (preg_match('|\*/$|', $content) === 0) {
329
 
                            // We don't have to calculate the column for the
330
 
                            // start of the comment as there is a whitespace
331
 
                            // token before it.
332
 
                            $commentOpen = true;
333
 
                        }
334
 
                    } else if ($commentOpen === true) {
335
 
                        if ($content === '') {
336
 
                            // We are in a comment, but this line has nothing on it
337
 
                            // so let's skip it.
338
 
                            continue;
339
 
                        }
340
 
 
341
 
                        $contentLength = strlen($tokens[$firstToken]['content']);
342
 
                        $trimmedContentLength
343
 
                            = strlen(ltrim($tokens[$firstToken]['content']));
344
 
 
345
 
                        $column = ($contentLength - $trimmedContentLength + 1);
346
 
                        if (preg_match('|\*/$|', $content) !== 0) {
347
 
                            $commentOpen = false;
348
 
                        }
349
 
 
350
 
                        // We are in a comment, so the indent does not have to
351
 
                        // be exact. The important thing is that the comment opens
352
 
                        // at the correct column and nothing sits closer to the left
353
 
                        // than that opening column.
354
 
                        if ($column > $indent) {
355
 
                            continue;
356
 
                        }
357
 
                    }//end if
358
 
                }//end if
359
 
 
360
 
                // The token at the start of the line, needs to have its' column
361
 
                // greater than the relative indent we set above. If it is less,
362
 
                // an error should be shown.
363
 
                if ($column !== $indent) {
364
 
                    if ($this->exact === true || $column < $indent) {
365
 
                        $type  = 'IncorrectExact';
366
 
                        $error = 'Line indented incorrectly; expected ';
367
 
                        if ($this->exact === false) {
368
 
                            $error .= 'at least ';
369
 
                            $type   = 'Incorrect';
370
 
                        }
371
 
 
372
 
                        $error .= '%s spaces, found %s';
373
 
                        $data = array(
374
 
                                 ($indent - 1),
375
 
                                 ($column - 1),
376
 
                                );
377
 
                        $phpcsFile->addError($error, $firstToken, $type, $data);
378
 
                    }
379
 
                }//end if
380
 
            }//end if
381
 
        }//end for
382
 
 
383
 
    }//end process()
384
 
 
385
 
 
386
 
    /**
387
 
     * Calculates the expected indent of a token.
388
 
     *
389
 
     * Returns the column at which the token should be indented to, so 1 means
390
 
     * that the token should not be indented at all.
391
 
     *
392
 
     * @param array $tokens   The stack of tokens for this file.
393
 
     * @param int   $stackPtr The position of the token to get indent for.
394
 
     *
395
 
     * @return int
396
 
     */
397
 
    protected function calculateExpectedIndent(array $tokens, $stackPtr)
398
 
    {
399
 
        $conditionStack = array();
400
 
 
401
 
        $inParenthesis = false;
402
 
        if (isset($tokens[$stackPtr]['nested_parenthesis']) === true
403
 
            && empty($tokens[$stackPtr]['nested_parenthesis']) === false
404
 
        ) {
405
 
            $inParenthesis = true;
406
 
        }
407
 
 
408
 
        // Empty conditions array (top level structure).
409
 
        if (empty($tokens[$stackPtr]['conditions']) === true) {
410
 
            if ($inParenthesis === true) {
411
 
                // Wrapped in parenthesis means it is probably in a
412
 
                // function call (like a closure) so we have to assume indent
413
 
                // is correct here and someone else will check it more
414
 
                // carefully in another sniff.
415
 
                return $tokens[$stackPtr]['column'];
416
 
            } else {
417
 
                return ($this->_openTagIndents[0] + 1);
418
 
            }
419
 
        }
420
 
 
421
 
        $indent = 0;
422
 
 
423
 
        $tokenConditions = $tokens[$stackPtr]['conditions'];
424
 
        foreach ($tokenConditions as $id => $condition) {
425
 
            // If it's not an indenting scope i.e., it's in our array of
426
 
            // scopes that don't indent, skip it.
427
 
            if (in_array($condition, $this->nonIndentingScopes) === true) {
428
 
                continue;
429
 
            }
430
 
 
431
 
            if ($condition === T_CLOSURE && $inParenthesis === true) {
432
 
                // Closures cause problems with indents when they are
433
 
                // used as function arguments because the code inside them
434
 
                // is not technically inside the function yet, so the indent
435
 
                // is always off by one. So instead, use the
436
 
                // indent of the closure as the base value.
437
 
                $lastContent = $id;
438
 
                for ($i = ($id - 1); $i > 0; $i--) {
439
 
                    if ($tokens[$i]['line'] !== $tokens[$id]['line']) {
440
 
                        // Changed lines, so the last content we saw is what
441
 
                        // we want.
442
 
                        break;
443
 
                    }
444
 
 
445
 
                    if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) {
446
 
                        $lastContent = $i;
447
 
                    }
448
 
                }
449
 
 
450
 
                $indent = ($tokens[$lastContent]['column'] - 1);
451
 
            }
452
 
 
453
 
            $indent += $this->indent;
454
 
        }//end foreach
455
 
 
456
 
        // Increase by 1 to indiciate that the code should start at a specific column.
457
 
        // E.g., code indented 4 spaces should start at column 5.
458
 
        $indent++;
459
 
 
460
 
        // Take the indent of the open tag into account.
461
 
        $indent += $this->_openTagIndents[0];
462
 
 
463
 
        return $indent;
464
 
 
465
 
    }//end calculateExpectedIndent()
466
 
 
467
 
 
468
 
}//end class
469
 
 
470
 
?>