From edc69a3326e1c943ba57cb92c7482dcc16b09222 Mon Sep 17 00:00:00 2001 From: Alan Li Date: Tue, 7 Mar 2017 17:35:50 -0800 Subject: [PATCH 01/12] avoid generating stubs. --- src/ComposerScripts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ComposerScripts.php b/src/ComposerScripts.php index 487c39d..6cae6d7 100644 --- a/src/ComposerScripts.php +++ b/src/ComposerScripts.php @@ -45,7 +45,7 @@ class ComposerScripts } $uris = yield $finder->find("$stubsLocation/**/*.php"); - + $uris = []; foreach ($uris as $uri) { echo "Parsing $uri\n"; $content = yield $contentRetriever->retrieve($uri); From e63256b438a95f70c998e86bca7c47372391edcb Mon Sep 17 00:00:00 2001 From: Alan Li Date: Wed, 10 May 2017 10:11:14 -0400 Subject: [PATCH 02/12] Fix text document loader bug. --- src/PhpDocumentLoader.php | 2 +- src/Server/TextDocument.php | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/PhpDocumentLoader.php b/src/PhpDocumentLoader.php index 57a7e9c..e683180 100644 --- a/src/PhpDocumentLoader.php +++ b/src/PhpDocumentLoader.php @@ -20,7 +20,7 @@ class PhpDocumentLoader * * @var PhpDocument */ - private $documents = []; + public $documents = []; /** * @var ContentRetriever diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 5a2819e..ff3646f 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -261,7 +261,15 @@ class TextDocument public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise { return coroutine(function () use ($textDocument, $position) { - $document = yield $this->documentLoader->getOrLoad($textDocument->uri); + $documentLoader = $this->documentLoader;//->getOrLoad($textDocument->uri); + $document = null; + if (isset($documentLoader->documents[$textDocument->uri])) { + $document = $documentLoader->documents[$textDocument->uri]; + } else { + $document = yield $documentLoader->load($textDocument->uri); + $documentLoader->documents[$textDocument->uri] = $document; + } + $node = $document->getNodeAtPosition($position); if ($node === null) { return []; From b8ae04d56b5cf520589cad7d04b35ddffb285ded Mon Sep 17 00:00:00 2001 From: Alan Li Date: Wed, 10 May 2017 14:29:52 -0400 Subject: [PATCH 03/12] Make indexing sync-ed. --- src/LanguageServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index 118dd93..a0a8168 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -232,7 +232,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher $this->composerLock, $this->composerJson ); - $indexer->index()->otherwise('\\LanguageServer\\crash'); + yield $indexer->index()->otherwise('\\LanguageServer\\crash'); } From c810f49633b42169dcffd76bfdcbbda60e278e2e Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Thu, 1 Jun 2017 17:43:03 +0800 Subject: [PATCH 04/12] skip large file by treat it as empty file --- src/PhpDocumentLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpDocumentLoader.php b/src/PhpDocumentLoader.php index e683180..9d7b642 100644 --- a/src/PhpDocumentLoader.php +++ b/src/PhpDocumentLoader.php @@ -110,7 +110,7 @@ class PhpDocumentLoader $content = yield $this->contentRetriever->retrieve($uri); $size = strlen($content); if ($size > $limit) { - throw new ContentTooLargeException($uri, $size, $limit); + return $this->create($uri, ""); } if (isset($this->documents[$uri])) { From f1645a4b8f7b6639bfdffb9dafd1f79da0598b57 Mon Sep 17 00:00:00 2001 From: Alan Li Date: Mon, 12 Jun 2017 21:16:44 -0400 Subject: [PATCH 05/12] Basic infrastructure to index CodeIgnitor's dynamically loaded module. --- src/NodeVisitor/DynamicLoader.php | 92 +++++++++++++++++++++++++++++++ src/PhpDocument.php | 27 +++++++++ 2 files changed, 119 insertions(+) create mode 100644 src/NodeVisitor/DynamicLoader.php diff --git a/src/NodeVisitor/DynamicLoader.php b/src/NodeVisitor/DynamicLoader.php new file mode 100644 index 0000000..454dbc0 --- /dev/null +++ b/src/NodeVisitor/DynamicLoader.php @@ -0,0 +1,92 @@ +definitionCollector = $definitionCollector; + $this->definitionResolver = $definitionResolver; + } + + // 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 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; + + // $node's FQN is "className->memberSuffix()". But we need to construct loaded name. + $def = $this->findModelDef($argstr); + + //var_dump($def); + + // if we cannot find definition, just return. + if ($def === NULL) { + return; + } + + $fqn = $def->fqn; + $resolver = $this->definitionResolver; + + // add fqn to nodes and definitions. + $this->definitionCollector->nodes[$fqn] = $node; + + // 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. + + $this->definitionCollector->definitions[$fqn] = $definition; + } +} diff --git a/src/PhpDocument.php b/src/PhpDocument.php index ff3cf8b..e400fc5 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -3,6 +3,16 @@ declare(strict_types = 1); namespace LanguageServer; +use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, TextEdit}; +use LanguageServer\NodeVisitor\{ + NodeAtPositionFinder, + ReferencesAdder, + DocBlockParser, + DefinitionCollector, + ColumnCalculator, + ReferencesCollector, + DynamicLoader +}; use LanguageServer\Index\Index; use LanguageServer\Protocol\{ Diagnostic, Position, Range @@ -152,6 +162,23 @@ class PhpDocument $this->definitionNodes = $treeAnalyzer->getDefinitionNodes(); + // Report errors from parsing docblocks + foreach ($docBlockParser->errors as $error) { + $this->diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::WARNING, 'php'); + } + + $traverser = new NodeTraverser; + + // Collect all definitions + $definitionCollector = new DefinitionCollector($this->definitionResolver); + $traverser->addVisitor($definitionCollector); + + $traverser->addVisitor(new DynamicLoader($definitionCollector, $this->definitionResolver)); + + // Collect all references + $referencesCollector = new ReferencesCollector($this->definitionResolver); + $traverser->addVisitor($referencesCollector); + $this->referenceNodes = $treeAnalyzer->getReferenceNodes(); foreach ($this->definitions as $fqn => $definition) { From 39de0df8d884aae34fa9257c0482e5568001d021 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Wed, 14 Jun 2017 23:30:12 +0800 Subject: [PATCH 06/12] 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; } } From 420c22bd93d838d61918703fe2ebf8f52448e7cc Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Thu, 15 Jun 2017 02:45:24 +0800 Subject: [PATCH 07/12] Support load->helper --- src/NodeVisitor/DynamicLoader.php | 53 ++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/NodeVisitor/DynamicLoader.php b/src/NodeVisitor/DynamicLoader.php index 2a6c9fa..eae0b13 100644 --- a/src/NodeVisitor/DynamicLoader.php +++ b/src/NodeVisitor/DynamicLoader.php @@ -31,7 +31,7 @@ class DynamicLoader extends NodeVisitorAbstract return; } - if ($node->name !== 'model' && $node->name !== 'library') { + if ($node->name !== 'model' && $node->name !== 'library' && $node->name !== 'helper') { return; } @@ -45,26 +45,41 @@ class DynamicLoader extends NodeVisitorAbstract return; } - // make sure the first argument is a string. - if (!($node->args[0]->value instanceof Node\Scalar\String_)) { - return; - } + $nameNode = NULL; + if ($node->args[0]->value instanceof Node\Scalar\String_) { + // make sure the first argument is a string. - $argNode = $node->args[0]; - $argstr = $argNode->value->value; - $argparts = explode('\\', $argstr); - $modelName = array_pop($argparts); - $fieldName = $modelName; + if ($argSize == 2) { + $nameNode = $node->args[1]->value; + } + $this->createDefintion($node, $node->args[0]->value, $nameNode); + } else if ($node->args[0]->value instanceof Node\Expr\Array_) { + $elems = $node->args[0]->value->items; + foreach ($elems as $item) { + if ($item->value instanceof Node\Scalar\String_) { + $this->createDefintion($node, $item->value, $nameNode); + } + } + } + } + + + public function createDefintion($callNode, $entityNode, $nameNode) + { + $entityString = $entityNode->value; + $entityParts = explode('\\', $entityString); + $enityName = array_pop($entityParts); + $fieldName = $enityName; // deal with case like: $this->_CI->load->model('users_mdl', 'hahaha'); - if ($argSize == 2) { - if (!($node->args[1]->value instanceof Node\Scalar\String_)) { + if ($callNode->name = "model" && $nameNode !== NULL) { + if (!($nameNode instanceof Node\Scalar\String_)) { return; } - $fieldName = $node->args[1]->value->value; + $fieldName = $nameNode->value; } - $enclosedClass = $node; + $enclosedClass = $callNode; $fqn = NULL; $classFqn = NULL; while ($enclosedClass !== NULL) { @@ -81,18 +96,18 @@ class DynamicLoader extends NodeVisitorAbstract return; } - // add fqn to nodes and definitions. - $this->definitionCollector->nodes[$fqn] = $argNode; + // add fqn to nodes and definitions. + $this->definitionCollector->nodes[$fqn] = $entityNode; // Create symbol // $classFqnParts = preg_split('/(::|->|\\\\)/', $fqn); // array_pop($classFqnParts); // $classFqn = implode('\\', $classFqnParts); - $sym = new SymbolInformation($fieldName, SymbolKind::PROPERTY, Location::fromNode($argNode), $classFqn); + $sym = new SymbolInformation($fieldName, SymbolKind::PROPERTY, Location::fromNode($entityNode), $classFqn); // Create type - array_push($argparts, ucwords($modelName)); - $typeName = implode('\\', $argparts); + array_push($entityParts, ucwords($enityName)); + $typeName = implode('\\', $entityParts); $type = new Types\Object_(new Fqsen('\\' . $typeName)); // Create defintion from symbol, type and all others From c58407e8ad7ec67518c713164808a282e17982a8 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Thu, 15 Jun 2017 05:28:49 +0800 Subject: [PATCH 08/12] Try to lookup base class member while resolveExpressionNodeToType --- src/DefinitionResolver.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index 51c5955..bef6b9a 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -679,6 +679,29 @@ class DefinitionResolver return new Types\Object_(new Fqsen('\\' . $classFqn)); } return $def->type; + $memberSuffix = '->' . $expr->name; + if ($expr instanceof Node\Expr\MethodCall) { + $memberSuffix .= '()'; + } + + // Find the right class that implements the member + $implementorFqns = [$classFqn]; + while ($implementorFqn = array_shift($implementorFqns)) { + // If the member FQN exists, return it + $def = $this->index->getDefinition($implementorFqn . $memberSuffix); + if ($def) { + return $def->type; + } + // Get Definition of implementor class + $implementorDef = $this->index->getDefinition($implementorFqn); + // If it doesn't exist, return the initial guess + if ($implementorDef === null) { + break; + } + // Repeat for parent class + if ($implementorDef->extends) { + foreach ($implementorDef->extends as $extends) { + $implementorFqns[] = $extends; } } } From 737ee911e180563934d416d94efd7df1d6f414eb Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Thu, 15 Jun 2017 05:30:20 +0800 Subject: [PATCH 09/12] Don't need references collector in our use case --- src/PhpDocument.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpDocument.php b/src/PhpDocument.php index e400fc5..4d88a02 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -173,11 +173,11 @@ class PhpDocument $definitionCollector = new DefinitionCollector($this->definitionResolver); $traverser->addVisitor($definitionCollector); - $traverser->addVisitor(new DynamicLoader($definitionCollector, $this->definitionResolver)); + $traverser->addVisitor(new DynamicLoader($definitionCollector)); // Collect all references $referencesCollector = new ReferencesCollector($this->definitionResolver); - $traverser->addVisitor($referencesCollector); + //$traverser->addVisitor($referencesCollector); $this->referenceNodes = $treeAnalyzer->getReferenceNodes(); From 5a0fa66a346449cc3d9b01685a0f4c20f24609c6 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Thu, 15 Jun 2017 12:30:40 +0800 Subject: [PATCH 10/12] simplify entity name assumption --- src/NodeVisitor/DynamicLoader.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NodeVisitor/DynamicLoader.php b/src/NodeVisitor/DynamicLoader.php index eae0b13..2d61a68 100644 --- a/src/NodeVisitor/DynamicLoader.php +++ b/src/NodeVisitor/DynamicLoader.php @@ -106,8 +106,9 @@ class DynamicLoader extends NodeVisitorAbstract $sym = new SymbolInformation($fieldName, SymbolKind::PROPERTY, Location::fromNode($entityNode), $classFqn); // Create type - array_push($entityParts, ucwords($enityName)); - $typeName = implode('\\', $entityParts); + // array_push($entityParts, ucwords($enityName)); + // $typeName = implode('\\', $entityParts); + $typeName = ucwords($enityName); $type = new Types\Object_(new Fqsen('\\' . $typeName)); // Create defintion from symbol, type and all others From c41a492f3b20dc5ea62b4c1fa3675b47250dd805 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Fri, 16 Jun 2017 21:09:31 +0800 Subject: [PATCH 11/12] Fix entity path splitter --- src/NodeVisitor/DynamicLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NodeVisitor/DynamicLoader.php b/src/NodeVisitor/DynamicLoader.php index 2d61a68..9201c5b 100644 --- a/src/NodeVisitor/DynamicLoader.php +++ b/src/NodeVisitor/DynamicLoader.php @@ -67,7 +67,7 @@ class DynamicLoader extends NodeVisitorAbstract public function createDefintion($callNode, $entityNode, $nameNode) { $entityString = $entityNode->value; - $entityParts = explode('\\', $entityString); + $entityParts = explode('/', $entityString); $enityName = array_pop($entityParts); $fieldName = $enityName; From 2550d31c8ea07aa5afc898586126ba3e1b9a258d Mon Sep 17 00:00:00 2001 From: Alan Li Date: Mon, 19 Jun 2017 21:39:10 -0400 Subject: [PATCH 12/12] PHP framework to support module autoload in CodeIgniter. --- src/DefinitionResolver.php | 10 ++ src/Indexer.php | 12 ++ src/NodeVisitor/DynamicLoader.php | 185 +++++++++++++++++++++++++++++- src/PhpDocument.php | 24 +++- 4 files changed, 223 insertions(+), 8 deletions(-) diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index bef6b9a..50aac33 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -34,6 +34,16 @@ class DefinitionResolver */ private $docBlockFactory; + + public $autoloadInfo; + + // The followings are autoloading properties. + public $autoloadLibraries; + public $autoloadClasses; + public $autoloadModels; + public $autoloadLanguages; + + /** * @param ReadableIndex $index */ diff --git a/src/Indexer.php b/src/Indexer.php index 9f8749d..0a55b86 100644 --- a/src/Indexer.php +++ b/src/Indexer.php @@ -118,6 +118,18 @@ class Indexer /** @var string[][] */ $deps = []; + $ending = "application/config/autoload.php"; + $endingLength = strlen($ending); + foreach ($uris as $uri) { + $found = (substr($uri, -$endingLength) === $ending); + + // if found, put it to the beginning of the list so that it gets analyzed first. + if ($found) { + array_unshift($uris, $uri); + break; + } + } + foreach ($uris as $uri) { $packageName = getPackageName($uri, $this->composerJson); if ($this->composerLock !== null && $packageName) { diff --git a/src/NodeVisitor/DynamicLoader.php b/src/NodeVisitor/DynamicLoader.php index 9201c5b..7f83500 100644 --- a/src/NodeVisitor/DynamicLoader.php +++ b/src/NodeVisitor/DynamicLoader.php @@ -17,16 +17,146 @@ class DynamicLoader extends NodeVisitorAbstract { public $definitionCollector; public $prettyPrinter; + public $definitionResolver; + + private $collectAutoload; - public function __construct(DefinitionCollector $definitionCollector) + public function __construct(DefinitionCollector $definitionCollector, DefinitionResolver $definitionResolver, bool $collectAutoload) { $this->definitionCollector = $definitionCollector; + $this->definitionResolver = $definitionResolver; + $this->collectAutoload = $collectAutoload; $this->prettyPrinter = new PrettyPrinter; } + public function visitAutoloadClassDeclaration(Node $node) { + if (!($node instanceof Node\Stmt\Class_)) { + return; + } + + $extends = $node->extends; + if (!isset($extends->parts)) { + return; + } + $shouldAutoload = false; + foreach ($extends->parts as $part) { + // TODO: add more criteria here? + if ($part === "CI_Controller" || $part === "ST_Controller" || + $part === "ST_Auth_Controller") { + $shouldAutoload = true; + break; + } + } + + if (!$shouldAutoload) { + return; + } + + if (isset($this->definitionResolver->autoloadLibraries)) { + foreach ($this->definitionResolver->autoloadLibraries as $key => $value) { + $this->createAutoloadDefinition($node, $value); + } + } + + if (isset($this->definitionResolver->autoloadModels)) { + foreach ($this->definitionResolver->autoloadModels as $key => $value) { + $this->createAutoloadDefinition($node, $value); + } + } + + if (isset($this->definitionResolver->autoloadHelpers)) { + foreach ($this->definitionResolver->autoloadHelpers as $key => $value) { + $this->createAutoloadDefinition($node, $value); + } + } + + if (isset($this->definitionResolver->autoloadConfig)) { + foreach ($this->definitionResolver->autoloadConfig as $key => $value) { + $this->createAutoloadDefinition($node, $value); + } + } + + if (isset($this->definitionResolver->autoloadLanguage)) { + foreach ($this->definitionResolver->autoloadLanguage as $key => $value) { + $this->createAutoloadDefinition($node, $value); + } + } + } + + public function visitAutoloadNode(Node $node) { + // looking at array assignments. + if (!($node instanceof Node\Expr\Assign)) { + return; + } + + // check left hand side. + $lhs = $node->var; + if (!($lhs instanceof Node\Expr\ArrayDimFetch)) { + return; + } + + $dimFetchVar = $lhs->var; + if (!($dimFetchVar instanceof Node\Expr\Variable)) { + return; + } + + if ($dimFetchVar->name !== "autoload") { + return; + } + // end of checking left hand side. + + $dim = $lhs->dim; + if (!($dim instanceof Node\Scalar\String_)) { + return; + } + // TODO: support more than libraries + $target = $dim->value; + + // extract right hand side. + $rhs = $node->expr; + if (!($rhs instanceof Node\Expr\Array_)) { + return; + } + + // $target -> $node reference + $arrayOfLibs = $rhs->items; + foreach ($arrayOfLibs as $lib) { + $libName = $lib->value->value; + switch ($target) { + case "libraries": + $this->definitionResolver->autoloadLibraries[$libName] = $lib; + break; + case "helper": + $this->definitionResolver->autoloadHelpers[$libName] = $lib; + break; + case "config": + $this->definitionResolver->autoloadConfig[$libName] = $lib; + break; + case "model": + $this->definitionResolver->autoloadModels[$libName] = $lib; + break; + case "language": + $this->definitionResolver->autoloadLanguage[$libName] = $lib; + break; + } + } + + } + public function enterNode(Node $node) { - // check its name is 'model' + // handling autoloading. + if ($this->collectAutoload) { + // records autoloading fields into definition resolver. + $this->visitAutoloadNode($node); + } + + // spits autoloading fields to a class that is derived from controller classes. + $this->visitAutoloadClassDeclaration($node); + + // The follwoing is for handling dynamic loading. (Finished) + + // check its name is 'model', 'library' or 'helper'. if (!($node instanceof Node\Expr\MethodCall)) { return; } @@ -52,26 +182,69 @@ class DynamicLoader extends NodeVisitorAbstract if ($argSize == 2) { $nameNode = $node->args[1]->value; } - $this->createDefintion($node, $node->args[0]->value, $nameNode); + $this->createDefinition($node, $node->args[0]->value, $nameNode); } else if ($node->args[0]->value instanceof Node\Expr\Array_) { $elems = $node->args[0]->value->items; foreach ($elems as $item) { if ($item->value instanceof Node\Scalar\String_) { - $this->createDefintion($node, $item->value, $nameNode); + $this->createDefinition($node, $item->value, $nameNode); } } } } + // copied from createDefinition and tailored. + public function createAutoloadDefinition(Node $classNode, Node $entityNode) + { + $fieldName = $entityNode->value->value; - public function createDefintion($callNode, $entityNode, $nameNode) + $enclosedClass = $classNode; + $classFqn = $enclosedClass->namespacedName->toString(); + $fqn = $classFqn . "->" . $fieldName; + + // if we cannot find definition, just return. + if ($fqn === NULL) { + return; + } + + // add fqn to nodes and definitions. + $this->definitionCollector->nodes[$fqn] = $entityNode; + + // Create symbol +// $classFqnParts = preg_split('/(::|->|\\\\)/', $fqn); +// array_pop($classFqnParts); +// $classFqn = implode('\\', $classFqnParts); + $sym = new SymbolInformation($fieldName, SymbolKind::PROPERTY, Location::fromNode($entityNode), $classFqn); + + // Create type + // array_push($entityParts, ucwords($enityName)); + // $typeName = implode('\\', $entityParts); + $typeName = ucwords($fieldName); + $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; + } + + public function createDefinition($callNode, $entityNode, $nameNode) { $entityString = $entityNode->value; $entityParts = explode('/', $entityString); $enityName = array_pop($entityParts); $fieldName = $enityName; - // deal with case like: $this->_CI->load->model('users_mdl', 'hahaha'); + // deal with case like: $this->_CI->load->model('users_mdl', 'hahaha'); if ($callNode->name = "model" && $nameNode !== NULL) { if (!($nameNode instanceof Node\Scalar\String_)) { return; diff --git a/src/PhpDocument.php b/src/PhpDocument.php index 4d88a02..08c6865 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -115,6 +115,10 @@ class PhpDocument $this->updateContent($content); } + private function isVisitingAutoload() { + return false; + } + /** * Get all references of a fully qualified name * @@ -156,7 +160,23 @@ class PhpDocument $treeAnalyzer = new TreeAnalyzer($this->parser, $content, $this->docBlockFactory, $this->definitionResolver, $this->uri); - $this->diagnostics = $treeAnalyzer->getDiagnostics(); + $this->diagnostics = []; + foreach ($errorHandler->getErrors() as $error) { + $this->diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php'); + } + + // figure out if it is analyzing an autoload file. + $isAutoload = false; + $ending = "application/config/autoload.php"; + $endingLength = strlen($ending); + $isAutoload = (substr($this->uri, -$endingLength) === $ending); + + // $stmts can be null in case of a fatal parsing error + if ($stmts) { + $traverser = new NodeTraverser; + + // Resolve aliased names to FQNs + $traverser->addVisitor(new NameResolver($errorHandler)); $this->definitions = $treeAnalyzer->getDefinitions(); @@ -173,7 +193,7 @@ class PhpDocument $definitionCollector = new DefinitionCollector($this->definitionResolver); $traverser->addVisitor($definitionCollector); - $traverser->addVisitor(new DynamicLoader($definitionCollector)); + $traverser->addVisitor(new DynamicLoader($definitionCollector, $this->definitionResolver, $isAutoload)); // Collect all references $referencesCollector = new ReferencesCollector($this->definitionResolver);