Correctly create defintion for dynamically load model/library
parent
f1645a4b8f
commit
39de0df8d8
|
@ -3,90 +3,110 @@ declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer\NodeVisitor;
|
namespace LanguageServer\NodeVisitor;
|
||||||
|
|
||||||
|
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
||||||
use PhpParser\{NodeVisitorAbstract, Node};
|
use PhpParser\{NodeVisitorAbstract, Node};
|
||||||
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
||||||
|
|
||||||
use LanguageServer\{Definition, DefinitionResolver};
|
use LanguageServer\{Definition, DefinitionResolver};
|
||||||
use LanguageServer\Protocol\{SymbolInformation, SymbolKind};
|
use LanguageServer\Protocol\{SymbolInformation, SymbolKind, Location};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects definitions of classes, interfaces, traits, methods, properties and constants
|
* Collects definitions created dynamically by framework such as CodeIgniter
|
||||||
* Depends on ReferencesAdder and NameResolver
|
|
||||||
*/
|
*/
|
||||||
class DynamicLoader extends NodeVisitorAbstract
|
class DynamicLoader extends NodeVisitorAbstract
|
||||||
{
|
{
|
||||||
public $definitionCollector;
|
public $definitionCollector;
|
||||||
public $definitionResolver;
|
public $prettyPrinter;
|
||||||
|
|
||||||
public function __construct(DefinitionCollector $definitionCollector,
|
public function __construct(DefinitionCollector $definitionCollector)
|
||||||
DefinitionResolver $definitionResolver)
|
|
||||||
{
|
{
|
||||||
$this->definitionCollector = $definitionCollector;
|
$this->definitionCollector = $definitionCollector;
|
||||||
$this->definitionResolver = $definitionResolver;
|
$this->prettyPrinter = new PrettyPrinter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// using the lowercase $modelName to find the FQN
|
|
||||||
public function findModelDef(String $filepath) {
|
|
||||||
$filename = $filepath . ".php";
|
|
||||||
foreach ($this->definitionCollector->definitions as $def) {
|
|
||||||
$fqn = $def->fqn;
|
|
||||||
$si = $def->symbolInformation;
|
|
||||||
|
|
||||||
if (!isset($si->location->uri)) continue;
|
|
||||||
$uri = strtolower($si->location->uri);
|
|
||||||
|
|
||||||
$endsWith = substr_compare($uri, $filename, strlen($uri) - strlen($filename)) === 0;
|
|
||||||
|
|
||||||
// if the file matches, find the first class
|
|
||||||
if ($endsWith && $si->kind === SymbolKind::CLASS_) {
|
|
||||||
return $def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function enterNode(Node $node)
|
public function enterNode(Node $node)
|
||||||
{
|
{
|
||||||
// check its name is 'model'
|
// check its name is 'model'
|
||||||
if (!($node instanceof Node\Expr\MethodCall && $node->name === 'model')) {
|
if (!($node instanceof Node\Expr\MethodCall)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($node->name !== 'model' && $node->name !== 'library') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// check its caller is a 'load'
|
// check its caller is a 'load'
|
||||||
$caller = $node->getAttribute('previousSibling');
|
if (!isset($node->var) || !isset($node->var->name) || $node->var->name !== 'load') {
|
||||||
if (!($caller instanceof Node\Expr\PropertyFetch && $caller->name !== 'load')) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the first argument is a string.
|
|
||||||
if (!(isset($node->args[0]) && $node->args[0]->value instanceof Node\Scalar\String_)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//if(!($node instanceof Node\Expr\MethodCall && isset($node->args[0]))) return;
|
|
||||||
|
|
||||||
$argstr = $node->args[0]->value->value;
|
|
||||||
|
|
||||||
// $node's FQN is "className->memberSuffix()". But we need to construct loaded name.
|
$argSize = count($node->args);
|
||||||
$def = $this->findModelDef($argstr);
|
if ($argSize == 0 || $argSize == 3) { // when argSize = 3 it's loading from db
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//var_dump($def);
|
// make sure the first argument is a string.
|
||||||
|
if (!($node->args[0]->value instanceof Node\Scalar\String_)) {
|
||||||
// if we cannot find definition, just return.
|
return;
|
||||||
if ($def === NULL) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fqn = $def->fqn;
|
$argNode = $node->args[0];
|
||||||
$resolver = $this->definitionResolver;
|
$argstr = $argNode->value->value;
|
||||||
|
$argparts = explode('\\', $argstr);
|
||||||
|
$modelName = array_pop($argparts);
|
||||||
|
$fieldName = $modelName;
|
||||||
|
|
||||||
|
// deal with case like: $this->_CI->load->model('users_mdl', 'hahaha');
|
||||||
|
if ($argSize == 2) {
|
||||||
|
if (!($node->args[1]->value instanceof Node\Scalar\String_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$fieldName = $node->args[1]->value->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$enclosedClass = $node;
|
||||||
|
$fqn = NULL;
|
||||||
|
$classFqn = NULL;
|
||||||
|
while ($enclosedClass !== NULL) {
|
||||||
|
$enclosedClass = $enclosedClass->getAttribute('parentNode');
|
||||||
|
if ($enclosedClass instanceof Node\Stmt\ClassLike && isset($enclosedClass->name)) {
|
||||||
|
$classFqn = $enclosedClass->namespacedName->toString();
|
||||||
|
$fqn = $classFqn . '->' . $fieldName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we cannot find definition, just return.
|
||||||
|
if ($fqn === NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// add fqn to nodes and definitions.
|
// add fqn to nodes and definitions.
|
||||||
$this->definitionCollector->nodes[$fqn] = $node;
|
$this->definitionCollector->nodes[$fqn] = $argNode;
|
||||||
|
|
||||||
// Definition is created based on types and context of $node. we should construct by ourselves.
|
// Create symbol
|
||||||
$definition = $resolver->createDefinitionFromNode($node, $fqn);
|
// $classFqnParts = preg_split('/(::|->|\\\\)/', $fqn);
|
||||||
// Have to override type:
|
// array_pop($classFqnParts);
|
||||||
$definition->type = new Types\Object_;
|
// $classFqn = implode('\\', $classFqnParts);
|
||||||
// TODO: check if setting document path is correct.
|
$sym = new SymbolInformation($fieldName, SymbolKind::PROPERTY, Location::fromNode($argNode), $classFqn);
|
||||||
|
|
||||||
$this->definitionCollector->definitions[$fqn] = $definition;
|
// Create type
|
||||||
|
array_push($argparts, ucwords($modelName));
|
||||||
|
$typeName = implode('\\', $argparts);
|
||||||
|
$type = new Types\Object_(new Fqsen('\\' . $typeName));
|
||||||
|
|
||||||
|
// Create defintion from symbol, type and all others
|
||||||
|
$def = new Definition;
|
||||||
|
$def->canBeInstantiated = false;
|
||||||
|
$def->isGlobal = false; // TODO check the meaning of this, why public field has this set to false?
|
||||||
|
$def->isStatic = false; // it should not be a static field
|
||||||
|
$def->fqn = $fqn;
|
||||||
|
$def->symbolInformation = $sym;
|
||||||
|
$def->type = $type;
|
||||||
|
// Maybe this is not the best
|
||||||
|
$def->declarationLine = $fieldName; // $this->prettyPrinter->prettyPrint([$argNode]);
|
||||||
|
$def->documentation = "Dynamically Generated Field: " . $fieldName;
|
||||||
|
|
||||||
|
$this->definitionCollector->definitions[$fqn] = $def;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue