comparison vendor/phpunit/php-token-stream/src/Token/Stream.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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 }