mirror of
https://github.com/torrentpier/torrentpier-lts.git
synced 2025-02-28 15:10:54 +03:00
690 lines
19 KiB
PHP
690 lines
19 KiB
PHP
<?php
|
|
/**
|
|
* Zend Framework (http://framework.zend.com/)
|
|
*
|
|
* @link http://github.com/zendframework/zf2 for the canonical source repository
|
|
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
|
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
|
*/
|
|
|
|
namespace Zend\Code\Scanner;
|
|
|
|
use Zend\Code\Annotation\AnnotationManager;
|
|
use Zend\Code\Exception;
|
|
use Zend\Code\NameInformation;
|
|
|
|
class TokenArrayScanner implements ScannerInterface
|
|
{
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $isScanned = false;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $tokens = array();
|
|
|
|
/**
|
|
* @var null
|
|
*/
|
|
protected $docComment = null;
|
|
|
|
/**
|
|
* @var NameInformation
|
|
*/
|
|
protected $nameInformation = null;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $infos = array();
|
|
|
|
/**
|
|
* @var AnnotationManager
|
|
*/
|
|
protected $annotationManager = null;
|
|
|
|
/**
|
|
* @param null|array $tokens
|
|
* @param null|AnnotationManager $annotationManager
|
|
*/
|
|
public function __construct($tokens, AnnotationManager $annotationManager = null)
|
|
{
|
|
$this->tokens = $tokens;
|
|
$this->annotationManager = $annotationManager;
|
|
}
|
|
|
|
/**
|
|
* @return AnnotationManager
|
|
*/
|
|
public function getAnnotationManager()
|
|
{
|
|
return $this->annotationManager;
|
|
}
|
|
|
|
/**
|
|
* Get doc comment
|
|
*
|
|
* @todo Assignment of $this->docComment should probably be done in scan()
|
|
* and then $this->getDocComment() just retrieves it.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getDocComment()
|
|
{
|
|
foreach ($this->tokens as $token) {
|
|
$type = $token[0];
|
|
$value = $token[1];
|
|
if (($type == T_OPEN_TAG) || ($type == T_WHITESPACE)) {
|
|
continue;
|
|
} elseif ($type == T_DOC_COMMENT) {
|
|
$this->docComment = $value;
|
|
|
|
return $this->docComment;
|
|
} else {
|
|
// Only whitespace is allowed before file docblocks
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getNamespaces()
|
|
{
|
|
$this->scan();
|
|
|
|
$namespaces = array();
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] == 'namespace') {
|
|
$namespaces[] = $info['namespace'];
|
|
}
|
|
}
|
|
|
|
return $namespaces;
|
|
}
|
|
|
|
/**
|
|
* @param null|string $namespace
|
|
* @return array|null
|
|
*/
|
|
public function getUses($namespace = null)
|
|
{
|
|
$this->scan();
|
|
|
|
return $this->getUsesNoScan($namespace);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getIncludes()
|
|
{
|
|
$this->scan();
|
|
// @todo Implement getIncludes() in TokenArrayScanner
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getClassNames()
|
|
{
|
|
$this->scan();
|
|
|
|
$return = array();
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] != 'class') {
|
|
continue;
|
|
}
|
|
|
|
$return[] = $info['name'];
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* @return ClassScanner[]
|
|
*/
|
|
public function getClasses()
|
|
{
|
|
$this->scan();
|
|
|
|
$return = array();
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] != 'class') {
|
|
continue;
|
|
}
|
|
|
|
$return[] = $this->getClass($info['name']);
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Return the class object from this scanner
|
|
*
|
|
* @param string|int $name
|
|
* @throws Exception\InvalidArgumentException
|
|
* @return ClassScanner
|
|
*/
|
|
public function getClass($name)
|
|
{
|
|
$this->scan();
|
|
|
|
if (is_int($name)) {
|
|
$info = $this->infos[$name];
|
|
if ($info['type'] != 'class') {
|
|
throw new Exception\InvalidArgumentException('Index of info offset is not about a class');
|
|
}
|
|
} elseif (is_string($name)) {
|
|
$classFound = false;
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] === 'class' && $info['name'] === $name) {
|
|
$classFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$classFound) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return new ClassScanner(
|
|
array_slice(
|
|
$this->tokens,
|
|
$info['tokenStart'],
|
|
($info['tokenEnd'] - $info['tokenStart'] + 1)
|
|
), // zero indexed array
|
|
new NameInformation($info['namespace'], $info['uses'])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $className
|
|
* @return bool|null|NameInformation
|
|
*/
|
|
public function getClassNameInformation($className)
|
|
{
|
|
$this->scan();
|
|
|
|
$classFound = false;
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] === 'class' && $info['name'] === $className) {
|
|
$classFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$classFound) {
|
|
return false;
|
|
}
|
|
|
|
if (!isset($info)) {
|
|
return;
|
|
}
|
|
|
|
return new NameInformation($info['namespace'], $info['uses']);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getFunctionNames()
|
|
{
|
|
$this->scan();
|
|
$functionNames = array();
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] == 'function') {
|
|
$functionNames[] = $info['name'];
|
|
}
|
|
}
|
|
|
|
return $functionNames;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getFunctions()
|
|
{
|
|
$this->scan();
|
|
|
|
$functions = array();
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] == 'function') {
|
|
// @todo $functions[] = new FunctionScanner($info['name']);
|
|
}
|
|
}
|
|
|
|
return $functions;
|
|
}
|
|
|
|
/**
|
|
* Export
|
|
*
|
|
* @param $tokens
|
|
*/
|
|
public static function export($tokens)
|
|
{
|
|
// @todo
|
|
}
|
|
|
|
public function __toString()
|
|
{
|
|
// @todo
|
|
}
|
|
|
|
/**
|
|
* Scan
|
|
*
|
|
* @todo: $this->docComment should be assigned for valid docblock during
|
|
* the scan instead of $this->getDocComment() (starting with
|
|
* T_DOC_COMMENT case)
|
|
*
|
|
* @throws Exception\RuntimeException
|
|
*/
|
|
protected function scan()
|
|
{
|
|
if ($this->isScanned) {
|
|
return;
|
|
}
|
|
|
|
if (!$this->tokens) {
|
|
throw new Exception\RuntimeException('No tokens were provided');
|
|
}
|
|
|
|
/**
|
|
* Define PHP 5.4 'trait' token constant.
|
|
*/
|
|
if (!defined('T_TRAIT')) {
|
|
define('T_TRAIT', 42001);
|
|
}
|
|
|
|
/**
|
|
* Variables & Setup
|
|
*/
|
|
|
|
$tokens = &$this->tokens; // localize
|
|
$infos = &$this->infos; // localize
|
|
$tokenIndex = null;
|
|
$token = null;
|
|
$tokenType = null;
|
|
$tokenContent = null;
|
|
$tokenLine = null;
|
|
$namespace = null;
|
|
$docCommentIndex = false;
|
|
$infoIndex = 0;
|
|
|
|
/*
|
|
* MACRO creation
|
|
*/
|
|
$MACRO_TOKEN_ADVANCE = function () use (&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) {
|
|
$tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1;
|
|
if (!isset($tokens[$tokenIndex])) {
|
|
$token = false;
|
|
$tokenContent = false;
|
|
$tokenType = false;
|
|
$tokenLine = false;
|
|
|
|
return false;
|
|
}
|
|
if (is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"') {
|
|
do {
|
|
$tokenIndex++;
|
|
} while (!(is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"'));
|
|
}
|
|
$token = $tokens[$tokenIndex];
|
|
if (is_array($token)) {
|
|
list($tokenType, $tokenContent, $tokenLine) = $token;
|
|
} else {
|
|
$tokenType = null;
|
|
$tokenContent = $token;
|
|
}
|
|
|
|
return $tokenIndex;
|
|
};
|
|
$MACRO_TOKEN_LOGICAL_START_INDEX = function () use (&$tokenIndex, &$docCommentIndex) {
|
|
return ($docCommentIndex === false) ? $tokenIndex : $docCommentIndex;
|
|
};
|
|
$MACRO_DOC_COMMENT_START = function () use (&$tokenIndex, &$docCommentIndex) {
|
|
$docCommentIndex = $tokenIndex;
|
|
|
|
return $docCommentIndex;
|
|
};
|
|
$MACRO_DOC_COMMENT_VALIDATE = function () use (&$tokenType, &$docCommentIndex) {
|
|
static $validTrailingTokens = null;
|
|
if ($validTrailingTokens === null) {
|
|
$validTrailingTokens = array(T_WHITESPACE, T_FINAL, T_ABSTRACT, T_INTERFACE, T_CLASS, T_FUNCTION);
|
|
}
|
|
if ($docCommentIndex !== false && !in_array($tokenType, $validTrailingTokens)) {
|
|
$docCommentIndex = false;
|
|
}
|
|
|
|
return $docCommentIndex;
|
|
};
|
|
$MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
|
|
$infos[$infoIndex]['tokenEnd'] = $tokenIndex;
|
|
$infos[$infoIndex]['lineEnd'] = $tokenLine;
|
|
$infoIndex++;
|
|
|
|
return $infoIndex;
|
|
};
|
|
|
|
/**
|
|
* START FINITE STATE MACHINE FOR SCANNING TOKENS
|
|
*/
|
|
|
|
// Initialize token
|
|
$MACRO_TOKEN_ADVANCE();
|
|
|
|
SCANNER_TOP:
|
|
|
|
if ($token === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
|
|
// Validate current doc comment index
|
|
$MACRO_DOC_COMMENT_VALIDATE();
|
|
|
|
switch ($tokenType) {
|
|
|
|
case T_DOC_COMMENT:
|
|
|
|
$MACRO_DOC_COMMENT_START();
|
|
goto SCANNER_CONTINUE;
|
|
//goto no break needed
|
|
|
|
case T_NAMESPACE:
|
|
|
|
$infos[$infoIndex] = array(
|
|
'type' => 'namespace',
|
|
'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
|
|
'tokenEnd' => null,
|
|
'lineStart' => $token[2],
|
|
'lineEnd' => null,
|
|
'namespace' => null,
|
|
);
|
|
|
|
// start processing with next token
|
|
if ($MACRO_TOKEN_ADVANCE() === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
|
|
SCANNER_NAMESPACE_TOP:
|
|
|
|
if ($tokenType === null && $tokenContent === ';' || $tokenContent === '{') {
|
|
goto SCANNER_NAMESPACE_END;
|
|
}
|
|
|
|
if ($tokenType === T_WHITESPACE) {
|
|
goto SCANNER_NAMESPACE_CONTINUE;
|
|
}
|
|
|
|
if ($tokenType === T_NS_SEPARATOR || $tokenType === T_STRING) {
|
|
$infos[$infoIndex]['namespace'] .= $tokenContent;
|
|
}
|
|
|
|
SCANNER_NAMESPACE_CONTINUE:
|
|
|
|
if ($MACRO_TOKEN_ADVANCE() === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
goto SCANNER_NAMESPACE_TOP;
|
|
|
|
SCANNER_NAMESPACE_END:
|
|
|
|
$namespace = $infos[$infoIndex]['namespace'];
|
|
|
|
$MACRO_INFO_ADVANCE();
|
|
goto SCANNER_CONTINUE;
|
|
//goto no break needed
|
|
|
|
case T_USE:
|
|
|
|
$infos[$infoIndex] = array(
|
|
'type' => 'use',
|
|
'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
|
|
'tokenEnd' => null,
|
|
'lineStart' => $tokens[$tokenIndex][2],
|
|
'lineEnd' => null,
|
|
'namespace' => $namespace,
|
|
'statements' => array(0 => array('use' => null,
|
|
'as' => null)),
|
|
);
|
|
|
|
$useStatementIndex = 0;
|
|
$useAsContext = false;
|
|
|
|
// start processing with next token
|
|
if ($MACRO_TOKEN_ADVANCE() === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
|
|
SCANNER_USE_TOP:
|
|
|
|
if ($tokenType === null) {
|
|
if ($tokenContent === ';') {
|
|
goto SCANNER_USE_END;
|
|
} elseif ($tokenContent === ',') {
|
|
$useAsContext = false;
|
|
$useStatementIndex++;
|
|
$infos[$infoIndex]['statements'][$useStatementIndex] = array('use' => null,
|
|
'as' => null);
|
|
}
|
|
}
|
|
|
|
// ANALYZE
|
|
if ($tokenType !== null) {
|
|
if ($tokenType == T_AS) {
|
|
$useAsContext = true;
|
|
goto SCANNER_USE_CONTINUE;
|
|
}
|
|
|
|
if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) {
|
|
if ($useAsContext == false) {
|
|
$infos[$infoIndex]['statements'][$useStatementIndex]['use'] .= $tokenContent;
|
|
} else {
|
|
$infos[$infoIndex]['statements'][$useStatementIndex]['as'] = $tokenContent;
|
|
}
|
|
}
|
|
}
|
|
|
|
SCANNER_USE_CONTINUE:
|
|
|
|
if ($MACRO_TOKEN_ADVANCE() === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
goto SCANNER_USE_TOP;
|
|
|
|
SCANNER_USE_END:
|
|
|
|
$MACRO_INFO_ADVANCE();
|
|
goto SCANNER_CONTINUE;
|
|
//goto no break needed
|
|
|
|
case T_INCLUDE:
|
|
case T_INCLUDE_ONCE:
|
|
case T_REQUIRE:
|
|
case T_REQUIRE_ONCE:
|
|
|
|
// Static for performance
|
|
static $includeTypes = array(
|
|
T_INCLUDE => 'include',
|
|
T_INCLUDE_ONCE => 'include_once',
|
|
T_REQUIRE => 'require',
|
|
T_REQUIRE_ONCE => 'require_once'
|
|
);
|
|
|
|
$infos[$infoIndex] = array(
|
|
'type' => 'include',
|
|
'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
|
|
'tokenEnd' => null,
|
|
'lineStart' => $tokens[$tokenIndex][2],
|
|
'lineEnd' => null,
|
|
'includeType' => $includeTypes[$tokens[$tokenIndex][0]],
|
|
'path' => '',
|
|
);
|
|
|
|
// start processing with next token
|
|
if ($MACRO_TOKEN_ADVANCE() === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
|
|
SCANNER_INCLUDE_TOP:
|
|
|
|
if ($tokenType === null && $tokenContent === ';') {
|
|
goto SCANNER_INCLUDE_END;
|
|
}
|
|
|
|
$infos[$infoIndex]['path'] .= $tokenContent;
|
|
|
|
SCANNER_INCLUDE_CONTINUE:
|
|
|
|
if ($MACRO_TOKEN_ADVANCE() === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
goto SCANNER_INCLUDE_TOP;
|
|
|
|
SCANNER_INCLUDE_END:
|
|
|
|
$MACRO_INFO_ADVANCE();
|
|
goto SCANNER_CONTINUE;
|
|
//goto no break needed
|
|
|
|
case T_FUNCTION:
|
|
case T_FINAL:
|
|
case T_ABSTRACT:
|
|
case T_CLASS:
|
|
case T_INTERFACE:
|
|
case T_TRAIT:
|
|
|
|
$infos[$infoIndex] = array(
|
|
'type' => ($tokenType === T_FUNCTION) ? 'function' : 'class',
|
|
'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
|
|
'tokenEnd' => null,
|
|
'lineStart' => $tokens[$tokenIndex][2],
|
|
'lineEnd' => null,
|
|
'namespace' => $namespace,
|
|
'uses' => $this->getUsesNoScan($namespace),
|
|
'name' => null,
|
|
'shortName' => null,
|
|
);
|
|
|
|
$classBraceCount = 0;
|
|
|
|
// start processing with current token
|
|
|
|
SCANNER_CLASS_TOP:
|
|
|
|
// process the name
|
|
if ($infos[$infoIndex]['shortName'] == ''
|
|
&& (($tokenType === T_CLASS || $tokenType === T_INTERFACE || $tokenType === T_TRAIT) && $infos[$infoIndex]['type'] === 'class'
|
|
|| ($tokenType === T_FUNCTION && $infos[$infoIndex]['type'] === 'function'))
|
|
) {
|
|
$infos[$infoIndex]['shortName'] = $tokens[$tokenIndex + 2][1];
|
|
$infos[$infoIndex]['name'] = (($namespace !== null) ? $namespace . '\\' : '') . $infos[$infoIndex]['shortName'];
|
|
}
|
|
|
|
if ($tokenType === null) {
|
|
if ($tokenContent == '{') {
|
|
$classBraceCount++;
|
|
}
|
|
if ($tokenContent == '}') {
|
|
$classBraceCount--;
|
|
if ($classBraceCount === 0) {
|
|
goto SCANNER_CLASS_END;
|
|
}
|
|
}
|
|
}
|
|
|
|
SCANNER_CLASS_CONTINUE:
|
|
|
|
if ($MACRO_TOKEN_ADVANCE() === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
goto SCANNER_CLASS_TOP;
|
|
|
|
SCANNER_CLASS_END:
|
|
|
|
$MACRO_INFO_ADVANCE();
|
|
goto SCANNER_CONTINUE;
|
|
|
|
}
|
|
|
|
SCANNER_CONTINUE:
|
|
|
|
if ($MACRO_TOKEN_ADVANCE() === false) {
|
|
goto SCANNER_END;
|
|
}
|
|
goto SCANNER_TOP;
|
|
|
|
SCANNER_END:
|
|
|
|
/**
|
|
* END FINITE STATE MACHINE FOR SCANNING TOKENS
|
|
*/
|
|
|
|
$this->isScanned = true;
|
|
}
|
|
|
|
/**
|
|
* Check for namespace
|
|
*
|
|
* @param string $namespace
|
|
* @return bool
|
|
*/
|
|
public function hasNamespace($namespace)
|
|
{
|
|
$this->scan();
|
|
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] == 'namespace' && $info['namespace'] == $namespace) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param string $namespace
|
|
* @return null|array
|
|
* @throws Exception\InvalidArgumentException
|
|
*/
|
|
protected function getUsesNoScan($namespace)
|
|
{
|
|
$namespaces = array();
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] == 'namespace') {
|
|
$namespaces[] = $info['namespace'];
|
|
}
|
|
}
|
|
|
|
if ($namespace === null) {
|
|
$namespace = array_shift($namespaces);
|
|
} elseif (!is_string($namespace)) {
|
|
throw new Exception\InvalidArgumentException('Invalid namespace provided');
|
|
} elseif (!in_array($namespace, $namespaces)) {
|
|
return;
|
|
}
|
|
|
|
$uses = array();
|
|
foreach ($this->infos as $info) {
|
|
if ($info['type'] !== 'use') {
|
|
continue;
|
|
}
|
|
foreach ($info['statements'] as $statement) {
|
|
if ($info['namespace'] == $namespace) {
|
|
$uses[] = $statement;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $uses;
|
|
}
|
|
}
|