Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHP CodeIntel Updates #3770

Merged
merged 6 commits into from
Nov 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/codeintel/lib/codeintel2/oop/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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":
Expand Down
88 changes: 61 additions & 27 deletions src/codeintel/lib/codeintel2/tree_php.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -550,15 +551,18 @@ 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:
if subelem.get("ilk") == "class":
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
Expand Down Expand Up @@ -1121,11 +1125,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

Expand Down Expand Up @@ -1219,6 +1245,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")
Expand All @@ -1242,21 +1269,27 @@ 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
try:
hit = self._hit_from_citdl(expr, scoperef)
except CodeIntelError:
hit = None
self.attemptingPSR4Autoloading = False
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")
Expand All @@ -1282,12 +1315,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


Expand Down
Loading