Subversion Repositories Applications.framework

Rev

Rev 5 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
5 aurelien 1
<?php
2
/**
3
 * A PHP_CodeSniffer_File object represents a PHP source file and the tokens
4
 * associated with it.
5
 *
6
 * PHP version 5
7
 *
8
 * @category  PHP
9
 * @package   PHP_CodeSniffer
10
 * @author    Greg Sherwood <gsherwood@squiz.net>
11
 * @author    Marc McIntyre <mmcintyre@squiz.net>
12
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
13
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
34 aurelien 14
 * @version   CVS: $Id: File.php 34 2009-04-09 07:34:39Z aurelien $
5 aurelien 15
 * @link      http://pear.php.net/package/PHP_CodeSniffer
16
 */
17
 
18
/**
19
 * A PHP_CodeSniffer_File object represents a PHP source file and the tokens
20
 * associated with it.
21
 *
22
 * It provides a means for traversing the token stack, along with
23
 * other token related operations. If a PHP_CodeSniffer_Sniff finds and error or
24
 *  warning within a PHP_CodeSniffer_File, you can raise an error using the
25
 *  addError() or addWarning() methods.
26
 *
27
 * <b>Token Information</b>
28
 *
29
 * Each token within the stack contains information about itself:
30
 *
31
 * <code>
32
 *   array(
33
 *    'code'       => 301,       // the token type code (see token_get_all())
34
 *    'content'    => 'if',      // the token content
35
 *    'type'       => 'T_IF',    // the token name
36
 *    'line'       => 56,        // the line number when the token is located
37
 *    'column'     => 12,        // the column in the line where this token
38
 *                               // starts (starts from 1)
39
 *    'level'      => 2          // the depth a token is within the scopes open
40
 *    'conditions' => array(     // a list of scope condition token
41
 *                               // positions => codes that
42
 *                     2 => 50,  // openened the scopes that this token exists
43
 *                     9 => 353, // in (see conditional tokens section below)
44
 *                    ),
45
 *   );
46
 * </code>
47
 *
48
 * <b>Conditional Tokens</b>
49
 *
50
 * In addition to the standard token fields, conditions contain information to
51
 * determine where their scope begins and ends:
52
 *
53
 * <code>
54
 *   array(
55
 *    'scope_condition' => 38, // the token position of the condition
56
 *    'scope_opener'    => 41, // the token position that started the scope
57
 *    'scope_closer'    => 70, // the token position that ended the scope
58
 *   );
59
 * </code>
60
 *
61
 * The condition, the scope opener and the scope closer each contain this
62
 * information.
63
 *
64
 * <b>Parenthesis Tokens</b>
65
 *
66
 * Each parenthesis token (T_OPEN_PARENTHESIS and T_CLOSE_PARENTHESIS) has a
67
 * reference to their opening and closing parenthesis, one being itself, the
68
 * other being its oposite.
69
 *
70
 * <code>
71
 *   array(
72
 *    'parenthesis_opener' => 34,
73
 *    'parenthesis_closer' => 40,
74
 *   );
75
 * </code>
76
 *
77
 * Some tokens can "own" a set of parethesis. For example a T_FUNCTION token
78
 * has parenthesis around its argument list. These tokens also have the
79
 * parenthesis_opener and and parenthesis_closer indicies. Not all parethesis
80
 * have owners, for example parenthesis used for arithmetic operations and
81
 * function calls. The parenthesis tokens that have an owner have the following
82
 * auxilery array indicies.
83
 *
84
 * <code>
85
 *   array(
86
 *    'parenthesis_opener' => 34,
87
 *    'parenthesis_closer' => 40,
88
 *    'parenthesis_owner'  => 33,
89
 *   );
90
 * </code>
91
 *
92
 * Each token within a set of parenthesis also has an array indicy
93
 * 'nested_parenthesis' which is an array of the
94
 * left parenthesis => right parenthesis token positions.
95
 *
96
 * <code>
97
 *   'nested_parentheisis' => array(
98
 *                             12 => 15
99
 *                             11 => 14
100
 *                            );
101
 * </code>
102
 *
103
 * <b>Extended Tokens</b>
104
 *
105
 * PHP_CodeSniffer extends and augments some of the tokens created by
106
 * <i>token_get_all()</i>. A full list of these tokens can be seen in the
107
 * <i>Tokens.php</i> file.
108
 *
109
 * @category  PHP
110
 * @package   PHP_CodeSniffer
111
 * @author    Greg Sherwood <gsherwood@squiz.net>
112
 * @author    Marc McIntyre <mmcintyre@squiz.net>
113
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
114
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
115
 * @version   Release: 1.2.0RC1
116
 * @link      http://pear.php.net/package/PHP_CodeSniffer
117
 */
118
class PHP_CodeSniffer_File
119
{
120
 
121
    /**
122
     * The absolute path to the file associated with this object.
123
     *
124
     * @var string
125
     */
126
    private $_file = '';
127
 
128
    /**
129
     * The EOL character this file uses.
130
     *
131
     * @var string
132
     */
133
    public $eolChar = '';
134
 
135
    /**
136
     * The tokenizer being used for this file.
137
     *
138
     * @var object
139
     */
140
    public $tokenizer = null;
141
 
142
    /**
143
     * The tokenizer being used for this file.
144
     *
145
     * @var string
146
     */
147
    public $tokenizerType = 'PHP';
148
 
149
    /**
150
     * The number of tokens in this file.
151
     *
152
     * Stored here to save calling count() everywhere.
153
     *
154
     * @var int
155
     */
156
    public $numTokens = 0;
157
 
158
    /**
159
     * The tokens stack map.
160
     *
161
     * Note that the tokens in this array differ in format to the tokens
162
     * produced by token_get_all(). Tokens are initially produced with
163
     * token_get_all(), then augmented so that it's easier to process them.
164
     *
165
     * @var array()
166
     * @see Tokens.php
167
     */
168
    private $_tokens = array();
169
 
170
    /**
171
     * The errors raised from PHP_CodeSniffer_Sniffs.
172
     *
173
     * @var array()
174
     * @see getErrors()
175
     */
176
    private $_errors = array();
177
 
178
    /**
179
     * The warnings raised form PHP_CodeSniffer_Sniffs.
180
     *
181
     * @var array()
182
     * @see getWarnings()
183
     */
184
    private $_warnings = array();
185
 
186
    /**
187
     * The total number of errors raised.
188
     *
189
     * @var int
190
     */
191
    private $_errorCount = 0;
192
 
193
    /**
194
     * The total number of warnings raised.
195
     *
196
     * @var int
197
     */
198
    private $_warningCount = 0;
199
 
200
    /**
201
     * An array of sniffs listening to this file's processing.
202
     *
203
     * @var array(PHP_CodeSniffer_Sniff)
204
     */
205
    private $_listeners = array();
206
 
207
     /**
208
     * The class name of the sniff currently processing the file.
209
     *
210
     * @var string
211
     */
212
    private $_activeListener = '';
213
 
214
    /**
215
     * A constant to represent an error in PHP_CodeSniffer.
216
     *
217
     * @var int
218
     */
219
    const ERROR = 0;
220
 
221
    /**
222
     * A constant to represent a warning in PHP_CodeSniffer.
223
     *
224
     * @var int
225
     */
226
    const WARNING = 1;
227
 
228
    /**
229
     * An array of extensions mapping to the tokenizer to use.
230
     *
231
     * This value gets set by PHP_CodeSniffer when the object is created.
232
     *
233
     * @var array
234
     */
235
    protected $tokenizers = array();
236
 
237
 
238
    /**
239
     * Constructs a PHP_CodeSniffer_File.
240
     *
241
     * @param string        $file       The absolute path to the file
242
     *                                  to process.
243
     * @param array(string) $listeners  The initial listeners listening
244
     *                                  to processing of this file.
245
     * @param array         $tokenizers An array of extensions mapping
246
     *                                  to the tokenizer to use.
247
     *
248
     * @throws PHP_CodeSniffer_Exception If the register() method does
249
     *                                   not return an array.
250
     */
251
    public function __construct($file, array $listeners, array $tokenizers)
252
    {
253
        $this->_file      = trim($file);
254
        $this->_listeners = $listeners;
255
        $this->tokenizers = $tokenizers;
256
 
257
    }//end __construct()
258
 
259
 
260
    /**
261
     * Sets the name of the currently active sniff.
262
     *
263
     * @param string $activeListener The class name of the current sniff.
264
     *
265
     * @return void
266
     */
267
    public function setActiveListener($activeListener)
268
    {
269
        $this->_activeListener = $activeListener;
270
 
271
    }//end setActiveListener()
272
 
273
 
274
    /**
275
     * Adds a listener to the token stack that listens to the specific tokens.
276
     *
277
     * When PHP_CodeSniffer encounters on the the tokens specified in $tokens, it
278
     *  invokes the process method of the sniff.
279
     *
280
     * @param PHP_CodeSniffer_Sniff $listener The listener to add to the
281
     *                                        listener stack.
282
     * @param array(int)            $tokens   The token types the listener wishes to
283
     *                                        listen to.
284
     *
285
     * @return void
286
     */
287
    public function addTokenListener(PHP_CodeSniffer_Sniff $listener, array $tokens)
288
    {
289
        foreach ($tokens as $token) {
290
            if (isset($this->_listeners[$token]) === false) {
291
                $this->_listeners[$token] = array();
292
            }
293
 
294
            if (in_array($listener, $this->_listeners[$token], true) === false) {
295
                $this->_listeners[$token][] = $listener;
296
            }
297
        }
298
 
299
    }//end addTokenListener()
300
 
301
 
302
    /**
303
     * Removes a listener from listening from the specified tokens.
304
     *
305
     * @param PHP_CodeSniffer_Sniff $listener The listener to remove from the
306
     *                                        listener stack.
307
     * @param array(int)            $tokens   The token types the listener wishes to
308
     *                                        stop listen to.
309
     *
310
     * @return void
311
     */
312
    public function removeTokenListener(
313
        PHP_CodeSniffer_Sniff $listener,
314
        array $tokens
315
    ) {
316
        foreach ($tokens as $token) {
317
            if (isset($this->_listeners[$token]) === false) {
318
                continue;
319
            }
320
 
321
            if (in_array($listener, $this->_listeners[$token]) === true) {
322
                foreach ($this->_listeners[$token] as $pos => $value) {
323
                    if ($value === $listener) {
324
                        unset($this->_listeners[$token][$pos]);
325
                    }
326
                }
327
            }
328
        }
329
 
330
    }//end removeTokenListener()
331
 
332
 
333
    /**
334
     * Returns the token stack for this file.
335
     *
336
     * @return array()
337
     */
338
    public function getTokens()
339
    {
340
        return $this->_tokens;
341
 
342
    }//end getTokens()
343
 
344
 
345
    /**
346
     * Starts the stack traversal and tells listeners when tokens are found.
347
     *
348
     * @param string $contents The contents to parse. If NULL, the content
349
     *                         is taken from the file system.
350
     *
351
     * @return void
352
     */
353
    public function start($contents=null)
354
    {
355
        $this->_parse($contents);
356
 
357
        if (PHP_CODESNIFFER_VERBOSITY > 2) {
358
            echo "\t*** START TOKEN PROCESSING ***".PHP_EOL;
359
        }
360
 
361
        // Foreach of the listeners that have registed to listen for this
362
        // token, get them to process it.
363
        foreach ($this->_tokens as $stackPtr => $token) {
364
            if (PHP_CODESNIFFER_VERBOSITY > 2) {
365
                $type    = $token['type'];
366
                $content = str_replace($this->eolChar, '\n', $token['content']);
367
                echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL;
368
            }
369
 
370
            $tokenType = $token['code'];
371
            if (isset($this->_listeners[$tokenType]) === true) {
372
                foreach ($this->_listeners[$tokenType] as $listener) {
373
                    // Make sure this sniff supports the tokenizer
374
                    // we are currently using.
375
                    $vars = get_class_vars(get_class($listener));
376
                    if (isset($vars['supportedTokenizers']) === true) {
377
                        if (in_array($this->tokenizerType, $vars['supportedTokenizers']) === false) {
378
                            continue;
379
                        }
380
                    } else {
381
                        // The default supported tokenizer is PHP.
382
                        if ($this->tokenizerType !== 'PHP') {
383
                            continue;
384
                        }
385
                    }
386
 
387
                    $this->setActiveListener(get_class($listener));
388
 
389
                    if (PHP_CODESNIFFER_VERBOSITY > 2) {
390
                        $startTime = microtime(true);
391
                        echo "\t\t\tProcessing ".$this->_activeListener.'... ';
392
                    }
393
 
394
                    $listener->process($this, $stackPtr);
395
                    $this->_activeListener = '';
396
 
397
                    if (PHP_CODESNIFFER_VERBOSITY > 2) {
398
                        $timeTaken = round((microtime(true) - $startTime), 4);
399
                        echo "DONE in $timeTaken seconds".PHP_EOL;
400
                    }
401
                }//end foreach
402
            }//end if
403
        }//end foreach
404
 
405
        if (PHP_CODESNIFFER_VERBOSITY > 2) {
406
            echo "\t*** END TOKEN PROCESSING ***".PHP_EOL;
407
        }
408
 
409
    }//end start()
410
 
411
 
412
    /**
413
     * Remove vars stored in this sniff that are no longer required.
414
     *
415
     * @return void
416
     */
417
    public function cleanUp()
418
    {
419
        $this->_tokens    = null;
420
        $this->_listeners = null;
421
 
422
    }//end cleanUp()
423
 
424
 
425
    /**
426
     * Tokenizes the file and preapres it for the test run.
427
     *
428
     * @param string $contents The contents to parse. If NULL, the content
429
     *                         is taken from the file system.
430
     *
431
     * @return void
432
     */
433
    private function _parse($contents=null)
434
    {
435
        $this->eolChar = self::detectLineEndings($this->_file, $contents);
436
 
437
        // Determine the tokenizer from the file extension.
438
        $fileParts = explode('.', $this->_file);
439
        $extension = array_pop($fileParts);
440
        if (isset($this->tokenizers[$extension]) === true) {
441
            $tokenizerClass      = 'PHP_CodeSniffer_Tokenizers_'.$this->tokenizers[$extension];
442
            $this->tokenizerType = $this->tokenizers[$extension];
443
        } else {
444
            // Revert to default.
445
            $tokenizerClass = 'PHP_CodeSniffer_Tokenizers_'.$this->tokenizerType;
446
        }
447
 
448
        $tokenizer       = new $tokenizerClass();
449
        $this->tokenizer = $tokenizer;
450
 
451
        if ($contents === null) {
452
            $contents = file_get_contents($this->_file);
453
        }
454
 
455
        $this->_tokens = self::tokenizeString($contents, $tokenizer, $this->eolChar);
456
        $this->numTokens = count($this->_tokens);
457
 
458
        if (PHP_CODESNIFFER_VERBOSITY > 0) {
459
            if ($this->numTokens === 0) {
460
                $numLines = 0;
461
            } else {
462
                $numLines = $this->_tokens[($this->numTokens - 1)]['line'];
463
            }
464
 
465
            echo "[$this->numTokens tokens in $numLines lines]... ";
466
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
467
                echo PHP_EOL;
468
            }
469
        }
470
 
471
    }//end _parse()
