Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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