From 39de0df8d884aae34fa9257c0482e5568001d021 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Wed, 14 Jun 2017 23:30:12 +0800 Subject: [PATCH] Correctly create defintion for dynamically load model/library --- src/NodeVisitor/DynamicLoader.php | 140 +++++++++++++++++------------- 1 file changed, 80 insertions(+), 60 deletions(-) diff --git a/src/NodeVisitor/DynamicLoader.php b/src/NodeVisitor/DynamicLoader.php index 454dbc0..2a6c9fa 100644 --- a/src/NodeVisitor/DynamicLoader.php +++ b/src/NodeVisitor/DynamicLoader.php @@ -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; } }