472
 
473
 
474
    /**
475
     * Opens a file and detects the EOL character being used.
476
     *
477
     * @param string $file     The full path to the file.
478
     * @param string $contents The contents to parse. If NULL, the content
479
     *                         is taken from the file system.
480
     *
481
     * @return string
482
     * @throws PHP_CodeSniffer_Exception If $file could not be opened.
483
     */
484
    public static function detectLineEndings($file, $contents=null)
485
    {
486
        if ($contents === null) {
487
            // Determine the newline character being used in this file.
488
            // Will be either \r, \r\n or \n.
489
            $handle = fopen($file, 'r');
490
            if ($handle === false) {
491
                $error = 'Error opening file; could not auto-detect line endings';
492
                throw new PHP_CodeSniffer_Exception($error);
493
            }
494
 
495
            $firstLine = fgets($handle);
496
            fclose($handle);
497
 
498
            $eolChar = substr($firstLine, -1);
499
            if ($eolChar === "\n") {
500
                $secondLastChar = substr($firstLine, -2, 1);
501
                if ($secondLastChar === "\r") {
502
                    $eolChar = "\r\n";
503
                }
504
            }
505
        } else {
506
            if (preg_match("/\r\n?|\n/", $contents, $matches) !== 1) {
507
                $error = 'Could not auto-detect line endings from content';
508
                throw new PHP_CodeSniffer_Exception($error);
509
            }
510
 
511
            $eolChar = $matches[0];
512
        }//end if
513
 
514
        return $eolChar;
515
 
516
    }//end detectLineEndings()
517
 
518
 
519
    /**
520
     * Adds an error to the error stack.
521
     *
522
     * @param string $error    The error message.
523
     * @param int    $stackPtr The stack position where the error occured.
524
     *
525
     * @return void
526
     */
527
    public function addError($error, $stackPtr)
528
    {
529
        // Work out which sniff generated the error.
530
        $parts = explode('_', $this->_activeListener);
531
        $sniff = $parts[0].'.'.$parts[2].'.'.$parts[3];
532
 
533
        if ($stackPtr === null) {
534
            $lineNum = 1;
535
            $column  = 1;
536
        } else {
537
            $lineNum = $this->_tokens[$stackPtr]['line'];
538
            $column  = $this->_tokens[$stackPtr]['column'];
539
        }
540
 
541
        if (isset($this->_errors[$lineNum]) === false) {
542
            $this->errors[$lineNum] = array();
543
        }
544
 
545
        if (isset($this->_errors[$lineNum][$column]) === false) {
546
            $this->errors[$lineNum][$column] = array();
547
        }
548
 
549
        $this->_errors[$lineNum][$column][] = array(
550
                                               'message' => $error,
551
                                               'source'  => $sniff,
552
                                              );
553
        $this->_errorCount++;
554
 
555
    }//end addError()
556
 
557
 
558
    /**
559
     * Adds an warning to the warning stack.
560
     *
561
     * @param string $warning  The error message.
562
     * @param int    $stackPtr The stack position where the error occured.
563
     *
564
     * @return void
565
     */
566
    public function addWarning($warning, $stackPtr)
567
    {
568
        // Work out which sniff generated the warning.
569
        $parts = explode('_', $this->_activeListener);
570
        $sniff = $parts[0].'.'.$parts[2].'.'.$parts[3];
571
 
572
        if ($stackPtr === null) {
573
            $lineNum = 1;
574
            $column  = 1;
575
        } else {
576
            $lineNum = $this->_tokens[$stackPtr]['line'];
577
            $column  = $this->_tokens[$stackPtr]['column'];
578
        }
579
 
580
        if (isset($this->_warnings[$lineNum]) === false) {
581
            $this->_warnings[$lineNum] = array();
582
        }
583
 
584
        if (isset($this->_warnings[$lineNum][$column]) === false) {
585
            $this->_warnings[$lineNum][$column] = array();
586
        }
587
 
588
        $this->_warnings[$lineNum][$column][] = array(
589
                                               'message' => $warning,
590
                                               'source'  => $sniff,
591
                                              );
592
        $this->_warningCount++;
593
 
594
    }//end addWarning()
595
 
596
 
597
    /**
598
     * Returns the number of errors raised.
599
     *
600
     * @return int
601
     */
602
    public function getErrorCount()
603
    {
604
        return $this->_errorCount;
605
 
606
    }//end getErrorCount()
607
 
608
 
609
    /**
610
     * Returns the number of warnings raised.
611
     *
612
     * @return int
613
     */
614
    public function getWarningCount()
615
    {
616
        return $this->_warningCount;
617
 
618
    }//end getWarningCount()
619
 
620
 
621
    /**
622
     * Returns the errors raised from processing this file.
623
     *
624
     * @return array
625
     */
626
    public function getErrors()
627
    {
628
        return $this->_errors;
629
 
630
    }//end getErrors()
631
 
632
 
633
    /**
634
     * Returns the warnings raised from processing this file.
635
     *
636
     * @return array
637
     */
638
    public function getWarnings()
639
    {
640
        return $this->_warnings;
641
 
642
    }//end getWarnings()
643
 
644
 
645
    /**
646
     * Returns the absolute filename of this file.
647
     *
648
     * @return string
649
     */
650
    public function getFilename()
651
    {
652
        return $this->_file;
653
 
654
    }//end getFilename()
655
 
656
 
657
    /**
658
     * Creates an array of tokens when given some PHP code.
659
     *
660
     * Starts by using token_get_all() but does a lot of extra processing
661
     * to insert information about the context of the token.
662
     *
663
     * @param string $string    The string to tokenize.
664
     * @param object $tokenizer A tokenizer class to use to tokenize the string.
665
     * @param string $eolChar   The EOL character to use for splitting strings.
666
     *
667
     * @return array
668
     */
669
    public static function tokenizeString($string, $tokenizer, $eolChar='\n')
670
    {
671
        $tokens = $tokenizer->tokenizeString($string, $eolChar);
672
 
673
        self::_createLineMap($tokens, $tokenizer, $eolChar);
674
        self::_createBracketMap($tokens, $tokenizer, $eolChar);
675
        self::_createParenthesisMap($tokens, $tokenizer, $eolChar);
676
        self::_createParenthesisNestingMap($tokens, $tokenizer, $eolChar);
677
        self::_createScopeMap($tokens, $tokenizer, $eolChar);
678
 
679
        // If we know the width of each tab, convert tabs
680
        // into spaces so sniffs can use one method of checking.
681
        if (PHP_CODESNIFFER_TAB_WIDTH > 0) {
682
            self::_convertTabs($tokens, $tokenizer, $eolChar);
683
        }
684
 
685
        // Column map requires the line map to be complete.
686
        self::_createColumnMap($tokens, $tokenizer, $eolChar);
687
        self::_createLevelMap($tokens, $tokenizer, $eolChar);
688
 
689
        // Allow the tokenizer to do additional processing if required.
690
        $tokenizer->processAdditional($tokens, $eolChar);
691
 
692
        return $tokens;
693
 
694
    }//end tokenizeString()
695
 
696
 
697
    /**
698
     * Creates a map of tokens => line numbers for each token.
699
     *
700
     * @param array  &$tokens   The array of tokens to process.
701
     * @param object $tokenizer The tokenizer being used to process this file.
702
     * @param string $eolChar   The EOL character to use for splitting strings.
703
     *
704
     * @return void
705
     */
706
    private static function _createLineMap(&$tokens, $tokenizer, $eolChar)
707
    {
708
        $lineNumber = 1;
709
        $count      = count($tokens);
710
 
711
        for ($i = 0; $i < $count; $i++) {
712
            $tokens[$i]['line'] = $lineNumber;
713
            if ($tokens[$i]['content'] === '') {
714
                continue;
715
            }
716
 
717
            $lineNumber += substr_count($tokens[$i]['content'], $eolChar);
718
        }
719
 
720
    }//end _createLineMap()
721
 
722
 
723
    /**
724
     * Converts tabs into spaces.
725
     *
726
     * Each tab can represent between 1 and $width spaces, so
727
     * this cannot be a straight string replace.
728
     *
729
     * @param array  &$tokens   The array of tokens to process.
730
     * @param object $tokenizer The tokenizer being used to process this file.
731
     * @param string $eolChar   The EOL character to use for splitting strings.
732
     *
733
     * @return void
734
     */
735
    private static function _convertTabs(&$tokens, $tokenizer, $eolChar)
736
    {
737
        $currColumn = 1;
738
        $count      = count($tokens);
739
 
740
        for ($i = 0; $i < $count; $i++) {
741
            $tokenContent = $tokens[$i]['content'];
742
 
743
            if (strpos($tokenContent, "\t") === false) {
744
                // There are no tabs in this content.
745
                $currColumn += (strlen($tokenContent) - 1);
746
            } else {
747
                // We need to determine the length of each tab.
748
                $tabs = preg_split(
749
                    "|(\t)|",
750
                    $tokenContent,
751
                    -1,
752
                    PREG_SPLIT_DELIM_CAPTURE
753
                );
754
 
755
                $tabNum       = 0;
756
                $adjustedTab  = false;
757
                $tabsToSpaces = array();
758
                $newContent   = '';
759
 
760
                foreach ($tabs as $content) {
761
                    if ($content === '') {
762
                        continue;
763
                    }
764
 
765
                    if (strpos($content, "\t") === false) {
766
                        // This piece of content is not a tab.
767
                        $currColumn += strlen($content);
768
                        $newContent .= $content;
769
                    } else {
770
                        $lastCurrColumn = $currColumn;
771
                        $tabNum++;
772
 
773
                        // Move the pointer to the next tab stop.
774
                        if (($currColumn % PHP_CODESNIFFER_TAB_WIDTH) === 0) {
775
                            // This is the first tab, and we are already at a
776
                            // tab stop, so this tab counts as a single space.
777
                            $currColumn++;
778
                            $adjustedTab = true;
779
                        } else {
780
                            $currColumn++;
781
                            while (($currColumn % PHP_CODESNIFFER_TAB_WIDTH) != 0) {
782
                                $currColumn++;
783
                            }
784
 
785
                            $currColumn++;
786
                        }
787
 
788
                        $length      = ($currColumn - $lastCurrColumn);
789
                        $newContent .= str_repeat(' ', $length);
790
                    }//end if
791
                }//end foreach
792
 
793
                if ($tabNum === 1 && $adjustedTab === true) {
794
                    $currColumn--;
795
                }
796
 
797
                $tokens[$i]['content'] = $newContent;
798
            }//end if
799
 
800
            if (isset($tokens[($i + 1)]['line']) === true
801
                && $tokens[($i + 1)]['line'] !== $tokens[$i]['line']
802
            ) {
803
                $currColumn = 1;
804
            } else {
805
                $currColumn++;
806
            }
807
        }//end for
808
 
809
    }//end _convertTabs()
810
 
811
 
812
    /**
813
     * Creates a column map.
814
     *
815
     * The column map indicates where the token started on the line where it
816
     * exists.
817
     *
818
     * @param array  &$tokens   The array of tokens to process.
819
     * @param object $tokenizer The tokenizer being used to process this file.
820
     * @param string $eolChar   The EOL character to use for splitting strings.
821
     *
822
     * @return void
823
     */
824
    private static function _createColumnMap(&$tokens, $tokenizer, $eolChar)
825
    {
826
        $currColumn = 1;
827
        $count      = count($tokens);
828
 
829
        for ($i = 0; $i < $count; $i++) {
830
            $tokens[$i]['column'] = $currColumn;
831
            if (isset($tokens[($i + 1)]['line']) === true
832
                && $tokens[($i + 1)]['line'] !== $tokens[$i]['line']
833
            ) {
834
                $currColumn = 1;
835
            } else {
836
                $currColumn += strlen($tokens[$i]['content']);
837
            }
838
        }
839
 
840
    }//end _createColumnMap()
841
 
842
 
843
    /**
844
     * Creates a map for opening and closing of square brackets.
845
     *
846
     * Each bracket token (T_OPEN_SQUARE_BRACKET and T_CLOSE_SQUARE_BRACKET)
847
     * has a reference to their opening and closing bracket
848
     * (bracket_opener and bracket_closer).
849
     *
850
     * @param array  &$tokens   The array of tokens to process.
851
     * @param object $tokenizer The tokenizer being used to process this file.
852
     * @param string $eolChar   The EOL character to use for splitting strings.
853
     *
854
     * @return void
855
     */
856
    private static function _createBracketMap(&$tokens, $tokenizer, $eolChar)
857
    {
858
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
859
            echo "\t*** START BRACKET MAP ***".PHP_EOL;
860
        }
861
 
862
        $squareOpeners = array();
863
        $curlyOpeners  = array();
864
        $numTokens     = count($tokens);
865
 
866
        for ($i = 0; $i < $numTokens; $i++) {
867
            switch ($tokens[$i]['code']) {
868
            case T_OPEN_SQUARE_BRACKET:
869
                $squareOpeners[] = $i;
870
 
871
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
872
                    echo str_repeat("\t", count($squareOpeners));
873
                    echo str_repeat("\t", count($curlyOpeners));
874
                    echo "=> Found square bracket opener at $i".PHP_EOL;
875
                }
876
 
877
                break;
878
            case T_OPEN_CURLY_BRACKET:
879
                if (isset($tokens[$i]['scope_closer']) === false) {
880
                    $curlyOpeners[] = $i;
881
 
882
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
883
                        echo str_repeat("\t", count($squareOpeners));
884
                        echo str_repeat("\t", count($curlyOpeners));
885
                        echo "=> Found curly bracket opener at $i".PHP_EOL;
886
                    }
887
                }
888
                break;
889
            case T_CLOSE_SQUARE_BRACKET:
890
                if (empty($squareOpeners) === false) {
891
                    $opener                            = array_pop($squareOpeners);
892
                    $tokens[$i]['bracket_opener']      = $opener;
893
                    $tokens[$i]['bracket_closer']      = $i;
894
                    $tokens[$opener]['bracket_opener'] = $opener;
895
                    $tokens[$opener]['bracket_closer'] = $i;
896
 
897
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
898
                        echo str_repeat("\t", count($squareOpeners));
899
                        echo str_repeat("\t", count($curlyOpeners));
900
                        echo "\t=> Found square bracket closer at $i for $opener".PHP_EOL;
901
                    }
902
                }
903
                break;
904
            case T_CLOSE_CURLY_BRACKET:
905
                if (empty($curlyOpeners) === false
906
                    && isset($tokens[$i]['scope_opener']) === false
907
                ) {
908
                    $opener                            = array_pop($curlyOpeners);
909
                    $tokens[$i]['bracket_opener']      = $opener;
910
                    $tokens[$i]['bracket_closer']      = $i;
911
                    $tokens[$opener]['bracket_opener'] = $opener;
912
                    $tokens[$opener]['bracket_closer'] = $i;
913
 
914
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
915
                        echo str_repeat("\t", count($squareOpeners));
916
                        echo str_repeat("\t", count($curlyOpeners));
917
                        echo "\t=> Found curly bracket closer at $i for $opener".PHP_EOL;
918
                    }
919
                }
920
                break;
921
            default:
922
                continue;
923
            }//end switch
924
        }//end for
925
 
926
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
927
            echo "\t*** END BRACKET MAP ***".PHP_EOL;
928
        }
929
 
930
    }//end _createBracketMap()
931
 
932
 
