1
0
Fork 0

Correctly create defintion for dynamically load model/library

pull/483/head
Fuyao Zhao 2017-06-14 23:30:12 +08:00 committed by Alan Li
parent f1645a4b8f
commit 39de0df8d8
1 changed files with 80 additions and 60 deletions

View File

@ -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;
} }
} }