1
0
Fork 0

Move logic out of Definition class

pull/155/head
Felix Becker 2016-11-17 21:52:48 +01:00
parent fb9efd4727
commit 13e42cc7d2
5 changed files with 138 additions and 133 deletions

View File

@ -45,127 +45,4 @@ class Definition
* @var \phpDocumentor\Type|null * @var \phpDocumentor\Type|null
*/ */
public $type; public $type;
/**
* Returns the type a reference to this symbol will resolve to.
* For properties and constants, this is the type of the property/constant.
* For functions and methods, this is the return type.
* For classes and interfaces, this is the class type (object).
* Variables are not indexed for performance reasons.
* Can also be a compound type.
* If it is unknown, will be Types\Mixed.
* Returns null if the node does not have a type.
*
* @param Node $node
* @return \phpDocumentor\Type|null
*/
public static function getTypeFromNode(Node $node)
{
if ($node instanceof Node\Param) {
// Parameters
$docBlock = $node->getAttribute('docBlock');
if ($docBlock !== null && count($paramTags = $docBlock->getTagsByName('param')) > 0) {
// Use @param tag
return $paramTags[0]->getType();
}
if ($node->type !== null) {
// Use PHP7 return type hint
if (is_string($node->type)) {
// Resolve a string like "bool" to a type object
$type = (new TypeResolver)->resolve($node->type);
}
$type = new Types\Object_(new Fqsen('\\' . (string)$node->type));
if ($node->default !== null) {
if (is_string($node->default)) {
// Resolve a string like "bool" to a type object
$defaultType = (new TypeResolver)->resolve($node->default);
}
$defaultType = new Types\Object_(new Fqsen('\\' . (string)$node->default));
$type = new Types\Compound([$type, $defaultType]);
}
}
// Unknown parameter type
return new Types\Mixed;
}
if ($node instanceof Node\FunctionLike) {
// Functions/methods
$docBlock = $node->getAttribute('docBlock');
if ($docBlock !== null && count($returnTags = $docBlock->getTagsByName('return')) > 0) {
// Use @return tag
return $returnTags[0]->getType();
}
if ($node->returnType !== null) {
// Use PHP7 return type hint
if (is_string($node->returnType)) {
// Resolve a string like "bool" to a type object
return (new TypeResolver)->resolve($node->returnType);
}
return new Types\Object_(new Fqsen('\\' . (string)$node->returnType));
}
// Unknown return type
return new Types\Mixed;
}
if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) {
// Property or constant
$docBlock = $node->getAttribute('parentNode')->getAttribute('docBlock');
if ($docBlock !== null && count($varTags = $docBlock->getTagsByName('var')) > 0) {
// Use @var tag
return $varTags[0]->getType();
}
// TODO: read @property tags of class
// TODO: Try to infer the type from default value / constant value
// Unknown
return new Types\Mixed;
}
return null;
}
/**
* Returns the fully qualified name (FQN) that is defined by a node
* Returns null if the node does not declare any symbol that can be referenced by an FQN
*
* @param Node $node
* @return string|null
*/
public static function getDefinedFqn(Node $node)
{
// Anonymous classes don't count as a definition
if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) {
// Class, interface or trait declaration
return (string)$node->namespacedName;
} else if ($node instanceof Node\Stmt\Function_) {
// Function: use functionName() as the name
return (string)$node->namespacedName . '()';
} else if ($node instanceof Node\Stmt\ClassMethod) {
// Class method: use ClassName::methodName() as name
$class = $node->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return null;
}
return (string)$class->namespacedName . '::' . (string)$node->name . '()';
} else if ($node instanceof Node\Stmt\PropertyProperty) {
// Property: use ClassName::propertyName as name
$class = $node->getAttribute('parentNode')->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return null;
}
return (string)$class->namespacedName . '::' . (string)$node->name;
} else if ($node instanceof Node\Const_) {
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Stmt\Const_) {
// Basic constant: use CONSTANT_NAME as name
return (string)$node->namespacedName;
}
if ($parent instanceof Node\Stmt\ClassConst) {
// Class constant: use ClassName::CONSTANT_NAME as name
$class = $parent->getAttribute('parentNode');
if (!isset($class->name) || $class->name instanceof Node\Expr) {
return null;
}
return (string)$class->namespacedName . '::' . $node->name;
}
}
}
} }

View File

