From b7f5d6bb759235a582b7efa902ae2a3e427aef71 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 6 Jul 2019 13:48:50 -0400 Subject: [PATCH 1/6] Use FQN for PHP types --- src/udl/skel/PHP/pylib/lang_php.py | 125 +++++++++++++++++++++++------ 1 file changed, 101 insertions(+), 24 deletions(-) diff --git a/src/udl/skel/PHP/pylib/lang_php.py b/src/udl/skel/PHP/pylib/lang_php.py index 4e7ba5cfb9..3d80d75743 100644 --- a/src/udl/skel/PHP/pylib/lang_php.py +++ b/src/udl/skel/PHP/pylib/lang_php.py @@ -1179,13 +1179,12 @@ def _buf_indep_libs_from_env(self, env): # Warn the user if there is a huge number of import dirs that # might slow down completion. - all_dirs = list(extra_dirs) + list(include_dirs) - num_import_dirs = len(all_dirs) + num_import_dirs = len(extra_dirs) + len(include_dirs) if num_import_dirs > 100: msg = "This buffer is configured with %d %s import dirs: " \ "this may result in poor completion performance" % \ (num_import_dirs, self.lang) - self.mgr.report_message(msg, "\n".join(all_dirs)) + self.mgr.report_message(msg, "\n".join(list(extra_dirs) + list(include_dirs))) # - cataloglib, stdlib catalog_selections = env.get_pref("codeintel_selected_catalogs") @@ -1465,8 +1464,64 @@ def sortByLine(seq): return seq +_phpdoc_primitive_types = [ + "null", + "int", "integer", + "bool", "boolean", + "float", "double", + "string", + "array", "object", + "callable", "iterable", + "resource", + "mixed" +] +def _getFQNForType(varType, fileinfo, namespace): + """ + Convert variable type (PHPDoc style) to FQN + """ + # Handle int[] + if varType.endswith("[]"): + return _getFQNForTypePiece(varType[:-2], fileinfo, namespace) + "[]" + # Handle (...) + if varType.startswith("(") and varType.endswith(")"): + return _getFQNForTypePiece(varType[1:-1], fileinfo, namespace) + # Handle int|null + varTypes = varType.split("|"); + rtn = "" + for varTypePiece in varTypes: + if rtn != "": + rtn += "|" + rtn += _getFQNForTypePiece(varTypePiece, fileinfo, namespace) + return rtn + +def _getFQNForTypePiece(varType, fileinfo, namespace): + """ + Convert variable type (PHPDoc style) to FQN + """ + if not "\\" in varType and not varType in _phpdoc_primitive_types: + imports_to_check = None + if namespace: + imports_to_check = namespace.includes + elif fileinfo: + imports_to_check = fileinfo.includes + if imports_to_check: + # Check for use statements + for use_import in imports_to_check: + if use_import.alias == varType or use_import.symbol == varType: + varType = use_import.name + "\\" + use_import.symbol + if varType[0] != "\\": + varType = "\\" + varType + break; + else: + # Add namespace if not found + if namespace: + varType = namespace.name + "\\" + varType + if varType[0] != "\\": + varType = "\\" + varType + return varType + class PHPArg: - def __init__(self, name, citdl=None, signature=None, default=None): + def __init__(self, name, citdl=None, signature=None, default=None, namespace=None, fileinfo=None): """Set details for a function argument""" self.name = name self.citdl = citdl @@ -1478,6 +1533,8 @@ def __init__(self, name, citdl=None, signature=None, default=None): else: self.signature = "$%s" % (name, ) self.default = default + self.namespace = namespace + self.fileinfo = fileinfo def __repr__(self): return self.signature @@ -1491,7 +1548,10 @@ def updateCitdl(self, citdl): self.signature = "%s %s" % (citdl, self.signature.split(" ", 1)[1]) def toElementTree(self, cixelement, linestart=None): - cixarg = addCixArgument(cixelement, self.name, argtype=self.citdl) + argtype = self.citdl + if argtype: + argtype = _getFQNForType(argtype, self.fileinfo, self.namespace) + cixarg = addCixArgument(cixelement, self.name, argtype=argtype) if self.default: cixarg.attrib["default"] = self.default if linestart is not None: @@ -1505,7 +1565,7 @@ class PHPVariable: _ignored_php_types = ("object", "mixed") def __init__(self, name, line, vartype='', attributes='', doc=None, - fromPHPDoc=False, namespace=None): + fromPHPDoc=False, namespace=None, fileinfo=None): self.name = name self.types = [(line, vartype, fromPHPDoc)] self.linestart = line @@ -1516,6 +1576,8 @@ def __init__(self, name, line, vartype='', attributes='', doc=None, else: self.attributes = None self.doc = doc + self.fileinfo = fileinfo + self.namespace = namespace self.created_namespace = None if namespace: self.created_namespace = namespace.name @@ -1554,6 +1616,8 @@ def toElementTree(self, cixblob): vartype = None break # only consider the first @var if name not given + if vartype: + vartype = _getFQNForType(vartype, self.fileinfo, self.namespace) if not vartype and self.types: d = {} max_count = 0 @@ -1584,8 +1648,8 @@ def toElementTree(self, cixblob): return cixelement class PHPConstant(PHPVariable): - def __init__(self, name, line, vartype=''): - PHPVariable.__init__(self, name, line, vartype) + def __init__(self, name, line, vartype='', fileinfo=None): + PHPVariable.__init__(self, name, line, vartype, fileinfo=fileinfo) def __repr__(self): return "constant %s line %s type %s\n"\ @@ -1599,7 +1663,8 @@ def toElementTree(self, cixblob): class PHPFunction: def __init__(self, funcname, phpArgs, lineno, depth=0, attributes=None, doc=None, classname='', classparent='', - returnType=None, returnByRef=False): + returnType=None, returnByRef=False, + fileinfo=None, namespace=None): self.name = funcname self.args = phpArgs self.linestart = lineno @@ -1607,6 +1672,8 @@ def __init__(self, funcname, phpArgs, lineno, depth=0, self.depth = depth self.classname = classname self.classparent = classparent + self.namespace = namespace + self.fileinfo = fileinfo self.returnType = returnType self.returnByRef = returnByRef self.variables = {} # all variables used in class @@ -1643,10 +1710,11 @@ def __init__(self, funcname, phpArgs, lineno, depth=0, phpArg.updateCitdl(argInfo[0]) break else: - self.args.append(PHPArg(argInfo[1], citdl=argInfo[0])) + self.args.append(PHPArg(argInfo[1], citdl=argInfo[0], namespace=namespace, fileinfo=fileinfo)) if docinfo[2]: self.returnType = docinfo[2][0] if self.returnType: + # Update signature self.signature = '%s %s' % (self.returnType.lower(), self.signature, ) self.signature += "(" if self.args: @@ -1733,7 +1801,7 @@ def toElementTree(self, cixblob): for phpArg in self.args: phpArg.toElementTree(cixelement, self.linestart) if self.returnType: - addCixReturns(cixelement, self.returnType) + addCixReturns(cixelement, _getFQNForType(self.returnType, self.fileinfo, self.namespace)) # Add a "this" and "self" member for class functions #if self.classname: # createCixVariable(cixelement, "this", vartype=self.classname) @@ -1819,7 +1887,7 @@ class PHPClass: _re_magic_method = re.compile(r'^\s*@method\s+((?P[\w\\()|\[\]]+)\s+)?(?P\w+)(\(\))?(?P.*?)$', re.M|re.U) def __init__(self, name, extends, lineno, depth, attributes=None, - interfaces=None, doc=None): + interfaces=None, doc=None, namespace=None, fileinfo=None): self.name = name self.extends = extends self.linestart = lineno @@ -1848,7 +1916,7 @@ def __init__(self, name, extends, lineno, depth, attributes=None, all_matches = re.findall(self._re_magic_property, self.doc) for match in all_matches: varname = match[4][1:] # skip "$" in the name. - v = PHPVariable(varname, lineno, resolveDocStringTypeToType(match[3]), doc=match[5]) + v = PHPVariable(varname, lineno, resolveDocStringTypeToType(match[3]), doc=match[5], fileinfo=fileinfo) self.members[varname] = v if self.doc.find("@method") >= 0: all_matches = re.findall(self._re_magic_method, self.doc) @@ -1860,7 +1928,8 @@ def __init__(self, name, extends, lineno, depth, attributes=None, fndoc = match[4] phpArgs = [] fn = PHPFunction(fnname, phpArgs, lineno, depth=self.depth+1, - doc=fndoc, returnType=citdl) + doc=fndoc, returnType=citdl, + fileinfo=fileinfo, namespace=namespace) self.functions[fnname] = fn def __repr__(self): @@ -2250,7 +2319,9 @@ def addFunction(self, name, phpArgs=None, attributes=None, doc=None, classname=classname, classparent=extendsName, returnType=returnType, - returnByRef=returnByRef) + returnByRef=returnByRef, + fileinfo=self.fileinfo, + namespace=self.currentNamespace) if self.currentClass: self.currentClass.functions[self.currentFunction.name] = self.currentFunction elif self.currentNamespace: @@ -2281,7 +2352,9 @@ def addClass(self, name, extends=None, attributes=None, interfaces=None, doc=Non self.depth, attributes, interfaces, - doc=doc) + doc=doc, + fileinfo=self.fileinfo, + namespace=self.currentNamespace) toScope.classes[self.currentClass.name] = self.currentClass log.debug("%s: %s extends %s interfaces %s attributes %s on line %d in %s at depth %d\nDOCS: %s", self.currentClass.cixtype, @@ -2302,7 +2375,8 @@ def addClassMember(self, name, vartype, attributes=None, doc=None, forceToClass= self.currentFunction.variables[name] = PHPVariable(name, self.lineno, vartype, - doc=doc) + doc=doc, + fileinfo=self.fileinfo) elif vartype: log.debug("Adding type information for VAR: %r, vartype: %r", name, vartype) @@ -2314,7 +2388,8 @@ def addClassMember(self, name, vartype, attributes=None, doc=None, forceToClass= self.currentClass.members[name] = PHPVariable(name, self.lineno, vartype, attributes, - doc=doc) + doc=doc, + fileinfo=self.fileinfo) elif vartype: log.debug("Adding type information for CLASSMBR: %r, vartype: %r", name, vartype) @@ -2327,7 +2402,7 @@ def addClassConstant(self, name, vartype, doc=None): if phpConstant is None: log.debug("CLASS CONST: %r", name) self.currentClass.constants[name] = PHPConstant(name, self.lineno, - vartype) + vartype, fileinfo=self.fileinfo) elif vartype: log.debug("Adding type information for CLASS CONST: %r, " "vartype: %r", name, vartype) @@ -2401,7 +2476,8 @@ def addVariable(self, name, vartype='', attributes=None, doc=None, not self.currentFunction.hasArgumentWithName(name): phpVariable = PHPVariable(name, self.lineno, vartype, attributes, doc=doc, - fromPHPDoc=fromPHPDoc) + fromPHPDoc=fromPHPDoc, + fileinfo=self.fileinfo) self.currentFunction.variables[name] = phpVariable already_existed = False elif self.currentClass and \ @@ -2420,7 +2496,8 @@ def addVariable(self, name, vartype='', attributes=None, doc=None, phpVariable = PHPVariable(name, self.lineno, vartype, attributes, doc=doc, fromPHPDoc=fromPHPDoc, - namespace=self.currentNamespace) + namespace=self.currentNamespace, + fileinfo=self.fileinfo) self.fileinfo.variables[name] = phpVariable already_existed = False @@ -2446,7 +2523,7 @@ def addConstant(self, name, vartype='', doc=None): if phpConstant is None: if vartype and isinstance(vartype, (list, tuple)): vartype = ".".join(vartype) - toScope.constants[name] = PHPConstant(name, self.lineno, vartype) + toScope.constants[name] = PHPConstant(name, self.lineno, vartype, fileinfo=self.fileinfo) def addDefine(self, name, vartype='', doc=None): """Add a define at the global or namelisted scope level.""" @@ -2475,7 +2552,7 @@ def addDefine(self, name, vartype='', doc=None): if phpConstant is None: if vartype and isinstance(vartype, (list, tuple)): vartype = ".".join(vartype) - toScope.constants[const_name] = PHPConstant(const_name, self.lineno, vartype) + toScope.constants[const_name] = PHPConstant(const_name, self.lineno, vartype, fileinfo=self.fileinfo) def _parseOneArgument(self, styles, text): """Create a PHPArg object from the given text""" @@ -2533,7 +2610,7 @@ def _parseOneArgument(self, styles, text): sig_parts += text[pos:] if name is not None: return PHPArg(name, citdl=citdl, signature="".join(sig_parts), - default=default) + default=default, namespace=self.currentNamespace, fileinfo=self.fileinfo) def _getArgumentsFromPos(self, styles, text, pos): """Return a list of PHPArg objects""" From 075d6f053cf7d363122081db029ae9c79700d2b1 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 6 Jul 2019 13:51:38 -0400 Subject: [PATCH 2/6] Skip outdated trg-from-pos requests. - For large files, trg-from-pos requests can back up as you type. I can't think of any reason why we'd need to process a request if we got a newer request for a new position. --- src/codeintel/lib/codeintel2/oop/driver.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/codeintel/lib/codeintel2/oop/driver.py b/src/codeintel/lib/codeintel2/oop/driver.py index d1e4d4dd6d..ba5c9b9bd9 100644 --- a/src/codeintel/lib/codeintel2/oop/driver.py +++ b/src/codeintel/lib/codeintel2/oop/driver.py @@ -171,6 +171,7 @@ def __init__(self, db_base_dir=None, fd_in=sys.stdin, fd_out=sys.stdout): self.queue = collections.deque() self.queue_cv = threading.Condition() + self.pending_trg_from_pos_cnts = {} self.env = Environment(name="global", send_fn=functools.partial(self.send, request=None)) @@ -531,6 +532,11 @@ def start(self): else: log.debug("queuing request %r", request) with self.queue_cv: + if request.get("command") == "trg-from-pos" and request.path: + if request.path in self.pending_trg_from_pos_cnts: + self.pending_trg_from_pos_cnts[request.path] += 1 + else: + self.pending_trg_from_pos_cnts[request.path] = 1 self.queue.appendleft(request) self.queue_cv.notify() elif ch in "0123456789": @@ -877,6 +883,20 @@ def do_trg_from_pos(self, request, driver): except AttributeError: raise RequestFailure(message="No position given for trigger") buf = driver.get_buffer(request) + + # If there's more than 1 pending requests, ignore until we get to the + # latest one. The others aren't important anymore + try: + if request.path in driver.pending_trg_from_pos_cnts: + if driver.pending_trg_from_pos_cnts[request.path] == 1: + del driver.pending_trg_from_pos_cnts[request.path] + else: + driver.pending_trg_from_pos_cnts[request.path] -= 1 + driver.send(trg=None) + return + except: + log.error("Failed to check for pending requests") + if "curr-pos" in request: trg = buf.preceding_trg_from_pos(pos, int(request["curr-pos"])) elif request.get("type", None) == "defn": From ec91c5a26ab1b18a11e640c0e2d36c7896da5edd Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 6 Jul 2019 14:05:46 -0400 Subject: [PATCH 3/6] PSR4 updates - Allow filenames like MyClass.class.php - When doing PSR4 lookup, only concider paths that match namespace - Do not look at global scope with doing PSR4 lookups --- src/codeintel/lib/codeintel2/tree_php.py | 79 ++++++++++++++++-------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/src/codeintel/lib/codeintel2/tree_php.py b/src/codeintel/lib/codeintel2/tree_php.py index 1bc405e609..aa11618a4c 100644 --- a/src/codeintel/lib/codeintel2/tree_php.py +++ b/src/codeintel/lib/codeintel2/tree_php.py @@ -40,6 +40,7 @@ from codeintel2.common import * from codeintel2.tree import TreeEvaluator from codeintel2.util import make_short_name_dict, banner +from os import sep as os_sep php_magic_global_method_data = { @@ -558,7 +559,7 @@ def _classes_from_scope(self, expr, scoperef, allowGlobalClasses=False): class_name = subelem.get("name") file_name = scope[0].get("name") if file_name.endswith(".php") \ - and class_name == file_name[:-4] \ + and file_name.startswith(class_name + ".") \ and class_name not in classes: classes.append(('class', class_name)) break # PSR-4 allows only one class per file @@ -1121,11 +1122,33 @@ def _hits_from_namespace(self, fqn, scoperef): lpath = (fqn, ) libs = [self.buf.stdlib] + self.libs + # If not doing a PSR4 lookup, filter libs to those matching expected path + expectedPath = None + if self.attemptingPSR4Autoloading: + try: + fqnPaths = fqn.split("\\") + if len(fqnPaths) > 2: # We only want to do this if there's a namespace + fqnPaths.pop() # Get just namespace + expectedPath = os_sep.join(fqnPaths) + except: + self.error("Failed to setup expected PSR4 path") for lib in libs: - lib_hits = lib.hits_from_lpath(lpath) - if lib_hits: - self.log("_hits_from_namespace:: found in lib: %r", lib) - hits += lib_hits + # Skip if PSR4 lookup + psr4Skip = False + if expectedPath: + try: + for libDir in lib.dirs: + if libDir.find(expectedPath) != -1: + break + else: + psr4Skip = True + except AttributeError: + pass + if not psr4Skip: + lib_hits = lib.hits_from_lpath(lpath) + if lib_hits: + self.log("_hits_from_namespace:: found in lib: %r", lib) + hits += lib_hits return hits @@ -1219,6 +1242,7 @@ def _hits_from_first_part(self, tokens, scoperef): if elem.tag == "scope": self.log("_hits_from_first_part:: checking namespace aliases") imports = [child for child in elem if child.tag == "import"] + attempted_psr4_exprs = {} for child in imports: module = child.get("module") symbol = child.get("symbol") @@ -1242,21 +1266,23 @@ def _hits_from_first_part(self, tokens, scoperef): # For a given class name, search through all imported # namespaces for that class, then double-check the # class' filename matches the class name. - expr = "%s\\%s\\%s" % (module, symbol, first_token) - self.attemptingPSR4Autoloading = True - hit = self._hit_from_citdl(expr, scoperef) - self.attemptingPSR4Autoloading = False - found_elem, scope = hit - if found_elem and scope and \ - found_elem.get("ilk") == "class": - # TODO: technically PSR-4 requires only one class - # per file. Ideally we'd check for that here, but - # that's a bit more work that may not be worth it. - class_name = found_elem.get("name") - file_name = scope[0].get("name") - if file_name.endswith(".php") \ - and class_name == file_name[:-4]: - return ([hit], 1) + expr = "%s\\%s" % (module, first_token) + if expr not in attempted_psr4_exprs: + attempted_psr4_exprs[expr] = True + self.attemptingPSR4Autoloading = True + hit = self._hit_from_citdl(expr, scoperef) + self.attemptingPSR4Autoloading = False + found_elem, scope = hit + if found_elem and scope and \ + found_elem.get("ilk") == "class": + # TODO: technically PSR-4 requires only one class + # per file. Ideally we'd check for that here, but + # that's a bit more work that may not be worth it. + class_name = found_elem.get("name") + file_name = scope[0].get("name") + if file_name.endswith(".php") \ + and file_name.startswith(class_name + "."): + return ([hit], 1) else: if "\\" not in first_token and elem.get("ilk") == "namespace": self.log("_hits_from_first_part:: checking for a FQN hit") @@ -1282,12 +1308,13 @@ def _hits_from_first_part(self, tokens, scoperef): break # elem and scoperef *are* for the global level - hit, nconsumed = self._hit_from_elem_imports(tokens, elem) - if hit is not None and self._return_with_hit(hit, nconsumed): - self.log("_hits_from_first_part:: pt4: is '%s' accessible on %s? yes, " - "imported: %s", - '.'.join(tokens[:nconsumed]), scoperef, hit[0]) - return ([hit], nconsumed) + if not self.attemptingPSR4Autoloading: + hit, nconsumed = self._hit_from_elem_imports(tokens, elem) + if hit is not None and self._return_with_hit(hit, nconsumed): + self.log("_hits_from_first_part:: pt4: is '%s' accessible on %s? yes, " + "imported: %s", + '.'.join(tokens[:nconsumed]), scoperef, hit[0]) + return ([hit], nconsumed) return None, None From 2069894fe7597b25750340458864ecd725e33fcd Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 6 Jul 2019 15:23:13 -0400 Subject: [PATCH 4/6] Use scandir for PHP import lookup. --- src/udl/skel/PHP/pylib/lang_php.py | 42 +++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/udl/skel/PHP/pylib/lang_php.py b/src/udl/skel/PHP/pylib/lang_php.py index 3d80d75743..8355f644e8 100644 --- a/src/udl/skel/PHP/pylib/lang_php.py +++ b/src/udl/skel/PHP/pylib/lang_php.py @@ -79,6 +79,12 @@ if _xpcom_: from xpcom.server import UnwrapObject +# Get scandir +scandir = None +try: + from scandir import scandir +except ImportError: + pass #---- global data @@ -1337,21 +1343,33 @@ def find_importables_in_dir(self, dir, env=None): if not env: env = self.mgr.env + dirs, nondirs = set(), set() try: - names = os.listdir(dir) + if scandir: + for dirEntry in scandir(dir): + try: + if dirEntry.is_dir(): + dirs.add(dirEntry.name) + else: + nondirs.add(dirEntry.name) + except UnicodeDecodeError: + # Hit a filename that cannot be encoded in the default encoding. + # Just skip it. (Bug 82268) + pass + else: + names = os.listdir(dir) + for name in names: + try: + if isdir(join(dir, name)): + dirs.add(name) + else: + nondirs.add(name) + except UnicodeDecodeError: + # Hit a filename that cannot be encoded in the default encoding. + # Just skip it. (Bug 82268) + pass except OSError, ex: return {} - dirs, nondirs = set(), set() - for name in names: - try: - if isdir(join(dir, name)): - dirs.add(name) - else: - nondirs.add(name) - except UnicodeDecodeError: - # Hit a filename that cannot be encoded in the default encoding. - # Just skip it. (Bug 82268) - pass importables = {} patterns = env.assoc_patterns_from_lang("PHP") From dff4d5c7dcd36d03d240d351d2f5ffc61c643b5c Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 6 Jul 2019 15:48:24 -0400 Subject: [PATCH 5/6] Remove semicolons --- src/udl/skel/PHP/pylib/lang_php.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/udl/skel/PHP/pylib/lang_php.py b/src/udl/skel/PHP/pylib/lang_php.py index 8355f644e8..e5078906f4 100644 --- a/src/udl/skel/PHP/pylib/lang_php.py +++ b/src/udl/skel/PHP/pylib/lang_php.py @@ -1504,7 +1504,7 @@ def _getFQNForType(varType, fileinfo, namespace): if varType.startswith("(") and varType.endswith(")"): return _getFQNForTypePiece(varType[1:-1], fileinfo, namespace) # Handle int|null - varTypes = varType.split("|"); + varTypes = varType.split("|") rtn = "" for varTypePiece in varTypes: if rtn != "": @@ -1529,7 +1529,7 @@ def _getFQNForTypePiece(varType, fileinfo, namespace): varType = use_import.name + "\\" + use_import.symbol if varType[0] != "\\": varType = "\\" + varType - break; + break else: # Add namespace if not found if namespace: From 330a1dbb2cf11755316d19b94e8a5c82a8b6bd24 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Thu, 22 Aug 2019 22:38:59 -0400 Subject: [PATCH 6/6] Don't let PSR4 CodeIntelError kill a lookup --- src/codeintel/lib/codeintel2/tree_php.py | 33 ++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/codeintel/lib/codeintel2/tree_php.py b/src/codeintel/lib/codeintel2/tree_php.py index aa11618a4c..745cbcd127 100644 --- a/src/codeintel/lib/codeintel2/tree_php.py +++ b/src/codeintel/lib/codeintel2/tree_php.py @@ -551,7 +551,10 @@ def _classes_from_scope(self, expr, scoperef, allowGlobalClasses=False): and child.get("symbol"): module = "\\%s\\%s" % (child.get("module"), child.get("symbol")) self.attemptingPSR4Autoloading = True - elem, scope = self._hit_from_citdl(module, scoperef) + try: + elem, scope = self._hit_from_citdl(module, scoperef) + except CodeIntelError: + elem = None self.attemptingPSR4Autoloading = False if elem and scope: for subelem in elem: @@ -1270,19 +1273,23 @@ def _hits_from_first_part(self, tokens, scoperef): if expr not in attempted_psr4_exprs: attempted_psr4_exprs[expr] = True self.attemptingPSR4Autoloading = True - hit = self._hit_from_citdl(expr, scoperef) + try: + hit = self._hit_from_citdl(expr, scoperef) + except CodeIntelError: + hit = None self.attemptingPSR4Autoloading = False - found_elem, scope = hit - if found_elem and scope and \ - found_elem.get("ilk") == "class": - # TODO: technically PSR-4 requires only one class - # per file. Ideally we'd check for that here, but - # that's a bit more work that may not be worth it. - class_name = found_elem.get("name") - file_name = scope[0].get("name") - if file_name.endswith(".php") \ - and file_name.startswith(class_name + "."): - return ([hit], 1) + if hit: + found_elem, scope = hit + if found_elem and scope and \ + found_elem.get("ilk") == "class": + # TODO: technically PSR-4 requires only one class + # per file. Ideally we'd check for that here, but + # that's a bit more work that may not be worth it. + class_name = found_elem.get("name") + file_name = scope[0].get("name") + if file_name.endswith(".php") \ + and file_name.startswith(class_name + "."): + return ([hit], 1) else: if "\\" not in first_token and elem.get("ilk") == "namespace": self.log("_hits_from_first_part:: checking for a FQN hit")