Skip to content

Commit

Permalink
Merge pull request #3770 from ssigwart/phpCodeintel
Browse files Browse the repository at this point in the history
PHP CodeIntel Updates
  • Loading branch information
th3coop authored Nov 26, 2019
2 parents ecdbccc + 330a1db commit 4266ad5
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 63 deletions.
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 @@ -172,6 +172,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 @@ -532,6 +533,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 @@ -878,6 +884,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

0 comments on commit 4266ad5

Please sign in to comment.