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);