Correctly create defintion for dynamically load model/library
parent
f1645a4b8f
commit
39de0df8d8
|
@ -3,90 +3,110 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
||||
use PhpParser\{NodeVisitorAbstract, Node};
|
||||
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
||||
|
||||
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
|
||||
* Depends on ReferencesAdder and NameResolver
|
||||
* Collects definitions created dynamically by framework such as CodeIgniter
|
||||
*/
|
||||
class DynamicLoader extends NodeVisitorAbstract
|
||||
{
|
||||
public $definitionCollector;
|
||||
public $definitionResolver;
|
||||
public $prettyPrinter;
|
||||
|
||||
public function __construct(DefinitionCollector $definitionCollector,
|
||||
DefinitionResolver $definitionResolver)
|
||||
public function __construct(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)
|
||||
{
|
||||
// check its name is 'model'
|
||||
if (!($node instanceof Node\Expr\MethodCall && $node->name === 'model')) {
|
||||
return;
|
||||
}
|
||||
// check its name is 'model'
|
||||
if (!($node instanceof Node\Expr\MethodCall)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($node->name !== 'model' && $node->name !== 'library') {
|
||||
return;
|
||||
}
|
||||
|
||||
// check its caller is a 'load'
|
||||
$caller = $node->getAttribute('previousSibling');
|
||||
if (!($caller instanceof Node\Expr\PropertyFetch && $caller->name !== 'load')) {
|
||||
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;
|
||||
if (!isset($node->var) || !isset($node->var->name) || $node->var->name !== 'load') {
|
||||
return;
|
||||
}
|
||||
|
||||
// $node's FQN is "className->memberSuffix()". But we need to construct loaded name.
|
||||
$def = $this->findModelDef($argstr);
|
||||
$argSize = count($node->args);
|
||||
if ($argSize == 0 || $argSize == 3) { // when argSize = 3 it's loading from db
|
||||
return;
|
||||
}
|
||||
|
||||
//var_dump($def);
|
||||
|
||||
// if we cannot find definition, just return.
|
||||
if ($def === NULL) {
|
||||
return;
|
||||
}
|
||||
// make sure the first argument is a string.
|
||||
if (!($node->args[0]->value instanceof Node\Scalar\String_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fqn = $def->fqn;
|
||||
$resolver = $this->definitionResolver;
|
||||
$argNode = $node->args[0];
|
||||
$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.
|
||||
$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.
|
||||
$definition = $resolver->createDefinitionFromNode($node, $fqn);
|
||||
// Have to override type:
|
||||
$definition->type = new Types\Object_;
|
||||
// TODO: check if setting document path is correct.
|
||||
// Create symbol
|
||||
// $classFqnParts = preg_split('/(::|->|\\\\)/', $fqn);
|
||||
// array_pop($classFqnParts);
|
||||
// $classFqn = implode('\\', $classFqnParts);
|
||||
$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