933
    /**
934
     * Creates a map for opening and closing of parenthesis.
935
     *
936
     * Each parenthesis token (T_OPEN_PARENTHESIS and T_CLOSE_PARENTHESIS) has a
937
     * reference to their opening and closing parenthesis (parenthesis_opener
938
     * and parenthesis_closer).
939
     *
940
     * @param array  &$tokens   The array of tokens to process.
941
     * @param object $tokenizer The tokenizer being used to process this file.
942
     * @param string $eolChar   The EOL character to use for splitting strings.
943
     *
944
     * @return void
945
     */
946
    private static function _createParenthesisMap(&$tokens, $tokenizer, $eolChar)
947
    {
948
        $openers   = array();
949
        $numTokens = count($tokens);
950
        $openOwner = null;
951
 
952
        for ($i = 0; $i < $numTokens; $i++) {
953
            if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$parenthesisOpeners) === true) {
954
                $tokens[$i]['parenthesis_opener'] = null;
955
                $tokens[$i]['parenthesis_closer'] = null;
956
                $tokens[$i]['parenthesis_owner']  = $i;
957
                $openOwner                        = $i;
958
            } else if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
959
                $openers[]                        = $i;
960
                $tokens[$i]['parenthesis_opener'] = $i;
961
                if ($openOwner !== null) {
962
                    $tokens[$openOwner]['parenthesis_opener'] = $i;
963
                    $tokens[$i]['parenthesis_owner']          = $openOwner;
964
                    $openOwner                                = null;
965
                }
966
            } else if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
967
                // Did we set an owner for this set of parenthesis?
968
                $numOpeners = count($openers);
969
                if ($numOpeners !== 0) {
970
                    $opener = array_pop($openers);
971
                    if (isset($tokens[$opener]['parenthesis_owner']) === true) {
972
                        $owner = $tokens[$opener]['parenthesis_owner'];
973
 
974
                        $tokens[$owner]['parenthesis_closer'] = $i;
975
                        $tokens[$i]['parenthesis_owner']      = $owner;
976
                    }
977
 
978
                    $tokens[$i]['parenthesis_opener']      = $opener;
979
                    $tokens[$i]['parenthesis_closer']      = $i;
980
                    $tokens[$opener]['parenthesis_closer'] = $i;
981
                }
982
            }//end if
983
        }//end for
984
 
985
    }//end _createParenthesisMap()
986
 
987
 
988
    /**
989
     * Creates a map for the parenthesis tokens that surround other tokens.
990
     *
991
     * @param array  &$tokens   The array of tokens to process.
992
     * @param object $tokenizer The tokenizer being used to process this file.
993
     * @param string $eolChar   The EOL character to use for splitting strings.
994
     *
995
     * @return void
996
     */
997
    private static function _createParenthesisNestingMap(
998
        &$tokens,
999
        $tokenizer,
1000
        $eolChar
1001
    ) {
1002
        $numTokens = count($tokens);
1003
        $map       = array();
1004
        for ($i = 0; $i < $numTokens; $i++) {
1005
            if (isset($tokens[$i]['parenthesis_opener']) === true
1006
                && $i === $tokens[$i]['parenthesis_opener']
1007
            ) {
1008
                if (empty($map) === false) {
1009
                    $tokens[$i]['nested_parenthesis'] = $map;
1010
                }
1011
 
1012
                if (isset($tokens[$i]['parenthesis_closer']) === true) {
1013
                    $map[$tokens[$i]['parenthesis_opener']]
1014
                        = $tokens[$i]['parenthesis_closer'];
1015
                }
1016
            } else if (isset($tokens[$i]['parenthesis_closer']) === true
1017
                && $i === $tokens[$i]['parenthesis_closer']
1018
            ) {
1019
                array_pop($map);
1020
                if (empty($map) === false) {
1021
                    $tokens[$i]['nested_parenthesis'] = $map;
1022
                }
1023
            } else {
1024
                if (empty($map) === false) {
1025
                    $tokens[$i]['nested_parenthesis'] = $map;
1026
                }
1027
            }
1028
        }//end for
1029
 
1030
    }//end _createParenthesisNestingMap()
1031
 
1032
 
1033
    /**
1034
     * Creates a scope map of tokens that open scopes.
1035
     *
1036
     * @param array  &$tokens   The array of tokens to process.
1037
     * @param object $tokenizer The tokenizer being used to process this file.
1038
     * @param string $eolChar   The EOL character to use for splitting strings.
1039
     *
1040
     * @return void
1041
     * @see _recurseScopeMap()
1042
     */
1043
    private static function _createScopeMap(&$tokens, $tokenizer, $eolChar)
1044
    {
1045
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1046
            echo "\t*** START SCOPE MAP ***".PHP_EOL;
1047
        }
1048
 
1049
        $numTokens = count($tokens);
1050
        for ($i = 0; $i < $numTokens; $i++) {
1051
            // Check to see if the current token starts a new scope.
1052
            if (isset($tokenizer->scopeOpeners[$tokens[$i]['code']]) === true) {
1053
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1054
                    $type    = $tokens[$i]['type'];
1055
                    $content = str_replace($eolChar, '\n', $tokens[$i]['content']);
1056
                    echo "\tStart scope map at $i: $type => $content".PHP_EOL;
1057
                }
1058
 
1059
                $i = self::_recurseScopeMap(
1060
                    $tokens,
1061
                    $numTokens,
1062
                    $tokenizer,
1063
                    $eolChar,
1064
                    $i
1065
                );
1066
            }
1067
        }//end for
1068
 
1069
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1070
            echo "\t*** END SCOPE MAP ***".PHP_EOL;
1071
        }
1072
 
1073
    }//end _createScopeMap()
1074
 
1075
 
1076
    /**
1077
     * Recurses though the scope openers to build a scope map.
1078
     *
1079
     * @param array  &$tokens   The array of tokens to process.
1080
     * @param int    $numTokens The size of the tokens array.
1081
     * @param object $tokenizer The tokenizer being used to process this file.
1082
     * @param string $eolChar   The EOL character to use for splitting strings.
1083
     * @param int    $stackPtr  The position in the stack of the token that
1084
     *                          opened the scope (eg. an IF token or FOR token).
1085
     * @param int    $depth     How many scope levels down we are.
1086
     * @param int    &$ignore   How many curly braces we are ignoring.
1087
     *
1088
     * @return int The position in the stack that closed the scope.
1089
     */
1090
    private static function _recurseScopeMap(
1091
        &$tokens,
1092
        $numTokens,
1093
        $tokenizer,
1094
        $eolChar,
1095
        $stackPtr,
1096
        $depth=1,
1097
        &$ignore=0
1098
    ) {
1099
        $opener    = null;
1100
        $currType  = $tokens[$stackPtr]['code'];
1101
        $startLine = $tokens[$stackPtr]['line'];
1102
 
1103
        // If the start token for this scope opener is the same as
1104
        // the scope token, we have already found our opener.
1105
        if ($currType === $tokenizer->scopeOpeners[$currType]['start']) {
1106
            $opener = $stackPtr;
1107
        }
1108
 
1109
        for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1110
            $tokenType = $tokens[$i]['code'];
1111
 
1112
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1113
                $type    = $tokens[$i]['type'];
1114
                $content = str_replace($eolChar, '\n', $tokens[$i]['content']);
1115
                echo str_repeat("\t", $depth);
1116
                echo "Process token $i [";
1117
                if ($opener !== null) {
1118
                    echo "opener:$opener;";
1119
                }
1120
 
1121
                if ($ignore > 0) {
1122
                    echo "ignore=$ignore;";
1123
                }
1124
 
1125
                echo "]: $type => $content".PHP_EOL;
1126
            }
1127
 
1128
            // Is this an opening condition ?
1129
            if (isset($tokenizer->scopeOpeners[$tokenType]) === true) {
1130
                if ($opener === null) {
1131
                    // Found another opening condition but still haven't
1132
                    // found our opener, so we are never going to find one.
1133
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1134
                        $type = $tokens[$stackPtr]['type'];
1135
                        echo str_repeat("\t", $depth);
1136
                        echo "=> Couldn't find scope opener for $stackPtr ($type), bailing".PHP_EOL;
1137
                    }
1138
 
1139
                    return $stackPtr;
1140
                }
1141
 
1142
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1143
                    echo str_repeat("\t", $depth);
1144
                    echo '* token is an opening condition *'.PHP_EOL;
1145
                }
1146
 
1147
                $isShared
1148
                    = ($tokenizer->scopeOpeners[$tokenType]['shared'] === true);
1149
 
1150
                if (isset($tokens[$i]['scope_condition']) === true) {
1151
                    // We've been here before.
1152
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1153
                        echo str_repeat("\t", $depth);
1154
                        echo '* already processed, skipping *'.PHP_EOL;
1155
                    }
1156
 
1157
                    if ($isShared === false
1158
                        && isset($tokens[$i]['scope_closer']) === true
1159
                    ) {
1160
                        $i = $tokens[$i]['scope_closer'];
1161
                    }
1162
 
1163
                    continue;
1164
                } else if ($currType === $tokenType
1165
                    && $isShared === false
1166
                    && $opener === null
1167
                ) {
1168
                    // We haven't yet found our opener, but we have found another
1169
                    // scope opener which is the same type as us, and we don't
1170
                    // share openers, so we will never find one.
1171
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1172
                        echo str_repeat("\t", $depth);
1173
                        echo '* it was another token\'s opener, bailing *'.PHP_EOL;
1174
                    }
1175
 
1176
                    return $stackPtr;
1177
                } else {
1178
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1179
                        echo str_repeat("\t", $depth);
1180
                        echo '* searching for opener *'.PHP_EOL;
1181
                    }
1182
 
1183
                    if ($tokenizer->scopeOpeners[$tokenType]['end'] === T_CLOSE_CURLY_BRACKET) {
1184
                        $oldIgnore = $ignore;
1185
                        $ignore = 0;
1186
                    }
1187
 
1188
                    $i = self::_recurseScopeMap(
1189
                        $tokens,
1190
                        $numTokens,
1191
                        $tokenizer,
1192
                        $eolChar,
1193
                        $i,
1194
                        ($depth + 1),
1195
                        $ignore
1196
                    );
1197
 
1198
                    if ($tokenizer->scopeOpeners[$tokenType]['end'] === T_CLOSE_CURLY_BRACKET) {
1199
                        $ignore = $oldIgnore;
1200
                    }
1201
                }//end if
1202
            }//end if start scope
1203
 
1204
            if ($tokenType === $tokenizer->scopeOpeners[$currType]['start']
1205
                && $opener === null
1206
            ) {
1207
                if ($tokenType === T_OPEN_CURLY_BRACKET) {
1208
                    // Make sure this is actually an opener and not a
1209
                    // string offset (e.g., $var{0}).
1210
                    for ($x = ($i - 1); $x > 0; $x--) {
1211
                        if (in_array($tokens[$x]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === true) {
1212
                            continue;
1213
                        } else {
1214
                            // If the first non-whitespace/comment token is a
1215
                            // variable or object operator then this is an opener
1216
                            // for a string offset and not a scope.
1217
                            if ($tokens[$x]['code'] === T_VARIABLE
1218
                                || $tokens[$x]['code'] === T_OBJECT_OPERATOR
1219
                            ) {
1220
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1221
                                    echo str_repeat("\t", $depth);
1222
                                    echo '* ignoring curly brace *'.PHP_EOL;
1223
                                }
1224
 
1225
                                $ignore++;
1226
                            }//end if
1227
 
1228
                            break;
1229
                        }//end if
1230
                    }//end for
1231
                }//end if
1232
 
1233
                if ($ignore === 0) {
1234
                    // We found the opening scope token for $currType.
1235
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1236
                        $type = $tokens[$stackPtr]['type'];
1237
                        echo str_repeat("\t", $depth);
1238
                        echo "=> Found scope opener for $stackPtr ($type)".PHP_EOL;
1239
                    }
1240
 
1241
                    $opener = $i;
1242
                }
1243
            } else if ($tokenType === $tokenizer->scopeOpeners[$currType]['end']
1244
                && $opener !== null
1245
            ) {
1246
                if ($ignore > 0 && $tokenType === T_CLOSE_CURLY_BRACKET) {
1247
                    // The last opening bracket must have been for a string
1248
                    // offset or alike, so let's ignore it.
1249
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1250
                        echo str_repeat("\t", $depth);
1251
                        echo '* finished ignoring curly brace *'.PHP_EOL;
1252
                    }
1253
 
1254
                    $ignore--;
1255
                } else {
1256
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1257
                        $type = $tokens[$stackPtr]['type'];
1258
                        echo str_repeat("\t", $depth);
1259
                        echo "=> Found scope closer for $stackPtr ($type)".PHP_EOL;
1260
                    }
1261
 
1262
                    foreach (array($stackPtr, $opener, $i) as $token) {
1263
                        $tokens[$token]['scope_condition'] = $stackPtr;
1264
                        $tokens[$token]['scope_opener']    = $opener;
1265
                        $tokens[$token]['scope_closer']    = $i;
1266
                    }
1267
 
1268
                    if ($tokenizer->scopeOpeners[$tokens[$stackPtr]['code']]['shared'] === true) {
1269
                        return $opener;
1270
                    } else {
1271
                        return $i;
1272
                    }
1273
                }//end if
1274
            } else if ($tokenType === T_OPEN_PARENTHESIS) {
1275
                if (isset($tokens[$i]['parenthesis_owner']) === true) {
1276
                    $owner = $tokens[$i]['parenthesis_owner'];
1277
                    if (in_array($tokens[$owner]['code'], PHP_CodeSniffer_Tokens::$scopeOpeners) === true
1278
                        && isset($tokens[$i]['parenthesis_closer']) === true
1279
                    ) {
1280
                        // If we get into here, then we opened a parenthesis for
1281
                        // a scope (eg. an if or else if). We can just skip to
1282
                        // the closing parenthesis.
1283
                        $i = $tokens[$i]['parenthesis_closer'];
1284
 
1285
                        // Update the start of the line so that when we check to see
1286
                        // if the closing parenthesis is more than 3 lines away from
1287
                        // the statement, we check from the closing parenthesis.
1288
                        $startLine
1289
                            = $tokens[$tokens[$i]['parenthesis_closer']]['line'];
1290
 
1291
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1292
                            echo str_repeat("\t", $depth);
1293
                            echo '* skipping parenthesis *'.PHP_EOL;
1294
                        }
1295
                    }
1296
                }
1297
            } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) {
1298
                // We opened something that we don't have a scope opener for.
1299
                // Examples of this are curly brackets for string offsets etc.
1300
                // We want to ignore this so that we don't have an invalid scope
1301
                // map.
1302
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1303
                    echo str_repeat("\t", $depth);
1304
                    echo '* ignoring curly brace *'.PHP_EOL;
1305
                }
1306
 
1307
                $ignore++;
1308
            } else if ($opener === null
1309
                && isset($tokenizer->scopeOpeners[$currType]) === true
1310
            ) {
1311
                // If we still haven't found the opener after 3 lines,
1312
                // we're not going to find it, unless we know it requires
1313
                // an opener, in which case we better keep looking.
1314
                if ($tokens[$i]['line'] >= ($startLine + 3)) {
1315
                    if ($tokenizer->scopeOpeners[$currType]['strict'] === true) {
1316
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1317
                            $type  = $tokens[$stackPtr]['type'];
1318
                            $lines = ($tokens[$i]['line'] - $startLine);
1319
                            echo str_repeat("\t", $depth);
1320
                            echo "=> Still looking for $stackPtr ($type) scope opener after $lines lines".PHP_EOL;
1321
                        }
1322
                    } else {
1323
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1324
                            $type = $tokens[$stackPtr]['type'];
1325
                            echo str_repeat("\t", $depth);
1326
                            echo "=> Couldn't find scope opener for $stackPtr ($type), bailing".PHP_EOL;
1327
                        }
1328
 
1329
                        return $stackPtr;
1330
                    }
1331
                }
1332
            } else if ($opener !== null
1333
                && $tokenType !== T_BREAK
1334
                && in_array($tokenType, $tokenizer->endScopeTokens) === true
1335
            ) {
1336
                if (isset($tokens[$i]['scope_condition']) === false) {
1337
                    if ($ignore > 0) {
1338
                        // We found the end token for the opener we were ignoring.
1339
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1340
                            echo str_repeat("\t", $depth);
1341
                            echo '* finished ignoring curly brace *'.PHP_EOL;
1342
                        }
1343
 
1344
                        $ignore--;
1345
                    } else {
1346
                        // We found a token that closes the scope but it doesn't
1347
                        // have a condition, so it belongs to another token and
1348
                        // our token doesn't have a closer, so pretend this is
1349
                        // the closer.
1350
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1351
                            $type = $tokens[$stackPtr]['type'];
1352
                            echo str_repeat("\t", $depth);
1353
                            echo "=> Found (unexpected) scope closer for $stackPtr ($type)".PHP_EOL;
1354
                        }
1355
 
1356
                        foreach (array($stackPtr, $opener) as $token) {
1357
                            $tokens[$token]['scope_condition'] = $stackPtr;
1358
                            $tokens[$token]['scope_opener']    = $opener;
1359
                            $tokens[$token]['scope_closer']    = $i;
1360
                        }
1361
 
1362
                        return ($i - 1);
1363
                    }//end if
1364
                }//end if
1365
            }//end if
1366
        }//end for
1367
 
1368
        return $stackPtr;
1369
 
1370
    }//end _recurseScopeMap()
1371
 
1372
 
1373
    /**
1374
     * Constructs the level map.
1375
     *
1376
     * The level map adds a 'level' indice to each token which indicates the
1377
     * depth that a token within a set of scope blocks. It also adds a
1378
     * 'condition' indice which is an array of the scope conditions that opened
1379
     * each of the scopes - position 0 being the first scope opener.
1380
     *
1381
     * @param array  &$tokens   The array of tokens to process.
1382
     * @param object $tokenizer The tokenizer being used to process this file.
1383
     * @param string $eolChar   The EOL character to use for splitting strings.
1384
     *
1385
     * @return void
1386
     */
1387
    private static function _createLevelMap(&$tokens, $tokenizer, $eolChar)
1388
    {
1389
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1390
            echo "\t*** START LEVEL MAP ***".PHP_EOL;
1391
        }
1392
 
1393
        $numTokens  = count($tokens);
1394
        $level      = 0;
1395
        $conditions = array();
1396
        $lastOpener = null;
1397
        $openers    = array();
1398
 
1399
        for ($i = 0; $i < $numTokens; $i++) {
1400
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1401
                $type    = $tokens[$i]['type'];
1402
                $line    = $tokens[$i]['line'];
1403
                $content = str_replace($eolChar, '\n', $tokens[$i]['content']);
1404
                echo str_repeat("\t", ($level + 1));
1405
                echo "Process token $i on line $line [lvl:$level;";
1406
                if (empty($conditions) !== true) {
1407
                    $condString = 'conds;';
1408
                    foreach ($conditions as $condition) {
1409
                        $condString .= token_name($condition).',';
1410
                    }
1411
 
1412
                    echo rtrim($condString, ',').';';
1413
                }
1414
 
1415
                echo "]: $type => $content".PHP_EOL;
1416
            }
1417
 
1418
            $tokens[$i]['level']      = $level;
1419
            $tokens[$i]['conditions'] = $conditions;
1420
 
1421
            if (isset($tokens[$i]['scope_condition']) === true) {
1422
                // Check to see if this token opened the scope.
1423
                if ($tokens[$i]['scope_opener'] === $i) {
1424
                    $stackPtr = $tokens[$i]['scope_condition'];
1425
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1426
                        $type = $tokens[$stackPtr]['type'];
1427
                        echo str_repeat("\t", ($level + 1));
1428
                        echo "=> Found scope opener for $stackPtr ($type)".PHP_EOL;
1429
                    }
1430
 
1431
                    $stackPtr = $tokens[$i]['scope_condition'];
1432
 
1433
                    // If we find a scope opener that has a shared closer,
1434
                    // then we need to go back over the condition map that we
1435
                    // just created and fix ourselves as we just added some
1436
                    // conditions where there was none. This happens for T_CASE
1437
                    // statements that are using the same break statement.
1438
                    if ($lastOpener !== null && $tokens[$lastOpener]['scope_closer'] === $tokens[$i]['scope_closer']) {
1439
                        // This opener shares its closer with the previous opener,
1440
                        // but we still need to check if the two openers share their
1441
                        // closer with each other directly (like CASE and DEFAULT)
1442
                        // or if they are just sharing because one doesn't have a
1443
                        // closer (like CASE with no BREAK using a SWITCHes closer).
1444
                        $thisType = $tokens[$tokens[$i]['scope_condition']]['code'];
1445
                        $opener   = $tokens[$lastOpener]['scope_condition'];
1446
 
1447
                        $isShared = in_array(
1448
                            $tokens[$opener]['code'],
1449
                            $tokenizer->scopeOpeners[$thisType]['with']
1450
                        );
1451
 
1452
                        $sameEnd = ($tokenizer->scopeOpeners[$thisType]['end'] === $tokenizer->scopeOpeners[$tokens[$opener]['code']]['end']);
1453
                        if ($isShared === true && $sameEnd === true) {
1454
                            $badToken = $opener;
1455
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1456
                                $type = $tokens[$badToken]['type'];
1457
                                echo str_repeat("\t", ($level + 1));
1458
                                echo "* shared closer, cleaning up $badToken ($type) *".PHP_EOL;
1459
                            }
1460
 
1461
                            for ($x = $tokens[$i]['scope_condition']; $x <= $i; $x++) {
1462
                                $oldConditions = $tokens[$x]['conditions'];
1463
                                $oldLevel      = $tokens[$x]['level'];
1464
                                $tokens[$x]['level']--;
1465
                                unset($tokens[$x]['conditions'][$badToken]);
1466
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
1467
                                    $type     = $tokens[$x]['type'];
1468
                                    $oldConds = '';
1469
                                    foreach ($oldConditions as $condition) {
1470
                                        $oldConds .= token_name($condition).',';
1471
                                    }
1472
 
1473
                                    $oldConds = rtrim($oldConds, ',');
1474
 
1475
                                    $newConds = '';
1476
                                    foreach ($tokens[$x]['conditions'] as $condition) {
1477
                                        $newConds .= token_name($condition).',';
1478
                                    }
1479
 
1480
                                    $newConds = rtrim($newConds, ',');
1481
 
1482
                                    $newLevel = $tokens[$x]['level'];
1483
                                    echo str_repeat("\t", ($level + 1));
1484
                                    echo "* cleaned $x ($type) *".PHP_EOL;
1485
                                    echo str_repeat("\t", ($level + 2));
1486
                                    echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
1487
                                    echo str_repeat("\t", ($level + 2));
1488
                                    echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
1489
                                }//end if
1490
                            }//end for
1491
 
1492
                            unset($conditions[$badToken]);
1493
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1494
                                $type = $tokens[$badToken]['type'];
1495
                                echo str_repeat("\t", ($level + 1));
1496
                                echo "* token $badToken ($type) removed from conditions array *".PHP_EOL;
1497
                            }
1498
 
1499
                            unset ($openers[$lastOpener]);
1500
 
1501
                            $level--;
1502
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1503
                                echo str_repeat("\t", ($level + 2));
1504
                                echo '* level decreased *'.PHP_EOL;
1505
                            }
1506
                        }//end if
1507
                    }//end if
1508
 
1509
                    $level++;
1510
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1511
                        echo str_repeat("\t", ($level + 1));
1512
                        echo '* level increased *'.PHP_EOL;
1513
                    }
1514
 
1515
                    $conditions[$stackPtr] = $tokens[$stackPtr]['code'];
1516
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1517
                        $type = $tokens[$stackPtr]['type'];
1518
                        echo str_repeat("\t", ($level + 1));
1519
                        echo "* token $stackPtr ($type) added to conditions array *".PHP_EOL;
1520
                    }
1521
 
1522
                    $lastOpener = $tokens[$i]['scope_opener'];
1523
                    if ($lastOpener !== null) {
1524
                        $openers[$lastOpener] = $lastOpener;
1525
                    }
1526
                } else if ($tokens[$i]['scope_closer'] === $i) {
1527
                    foreach (array_reverse($openers) as $opener) {
1528
                        if ($tokens[$opener]['scope_closer'] === $i) {
1529
                            $oldOpener = array_pop($openers);
1530
                            if (empty($openers) === false) {
1531
                                $lastOpener           = array_pop($openers);
1532
                                $openers[$lastOpener] = $lastOpener;
1533
                            } else {
1534
                                $lastOpener = null;
1535
                            }
1536
 
1537
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1538
                                $type = $tokens[$oldOpener]['type'];
1539
                                echo str_repeat("\t", ($level + 1));
1540
                                echo "=> Found scope closer for $oldOpener ($type)".PHP_EOL;
1541
                            }
1542
 
1543
                            $oldCondition = array_pop($conditions);
1544
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1545
                                echo str_repeat("\t", ($level + 1));
1546
                                echo '* token '.token_name($oldCondition).' removed from conditions array *'.PHP_EOL;
1547
                            }
1548
 
1549
                            // Make sure this closer actually belongs to us.
1550
                            // Either the condition also has to think this is the
1551
                            // closer, or it has to allow sharing with us.
1552
                            $condition
1553
                                = $tokens[$tokens[$i]['scope_condition']]['code'];
1554
                            if ($condition !== $oldCondition) {
1555
                                if (in_array($condition, $tokenizer->scopeOpeners[$oldCondition]['with']) === false) {
1556
                                    $badToken = $tokens[$oldOpener]['scope_condition'];
1557
 
1558
                                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
1559
                                        $type = token_name($oldCondition);
1560
                                        echo str_repeat("\t", ($level + 1));
1561
                                        echo "* scope closer was bad, cleaning up $badToken ($type) *".PHP_EOL;
1562
                                    }
1563
 
1564
                                    for ($x = ($oldOpener + 1); $x <= $i; $x++) {
1565
                                        $oldConditions = $tokens[$x]['conditions'];
1566
                                        $oldLevel      = $tokens[$x]['level'];
1567
                                        $tokens[$x]['level']--;
1568
                                        unset($tokens[$x]['conditions'][$badToken]);
1569
                                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1570
                                            $type     = $tokens[$x]['type'];
1571
                                            $oldConds = '';
1572
                                            foreach ($oldConditions as $condition) {
1573
                                                $oldConds .= token_name($condition).',';
1574
                                            }
1575
 
1576
                                            $oldConds = rtrim($oldConds, ',');
1577
 
1578
                                            $newConds = '';
1579
                                            foreach ($tokens[$x]['conditions'] as $condition) {
1580
                                                $newConds .= token_name($condition).',';
1581
                                            }
1582
 
1583
                                            $newConds = rtrim($newConds, ',');
1584
 
1585
                                            $newLevel = $tokens[$x]['level'];
1586
                                            echo str_repeat("\t", ($level + 1));
1587
                                            echo "* cleaned $x ($type) *".PHP_EOL;
1588
                                            echo str_repeat("\t", ($level + 2));
1589
                                            echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
1590
                                            echo str_repeat("\t", ($level + 2));
1591
                                            echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
1592
                                        }//end if
1593
                                    }//end for
1594
                                }//end if
1595
                            }//end if
1596
 
1597
                            $level--;
1598
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
1599
                                echo str_repeat("\t", ($level + 2));
1600
                                echo '* level decreased *'.PHP_EOL;
1601
                            }
1602
 
1603
                            $tokens[$i]['level']      = $level;
1604
                            $tokens[$i]['conditions'] = $conditions;
1605
                        }//end if
1606
                    }//end foreach
1607
                }//end if
1608
            }//end if
1609
        }//end for
1610
 
1611
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
1612
            echo "\t*** END LEVEL MAP ***".PHP_EOL;
1613
        }
1614
 
1615
    }//end _createLevelMap()
1616
 
1617
 
1618
    /**
1619
     * Returns the declaration names for T_CLASS, T_INTERFACE and T_FUNCTION tokens.
1620
     *
1621
     * @param int $stackPtr The position of the declaration token which
1622
     *                      declared the class, interface or function.
1623
     *
1624
     * @return string The name of the class, interface or function.
1625
     * @throws PHP_CodeSniffer_Exception If the specified token is not of type
1626
     *                                   T_FUNCTION, T_CLASS or T_INTERFACE.
1627
     */
1628
    public function getDeclarationName($stackPtr)
1629
    {
1630
        $tokenCode = $this->_tokens[$stackPtr]['code'];
1631
        if ($tokenCode !== T_FUNCTION
1632
            && $tokenCode !== T_CLASS
1633
            && $tokenCode !== T_INTERFACE
1634
        ) {
1635
            throw new PHP_CodeSniffer_Exception('Token type is not T_FUNCTION, T_CLASS OR T_INTERFACE');
1636
        }
1637
 
1638
        $token = $this->findNext(T_STRING, $stackPtr);
1639
        return $this->_tokens[$token]['content'];
1640
 
1641
    }//end getDeclarationName()
1642
 
1643
 
1644
    /**
1645
     * Returns the method parameters for the specified T_FUNCTION token.
1646
     *
1647
     * Each parameter is in the following format:
1648
     *
1649
     * <code>
1650
     *   0 => array(
1651
     *         'name'              => '$var',  // The variable name.
1652
     *         'pass_by_reference' => false,   // Passed by reference.
1653
     *         'type_hint'         => string,  // Type hint for array or custom type
1654
     *        )
1655
     * </code>
1656
     *
1657
     * Parameters with default values have and additional array indice of
1658
     * 'default' with the value of the default as a string.
1659
     *
1660
     * @param int $stackPtr The position in the stack of the T_FUNCTION token
1661
     *                      to acquire the parameters for.
1662
     *
1663
     * @return array()
1664
     * @throws PHP_CodeSniffer_Exception If the specified $stackPtr is not of
1665
     *                                   type T_FUNCTION.
1666
     */
1667
    public function getMethodParameters($stackPtr)
1668
    {
1669
        if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION) {
1670
            throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION');
1671
        }
1672
 
1673
        $opener = $this->_tokens[$stackPtr]['parenthesis_opener'];
1674
        $closer = $this->_tokens[$stackPtr]['parenthesis_closer'];
1675
 
1676
        $vars            = array();
1677
        $currVar         = null;
1678
        $defaultStart    = null;
1679
        $paramCount      = 0;
1680
        $passByReference = false;
1681
        $typeHint        = '';
1682
 