@ -12,10 +12,12 @@ use function Sabre\Event\coroutine;
class DefinitionResolver class DefinitionResolver
{ {
private $project; private $project;
private $typeResolver;
public function __construct(Project $project) public function __construct(Project $project)
{ {
$this->project = $project; $this->project = $project;
$this->typeResolver = new TypeResolver;
} }
/** /**
@ -35,7 +37,7 @@ class DefinitionResolver
$def->symbolInformation = SymbolInformation::fromNode($defNode); $def->symbolInformation = SymbolInformation::fromNode($defNode);
if ($defNode instanceof Node\Param) { if ($defNode instanceof Node\Param) {
// Get parameter type // Get parameter type
$def->type = Definition::getTypeFromNode($defNode); $def->type = $this->getTypeFromNode($defNode);
} else { } else {
// Resolve the type of the assignment/closure use node // Resolve the type of the assignment/closure use node
$def->type = $this->resolveExpression($defNode); $def->type = $this->resolveExpression($defNode);
@ -244,7 +246,7 @@ class DefinitionResolver
return $this->resolveExpression($defNode); return $this->resolveExpression($defNode);
} }
if ($defNode instanceof Node\Param) { if ($defNode instanceof Node\Param) {
return Definition::getTypeFromNode($defNode); return $this->getTypeFromNode($defNode);
} }
} }
if ($expr instanceof Node\Expr\FuncCall) { if ($expr instanceof Node\Expr\FuncCall) {
@ -434,4 +436,123 @@ class DefinitionResolver
} }
return new Types\Mixed; return new Types\Mixed;
} }
/**
* Returns the type a reference to this symbol will resolve to.
* For properties and constants, this is the type of the property/constant.
* For functions and methods, this is the return type.
* For classes and interfaces, this is the class type (object).
* Variables are not indexed for performance reasons.
* Can also be a compound type.
* If it is unknown, will be Types\Mixed.
* Returns null if the node does not have a type.
*
* @param Node $node
* @return \phpDocumentor\Type|null
*/
public function getTypeFromNode(Node $node)
{
if ($node instanceof Node\Param) {
// Parameters
$docBlock = $node->getAttribute('docBlock');
if ($docBlock !== null && count($paramTags = $docBlock->getTagsByName('param')) > 0) {
// Use @param tag
return $paramTags[0]->getType();
}
if ($node->type !== null) {
// Use PHP7 return type hint
if (is_string($node->type)) {
// Resolve a string like "bool" to a type object
$type = $this->typeResolver->resolve($node->type);
}
$type = new Types\Object_(new Fqsen('\\' . (string)$node->type));
if ($node->default !== null) {
$defaultType = $this->resolveExpression($node->default);
$type = new Types\Compound([$type, $defaultType]);
}
}
// Unknown parameter type
return new Types\Mixed;
}
if ($node instanceof Node\FunctionLike) {
// Functions/methods
$docBlock = $node->getAttribute('docBlock');
if ($docBlock !== null && count($returnTags = $docBlock->getTagsByName('return')) > 0) {
// Use @return tag
return $returnTags[0]->getType();
}
if ($node->returnType !== null) {
// Use PHP7 return type hint
if (is_string($node->returnType)) {
// Resolve a string like "bool" to a type object
return $this->typeResolver->resolve($node->returnType);
}
return new Types\Object_(new Fqsen('\\' . (string)$node->returnType));
}
// Unknown return type
return new Types\Mixed;
}
if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) {
// Property or constant
$docBlock = $node->getAttribute('parentNode')->getAttribute('docBlock');
if ($docBlock !== null && count($varTags = $docBlock->getTagsByName('var')) > 0) {
// Use @var tag
return $varTags[0]->getType();
}
// TODO: read @property tags of class
// TODO: Try to infer the type from default value / constant value
// Unknown
return new Types\Mixed;
}
return null;
}
/**
* Returns the fully qualified name (FQN) that is defined by a node
* Returns null if the node does not declare any symbol that can be referenced by an FQN
*
* @param Node $node
* @return string|null
*/
public static function getDefinedFqn(Node $node)
{
// Anonymous classes don't count as a definition
if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) {
// Class, interface or trait declaration
return (string)$node->namespacedName;
} else if ($node instanceof Node\Stmt\Function_) {
// Function: use functionName() as the name
return (string)$node->namespacedName . '()';
} else if ($node instanceof Node\Stmt\ClassMethod) {
// Class method: use ClassName::methodName() as name
$class = $node->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return null;
}
return (string)$class->namespacedName . '::' . (string)$node->name . '()';
} else if ($node instanceof Node\Stmt\PropertyProperty) {
// Property: use ClassName::propertyName as name
$class = $node->getAttribute('parentNode')->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return null;
}
return (string)$class->namespacedName . '::' . (string)$node->name;
} else if ($node instanceof Node\Const_) {
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Stmt\Const_) {
// Basic constant: use CONSTANT_NAME as name
return (string)$node->namespacedName;
}
if ($parent instanceof Node\Stmt\ClassConst) {
// Class constant: use ClassName::CONSTANT_NAME as name
$class = $parent->getAttribute('parentNode');
if (!isset($class->name) || $class->name instanceof Node\Expr) {
return null;
}
return (string)$class->namespacedName . '::' . $node->name;
}
}
}
} }

