Minor dependency updates
[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                     $skip = 2;
180                 }
181
182                 $tokenClass = 'PHP_Token_' . $name;
183             } else {
184                 $text       = $token;
185                 $tokenClass = self::$customTokens[$token];
186             }
187
188             $this->tokens[] = new $tokenClass($text, $line, $this, $id++);
189             $lines          = substr_count($text, "\n");
190             $line          += $lines;
191
192             if ($tokenClass == 'PHP_Token_HALT_COMPILER') {
193                 break;
194             } elseif ($tokenClass == 'PHP_Token_COMMENT' ||
195                 $tokenClass == 'PHP_Token_DOC_COMMENT') {
196                 $this->linesOfCode['cloc'] += $lines + 1;
197             }
198
199             if ($name == 'DOUBLE_COLON') {
200                 $lastNonWhitespaceTokenWasDoubleColon = true;
201             } elseif ($name != 'WHITESPACE') {
202                 $lastNonWhitespaceTokenWasDoubleColon = false;
203             }
204
205             $i += $skip;
206         }
207
208         $this->linesOfCode['loc']   = substr_count($sourceCode, "\n");
209         $this->linesOfCode['ncloc'] = $this->linesOfCode['loc'] -
210                                       $this->linesOfCode['cloc'];
211     }
212
213     /**
214      * @return integer
215      */
216     public function count()
217     {
218         return count($this->tokens);
219     }
220
221     /**
222      * @return PHP_Token[]
223      */
224     public function tokens()
225     {
226         return $this->tokens;
227     }
228
229     /**
230      * @return array
231      */
232     public function getClasses()
233     {
234         if ($this->classes !== null) {
235             return $this->classes;
236         }
237
238         $this->parse();
239
240         return $this->classes;
241     }
242
243     /**
244      * @return array
245      */
246     public function getFunctions()
247     {
248         if ($this->functions !== null) {
249             return $this->functions;
250         }
251
252         $this->parse();
253
254         return $this->functions;
255     }
256
257     /**
258      * @return array
259      */
260     public function getInterfaces()
261     {
262         if ($this->interfaces !== null) {
263             return $this->interfaces;
264         }
265
266         $this->parse();
267
268         return $this->interfaces;
269     }
270
271     /**
272      * @return array
273      * @since  Method available since Release 1.1.0
274      */
275     public function getTraits()
276     {
277         if ($this->traits !== null) {
278             return $this->traits;
279         }
280
281         $this->parse();
282
283         return $this->traits;
284     }
285
286     /**
287      * Gets the names of all files that have been included
288      * using include(), include_once(), require() or require_once().
289      *
290      * Parameter $categorize set to TRUE causing this function to return a
291      * multi-dimensional array with categories in the keys of the first dimension
292      * and constants and their values in the second dimension.
293      *
294      * Parameter $category allow to filter following specific inclusion type
295      *
296      * @param bool   $categorize OPTIONAL
297      * @param string $category   OPTIONAL Either 'require_once', 'require',
298      *                                           'include_once', 'include'.
299      * @return array
300      * @since  Method available since Release 1.1.0
301      */
302     public function getIncludes($categorize = false, $category = null)
303     {
304         if ($this->includes === null) {
305             $this->includes = array(
306               'require_once' => array(),
307               'require'      => array(),
308               'include_once' => array(),
309               'include'      => array()
310             );
311
312             foreach ($this->tokens as $token) {
313                 switch (get_class($token)) {
314                     case 'PHP_Token_REQUIRE_ONCE':
315                     case 'PHP_Token_REQUIRE':
316                     case 'PHP_Token_INCLUDE_ONCE':
317                     case 'PHP_Token_INCLUDE':
318                         $this->includes[$token->getType()][] = $token->getName();
319                         break;
320                 }
321             }
322         }
323
324         if (isset($this->includes[$category])) {
325             $includes = $this->includes[$category];
326         } elseif ($categorize === false) {
327             $includes = array_merge(
328                 $this->includes['require_once'],
329                 $this->includes['require'],
330                 $this->includes['include_once'],
331                 $this->includes['include']
332             );
333         } else {
334             $includes = $this->includes;
335         }
336
337         return $includes;
338     }
339
340     /**
341      * Returns the name of the function or method a line belongs to.
342      *
343      * @return string or null if the line is not in a function or method
344      * @since  Method available since Release 1.2.0
345      */
346     public function getFunctionForLine($line)
347     {
348         $this->parse();
349
350         if (isset($this->lineToFunctionMap[$line])) {
351             return $this->lineToFunctionMap[$line];
352         }
353     }
354
355     protected function parse()
356     {
357         $this->interfaces = array();
358         $this->classes    = array();
359         $this->traits     = array();
360         $this->functions  = array();
361         $class            = array();
362         $classEndLine     = array();
363         $trait            = false;
364         $traitEndLine     = false;
365         $interface        = false;
366         $interfaceEndLine = false;
367
368         foreach ($this->tokens as $token) {
369             switch (get_class($token)) {
370                 case 'PHP_Token_HALT_COMPILER':
371                     return;
372
373                 case 'PHP_Token_INTERFACE':
374                     $interface        = $token->getName();
375                     $interfaceEndLine = $token->getEndLine();
376
377                     $this->interfaces[$interface] = array(
378                       'methods'   => array(),
379                       'parent'    => $token->getParent(),
380                       'keywords'  => $token->getKeywords(),
381                       'docblock'  => $token->getDocblock(),
382                       'startLine' => $token->getLine(),
383                       'endLine'   => $interfaceEndLine,
384                       'package'   => $token->getPackage(),
385                       'file'      => $this->filename
386                     );
387                     break;
388
389                 case 'PHP_Token_CLASS':
390                 case 'PHP_Token_TRAIT':
391                     $tmp = array(
392                       'methods'   => array(),
393                       'parent'    => $token->getParent(),
394                       'interfaces'=> $token->getInterfaces(),
395                       'keywords'  => $token->getKeywords(),
396                       'docblock'  => $token->getDocblock(),
397                       'startLine' => $token->getLine(),
398                       'endLine'   => $token->getEndLine(),
399                       'package'   => $token->getPackage(),
400                       'file'      => $this->filename
401                     );
402
403                     if ($token instanceof PHP_Token_CLASS) {
404                         $class[]        = $token->getName();
405                         $classEndLine[] = $token->getEndLine();
406
407                         if ($class[count($class)-1] != 'anonymous class') {
408                             $this->classes[$class[count($class)-1]] = $tmp;
409                         }
410                     } else {
411                         $trait                = $token->getName();
412                         $traitEndLine         = $token->getEndLine();
413                         $this->traits[$trait] = $tmp;
414                     }
415                     break;
416
417                 case 'PHP_Token_FUNCTION':
418                     $name = $token->getName();
419                     $tmp  = array(
420                       'docblock'  => $token->getDocblock(),
421                       'keywords'  => $token->getKeywords(),
422                       'visibility'=> $token->getVisibility(),
423                       'signature' => $token->getSignature(),
424                       'startLine' => $token->getLine(),
425                       'endLine'   => $token->getEndLine(),
426                       'ccn'       => $token->getCCN(),
427                       'file'      => $this->filename
428                     );
429
430                     if (empty($class) &&
431                         $trait === false &&
432                         $interface === false) {
433                         $this->functions[$name] = $tmp;
434
435                         $this->addFunctionToMap(
436                             $name,
437                             $tmp['startLine'],
438                             $tmp['endLine']
439                         );
440                     } elseif (!empty($class) && $class[count($class)-1] != 'anonymous class') {
441                         $this->classes[$class[count($class)-1]]['methods'][$name] = $tmp;
442
443                         $this->addFunctionToMap(
444                             $class[count($class)-1] . '::' . $name,
445                             $tmp['startLine'],
446                             $tmp['endLine']
447                         );
448                     } elseif ($trait !== false) {
449                         $this->traits[$trait]['methods'][$name] = $tmp;
450
451                         $this->addFunctionToMap(
452                             $trait . '::' . $name,
453                             $tmp['startLine'],
454                             $tmp['endLine']
455                         );
456                     } else {
457                         $this->interfaces[$interface]['methods'][$name] = $tmp;
458                     }
459                     break;
460
461                 case 'PHP_Token_CLOSE_CURLY':
462                     if (!empty($classEndLine) &&
463                         $classEndLine[count($classEndLine)-1] == $token->getLine()) {
464                         array_pop($classEndLine);
465                         array_pop($class);
466                     } elseif ($traitEndLine !== false &&
467                         $traitEndLine == $token->getLine()) {
468                         $trait        = false;
469                         $traitEndLine = false;
470                     } elseif ($interfaceEndLine !== false &&
471                         $interfaceEndLine == $token->getLine()) {
472                         $interface        = false;
473                         $interfaceEndLine = false;
474                     }
475                     break;
476             }
477         }
478     }
479
480     /**
481      * @return array
482      */
483     public function getLinesOfCode()
484     {
485         return $this->linesOfCode;
486     }
487
488     /**
489      */
490     public function rewind()
491     {
492         $this->position = 0;
493     }
494
495     /**
496      * @return boolean
497      */
498     public function valid()
499     {
500         return isset($this->tokens[$this->position]);
501     }
502
503     /**
504      * @return integer
505      */
506     public function key()
507     {
508         return $this->position;
509     }
510
511     /**
512      * @return PHP_Token
513      */
514     public function current()
515     {
516         return $this->tokens[$this->position];
517     }
518
519     /**
520      */
521     public function next()
522     {
523         $this->position++;
524     }
525
526     /**
527      * @param  integer $offset
528      * @return boolean
529      */
530     public function offsetExists($offset)
531     {
532         return isset($this->tokens[$offset]);
533     }
534
535     /**
536      * @param  integer $offset
537      * @return mixed
538      * @throws OutOfBoundsException
539      */
540     public function offsetGet($offset)
541     {
542         if (!$this->offsetExists($offset)) {
543             throw new OutOfBoundsException(
544                 sprintf(
545                     'No token at position "%s"',
546                     $offset
547                 )
548             );
549         }
550
551         return $this->tokens[$offset];
552     }
553
554     /**
555      * @param integer $offset
556      * @param mixed   $value
557      */
558     public function offsetSet($offset, $value)
559     {
560         $this->tokens[$offset] = $value;
561     }
562
563     /**
564      * @param  integer $offset
565      * @throws OutOfBoundsException
566      */
567     public function offsetUnset($offset)
568     {
569         if (!$this->offsetExists($offset)) {
570             throw new OutOfBoundsException(
571                 sprintf(
572                     'No token at position "%s"',
573                     $offset
574                 )
575             );
576         }
577
578         unset($this->tokens[$offset]);
579     }
580
581     /**
582      * Seek to an absolute position.
583      *
584      * @param  integer $position
585      * @throws OutOfBoundsException
586      */
587     public function seek($position)
588     {
589         $this->position = $position;
590
591         if (!$this->valid()) {
592             throw new OutOfBoundsException(
593                 sprintf(
594                     'No token at position "%s"',
595                     $this->position
596                 )
597             );
598         }
599     }
600
601     /**
602      * @param string  $name
603      * @param integer $startLine
604      * @param integer $endLine
605      */
606     private function addFunctionToMap($name, $startLine, $endLine)
607     {
608         for ($line = $startLine; $line <= $endLine; $line++) {
609             $this->lineToFunctionMap[$line] = $name;
610         }
611     }
612 }