Mercurial > hg > isophonics-drupal-site
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 } |