1683
        for ($i = ($opener + 1); $i <= $closer; $i++) {
1684
            // Check to see if this token has a parenthesis opener. If it does
1685
            // its likely to be an array, which might have arguments in it, which
1686
            // we cause problems in our parsing below, so lets just skip to the
1687
            // end of it.
1688
            if (isset($this->_tokens[$i]['parenthesis_opener']) === true) {
1689
                // Don't do this if its the close parenthesis for the method.
1690
                if ($i !== $this->_tokens[$i]['parenthesis_closer']) {
1691
                    $i = ($this->_tokens[$i]['parenthesis_closer'] + 1);
1692
                }
1693
            }
1694
 
1695
            switch ($this->_tokens[$i]['code']) {
1696
            case T_BITWISE_AND:
1697
                $passByReference = true;
1698
                break;
1699
            case T_VARIABLE:
1700
                $currVar = $i;
1701
                break;
1702
            case T_ARRAY_HINT:
1703
                $typeHint = $this->_tokens[$i]['content'];
1704
                break;
1705
            case T_STRING:
1706
                // This is a string, so it may be a type hint, but it could
1707
                // also be a constant used as a default value.
1708
                $prevComma = $this->findPrevious(T_COMMA, $i, $opener);
1709
                if ($prevComma !== false) {
1710
                    $nextEquals = $this->findNext(T_EQUAL, $prevComma, $i);
1711
                    if ($nextEquals !== false) {
1712
                        break;
1713
                    }
1714
                }
1715
 
1716
                $typeHint = $this->_tokens[$i]['content'];
1717
                break;
1718
            case T_CLOSE_PARENTHESIS:
1719
            case T_COMMA:
1720
                // If it's null, then there must be no parameters for this
1721
                // method.
1722
                if ($currVar === null) {
1723
                    continue;
1724
                }
1725
 
1726
                $vars[$paramCount]         = array();
1727
                $vars[$paramCount]['name'] = $this->_tokens[$currVar]['content'];
1728
 
1729
                if ($defaultStart !== null) {
1730
                    $vars[$paramCount]['default']
1731
                        = $this->getTokensAsString(
1732
                            $defaultStart,
1733
                            ($i - $defaultStart)
1734
                        );
1735
                }
1736
 
1737
                $vars[$paramCount]['pass_by_reference'] = $passByReference;
1738
                $vars[$paramCount]['type_hint']         = $typeHint;
1739
 
1740
                // Reset the vars, as we are about to process the next parameter.
1741
                $defaultStart    = null;
1742
                $passByReference = false;
1743
                $typeHint        = '';
1744
 
1745
                $paramCount++;
1746
                break;
1747
            case T_EQUAL:
1748
                $defaultStart = ($i + 1);
1749
                break;
1750
            }//end switch
1751
        }//end for
1752
 
1753
        return $vars;
1754
 
1755
    }//end getMethodParameters()
1756
 
1757
 
1758
    /**
1759
     * Returns the visibility and implementation properies of a method.
1760
     *
1761
     * The format of the array is:
1762
     * <code>
1763
     *   array(
1764
     *    'scope'           => 'public', // public private or protected
1765
     *    'scope_specified' => true,     // true is scope keyword was found.
1766
     *    'is_abstract'     => false,    // true if the abstract keyword was found.
1767
     *    'is_final'        => false,    // true if the final keyword was found.
1768
     *    'is_static'       => false,    // true if the static keyword was found.
1769
     *   );
1770
     * </code>
1771
     *
1772
     * @param int $stackPtr The position in the stack of the T_FUNCTION token to
1773
     *                      acquire the properties for.
1774
     *
1775
     * @return array
1776
     * @throws PHP_CodeSniffer_Exception If the specified position is not a
1777
     *                                   T_FUNCTION token.
1778
     */
1779
    public function getMethodProperties($stackPtr)
1780
    {
1781
        if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION) {
1782
            throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION');
1783
        }
1784
 
1785
        $valid = array(
1786
                  T_PUBLIC,
1787
                  T_PRIVATE,
1788
                  T_PROTECTED,
1789
                  T_STATIC,
1790
                  T_FINAL,
1791
                  T_ABSTRACT,
1792
                  T_WHITESPACE,
1793
                  T_COMMENT,
1794
                  T_DOC_COMMENT,
1795
                 );
1796
 
1797
        $scope          = 'public';
1798
        $scopeSpecified = false;
1799
        $isAbstract     = false;
1800
        $isFinal        = false;
1801
        $isStatic       = false;
1802
 
1803
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1804
            if (in_array($this->_tokens[$i]['code'], $valid) === false) {
1805
                break;
1806
            }
1807
 
1808
            switch ($this->_tokens[$i]['code']) {
1809
            case T_PUBLIC:
1810
                $scope          = 'public';
1811
                $scopeSpecified = true;
1812
                break;
1813
            case T_PRIVATE:
1814
                $scope          = 'private';
1815
                $scopeSpecified = true;
1816
                break;
1817
            case T_PROTECTED:
1818
                $scope          = 'protected';
1819
                $scopeSpecified = true;
1820
                break;
1821
            case T_ABSTRACT:
1822
                $isAbstract = true;
1823
                break;
1824
            case T_FINAL:
1825
                $isFinal = true;
1826
                break;
1827
            case T_STATIC:
1828
                $isStatic = true;
1829
                break;
1830
            }//end switch
1831
        }//end for
1832
 
1833
        return array(
1834
                'scope'           => $scope,
1835
                'scope_specified' => $scopeSpecified,
1836
                'is_abstract'     => $isAbstract,
1837
                'is_final'        => $isFinal,
1838
                'is_static'       => $isStatic,
1839
               );
1840
 
1841
    }//end getMethodProperties()
1842
 
1843
 
1844
    /**
1845
     * Returns the visibility and implementation properies of the class member
1846
     * variable found  at the specified position in the stack.
1847
     *
1848
     * The format of the array is:
1849
     *
1850
     * <code>
1851
     *   array(
1852
     *    'scope'       => 'public', // public private or protected
1853
     *    'is_static'   => false,    // true if the static keyword was found.
1854
     *   );
1855
     * </code>
1856
     *
1857
     * @param int $stackPtr The position in the stack of the T_VARIABLE token to
1858
     *                      acquire the properties for.
1859
     *
1860
     * @return array
1861
     * @throws PHP_CodeSniffer_Exception If the specified position is not a
1862
     *                                   T_VARIABLE token, or if the position is not
1863
     *                                   a class member variable.
1864
     */
1865
    public function getMemberProperties($stackPtr)
1866
    {
1867
        if ($this->_tokens[$stackPtr]['code'] !== T_VARIABLE) {
1868
            throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_VARIABLE');
1869
        }
1870
 
1871
        end($this->_tokens[$stackPtr]['conditions']);
1872
        $ptr = key($this->_tokens[$stackPtr]['conditions']);
1873
        if (isset($this->_tokens[$ptr]) === false
1874
            || $this->_tokens[$ptr]['code'] !== T_CLASS
1875
        ) {
1876
            if (isset($this->_tokens[$ptr]) === true
1877
                && $this->_tokens[$ptr]['code'] === T_INTERFACE
1878
            ) {
1879
                $error = 'Possible parse error: interfaces may not include member vars';
1880
                $this->addWarning($error, $stackPtr);
1881
                return array();
1882
            } else {
1883
                throw new PHP_CodeSniffer_Exception('$stackPtr is not a class member var');
1884
            }
1885
        }
1886
 
1887
        $valid = array(
1888
                  T_PUBLIC,
1889
                  T_PRIVATE,
1890
                  T_PROTECTED,
1891
                  T_STATIC,
1892
                  T_WHITESPACE,
1893
                  T_COMMENT,
1894
                  T_DOC_COMMENT,
1895
                 );
1896
 
1897
        $scope          = 'public';
1898
        $scopeSpecified = false;
1899
        $isStatic       = false;
1900
 
1901
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1902
            if (in_array($this->_tokens[$i]['code'], $valid) === false) {
1903
                break;
1904
            }
1905
 
1906
            switch ($this->_tokens[$i]['code']) {
1907
            case T_PUBLIC:
1908
                $scope          = 'public';
1909
                $scopeSpecified = true;
1910
                break;
1911
            case T_PRIVATE:
1912
                $scope          = 'private';
1913
                $scopeSpecified = true;
1914
                break;
1915
            case T_PROTECTED:
1916
                $scope          = 'protected';
1917
                $scopeSpecified = true;
1918
                break;
1919
            case T_STATIC:
1920
                $isStatic = true;
1921
                break;
1922
            }
1923
        }//end for
1924
 
1925
        return array(
1926
                'scope'           => $scope,
1927
                'scope_specified' => $scopeSpecified,
1928
                'is_static'       => $isStatic,
1929
               );
1930
 
1931
    }//end getMemberProperties()
1932
 
1933
 
1934
    /**
1935
     * Determine if the passed token is a reference operator.
1936
     *
1937
     * Returns true if the specified token position represents a reference.
1938
     * Returns false if the token represents a bitwise operator.
1939
     *
1940
     * @param int $stackPtr The position of the T_BITWISE_AND token.
1941
     *
1942
     * @return boolean
1943
     */
1944
    public function isReference($stackPtr)
1945
    {
1946
        if ($this->_tokens[$stackPtr]['code'] !== T_BITWISE_AND) {
1947
            return false;
1948
        }
1949
 
1950
        $tokenBefore = $this->findPrevious(
1951
            PHP_CodeSniffer_Tokens::$emptyTokens,
1952
            ($stackPtr - 1),
1953
            null,
1954
            true
1955
        );
1956
 
1957
        if ($this->_tokens[$tokenBefore]['code'] === T_FUNCTION) {
1958
            // Function returns a reference.
1959
            return true;
1960
        }
1961
 
1962
        if ($this->_tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) {
1963
            // Inside a foreach loop, this is a reference.
1964
            return true;
1965
        }
1966
 
1967
        if ($this->_tokens[$tokenBefore]['code'] === T_AS) {
1968
            // Inside a foreach loop, this is a reference.
1969
            return true;
1970
        }
1971
 
1972
        if (in_array($this->_tokens[$tokenBefore]['code'], PHP_CodeSniffer_Tokens::$assignmentTokens) === true) {
1973
            // This is directly after an assignment. It's a reference. Even if
1974
            // it is part of an operation, the other tests will handle it.
1975
            return true;
1976
        }
1977
 
1978
        if (isset($this->_tokens[$stackPtr]['nested_parenthesis']) === true) {
1979
            $brackets    = $this->_tokens[$stackPtr]['nested_parenthesis'];
1980
            $lastBracket = array_pop($brackets);
1981
            if (isset($this->_tokens[$lastBracket]['parenthesis_owner']) === true) {
1982
                $owner = $this->_tokens[$this->_tokens[$lastBracket]['parenthesis_owner']];
1983
                if ($owner['code'] === T_FUNCTION) {
1984
                    // Inside a function declaration, this is a reference.
1985
                    return true;
1986
                }
1987
            }
1988
        }
1989
 
1990
        return false;
1991
 
1992
    }//end isReference()
