c536d686e1013897fda5001dee5367951fedda82
[yaffs-website] / vendor / phpunit / php-token-stream / src / Token / Stream.php
1 <?php
2 /*
3  * This file is part of the PHP_TokenStream package.
4  *
5  * (c) Sebastian Bergmann <sebastian@phpunit.de>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * A stream of PHP tokens.
13  *
14  * @author    Sebastian Bergmann <sebastian@phpunit.de>
15  * @copyright Sebastian Bergmann <sebastian@phpunit.de>
16  * @license   http://www.opensource.org/licenses/BSD-3-Clause  The BSD 3-Clause License
17  * @link      http://github.com/sebastianbergmann/php-token-stream/tree
18  * @since     Class available since Release 1.0.0
19  */
20 class PHP_Token_Stream implements ArrayAccess, Countable, SeekableIterator
21 {
22     /**
23      * @var array
24      */
25     protected static $customTokens = array(
26         '(' => 'PHP_Token_OPEN_BRACKET',
27         ')' => 'PHP_Token_CLOSE_BRACKET',
28         '[' => 'PHP_Token_OPEN_SQUARE',
29         ']' => 'PHP_Token_CLOSE_SQUARE',
30         '{' => 'PHP_Token_OPEN_CURLY',
31         '}' => 'PHP_Token_CLOSE_CURLY',
32         ';' => 'PHP_Token_SEMICOLON',
33         '.' => 'PHP_Token_DOT',
34         ',' => 'PHP_Token_COMMA',
35         '=' => 'PHP_Token_EQUAL',
36         '<' => 'PHP_Token_LT',
37         '>' => 'PHP_Token_GT',
38         '+' => 'PHP_Token_PLUS',
39         '-' => 'PHP_Token_MINUS',
40         '*' => 'PHP_Token_MULT',
41         '/' => 'PHP_Token_DIV',
42         '?' => 'PHP_Token_QUESTION_MARK',
43         '!' => 'PHP_Token_EXCLAMATION_MARK',
44         ':' => 'PHP_Token_COLON',
45         '"' => 'PHP_Token_DOUBLE_QUOTES',
46         '@' => 'PHP_Token_AT',
47         '&' => 'PHP_Token_AMPERSAND',
48         '%' => 'PHP_Token_PERCENT',
49         '|' => 'PHP_Token_PIPE',
50         '$' => 'PHP_Token_DOLLAR',
51         '^' => 'PHP_Token_CARET',
52         '~' => 'PHP_Token_TILDE',
53         '`' => 'PHP_Token_BACKTICK'
54     );
55
56     /**
57      * @var string
58      */
59     protected $filename;
60
61     /**
62      * @var array
63      */
64     protected $tokens = array();
65
66     /**
67      * @var integer
68      */
69     protected $position = 0;
70
71     /**
72      * @var array
73      */
74     protected $linesOfCode = array('loc' => 0, 'cloc' => 0, 'ncloc' => 0);
75
76     /**
77      * @var array
78      */
79     protected $classes;
80
81     /**
82      * @var array
83      */
84     protected $functions;
85
86     /**
87      * @var array
88      */
89     protected $includes;
90
91     /**
92      * @var array
93      */
94     protected $interfaces;
95
96     /**
97      * @var array
98      */
99     protected $traits;
100
101     /**
102      * @var array
103      */
104     protected $lineToFunctionMap = array();
105
106     /**
107      * Constructor.
108      *
109      * @param string $sourceCode
110      */
111     public function __construct($sourceCode)
112     {
113         if (is_file($sourceCode)) {
114             $this->filename = $sourceCode;
115             $sourceCode     = file_get_contents($sourceCode);
116         }
117
118         $this->scan($sourceCode);
119     }
120
121     /**
122      * Destructor.
123      */
124     public function __destruct()
125     {
126         $this->tokens = array();
127     }
128
129     /**
130      * @return string
131      */
132     public function __toString()
133     {
134         $buffer = '';
135
136         foreach ($this as $token) {
137             $buffer .= $token;
138         }
139
140         return $buffer;
141     }
142
143     /**
144      * @return string
145      * @since  Method available since Release 1.1.0
146      */
147     public function getFilename()
148     {
149         return $this->filename;
150     }
151
152     /**
153      * Scans the source for sequences of characters and converts them into a
154      * stream of tokens.
155      *
156      * @param string $sourceCode
157      */
158     protected function scan($sourceCode)
159     {
160         $id        = 0;
161         $line      = 1;
162         $tokens    = token_get_all($sourceCode);
163         $numTokens = count($tokens);
164
165         $lastNonWhitespaceTokenWasDoubleColon = false;
166
167         for ($i = 0; $i < $numTokens; ++$i) {
168             $token = $tokens[$i];
169             $skip  = 0;
170
171             if (is_array($token)) {
172                 $name = substr(token_name($token[0]), 2);
173                 $text = $token[1];
174
175                 if ($lastNonWhitespaceTokenWasDoubleColon && $name == 'CLASS') {
176                     $name = 'CLASS_NAME_CONSTANT';
177                 } elseif ($name == 'USE' && isset($tokens[$i+2][0]) && $tokens[$i+2][0] == T_FUNCTION) {
178                     $name = 'USE_FUNCTION';
179                     $text .= $tokens[$i+1][1] . $tokens[$i+2][1];
180                     $skip = 2;
181                 }
182
183                 $tokenClass = 'PHP_Token_' . $name;
184             } else {
185                 $text       = $token;
186                 $tokenClass = self::$customTokens[$token];
187             }
188
189             $this->tokens[] = new $tokenClass($text, $line, $this, $id++);
190             $lines          = substr_count($text, "\n");
191             $line          += $lines;
192
193             if ($tokenClass == 'PHP_Token_HALT_COMPILER') {
194                 break;
195             } elseif ($tokenClass == 'PHP_Token_COMMENT' ||
196                 $tokenClass == 'PHP_Token_DOC_COMMENT') {
197                 $this->linesOfCode['cloc'] += $lines + 1;
198             }
199
200             if ($name == 'DOUBLE_COLON') {
201                 $lastNonWhitespaceTokenWasDoubleColon = true;
202             } elseif ($name != 'WHITESPACE') {
203                 $lastNonWhitespaceTokenWasDoubleColon = false;
204             }
205
206             $i += $skip;
207         }
208
209         $this->linesOfCode['loc']   = substr_count($sourceCode, "\n");
210         $this->linesOfCode['ncloc'] = $this->linesOfCode['loc'] -
211                                       $this->linesOfCode['cloc'];
212     }
213
214     /**
215      * @return integer
216      */
217     public function count()
218     {
219         return count($this->tokens);
220     }
221
222     /**
223      * @return PHP_Token[]
224      */
225     public function tokens()
226     {
227         return $this->tokens;
228     }
229
230     /**
231      * @return array
232      */
233     public function getClasses()
234     {
235         if ($this->classes !== null) {
236             return $this->classes;
237         }
238
239         $this->parse();
240
241         return $this->classes;
242     }
243
244     /**
245      * @return array
246      */
247     public function getFunctions()
248     {
249         if ($this->functions !== null) {
250             return $this->functions;
251         }
252
253         $this->parse();
254
255         return $this->functions;
256     }
257
258     /**
259      * @return array
260      */
261     public function getInterfaces()
262     {
263         if ($this->interfaces !== null) {
264             return $this->interfaces;
265         }
266
267         $this->parse();
268
269         return $this->interfaces;
270     }
271
272     /**
273      * @return array
274      * @since  Method available since Release 1.1.0
275      */
276     public function getTraits()
277     {
278         if ($this->traits !== null) {
279             return $this->traits;
280         }
281
282         $this->parse();
283
284         return $this->traits;
285     }
286
287     /**
288      * Gets the names of all files that have been included
289      * using include(), include_once(), require() or require_once().
290      *
291      * Parameter $categorize set to TRUE causing this function to return a
292      * multi-dimensional array with categories in the keys of the first dimension
293      * and constants and their values in the second dimension.
294      *
295      * Parameter $category allow to filter following specific inclusion type
296      *
297      * @param bool   $categorize OPTIONAL
298      * @param string $category   OPTIONAL Either 'require_once', 'require',
299      *                                           'include_once', 'include'.
300      * @return array
301      * @since  Method available since Release 1.1.0
302      */
303     public function getIncludes($categorize = false, $category = null)
304     {
305         if ($this->includes === null) {
306             $this->includes = array(
307               'require_once' => array(),
308               'require'      => array(),
309               'include_once' => array(),
310               'include'      => array()
311             );
312
313             foreach ($this->tokens as $token) {
314                 switch (get_class($token)) {
315                     case 'PHP_Token_REQUIRE_ONCE':
316                     case 'PHP_Token_REQUIRE':
317                     case 'PHP_Token_INCLUDE_ONCE':
318                     case 'PHP_Token_INCLUDE':
319                         $this->includes[$token->getType()][] = $token->getName();
320                         break;
321                 }
322             }
323         }
324
325         if (isset($this->includes[$category])) {
326             $includes = $this->includes[$category];
327         } elseif ($categorize === false) {
328             $includes = array_merge(
329                 $this->includes['require_once'],
330                 $this->includes['require'],
331                 $this->includes['include_once'],
332                 $this->includes['include']
333             );
334         } else {
335             $includes = $this->includes;
336         }
337
338         return $includes;
339     }
340
341     /**
342      * Returns the name of the function or method a line belongs to.
343      *
344      * @return string or null if the line is not in a function or method
345      * @since  Method available since Release 1.2.0
346      */
347     public function getFunctionForLine($line)
348     {
349         $this->parse();
350
351         if (isset($this->lineToFunctionMap[$line])) {
352             return $this->lineToFunctionMap[$line];
353         }
354     }
355
356     protected function parse()
357     {
358         $this->interfaces = array();
359         $this->classes    = array();
360         $this->traits     = array();
361         $this->functions  = array();
362         $class            = array();
363         $classEndLine     = array();
364         $trait            = false;
365         $traitEndLine     = false;
366         $interface        = false;
367         $interfaceEndLine = false;
368
369         foreach ($this->tokens as $token) {
370             switch (get_class($token)) {
371                 case 'PHP_Token_HALT_COMPILER':
372                     return;
373
374                 case 'PHP_Token_INTERFACE':
375                     $interface        = $token->getName();
376                     $interfaceEndLine = $token->getEndLine();
377
378                     $this->interfaces[$interface] = array(
379                       'methods'   => array(),
380                       'parent'    => $token->getParent(),
381                       'keywords'  => $token->getKeywords(),
382                       'docblock'  => $token->getDocblock(),
383                       'startLine' => $token->getLine(),
384                       'endLine'   => $interfaceEndLine,
385                       'package'   => $token->getPackage(),
386                       'file'      => $this->filename
387                     );
388                     break;
389
390                 case 'PHP_Token_CLASS':
391                 case 'PHP_Token_TRAIT':
392                     $tmp = array(
393                       'methods'   => array(),
394                       'parent'    => $token->getParent(),
395                       'interfaces'=> $token->getInterfaces(),
396                       'keywords'  => $token->getKeywords(),
397                       'docblock'  => $token->getDocblock(),
398                       'startLine' => $token->getLine(),
399                       'endLine'   => $token->getEndLine(),
400                       'package'   => $token->getPackage(),
401                       'file'      => $this->filename
402                     );
403
404                     if ($token instanceof PHP_Token_CLASS) {
405                         $class[]        = $token->getName();
406                         $classEndLine[] = $token->getEndLine();
407
408                         if ($class[count($class)-1] != 'anonymous class') {
409                             $this->classes[$class[count($class)-1]] = $tmp;
410                         }
411                     } else {
412                         $trait                = $token->getName();
413                         $traitEndLine         = $token->getEndLine();
414                         $this->traits[$trait] = $tmp;
415                     }
416                     break;
417
418                 case 'PHP_Token_FUNCTION':
419                     $name = $token->getName();
420                     $tmp  = array(
421                       'docblock'  => $token->getDocblock(),
422                       'keywords'  => $token->getKeywords(),
423                       'visibility'=> $token->getVisibility(),
424                       'signature' => $token->getSignature(),
425                       'startLine' => $token->getLine(),
426                       'endLine'   => $token->getEndLine(),
427                       'ccn'       => $token->getCCN(),
428                       'file'      => $this->filename
429                     );
430
431                     if (empty($class) &&
432                         $trait === false &&
433                         $interface === false) {
434                         $this->functions[$name] = $tmp;
435
436                         $this->addFunctionToMap(
437                             $name,
438                             $tmp['startLine'],
439                             $tmp['endLine']
440                         );
441                     } elseif (!empty($class) && $class[count($class)-1] != 'anonymous class') {
442                         $this->classes[$class[count($class)-1]]['methods'][$name] = $tmp;
443
444                         $this->addFunctionToMap(
445                             $class[count($class)-1] . '::' . $name,
446                             $tmp['startLine'],
447                             $tmp['endLine']
448                         );
449                     } elseif ($trait !== false) {
450                         $this->traits[$trait]['methods'][$name] = $tmp;
451
452                         $this->addFunctionToMap(
453                             $trait . '::' . $name,
454                             $tmp['startLine'],
455                             $tmp['endLine']
456                         );
457                     } else {
458                         $this->interfaces[$interface]['methods'][$name] = $tmp;
459                     }
460                     break;
461
462                 case 'PHP_Token_CLOSE_CURLY':
463                     if (!empty($classEndLine) &&
464                         $classEndLine[count($classEndLine)-1] == $token->getLine()) {
465                         array_pop($classEndLine);
466                         array_pop($class);
467                     } elseif ($traitEndLine !== false &&
468                         $traitEndLine == $token->getLine()) {
469                         $trait        = false;
470                         $traitEndLine = false;
471                     } elseif ($interfaceEndLine !== false &&
472                         $interfaceEndLine == $token->getLine()) {
473                         $interface        = false;
474                         $interfaceEndLine = false;
475                     }
476                     break;
477             }
478         }
479     }
480
481     /**
482      * @return array
483      */
484     public function getLinesOfCode()
485     {
486         return $this->linesOfCode;
487     }
488
489     /**
490      */
491     public function rewind()
492     {
493         $this->position = 0;
494     }
495
496     /**
497      * @return boolean
498      */
499     public function valid()
500     {
501         return isset($this->tokens[$this->position]);
502     }
503
504     /**
505      * @return integer
506      */
507     public function key()
508     {
509         return $this->position;
510     }
511
512     /**
513      * @return PHP_Token
514      */
515     public function current()
516     {
517         return $this->tokens[$this->position];
518     }
519
520     /**
521      */
522     public function next()
523     {
524         $this->position++;
525     }
526
527     /**
528      * @param  integer $offset
529      * @return boolean
530      */
531     public function offsetExists($offset)
532     {
533         return isset($this->tokens[$offset]);
534     }
535
536     /**
537      * @param  integer $offset
538      * @return mixed
539      * @throws OutOfBoundsException
540      */
541     public function offsetGet($offset)
542     {
543         if (!$this->offsetExists($offset)) {
544             throw new OutOfBoundsException(
545                 sprintf(
546                     'No token at position "%s"',
547                     $offset
548                 )
549             );
550         }
551
552         return $this->tokens[$offset];
553     }
554
555     /**
556      * @param integer $offset
557      * @param mixed   $value
558      */
559     public function offsetSet($offset, $value)
560     {
561         $this->tokens[$offset] = $value;
562     }
563
564     /**
565      * @param  integer $offset
566      * @throws OutOfBoundsException
567      */
568     public function offsetUnset($offset)
569     {
570         if (!$this->offsetExists($offset)) {
571             throw new OutOfBoundsException(
572                 sprintf(
573                     'No token at position "%s"',
574                     $offset
575                 )
576             );
577         }
578
579         unset($this->tokens[$offset]);
580     }
581
582     /**
583      * Seek to an absolute position.
584      *
585      * @param  integer $position
586      * @throws OutOfBoundsException
587      */
588     public function seek($position)
589     {
590         $this->position = $position;
591
592         if (!$this->valid()) {
593             throw new OutOfBoundsException(
594                 sprintf(
595                     'No token at position "%s"',
596                     $this->position
597                 )
598             );
599         }
600     }
601
602     /**
603      * @param string  $name
604      * @param integer $startLine
605      * @param integer $endLine
606      */
607     private function addFunctionToMap($name, $startLine, $endLine)
608     {
609         for ($line = $startLine; $line <= $endLine; $line++) {
610             $this->lineToFunctionMap[$line] = $name;
611         }
612     }
613 }