annotate vendor/phpunit/php-token-stream/src/Token/Stream.php @ 19:fa3358dc1485 tip

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