1993
 
1994
 
1995
    /**
1996
     * Returns the content of the tokens from the specified start position in
1997
     * the token stack for the specified legnth.
1998
     *
1999
     * @param int $start  The position to start from in the token stack.
2000
     * @param int $length The length of tokens to traverse from the start pos.
2001
     *
2002
     * @return string The token contents.
2003
     */
2004
    public function getTokensAsString($start, $length)
2005
    {
2006
        $str = '';
2007
        $end = ($start + $length);
2008
        for ($i = $start; $i < $end; $i++) {
2009
            $str .= $this->_tokens[$i]['content'];
2010
        }
2011
 
2012
        return $str;
2013
 
2014
    }//end getTokensAsString()
2015
 
2016
 
2017
    /**
2018
     * Returns the position of the next specified token(s).
2019
     *
2020
     * If a value is specified, the next token of the specified type(s)
2021
     * containing the specified value will be returned.
2022
     *
2023
     * Returns false if no token can be found.
2024
     *
2025
     * @param int|array $types   The type(s) of tokens to search for.
2026
     * @param int       $start   The position to start searching from in the
2027
     *                           token stack.
2028
     * @param int       $end     The end position to fail if no token is found.
2029
     *                           if not specified or null, end will default to
2030
     *                           the start of the token stack.
2031
     * @param bool      $exclude If true, find the next token that are NOT of
2032
     *                           the types specified in $types.
2033
     * @param string    $value   The value that the token(s) must be equal to.
2034
     *                           If value is ommited, tokens with any value will
2035
     *                           be returned.
2036
     * @param bool      $local   If true, tokens outside the current statement
2037
     *                           will not be cheked. IE. checking will stop
2038
     *                           at the next semi-colon found.
2039
     *
2040
     * @return int | bool
2041
     * @see findNext()
2042
     */
2043
    public function findPrevious(
2044
        $types,
2045
        $start,
2046
        $end=null,
2047
        $exclude=false,
2048
        $value=null,
2049
        $local=false
2050
    ) {
2051
        $types = (array) $types;
2052
 
2053
        if ($end === null) {
2054
            $end = 0;
2055
        }
2056
 
2057
        for ($i = $start; $i >= $end; $i--) {
2058
            $found = (bool) $exclude;
2059
            foreach ($types as $type) {
2060
                if ($this->_tokens[$i]['code'] === $type) {
2061
                    $found = !$exclude;
2062
                    break;
2063
                }
2064
            }
2065
 
2066
            if ($found === true) {
2067
                if ($value === null) {
2068
                    return $i;
2069
                } else if ($this->_tokens[$i]['content'] === $value) {
2070
                    return $i;
2071
                }
2072
            }
2073
 
2074
            if ($local === true && $this->_tokens[$i]['code'] === T_SEMICOLON) {
2075
                break;
2076
            }
2077
        }//end for
2078
 
2079
        return false;
2080
 
2081
    }//end findPrevious()
2082
 
2083
 
2084
    /**
2085
     * Returns the position of the next specified token(s).
2086
     *
2087
     * If a value is specified, the next token of the specified type(s)
2088
     * containing the specified value will be returned.
2089
     *
2090
     * Returns false if no token can be found.
2091
     *
2092
     * @param int|array $types   The type(s) of tokens to search for.
2093
     * @param int       $start   The position to start searching from in the
2094
     *                           token stack.
2095
     * @param int       $end     The end position to fail if no token is found.
2096
     *                           if not specified or null, end will default to
2097
     *                           the end of the token stack.
2098
     * @param bool      $exclude If true, find the next token that is NOT of
2099
     *                           a type specified in $types.
2100
     * @param string    $value   The value that the token(s) must be equal to.
2101
     *                           If value is ommited, tokens with any value will
2102
     *                           be returned.
2103
     * @param bool      $local   If true, tokens outside the current statement
2104
     *                           will not be cheked. IE. checking will stop
2105
     *                           at the next semi-colon found.
2106
     *
2107
     * @return int | bool
2108
     * @see findPrevious()
2109
     */
2110
    public function findNext(
2111
        $types,
2112
        $start,
2113
        $end=null,
2114
        $exclude=false,
2115
        $value=null,
2116
        $local=false
2117
    ) {
2118
        $types = (array) $types;
2119
 
2120
        if ($end === null || $end > $this->numTokens) {
2121
            $end = $this->numTokens;
2122
        }
2123
 
2124
        for ($i = $start; $i < $end; $i++) {
2125
            $found = (bool) $exclude;
2126
            foreach ($types as $type) {
2127
                if ($this->_tokens[$i]['code'] === $type) {
2128
                    $found = !$exclude;
2129
                    break;
2130
                }
2131
            }
2132
 
2133
            if ($found === true) {
2134
                if ($value === null) {
2135
                    return $i;
2136
                } else if ($this->_tokens[$i]['content'] === $value) {
2137
                    return $i;
2138
                }
2139
            }
2140
 
2141
            if ($local === true && $this->_tokens[$i]['code'] === T_SEMICOLON) {
2142
                break;
2143
            }
2144
        }//end for
2145
 
2146
        return false;
2147
 
2148
    }//end findNext()
2149
 
2150
 
2151
    /**
2152
     * Returns the position of the first token on a line, matching given type.
2153
     *
2154
     * Returns false if no token can be found.
2155
     *
2156
     * @param int|array $types   The type(s) of tokens to search for.
2157
     * @param int       $start   The position to start searching from in the
2158
     *                           token stack. The first token matching on
2159
     *                           this line before this token will be returned.
2160
     * @param bool      $exclude If true, find the token that is NOT of
2161
     *                           the types specified in $types.
2162
     * @param string    $value   The value that the token must be equal to.
2163
     *                           If value is ommited, tokens with any value will
2164
     *                           be returned.
2165
     *
2166
     * @return int | bool
2167
     */
2168
    public function findFirstOnLine($types, $start, $exclude=false, $value=null)
2169
    {
2170
        if (is_array($types) === false) {
2171
            $types = array($types);
2172
        }
2173
 
2174
        $foundToken = false;
2175
 
2176
        for ($i = $start; $i >= 0; $i--) {
2177
            if ($this->_tokens[$i]['line'] < $this->_tokens[$start]['line']) {
2178
                break;
2179
            }
2180
 
2181
            $found = $exclude;
2182
            foreach ($types as $type) {
2183
                if ($exclude === false) {
2184
                    if ($this->_tokens[$i]['code'] === $type) {
2185
                        $found = true;
2186
                        break;
2187
                    }
2188
                } else {
2189
                    if ($this->_tokens[$i]['code'] === $type) {
2190
                        $found = false;
2191
                        break;
2192
                    }
2193
                }
2194
            }
2195
 
2196
            if ($found === true) {
2197
                if ($value === null) {
2198
                    $foundToken = $i;
2199
                } else if ($this->_tokens[$i]['content'] === $value) {
2200
                    $foundToken = $i;
2201
                }
2202
            }
2203
        }//end for
2204
 
2205
        return $foundToken;
2206
 
2207
    }//end findFirstOnLine()
2208
 
2209
 
2210
    /**
2211
     * Determine if the passed token has a condition of one of the passed types.
2212
     *
2213
     * @param int       $stackPtr The position of the token we are checking.
2214
     * @param int|array $types    The type(s) of tokens to search for.
2215
     *
2216
     * @return boolean
2217
     */
2218
    public function hasCondition($stackPtr, $types)
2219
    {
2220
        // Check for the existence of the token.
2221
        if (isset($this->_tokens[$stackPtr]) === false) {
2222
            return false;
2223
        }
2224
 
2225
        // Make sure the token has conditions.
2226
        if (isset($this->_tokens[$stackPtr]['conditions']) === false) {
2227
            return false;
2228
        }
2229
 
2230
        $types      = (array) $types;
2231
        $conditions = $this->_tokens[$stackPtr]['conditions'];
2232
 
2233
        foreach ($types as $type) {
2234
            if (in_array($type, $conditions) === true) {
2235
                // We found a token with the required type.
2236
                return true;
2237
            }
2238
        }
2239
 
2240
        return false;
2241
 
2242
    }//end hasCondition()
2243
 
2244
 
2245
    /**
2246
     * Returns the name of the class that the specified class extends.
2247
     *
2248
     * Returns FALSE on error or if there is no extended class name.
2249
     *
2250
     * @param int $stackPtr The stack position of the class.
2251
     *
2252
     * @return string
2253
     */
2254
    public function findExtendedClassName($stackPtr)
2255
    {
2256
        // Check for the existence of the token.
2257
        if (isset($this->_tokens[$stackPtr]) === false) {
2258
            return false;
2259
        }
2260
 
2261
        if ($this->_tokens[$stackPtr]['code'] !== T_CLASS) {
2262
            return false;
2263
        }
2264
 
2265
        if (isset($this->_tokens[$stackPtr]['scope_closer']) === false) {
2266
            return false;
2267
        }
2268
 
2269
        $classCloserIndex = $this->_tokens[$stackPtr]['scope_closer'];
2270
        $extendsIndex     = $this->findNext(T_EXTENDS, $stackPtr, $classCloserIndex);
2271
        if (false === $extendsIndex) {
2272
            return false;
2273
        }
2274
 
2275
        $stringIndex = $this->findNext(T_STRING, $extendsIndex, $classCloserIndex);
2276
        if (false === $stringIndex) {
2277
            return false;
2278
        }
2279
 
2280
        return $this->_tokens[$stringIndex]['content'];
2281
 
2282
    }//end findExtendedClassName()
2283
 
2284
 
2285
}//end class
2286
 
2287
?>