mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
PHPParser optimisations and update
This commit is contained in:
parent
f8e3443c89
commit
cdb4a86e1c
@ -18,17 +18,17 @@
|
||||
"require": {
|
||||
"php": ">=5.5.0",
|
||||
"composer/installers": "~1.0",
|
||||
"monolog/monolog": "~1.11",
|
||||
"league/flysystem": "~1.0.12",
|
||||
"symfony/yaml": "~2.7",
|
||||
"embed/embed": "^2.6",
|
||||
"league/flysystem": "~1.0.12",
|
||||
"monolog/monolog": "~1.11",
|
||||
"nikic/php-parser": "^2 || ^3",
|
||||
"silverstripe/config": "^1@dev",
|
||||
"swiftmailer/swiftmailer": "~5.4",
|
||||
"symfony/cache": "^3.3@dev",
|
||||
"symfony/config": "^2.8",
|
||||
"symfony/translation": "^2.8",
|
||||
"vlucas/phpdotenv": "^2.4",
|
||||
"nikic/php-parser": "^2.1",
|
||||
"silverstripe/config": "^1@dev"
|
||||
"symfony/yaml": "~2.7",
|
||||
"vlucas/phpdotenv": "^2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/PHPUnit": "~4.8",
|
||||
|
86
src/Core/Manifest/ClassContentRemover.php
Normal file
86
src/Core/Manifest/ClassContentRemover.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
/**
|
||||
* Class ClassContentRemover
|
||||
* @package SilverStripe\Core\Manifest
|
||||
*
|
||||
* A utility class to clean the contents of a PHP file containing classes.
|
||||
*
|
||||
* It removes any code with in `$cut_off_depth` number of curly braces.
|
||||
*/
|
||||
class ClassContentRemover
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param int $cutOffDepth The number of levels of curly braces to go before ignoring the content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function remove_class_content($filePath, $cutOffDepth = 1)
|
||||
{
|
||||
|
||||
// Use PHP's built in method to strip comments and whitespace
|
||||
$contents = php_strip_whitespace($filePath);
|
||||
|
||||
if (!trim($contents)) {
|
||||
return $contents;
|
||||
}
|
||||
|
||||
if (!preg_match('/\b(?:class|interface|trait)/i', $contents)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// tokenize the file contents
|
||||
$tokens = token_get_all($contents);
|
||||
$cleanContents = '';
|
||||
$depth = 0;
|
||||
$startCounting = false;
|
||||
// iterate over all the tokens and only store the tokens that are outside $cutOffDepth
|
||||
foreach ($tokens as $token) {
|
||||
// only store the string literal of the token, that's all we need
|
||||
if (!is_array($token)) {
|
||||
$token = [
|
||||
T_STRING,
|
||||
$token,
|
||||
null
|
||||
];
|
||||
}
|
||||
|
||||
// only count if we see a class/interface/trait keyword
|
||||
if (!$startCounting && in_array($token[0], [T_CLASS, T_INTERFACE, T_TRAIT])) {
|
||||
$startCounting = true;
|
||||
}
|
||||
|
||||
// use curly braces as a sign of depth
|
||||
if ($token[1] === '{') {
|
||||
if ($depth < $cutOffDepth) {
|
||||
$cleanContents .= $token[1];
|
||||
}
|
||||
if ($startCounting) {
|
||||
++$depth;
|
||||
}
|
||||
} elseif ($token[1] === '}') {
|
||||
if ($startCounting) {
|
||||
--$depth;
|
||||
|
||||
// stop counting if we've just come out of the
|
||||
// class/interface/trait declaration
|
||||
if ($depth <= 0) {
|
||||
$startCounting = false;
|
||||
}
|
||||
}
|
||||
if ($depth < $cutOffDepth) {
|
||||
$cleanContents .= $token[1];
|
||||
}
|
||||
} elseif ($depth < $cutOffDepth) {
|
||||
$cleanContents .= $token[1];
|
||||
}
|
||||
}
|
||||
|
||||
// return cleaned class
|
||||
return trim($cleanContents);
|
||||
}
|
||||
}
|
@ -3,8 +3,9 @@
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use Exception;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use PhpParser\ParserFactory;
|
||||
use SilverStripe\Control\Director;
|
||||
|
||||
@ -48,127 +49,17 @@ class ClassManifest
|
||||
protected $traits = array();
|
||||
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
* @var \PhpParser\Parser
|
||||
*/
|
||||
public static function get_class_parser()
|
||||
{
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_CLASS,
|
||||
1 => array(T_WHITESPACE, 'optional' => true),
|
||||
2 => array(T_STRING, 'can_jump_to' => array(7, 14), 'save_to' => 'className'),
|
||||
3 => array(T_WHITESPACE, 'optional' => true),
|
||||
4 => T_EXTENDS,
|
||||
5 => array(T_WHITESPACE, 'optional' => true),
|
||||
6 => array(T_STRING, 'save_to' => 'extends[]', 'can_jump_to' => 14),
|
||||
7 => array(T_WHITESPACE, 'optional' => true),
|
||||
8 => T_IMPLEMENTS,
|
||||
9 => array(T_WHITESPACE, 'optional' => true),
|
||||
10 => array(T_STRING, 'can_jump_to' => 14, 'save_to' => 'interfaces[]'),
|
||||
11 => array(T_WHITESPACE, 'optional' => true),
|
||||
12 => array(',', 'can_jump_to' => 10, 'save_to' => 'interfaces[]'),
|
||||
13 => array(T_WHITESPACE, 'can_jump_to' => 10),
|
||||
14 => array(T_WHITESPACE, 'optional' => true),
|
||||
15 => '{',
|
||||
));
|
||||
}
|
||||
|
||||
private $parser;
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
* @var NodeTraverser
|
||||
*/
|
||||
public static function get_namespaced_class_parser()
|
||||
{
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_CLASS,
|
||||
1 => array(T_WHITESPACE, 'optional' => true),
|
||||
2 => array(T_STRING, 'can_jump_to' => array(8, 16), 'save_to' => 'className'),
|
||||
3 => array(T_WHITESPACE, 'optional' => true),
|
||||
4 => T_EXTENDS,
|
||||
5 => array(T_WHITESPACE, 'optional' => true),
|
||||
6 => array(T_NS_SEPARATOR, 'save_to' => 'extends[]', 'optional' => true),
|
||||
7 => array(T_STRING, 'save_to' => 'extends[]', 'can_jump_to' => array(6, 16)),
|
||||
8 => array(T_WHITESPACE, 'optional' => true),
|
||||
9 => T_IMPLEMENTS,
|
||||
10 => array(T_WHITESPACE, 'optional' => true),
|
||||
11 => array(T_NS_SEPARATOR, 'save_to' => 'interfaces[]', 'optional' => true),
|
||||
12 => array(T_STRING, 'can_jump_to' => array(11, 16), 'save_to' => 'interfaces[]'),
|
||||
13 => array(T_WHITESPACE, 'optional' => true),
|
||||
14 => array(',', 'can_jump_to' => 11, 'save_to' => 'interfaces[]'),
|
||||
15 => array(T_WHITESPACE, 'can_jump_to' => 11),
|
||||
16 => array(T_WHITESPACE, 'optional' => true),
|
||||
17 => '{',
|
||||
));
|
||||
}
|
||||
|
||||
private $traverser;
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
* @var ClassManifestVisitor
|
||||
*/
|
||||
public static function get_trait_parser()
|
||||
{
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_TRAIT,
|
||||
1 => array(T_WHITESPACE, 'optional' => true),
|
||||
2 => array(T_STRING, 'save_to' => 'traitName')
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function get_namespace_parser()
|
||||
{
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_NAMESPACE,
|
||||
1 => array(T_WHITESPACE, 'optional' => true),
|
||||
2 => array(T_NS_SEPARATOR, 'save_to' => 'namespaceName[]', 'optional' => true),
|
||||
3 => array(T_STRING, 'save_to' => 'namespaceName[]', 'can_jump_to' => 2),
|
||||
4 => array(T_WHITESPACE, 'optional' => true),
|
||||
5 => ';',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function get_interface_parser()
|
||||
{
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_INTERFACE,
|
||||
1 => array(T_WHITESPACE, 'optional' => true),
|
||||
2 => array(T_STRING, 'save_to' => 'interfaceName')
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link TokenisedRegularExpression} that extracts the namespaces imported with the 'use' keyword
|
||||
*
|
||||
* This searches symbols for a `use` followed by 1 or more namespaces which are optionally aliased using the `as`
|
||||
* keyword. The relevant matching tokens are added one-by-one into an array (using `save_to` param).
|
||||
*
|
||||
* eg: use Namespace\ClassName as Alias, OtherNamespace\ClassName;
|
||||
*
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function get_imported_namespace_parser()
|
||||
{
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_USE,
|
||||
1 => array(T_WHITESPACE, 'optional' => true),
|
||||
2 => array(T_NS_SEPARATOR, 'save_to' => 'importString[]', 'optional' => true),
|
||||
3 => array(T_STRING, 'save_to' => 'importString[]', 'can_jump_to' => array(2, 8)),
|
||||
4 => array(T_WHITESPACE, 'save_to' => 'importString[]'),
|
||||
5 => array(T_AS, 'save_to' => 'importString[]'),
|
||||
6 => array(T_WHITESPACE, 'save_to' => 'importString[]'),
|
||||
7 => array(T_STRING, 'save_to' => 'importString[]'),
|
||||
8 => array(T_WHITESPACE, 'optional' => true),
|
||||
9 => array(',', 'save_to' => 'importString[]', 'optional' => true, 'can_jump_to' => 2),
|
||||
10 => array(T_WHITESPACE, 'optional' => true, 'can_jump_to' => 2),
|
||||
11 => ';',
|
||||
));
|
||||
}
|
||||
|
||||
protected $parser;
|
||||
protected $traverser;
|
||||
protected $visitor;
|
||||
private $visitor;
|
||||
|
||||
/**
|
||||
* Constructs and initialises a new class manifest, either loading the data
|
||||
@ -189,11 +80,6 @@ class ClassManifest
|
||||
$this->cache = new $cacheClass('classmanifest'.($includeTests ? '_tests' : ''));
|
||||
$this->cacheKey = 'manifest';
|
||||
|
||||
$this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP5, new PhpParser\Lexer);
|
||||
$this->traverser = new NodeTraverser;
|
||||
$this->traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
|
||||
$this->traverser->addVisitor($this->visitor = new SilverStripeNodeVisitor);
|
||||
|
||||
if (!$forceRegen && $data = $this->cache->load($this->cacheKey)) {
|
||||
$this->classes = $data['classes'];
|
||||
$this->descendants = $data['descendants'];
|
||||
@ -207,6 +93,35 @@ class ClassManifest
|
||||
}
|
||||
}
|
||||
|
||||
public function getParser()
|
||||
{
|
||||
if (!$this->parser) {
|
||||
$this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
}
|
||||
|
||||
return $this->parser;
|
||||
}
|
||||
|
||||
public function getTraverser()
|
||||
{
|
||||
if (!$this->traverser) {
|
||||
$this->traverser = new NodeTraverser;
|
||||
$this->traverser->addVisitor(new NameResolver);
|
||||
$this->traverser->addVisitor($this->getVisitor());
|
||||
}
|
||||
|
||||
return $this->traverser;
|
||||
}
|
||||
|
||||
public function getVisitor()
|
||||
{
|
||||
if (!$this->visitor) {
|
||||
$this->visitor = new ClassManifestVisitor;
|
||||
}
|
||||
|
||||
return $this->visitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file path to a class or interface if it exists in the
|
||||
* manifest.
|
||||
@ -447,125 +362,6 @@ class ClassManifest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a the full namespaced declaration of a class (or interface) from a list of candidate imports
|
||||
*
|
||||
* This is typically used to determine the full class name in classes that have imported namesapced symbols (having
|
||||
* used the `use` keyword)
|
||||
*
|
||||
* NB: remember the '\\' is an escaped backslash and is interpreted as a single \
|
||||
*
|
||||
* @param string $class The class (or interface) name to find in the candidate imports
|
||||
* @param string $namespace The namespace that was declared for the classes definition (if there was one)
|
||||
* @param array $imports The list of imported symbols (Classes or Interfaces) to test against
|
||||
*
|
||||
* @return string The fully namespaced class name
|
||||
*/
|
||||
protected function findClassOrInterfaceFromCandidateImports($class, $namespace = '', $imports = array())
|
||||
{
|
||||
|
||||
//normalise the namespace
|
||||
$namespace = rtrim($namespace, '\\');
|
||||
|
||||
//by default we'll use the $class as our candidate
|
||||
$candidateClass = $class;
|
||||
|
||||
if (!$class) {
|
||||
return $candidateClass;
|
||||
}
|
||||
//if the class starts with a \ then it is explicitly in the global namespace and we don't need to do
|
||||
// anything else
|
||||
if (substr($class, 0, 1) == '\\') {
|
||||
$candidateClass = substr($class, 1);
|
||||
return $candidateClass;
|
||||
}
|
||||
//if there's a namespace, starting assumption is the class is defined in that namespace
|
||||
if ($namespace) {
|
||||
$candidateClass = $namespace . '\\' . $class;
|
||||
}
|
||||
|
||||
if (empty($imports)) {
|
||||
return $candidateClass;
|
||||
}
|
||||
|
||||
//normalised class name (PHP is case insensitive for symbols/namespaces
|
||||
$lClass = strtolower($class);
|
||||
|
||||
//go through all the imports and see if the class exists within one of them
|
||||
foreach ($imports as $alias => $import) {
|
||||
//normalise import
|
||||
$import = trim($import, '\\');
|
||||
|
||||
//if there is no string key, then there was no declared alias - we'll use the main declaration
|
||||
if (is_int($alias)) {
|
||||
$alias = strtolower($import);
|
||||
} else {
|
||||
$alias = strtolower($alias);
|
||||
}
|
||||
|
||||
//exact match? Then it's a class in the global namespace that was imported OR it's an alias of
|
||||
// another namespace
|
||||
// or if it ends with the \ClassName then it's the class we are looking for
|
||||
if ($lClass == $alias
|
||||
|| substr_compare(
|
||||
$alias,
|
||||
'\\' . $lClass,
|
||||
strlen($alias) - strlen($lClass) - 1,
|
||||
// -1 because the $lClass length is 1 longer due to \
|
||||
strlen($alias)
|
||||
) === 0
|
||||
) {
|
||||
$candidateClass = $import;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $candidateClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of array($alias => $import) from tokenizer's tokens of a PHP file
|
||||
*
|
||||
* NB: If there is no alias we don't set a key to the array
|
||||
*
|
||||
* @param array $tokens The parsed tokens from tokenizer's parsing of a PHP file
|
||||
*
|
||||
* @return array The array of imports as (optional) $alias => $import
|
||||
*/
|
||||
protected function getImportsFromTokens($tokens)
|
||||
{
|
||||
//parse out the imports
|
||||
$imports = self::get_imported_namespace_parser()->findAll($tokens);
|
||||
|
||||
//if there are any imports, clean them up
|
||||
// imports come to us as array('importString' => array([array of matching tokens]))
|
||||
// we need to join this nested array into a string and split out the alias and the import
|
||||
if (!empty($imports)) {
|
||||
$cleanImports = array();
|
||||
foreach ($imports as $import) {
|
||||
if (!empty($import['importString'])) {
|
||||
//join the array up into a string
|
||||
$importString = implode('', $import['importString']);
|
||||
//split at , to get each import declaration
|
||||
$importSet = explode(',', $importString);
|
||||
foreach ($importSet as $importDeclaration) {
|
||||
//split at ' as ' (any case) to see if we are aliasing the namespace
|
||||
$importDeclaration = preg_split('/\s+as\s+/i', $importDeclaration);
|
||||
//shift off the fully namespaced import
|
||||
$qualifiedImport = array_shift($importDeclaration);
|
||||
//if there are still items in the array, it's the alias
|
||||
if (!empty($importDeclaration)) {
|
||||
$cleanImports[array_shift($importDeclaration)] = $qualifiedImport;
|
||||
} else {
|
||||
$cleanImports[] = $qualifiedImport;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$imports = $cleanImports;
|
||||
}
|
||||
return $imports;
|
||||
}
|
||||
|
||||
public function handleFile($basename, $pathname, $depth)
|
||||
{
|
||||
if ($basename == self::CONF_FILE) {
|
||||
@ -575,8 +371,6 @@ class ClassManifest
|
||||
|
||||
$classes = null;
|
||||
$interfaces = null;
|
||||
$namespace = null;
|
||||
$imports = null;
|
||||
$traits = null;
|
||||
|
||||
// The results of individual file parses are cached, since only a few
|
||||
@ -589,78 +383,50 @@ class ClassManifest
|
||||
if ($data = $this->cache->load($key)) {
|
||||
$valid = (
|
||||
isset($data['classes']) && is_array($data['classes'])
|
||||
&& isset($data['interfaces']) && is_array($data['interfaces'])
|
||||
&& isset($data['namespace']) && is_string($data['namespace'])
|
||||
&& isset($data['imports']) && is_array($data['imports'])
|
||||
&& isset($data['traits']) && is_array($data['traits'])
|
||||
&& isset($data['interfaces'])
|
||||
&& is_array($data['interfaces'])
|
||||
&& isset($data['traits'])
|
||||
&& is_array($data['traits'])
|
||||
);
|
||||
|
||||
if ($valid) {
|
||||
$classes = $data['classes'];
|
||||
$interfaces = $data['interfaces'];
|
||||
$namespace = $data['namespace'];
|
||||
$imports = $data['imports'];
|
||||
$traits = $data['traits'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
$this->visitor->reset();
|
||||
$stmts = $this->parser->parse(file_get_contents($pathname));
|
||||
$this->traverser->traverse($stmts);
|
||||
$fileContents = ClassContentRemover::remove_class_content($pathname);
|
||||
try {
|
||||
$stmts = $this->getParser()->parse($fileContents);
|
||||
} catch (Error $e) {
|
||||
// if our mangled contents breaks, try again with the proper file contents
|
||||
$stmts = $this->getParser()->parse(file_get_contents($pathname));
|
||||
}
|
||||
$this->getTraverser()->traverse($stmts);
|
||||
|
||||
$classes = $this->visitor->getClasses();
|
||||
$traits = $this->visitor->getTraits();
|
||||
$namespace = $this->visitor->getNamespace();
|
||||
|
||||
$imports = [];//$this->getImportsFromTokens($tokens);
|
||||
|
||||
$interfaces = $this->visitor->getInterfaces();
|
||||
$classes = $this->getVisitor()->getClasses();
|
||||
$interfaces = $this->getVisitor()->getInterfaces();
|
||||
$traits = $this->getVisitor()->getTraits();
|
||||
|
||||
$cache = array(
|
||||
'classes' => $classes,
|
||||
'interfaces' => $interfaces,
|
||||
'namespace' => $namespace,
|
||||
'imports' => $imports,
|
||||
'traits' => $traits
|
||||
'traits' => $traits,
|
||||
);
|
||||
$this->cache->save($cache, $key);
|
||||
}
|
||||
|
||||
// Ensure namespace has no trailing slash, and namespaceBase does
|
||||
$namespaceBase = '';
|
||||
if ($namespace) {
|
||||
$namespace = rtrim($namespace, '\\');
|
||||
$namespaceBase = $namespace . '\\';
|
||||
}
|
||||
foreach ($classes as $className => $classInfo) {
|
||||
$extends = isset($classInfo['extends']) ? $classInfo['extends'] : null;
|
||||
$implements = isset($classInfo['interfaces']) ? $classInfo['interfaces'] : null;
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$name = $namespaceBase . $class['className'];
|
||||
$extends = isset($class['extends']) ? implode('', $class['extends']) : null;
|
||||
$implements = isset($class['interfaces']) ? $class['interfaces'] : null;
|
||||
|
||||
if ($extends) {
|
||||
$extends = $this->findClassOrInterfaceFromCandidateImports($extends, $namespace, $imports);
|
||||
}
|
||||
|
||||
if (!empty($implements)) {
|
||||
//join all the tokens
|
||||
$implements = implode('', $implements);
|
||||
//split at comma
|
||||
$implements = explode(',', $implements);
|
||||
//normalise interfaces
|
||||
foreach ($implements as &$interface) {
|
||||
$interface = $this->findClassOrInterfaceFromCandidateImports($interface, $namespace, $imports);
|
||||
}
|
||||
//release the var name
|
||||
unset($interface);
|
||||
}
|
||||
|
||||
$lowercaseName = strtolower($name);
|
||||
$lowercaseName = strtolower($className);
|
||||
if (array_key_exists($lowercaseName, $this->classes)) {
|
||||
throw new Exception(sprintf(
|
||||
'There are two files containing the "%s" class: "%s" and "%s"',
|
||||
$name,
|
||||
$className,
|
||||
$this->classes[$lowercaseName],
|
||||
$pathname
|
||||
));
|
||||
@ -669,15 +435,17 @@ class ClassManifest
|
||||
$this->classes[$lowercaseName] = $pathname;
|
||||
|
||||
if ($extends) {
|
||||
$extends = strtolower($extends);
|
||||
foreach ($extends as $ancestor) {
|
||||
$ancestor = strtolower($ancestor);
|
||||
|
||||
if (!isset($this->children[$extends])) {
|
||||
$this->children[$extends] = array($name);
|
||||
} else {
|
||||
$this->children[$extends][] = $name;
|
||||
if (!isset($this->children[$ancestor])) {
|
||||
$this->children[$ancestor] = array($className);
|
||||
} else {
|
||||
$this->children[$ancestor][] = $className;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->roots[] = $name;
|
||||
$this->roots[] = $className;
|
||||
}
|
||||
|
||||
if ($implements) {
|
||||
@ -685,19 +453,19 @@ class ClassManifest
|
||||
$interface = strtolower($interface);
|
||||
|
||||
if (!isset($this->implementors[$interface])) {
|
||||
$this->implementors[$interface] = array($name);
|
||||
$this->implementors[$interface] = array($className);
|
||||
} else {
|
||||
$this->implementors[$interface][] = $name;
|
||||
$this->implementors[$interface][] = $className;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($interfaces as $interface) {
|
||||
$this->interfaces[strtolower($namespaceBase . $interface['interfaceName'])] = $pathname;
|
||||
foreach ($interfaces as $interfaceName => $interfaceInfo) {
|
||||
$this->interfaces[strtolower($interfaceName)] = $pathname;
|
||||
}
|
||||
foreach ($traits as $trait) {
|
||||
$this->traits[strtolower($namespaceBase . $trait['traitName'])] = $pathname;
|
||||
foreach ($traits as $traitName => $traitInfo) {
|
||||
$this->traits[strtolower($traitName)] = $pathname;
|
||||
}
|
||||
}
|
||||
|
||||
@ -729,78 +497,3 @@ class ClassManifest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SilverStripeNodeVisitor extends NodeVisitorAbstract
|
||||
{
|
||||
|
||||
private $classes = [];
|
||||
|
||||
private $traits = [];
|
||||
|
||||
private $namespace = '';
|
||||
|
||||
private $interfaces = [];
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->classes = [];
|
||||
$this->traits = [];
|
||||
$this->namespace = '';
|
||||
$this->interfaces = [];
|
||||
}
|
||||
|
||||
public function enterNode(PhpParser\Node $node)
|
||||
{
|
||||
if ($node instanceof PhpParser\Node\Stmt\Class_) {
|
||||
$extends = [];
|
||||
$implements = [];
|
||||
|
||||
if ($node->extends) {
|
||||
$extends[] = (string)$node->extends;
|
||||
}
|
||||
|
||||
if ($node->implements) {
|
||||
foreach ($node->implements as $implement) {
|
||||
$implements[] = (string)$implement;
|
||||
}
|
||||
}
|
||||
|
||||
$this->classes[] = [
|
||||
'className' => $node->name,
|
||||
'extends' => $extends,
|
||||
'implements' => $implements
|
||||
];
|
||||
} else if ($node instanceof PhpParser\Node\Stmt\Trait_) {
|
||||
$this->traits[] = ['traitName' => (string)$node->name];
|
||||
} else if ($node instanceof PhpParser\Node\Stmt\Namespace_) {
|
||||
$this->namespace = (string)$node->name;
|
||||
} else if ($node instanceof PhpParser\Node\Stmt\Interface_) {
|
||||
$this->interfaces[] = ['interfaceName' => (string)$node->name];
|
||||
}
|
||||
|
||||
if (!$node instanceof PhpParser\Node\Stmt\Namespace_) {
|
||||
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
}
|
||||
|
||||
public function getClasses()
|
||||
{
|
||||
return $this->classes;
|
||||
}
|
||||
|
||||
public function getTraits()
|
||||
{
|
||||
return $this->traits;
|
||||
}
|
||||
|
||||
public function getNamespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function getInterfaces()
|
||||
{
|
||||
return $this->interfaces;
|
||||
}
|
||||
|
||||
}
|
||||
|
81
src/Core/Manifest/ClassManifestVisitor.php
Normal file
81
src/Core/Manifest/ClassManifestVisitor.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class ClassManifestVisitor extends NodeVisitorAbstract
|
||||
{
|
||||
|
||||
private $classes = [];
|
||||
|
||||
private $traits = [];
|
||||
|
||||
private $interfaces = [];
|
||||
|
||||
public function resetState()
|
||||
{
|
||||
$this->classes = [];
|
||||
$this->traits = [];
|
||||
$this->interfaces = [];
|
||||
}
|
||||
|
||||
public function beforeTraverse(array $nodes)
|
||||
{
|
||||
$this->resetState();
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Stmt\Class_) {
|
||||
$extends = '';
|
||||
$interfaces = [];
|
||||
|
||||
if ($node->extends) {
|
||||
$extends = array((string)$node->extends);
|
||||
}
|
||||
|
||||
if ($node->implements) {
|
||||
foreach ($node->implements as $interface) {
|
||||
$interfaces[] = (string)$interface;
|
||||
}
|
||||
}
|
||||
|
||||
$this->classes[(string)$node->namespacedName] = [
|
||||
'extends' => $extends,
|
||||
'interfaces' => $interfaces,
|
||||
];
|
||||
} elseif ($node instanceof Node\Stmt\Trait_) {
|
||||
$this->traits[(string)$node->namespacedName] = array();
|
||||
} elseif ($node instanceof Node\Stmt\Interface_) {
|
||||
$extends = array();
|
||||
foreach ($node->extends as $ancestor) {
|
||||
$extends[] = (string)$ancestor;
|
||||
}
|
||||
$this->interfaces[(string)$node->namespacedName] = [
|
||||
'extends' => $extends,
|
||||
];
|
||||
}
|
||||
if (!$node instanceof Node\Stmt\Namespace_) {
|
||||
//break out of traversal as we only need highlevel information here!
|
||||
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
}
|
||||
|
||||
public function getClasses()
|
||||
{
|
||||
return $this->classes;
|
||||
}
|
||||
|
||||
public function getTraits()
|
||||
{
|
||||
return $this->traits;
|
||||
}
|
||||
|
||||
public function getInterfaces()
|
||||
{
|
||||
return $this->interfaces;
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
/**
|
||||
* A tokenised regular expression is a parser, similar to a regular expression, that acts on tokens rather than
|
||||
* characters. This is a crucial component of the ManifestBuilder.
|
||||
*/
|
||||
class TokenisedRegularExpression
|
||||
{
|
||||
/**
|
||||
* The regular expression definition
|
||||
*/
|
||||
protected $expression;
|
||||
|
||||
/**
|
||||
* The first expression to match
|
||||
*/
|
||||
protected $firstMatch;
|
||||
|
||||
public function __construct($expression)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->firstMatch = is_array($expression[0]) ? $expression[0][0] : $expression[0];
|
||||
}
|
||||
|
||||
public function findAll($tokens)
|
||||
{
|
||||
$tokenTypes = array();
|
||||
foreach ($tokens as $i => $token) {
|
||||
if (is_array($token)) {
|
||||
$tokenType = $token[0];
|
||||
} else {
|
||||
$tokenType = $token;
|
||||
// Pre-process string tokens for matchFrom()
|
||||
$tokens[$i] = array($token, $token);
|
||||
}
|
||||
|
||||
if ($tokenType == $this->firstMatch) {
|
||||
$tokenTypes[$i] = $tokenType;
|
||||
}
|
||||
}
|
||||
|
||||
$allMatches = array();
|
||||
foreach ($tokenTypes as $startKey => $dud) {
|
||||
$matches = array();
|
||||
if ($this->matchFrom($startKey, 0, $tokens, $matches)) {
|
||||
$allMatches[] = $matches;
|
||||
}
|
||||
}
|
||||
|
||||
return $allMatches;
|
||||
}
|
||||
|
||||
public function matchFrom($tokenPos, $expressionPos, &$tokens, &$matches)
|
||||
{
|
||||
$expressionRule = $this->expression[$expressionPos];
|
||||
$expectation = is_array($expressionRule) ? $expressionRule[0] : $expressionRule;
|
||||
if (!is_array($expressionRule)) {
|
||||
$expressionRule = array();
|
||||
}
|
||||
|
||||
if ($expectation == $tokens[$tokenPos][0]) {
|
||||
if (isset($expressionRule['save_to'])) {
|
||||
// Append to an array
|
||||
if (substr($expressionRule['save_to'], -2) == '[]') {
|
||||
$matches[substr($expressionRule['save_to'], 0, -2)][] = $tokens[$tokenPos][1];
|
||||
} // Regular variable setting
|
||||
else {
|
||||
$matches[$expressionRule['save_to']] = $tokens[$tokenPos][1];
|
||||
}
|
||||
}
|
||||
|
||||
// End of the expression
|
||||
if (!isset($this->expression[$expressionPos+1])) {
|
||||
return true;
|
||||
|
||||
// Process next step as normal
|
||||
} elseif ($this->matchFrom($tokenPos+1, $expressionPos+1, $tokens, $matches)) {
|
||||
return true;
|
||||
|
||||
// This step is optional
|
||||
} elseif (isset($expressionRule['optional'])
|
||||
&& $this->matchFrom($tokenPos, $expressionPos+1, $tokens, $matches)) {
|
||||
return true;
|
||||
|
||||
// Process jumps
|
||||
} elseif (isset($expressionRule['can_jump_to'])) {
|
||||
if (is_array($expressionRule['can_jump_to'])) {
|
||||
foreach ($expressionRule['can_jump_to'] as $canJumpTo) {
|
||||
// can_jump_to & optional both set
|
||||
if (isset($expressionRule['optional'])
|
||||
&& $this->matchFrom($tokenPos, $canJumpTo, $tokens, $matches)) {
|
||||
return true;
|
||||
}
|
||||
// can_jump_to set (optional may or may not be set)
|
||||
if ($this->matchFrom($tokenPos+1, $canJumpTo, $tokens, $matches)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// can_jump_to & optional both set
|
||||
if (isset($expressionRule['optional'])
|
||||
&& $this->matchFrom($tokenPos, $expressionRule['can_jump_to'], $tokens, $matches)) {
|
||||
return true;
|
||||
}
|
||||
// can_jump_to set (optional may or may not be set)
|
||||
if ($this->matchFrom($tokenPos+1, $expressionRule['can_jump_to'], $tokens, $matches)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (isset($expressionRule['optional'])) {
|
||||
if (isset($this->expression[$expressionPos+1])) {
|
||||
return $this->matchFrom($tokenPos, $expressionPos+1, $tokens, $matches);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} elseif (in_array($tokens[$tokenPos][0], array(T_COMMENT, T_DOC_COMMENT, T_WHITESPACE))) {
|
||||
return $this->matchFrom($tokenPos + 1, $expressionPos, $tokens, $matches);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
52
tests/php/Core/Manifest/ClassContentRemoverTest.php
Normal file
52
tests/php/Core/Manifest/ClassContentRemoverTest.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use SilverStripe\Core\Manifest\ClassContentRemover;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class ClassContentRemoverTest extends SapphireTest
|
||||
{
|
||||
public function testRemoveClassContent()
|
||||
{
|
||||
$filePath = dirname(__FILE__) . '/fixtures/classcontentremover/ContentRemoverTestA.php';
|
||||
$cleanContents = ClassContentRemover::remove_class_content($filePath);
|
||||
|
||||
$expected = '<?php
|
||||
namespace TestNamespace\\Testing; use TestNamespace\\{Test1, Test2, Test3}; class MyTest extends Test1 implements Test2 {}';
|
||||
|
||||
$this->assertEquals($expected, $cleanContents);
|
||||
}
|
||||
|
||||
public function testRemoveClassContentConditional()
|
||||
{
|
||||
$filePath = dirname(__FILE__) . '/fixtures/classcontentremover/ContentRemoverTestB.php';
|
||||
$cleanContents = ClassContentRemover::remove_class_content($filePath);
|
||||
|
||||
$expected = '<?php
|
||||
namespace TestNamespace\\Testing; use TestNamespace\\{Test1, Test2, Test3}; if (class_exists(\'Class\')) { class MyTest extends Test1 implements Test2 {} class MyTest2 {} }';
|
||||
|
||||
$this->assertEquals($expected, $cleanContents);
|
||||
}
|
||||
|
||||
public function testRemoveClassContentNoClass()
|
||||
{
|
||||
$filePath = dirname(__FILE__) . '/fixtures/classcontentremover/ContentRemoverTestC.php';
|
||||
|
||||
$cleanContents = ClassContentRemover::remove_class_content($filePath);
|
||||
|
||||
$this->assertEmpty($cleanContents);
|
||||
}
|
||||
|
||||
public function testRemoveClassContentSillyMethod()
|
||||
{
|
||||
$filePath = dirname(__FILE__) . '/fixtures/classcontentremover/ContentRemoverTestD.php';
|
||||
|
||||
$cleanContents = ClassContentRemover::remove_class_content($filePath);
|
||||
|
||||
$expected = '<?php
|
||||
class SomeClass {} class AnotherClass {}';
|
||||
|
||||
$this->assertEquals($expected, $cleanContents);
|
||||
}
|
||||
}
|
@ -36,57 +36,6 @@ class NamespacedClassManifestTest extends SapphireTest
|
||||
ClassLoader::instance()->popManifest();
|
||||
}
|
||||
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
public function testGetImportedNamespaceParser()
|
||||
{
|
||||
$file = file_get_contents($this->base . DIRECTORY_SEPARATOR . 'module/classes/ClassI.php');
|
||||
$tokens = token_get_all($file);
|
||||
$parsedTokens = ClassManifest::get_imported_namespace_parser()->findAll($tokens);
|
||||
|
||||
$expectedItems = array(
|
||||
array('SilverStripe', '\\', 'Admin', '\\', 'ModelAdmin'),
|
||||
array('SilverStripe', '\\', 'Control', '\\', 'Controller', ' ', 'as', ' ', 'Cont'),
|
||||
array(
|
||||
'SilverStripe', '\\', 'Control', '\\', 'HTTPRequest', ' ', 'as', ' ', 'Request', ',',
|
||||
'SilverStripe', '\\', 'Control', '\\', 'HTTPResponse', ' ', 'as', ' ', 'Response', ',',
|
||||
'SilverStripe', '\\', 'Security', '\\', 'PermissionProvider', ' ', 'as', ' ', 'P',
|
||||
),
|
||||
array('silverstripe', '\\', 'test', '\\', 'ClassA'),
|
||||
array('\\', 'SilverStripe', '\\', 'Core', '\\', 'Object'),
|
||||
);
|
||||
|
||||
$this->assertEquals(count($expectedItems), count($parsedTokens));
|
||||
|
||||
foreach ($expectedItems as $i => $item) {
|
||||
$this->assertEquals($item, $parsedTokens[$i]['importString']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetImportsFromTokens()
|
||||
{
|
||||
$file = file_get_contents($this->base . DIRECTORY_SEPARATOR . 'module/classes/ClassI.php');
|
||||
$tokens = token_get_all($file);
|
||||
|
||||
$method = new ReflectionMethod($this->manifest, 'getImportsFromTokens');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$expectedImports = array(
|
||||
'SilverStripe\\Admin\\ModelAdmin',
|
||||
'Cont' => 'SilverStripe\\Control\\Controller',
|
||||
'Request' => 'SilverStripe\\Control\\HTTPRequest',
|
||||
'Response' => 'SilverStripe\\Control\\HTTPResponse',
|
||||
'P' => 'SilverStripe\\Security\\PermissionProvider',
|
||||
'silverstripe\\test\\ClassA',
|
||||
'\\SilverStripe\\Core\\Object',
|
||||
);
|
||||
|
||||
$imports = $method->invoke($this->manifest, $tokens);
|
||||
|
||||
$this->assertEquals($expectedImports, $imports);
|
||||
}
|
||||
|
||||
public function testClassInfoIsCorrect()
|
||||
{
|
||||
$this->assertContains('SilverStripe\Framework\Tests\ClassI', ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider'));
|
||||
@ -101,141 +50,6 @@ class NamespacedClassManifestTest extends SapphireTest
|
||||
$this->assertContains('SilverStripe\Framework\Tests\ClassI', ClassInfo::subclassesFor('SilverStripe\\Admin\\ModelAdmin'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @skipUpgrade
|
||||
*/
|
||||
public function testFindClassOrInterfaceFromCandidateImports()
|
||||
{
|
||||
$method = new ReflectionMethod($this->manifest, 'findClassOrInterfaceFromCandidateImports');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$this->assertTrue(ClassInfo::exists('silverstripe\test\ClassA'));
|
||||
|
||||
$this->assertEquals(
|
||||
'PermissionProvider',
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
[
|
||||
'\PermissionProvider',
|
||||
'Test\Namespace',
|
||||
array(
|
||||
'TestOnly',
|
||||
'Controller',
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'PermissionProvider',
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
array(
|
||||
'PermissionProvider',
|
||||
'Test\NAmespace',
|
||||
array(
|
||||
'PermissionProvider',
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEmpty(
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
array(
|
||||
'',
|
||||
'TextNamespace',
|
||||
array(
|
||||
'PermissionProvider',
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEmpty(
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
array(
|
||||
'',
|
||||
'',
|
||||
array()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'silverstripe\test\ClassA',
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
array(
|
||||
'ClassA',
|
||||
'Test\Namespace',
|
||||
array(
|
||||
'silverstripe\test\ClassA',
|
||||
'PermissionProvider',
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'ClassA',
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
array(
|
||||
'\ClassA',
|
||||
'Test\Namespace',
|
||||
array(
|
||||
'silverstripe\test',
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'ClassA',
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
array(
|
||||
'ClassA',
|
||||
'silverstripe\test',
|
||||
array(
|
||||
'\ClassA',
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'ClassA',
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
array(
|
||||
'Alias',
|
||||
'silverstripe\test',
|
||||
array(
|
||||
'Alias' => '\ClassA',
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'silverstripe\test\ClassA',
|
||||
$method->invokeArgs(
|
||||
$this->manifest,
|
||||
array(
|
||||
'ClassA',
|
||||
'silverstripe\test',
|
||||
array(
|
||||
'silverstripe\test\ClassB',
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetItemPath()
|
||||
{
|
||||
$expect = array(
|
||||
|
@ -1,201 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use SilverStripe\Core\Manifest\ClassManifest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class TokenisedRegularExpressionTest extends SapphireTest
|
||||
{
|
||||
public function getTokens()
|
||||
{
|
||||
return token_get_all(
|
||||
<<<PHP
|
||||
<?php
|
||||
|
||||
class ClassA {
|
||||
|
||||
}
|
||||
|
||||
class ClassB{
|
||||
|
||||
}
|
||||
|
||||
class ClassC extends ParentClassC {
|
||||
|
||||
}
|
||||
|
||||
class ClassD extends ParentClassD
|
||||
implements InterfaceA {
|
||||
|
||||
}
|
||||
|
||||
interface InterfaceA {
|
||||
|
||||
}
|
||||
|
||||
interface InterfaceB extends Something{
|
||||
|
||||
}
|
||||
|
||||
class ClassE extends ParentClassE
|
||||
implements InterfaceA,InterfaceB {
|
||||
|
||||
}
|
||||
|
||||
class ClassF extends ParentClassF
|
||||
implements InterfaceA, InterfaceB {
|
||||
|
||||
}
|
||||
|
||||
interface InterfaceC extends InterfaceA, InterfaceB {
|
||||
}
|
||||
interface InterfaceD extends InterfaceA, InterfaceB, InterfaceC {
|
||||
}
|
||||
|
||||
PHP
|
||||
);
|
||||
}
|
||||
|
||||
public function getNamespaceTokens()
|
||||
{
|
||||
return token_get_all(
|
||||
<<<PHP
|
||||
<?php
|
||||
|
||||
namespace silverstripe\\test;
|
||||
|
||||
class ClassA {
|
||||
|
||||
}
|
||||
|
||||
class ClassB extends ParentClassB {
|
||||
|
||||
}
|
||||
|
||||
class ClassC extends \\ParentClassC {
|
||||
|
||||
}
|
||||
|
||||
class ClassD extends subtest\\ParentClassD {
|
||||
|
||||
}
|
||||
|
||||
class ClassE implements InterfaceE {
|
||||
|
||||
}
|
||||
|
||||
class ClassF implements \\InterfaceF {
|
||||
|
||||
}
|
||||
|
||||
class ClassG implements subtest\\InterfaceG {
|
||||
|
||||
}
|
||||
|
||||
|
||||
PHP
|
||||
);
|
||||
}
|
||||
|
||||
public function testClassDefParser()
|
||||
{
|
||||
$parser = ClassManifest::get_class_parser();
|
||||
|
||||
$tokens = $this->getTokens();
|
||||
|
||||
$matches = $parser->findAll($tokens);
|
||||
$classes = array();
|
||||
if ($matches) {
|
||||
foreach ($matches as $match) {
|
||||
$classes[$match['className']] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertArrayHasKey('ClassA', $classes);
|
||||
$this->assertArrayHasKey('ClassB', $classes);
|
||||
|
||||
$this->assertArrayHasKey('ClassC', $classes);
|
||||
$this->assertEquals(array('ParentClassC'), $classes['ClassC']['extends']);
|
||||
|
||||
$this->assertArrayHasKey('ClassD', $classes);
|
||||
$this->assertEquals(array('ParentClassD'), $classes['ClassD']['extends']);
|
||||
$this->assertContains('InterfaceA', $classes['ClassD']['interfaces']);
|
||||
|
||||
$this->assertArrayHasKey('ClassE', $classes);
|
||||
$this->assertEquals(array('ParentClassE'), $classes['ClassE']['extends']);
|
||||
$this->assertContains('InterfaceA', $classes['ClassE']['interfaces']);
|
||||
$this->assertContains('InterfaceB', $classes['ClassE']['interfaces']);
|
||||
|
||||
$this->assertArrayHasKey('ClassF', $classes);
|
||||
$this->assertEquals(array('ParentClassF'), $classes['ClassF']['extends']);
|
||||
$this->assertContains('InterfaceA', $classes['ClassF']['interfaces']);
|
||||
$this->assertContains('InterfaceB', $classes['ClassF']['interfaces']);
|
||||
}
|
||||
|
||||
public function testNamesapcedClassDefParser()
|
||||
{
|
||||
$parser = ClassManifest::get_namespaced_class_parser();
|
||||
|
||||
$tokens = $this->getNamespaceTokens();
|
||||
|
||||
$matches = $parser->findAll($tokens);
|
||||
|
||||
$classes = array();
|
||||
if ($matches) {
|
||||
foreach ($matches as $match) {
|
||||
$classes[$match['className']] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertArrayHasKey('ClassA', $classes);
|
||||
$this->assertArrayHasKey('ClassB', $classes);
|
||||
$this->assertEquals(array('ParentClassB'), $classes['ClassB']['extends']);
|
||||
|
||||
$this->assertArrayHasKey('ClassC', $classes);
|
||||
$this->assertEquals(array('\\', 'ParentClassC'), $classes['ClassC']['extends']);
|
||||
|
||||
$this->assertArrayHasKey('ClassD', $classes);
|
||||
$this->assertEquals(array('subtest', '\\', 'ParentClassD'), $classes['ClassD']['extends']);
|
||||
|
||||
$this->assertArrayHasKey('ClassE', $classes);
|
||||
$this->assertContains('InterfaceE', $classes['ClassE']['interfaces']);
|
||||
|
||||
$this->assertArrayHasKey('ClassF', $classes);
|
||||
$this->assertEquals(array('\\', 'InterfaceF'), $classes['ClassF']['interfaces']);
|
||||
}
|
||||
|
||||
public function testInterfaceDefParser()
|
||||
{
|
||||
$parser = ClassManifest::get_interface_parser();
|
||||
|
||||
$tokens = $this->getTokens();
|
||||
|
||||
$matches = $parser->findAll($tokens);
|
||||
$interfaces = array();
|
||||
if ($matches) {
|
||||
foreach ($matches as $match) {
|
||||
$interfaces[$match['interfaceName']] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertArrayHasKey('InterfaceA', $interfaces);
|
||||
$this->assertArrayHasKey('InterfaceB', $interfaces);
|
||||
$this->assertArrayHasKey('InterfaceC', $interfaces);
|
||||
$this->assertArrayHasKey('InterfaceD', $interfaces);
|
||||
}
|
||||
|
||||
public function testNamespaceDefParser()
|
||||
{
|
||||
$parser = ClassManifest::get_namespace_parser();
|
||||
|
||||
$namespacedTokens = $this->getNamespaceTokens();
|
||||
$tokens = $this->getTokens();
|
||||
|
||||
$namespacedMatches = $parser->findAll($namespacedTokens);
|
||||
$matches = $parser->findAll($tokens);
|
||||
|
||||
$this->assertEquals(array(), $matches);
|
||||
$this->assertEquals(array('silverstripe', '\\', 'test'), $namespacedMatches[0]['namespaceName']);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace TestNamespace\Testing;
|
||||
|
||||
use TestNamespace\{Test1, Test2, Test3};
|
||||
|
||||
class MyTest extends Test1 implements Test2
|
||||
{
|
||||
|
||||
public function MyMethod()
|
||||
{
|
||||
//We shouldn't see anything in here
|
||||
$var = 1;
|
||||
$var += 1;
|
||||
return $var;
|
||||
}
|
||||
|
||||
public function MyNestedMethod()
|
||||
{
|
||||
$var = 1;
|
||||
for ($i = 0; $i < 5; ++$i) {
|
||||
if ($i % 2) {
|
||||
$var += $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace TestNamespace\Testing;
|
||||
|
||||
use TestNamespace\{Test1, Test2, Test3};
|
||||
|
||||
if (class_exists('Class')) {
|
||||
|
||||
class MyTest extends Test1 implements Test2
|
||||
{
|
||||
|
||||
public function MyMethod()
|
||||
{
|
||||
//We shouldn't see anything in here
|
||||
$var = 1;
|
||||
$var += 1;
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
public function MyNestedMethod()
|
||||
{
|
||||
$var = 1;
|
||||
for ($i = 0; $i < 5; ++$i) {
|
||||
if ($i % 2) {
|
||||
$var += $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyTest2
|
||||
{
|
||||
|
||||
public function SecondClassMethod() {
|
||||
return 'witty remark';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
$var = false;
|
||||
|
||||
if ($var) {
|
||||
$var = !$var;
|
||||
} else {
|
||||
$var = $var + 1;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
function bob()
|
||||
{
|
||||
return '{' . '{' . '{';
|
||||
}
|
||||
}
|
||||
|
||||
class AnotherClass
|
||||
{
|
||||
}
|
Loading…
Reference in New Issue
Block a user