View File

@ -4,7 +4,7 @@ declare(strict_types = 1);
namespace LanguageServer\NodeVisitor; namespace LanguageServer\NodeVisitor;
use PhpParser\{NodeVisitorAbstract, Node}; use PhpParser\{NodeVisitorAbstract, Node};
use LanguageServer\Definition; use LanguageServer\{Definition, DefinitionResolver};
use LanguageServer\Protocol\SymbolInformation; use LanguageServer\Protocol\SymbolInformation;
/** /**
@ -27,9 +27,16 @@ class DefinitionCollector extends NodeVisitorAbstract
*/ */
public $nodes = []; public $nodes = [];
public $definitionResolver;
public function __construct(DefinitionResolver $definitionResolver)
{
$this->definitionResolver = $definitionResolver;
}
public function enterNode(Node $node) public function enterNode(Node $node)
{ {
$fqn = Definition::getDefinedFqn($node); $fqn = DefinitionResolver::getDefinedFqn($node);
// Only index definitions with an FQN (no variables) // Only index definitions with an FQN (no variables)
if ($fqn === null) { if ($fqn === null) {
return; return;
@ -38,7 +45,7 @@ class DefinitionCollector extends NodeVisitorAbstract
$def = new Definition; $def = new Definition;
$def->fqn = $fqn; $def->fqn = $fqn;
$def->symbolInformation = SymbolInformation::fromNode($node, $fqn); $def->symbolInformation = SymbolInformation::fromNode($node, $fqn);
$def->type = Definition::getTypeFromNode($node); $def->type = $this->definitionResolver->getTypeFromNode($node);
$this->definitions[$fqn] = $def; $this->definitions[$fqn] = $def;
} }
} }

View File

@ -186,7 +186,7 @@ class PhpDocument
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
// Collect all definitions // Collect all definitions
$definitionCollector = new DefinitionCollector; $definitionCollector = new DefinitionCollector($this->definitionResolver);
$traverser->addVisitor($definitionCollector); $traverser->addVisitor($definitionCollector);
// Collect all references // Collect all references
@ -374,7 +374,7 @@ class PhpDocument
return $refCollector->nodes; return $refCollector->nodes;
} }
// Definition with a global FQN // Definition with a global FQN
$fqn = Definition::getDefinedFqn($node); $fqn = DefinitionResolver::getDefinedFqn($node);
if ($fqn === null) { if ($fqn === null) {
return []; return [];
} }

View File

@ -6,7 +6,7 @@ namespace LanguageServer\Tests\Server\TextDocument;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PhpParser\{NodeTraverser, Node}; use PhpParser\{NodeTraverser, Node};
use PhpParser\NodeVisitor\NameResolver; use PhpParser\NodeVisitor\NameResolver;
use LanguageServer\{LanguageClient, Project, PhpDocument, Parser}; use LanguageServer\{LanguageClient, Project, PhpDocument, Parser, DefinitionResolver};
use LanguageServer\Protocol\ClientCapabilities; use LanguageServer\Protocol\ClientCapabilities;
use LanguageServer\Tests\MockProtocolStream; use LanguageServer\Tests\MockProtocolStream;
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector}; use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
@ -24,7 +24,7 @@ class DefinitionCollectorTest extends TestCase
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver); $traverser->addVisitor(new NameResolver);
$traverser->addVisitor(new ReferencesAdder($document)); $traverser->addVisitor(new ReferencesAdder($document));
$definitionCollector = new DefinitionCollector; $definitionCollector = new DefinitionCollector(new DefinitionResolver($project));
$traverser->addVisitor($definitionCollector); $traverser->addVisitor($definitionCollector);
$stmts = $parser->parse(file_get_contents($uri)); $stmts = $parser->parse(file_get_contents($uri));
$traverser->traverse($stmts); $traverser->traverse($stmts);
@ -63,7 +63,7 @@ class DefinitionCollectorTest extends TestCase
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver); $traverser->addVisitor(new NameResolver);
$traverser->addVisitor(new ReferencesAdder($document)); $traverser->addVisitor(new ReferencesAdder($document));
$definitionCollector = new DefinitionCollector; $definitionCollector = new DefinitionCollector(new DefinitionResolver($project));
$traverser->addVisitor($definitionCollector); $traverser->addVisitor($definitionCollector);
$stmts = $parser->parse(file_get_contents($uri)); $stmts = $parser->parse(file_get_contents($uri));
$traverser->traverse($stmts); $traverser->traverse($stmts);