init
This commit is contained in:
commit
38355d2442
9083 changed files with 1225834 additions and 0 deletions
38
.venv/lib/python3.8/site-packages/rope/base/oi/__init__.py
Normal file
38
.venv/lib/python3.8/site-packages/rope/base/oi/__init__.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"""Rope object analysis and inference package
|
||||
|
||||
Rope makes some simplifying assumptions about a python program. It
|
||||
assumes that a program only performs assignments and function calls.
|
||||
Tracking assignments is simple and `PyName` objects handle that. The
|
||||
main problem is function calls. Rope uses these two approaches for
|
||||
obtaining call information:
|
||||
|
||||
* Static object analysis: `rope.base.pycore.PyCore.analyze_module()`
|
||||
|
||||
It can analyze modules to obtain information about functions. This
|
||||
is done by analyzing function calls in a module or scope. Currently
|
||||
SOA analyzes the scopes that are changed while saving or when the
|
||||
user asks to analyze a module. That is mainly because static
|
||||
analysis is time-consuming.
|
||||
|
||||
* Dynamic object analysis: `rope.base.pycore.PyCore.run_module()`
|
||||
|
||||
When you run a module or your testsuite, when DOA is enabled, it
|
||||
collects information about parameters passed to and objects returned
|
||||
from functions. The main problem with this approach is that it is
|
||||
quite slow; Not when looking up the information but when collecting
|
||||
them.
|
||||
|
||||
An instance of `rope.base.oi.objectinfo.ObjectInfoManager` can be used
|
||||
for accessing these information. It saves the data in a
|
||||
`rope.base.oi.objectdb.ObjectDB` internally.
|
||||
|
||||
Now if our objectdb does not know anything about a function and we
|
||||
need the value returned by it, static object inference, SOI, comes
|
||||
into play. It analyzes function body and tries to infer the object
|
||||
that is returned from it (we usually need the returned value for the
|
||||
given parameter objects).
|
||||
|
||||
Rope might collect and store information for other `PyName`, too.
|
||||
For instance rope stores the object builtin containers hold.
|
||||
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
233
.venv/lib/python3.8/site-packages/rope/base/oi/doa.py
Normal file
233
.venv/lib/python3.8/site-packages/rope/base/oi/doa.py
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
import marshal
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
|
||||
|
||||
def _compat_compare_digest(a, b):
|
||||
"""Implementation of hmac.compare_digest for python < 2.7.7.
|
||||
|
||||
This function uses an approach designed to prevent timing analysis by
|
||||
avoiding content-based short circuiting behaviour, making it appropriate
|
||||
for cryptography.
|
||||
"""
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
# Computes the bitwise difference of all characters in the two strings
|
||||
# before returning whether or not they are equal.
|
||||
difference = 0
|
||||
for (a_char, b_char) in zip(a, b):
|
||||
difference |= ord(a_char) ^ ord(b_char)
|
||||
return difference == 0
|
||||
|
||||
|
||||
try:
|
||||
from hmac import compare_digest
|
||||
except ImportError:
|
||||
compare_digest = _compat_compare_digest
|
||||
|
||||
|
||||
class PythonFileRunner(object):
|
||||
"""A class for running python project files"""
|
||||
|
||||
def __init__(
|
||||
self, pycore, file_, args=None, stdin=None, stdout=None, analyze_data=None
|
||||
):
|
||||
self.pycore = pycore
|
||||
self.file = file_
|
||||
self.analyze_data = analyze_data
|
||||
self.observers = []
|
||||
self.args = args
|
||||
self.stdin = stdin
|
||||
self.stdout = stdout
|
||||
|
||||
def run(self):
|
||||
"""Execute the process"""
|
||||
env = dict(os.environ)
|
||||
file_path = self.file.real_path
|
||||
path_folders = (
|
||||
self.pycore.project.get_source_folders()
|
||||
+ self.pycore.project.get_python_path_folders()
|
||||
)
|
||||
env["PYTHONPATH"] = os.pathsep.join(folder.real_path for folder in path_folders)
|
||||
runmod_path = self.pycore.project.find_module("rope.base.oi.runmod").real_path
|
||||
self.receiver = None
|
||||
self._init_data_receiving()
|
||||
send_info = "-"
|
||||
if self.receiver:
|
||||
send_info = self.receiver.get_send_info()
|
||||
args = [
|
||||
sys.executable,
|
||||
runmod_path,
|
||||
send_info,
|
||||
self.pycore.project.address,
|
||||
self.file.real_path,
|
||||
]
|
||||
if self.analyze_data is None:
|
||||
del args[1:4]
|
||||
if self.args is not None:
|
||||
args.extend(self.args)
|
||||
self.process = subprocess.Popen(
|
||||
executable=sys.executable,
|
||||
args=args,
|
||||
env=env,
|
||||
cwd=os.path.split(file_path)[0],
|
||||
stdin=self.stdin,
|
||||
stdout=self.stdout,
|
||||
stderr=self.stdout,
|
||||
close_fds=os.name != "nt",
|
||||
)
|
||||
|
||||
def _init_data_receiving(self):
|
||||
if self.analyze_data is None:
|
||||
return
|
||||
# Disabling FIFO data transfer due to blocking when running
|
||||
# unittests in the GUI.
|
||||
# XXX: Handle FIFO data transfer for `rope.ui.testview`
|
||||
if True or os.name == "nt":
|
||||
self.receiver = _SocketReceiver()
|
||||
else:
|
||||
self.receiver = _FIFOReceiver()
|
||||
self.receiving_thread = threading.Thread(target=self._receive_information)
|
||||
self.receiving_thread.setDaemon(True)
|
||||
self.receiving_thread.start()
|
||||
|
||||
def _receive_information(self):
|
||||
# temp = open('/dev/shm/info', 'wb')
|
||||
for data in self.receiver.receive_data():
|
||||
self.analyze_data(data)
|
||||
# temp.write(str(data) + '\n')
|
||||
# temp.close()
|
||||
for observer in self.observers:
|
||||
observer()
|
||||
|
||||
def wait_process(self):
|
||||
"""Wait for the process to finish"""
|
||||
self.process.wait()
|
||||
if self.analyze_data:
|
||||
self.receiving_thread.join()
|
||||
|
||||
def kill_process(self):
|
||||
"""Stop the process"""
|
||||
if self.process.poll() is not None:
|
||||
return
|
||||
try:
|
||||
if hasattr(self.process, "terminate"):
|
||||
self.process.terminate()
|
||||
elif os.name != "nt":
|
||||
os.kill(self.process.pid, 9)
|
||||
else:
|
||||
import ctypes
|
||||
|
||||
handle = int(self.process._handle)
|
||||
ctypes.windll.kernel32.TerminateProcess(handle, -1)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def add_finishing_observer(self, observer):
|
||||
"""Notify this observer when execution finishes"""
|
||||
self.observers.append(observer)
|
||||
|
||||
|
||||
class _MessageReceiver(object):
|
||||
def receive_data(self):
|
||||
pass
|
||||
|
||||
def get_send_info(self):
|
||||
pass
|
||||
|
||||
|
||||
class _SocketReceiver(_MessageReceiver):
|
||||
def __init__(self):
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.data_port = 3037
|
||||
self.key = os.urandom(32)
|
||||
|
||||
while self.data_port < 4000:
|
||||
try:
|
||||
self.server_socket.bind(("localhost", self.data_port))
|
||||
break
|
||||
except socket.error:
|
||||
self.data_port += 1
|
||||
self.server_socket.listen(1)
|
||||
|
||||
def get_send_info(self):
|
||||
return "%d:%s" % (self.data_port, base64.b64encode(self.key).decode("utf-8"))
|
||||
|
||||
def receive_data(self):
|
||||
conn, addr = self.server_socket.accept()
|
||||
self.server_socket.close()
|
||||
my_file = conn.makefile("rb")
|
||||
while True:
|
||||
# Received messages must meet the following criteria:
|
||||
# 1. Must be contained on a single line.
|
||||
# 2. Must be prefixed with a base64 encoded sha256 message digest
|
||||
# of the base64 encoded pickle data.
|
||||
# 3. Message digest must be computed using the correct key.
|
||||
#
|
||||
# Any messages received that do not meet these criteria will never
|
||||
# be unpickled and will be dropped silently.
|
||||
try:
|
||||
buf = my_file.readline()
|
||||
if len(buf) == 0:
|
||||
break
|
||||
|
||||
try:
|
||||
digest_end = buf.index(b":")
|
||||
buf_digest = base64.b64decode(buf[:digest_end])
|
||||
buf_data = buf[digest_end + 1 : -1]
|
||||
decoded_buf_data = base64.b64decode(buf_data)
|
||||
except:
|
||||
# Corrupted data; the payload cannot be trusted and just has
|
||||
# to be dropped. See CVE-2014-3539.
|
||||
continue
|
||||
|
||||
digest = hmac.new(self.key, buf_data, hashlib.sha256).digest()
|
||||
if not compare_digest(buf_digest, digest):
|
||||
# Signature mismatch; the payload cannot be trusted and just
|
||||
# has to be dropped. See CVE-2014-3539.
|
||||
continue
|
||||
|
||||
yield pickle.loads(decoded_buf_data)
|
||||
except EOFError:
|
||||
break
|
||||
my_file.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
class _FIFOReceiver(_MessageReceiver):
|
||||
def __init__(self):
|
||||
# XXX: this is insecure and might cause race conditions
|
||||
self.file_name = self._get_file_name()
|
||||
os.mkfifo(self.file_name)
|
||||
|
||||
def _get_file_name(self):
|
||||
prefix = tempfile.gettempdir() + "/__rope_"
|
||||
i = 0
|
||||
while os.path.exists(prefix + str(i).rjust(4, "0")):
|
||||
i += 1
|
||||
return prefix + str(i).rjust(4, "0")
|
||||
|
||||
def get_send_info(self):
|
||||
return self.file_name
|
||||
|
||||
def receive_data(self):
|
||||
my_file = open(self.file_name, "rb")
|
||||
while True:
|
||||
try:
|
||||
yield marshal.load(my_file)
|
||||
except EOFError:
|
||||
break
|
||||
my_file.close()
|
||||
os.remove(self.file_name)
|
||||
123
.venv/lib/python3.8/site-packages/rope/base/oi/memorydb.py
Normal file
123
.venv/lib/python3.8/site-packages/rope/base/oi/memorydb.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
from rope.base.oi import objectdb
|
||||
|
||||
|
||||
class MemoryDB(objectdb.FileDict):
|
||||
def __init__(self, project, persist=None):
|
||||
self.project = project
|
||||
self._persist = persist
|
||||
self.files = self
|
||||
self._load_files()
|
||||
self.project.data_files.add_write_hook(self.write)
|
||||
|
||||
def _load_files(self):
|
||||
self._files = {}
|
||||
if self.persist:
|
||||
result = self.project.data_files.read_data(
|
||||
"objectdb", compress=self.compress, import_=True
|
||||
)
|
||||
if result is not None:
|
||||
self._files = result
|
||||
|
||||
def keys(self):
|
||||
return self._files.keys()
|
||||
|
||||
def __iter__(self):
|
||||
for f in self._files:
|
||||
yield f
|
||||
|
||||
def __len__(self):
|
||||
return len(self._files)
|
||||
|
||||
def __setitem__(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._files
|
||||
|
||||
def __getitem__(self, key):
|
||||
return FileInfo(self._files[key])
|
||||
|
||||
def create(self, path):
|
||||
self._files[path] = {}
|
||||
|
||||
def rename(self, file, newfile):
|
||||
if file not in self._files:
|
||||
return
|
||||
self._files[newfile] = self._files[file]
|
||||
del self[file]
|
||||
|
||||
def __delitem__(self, file):
|
||||
del self._files[file]
|
||||
|
||||
def write(self):
|
||||
if self.persist:
|
||||
self.project.data_files.write_data("objectdb", self._files, self.compress)
|
||||
|
||||
@property
|
||||
def compress(self):
|
||||
return self.project.prefs.get("compress_objectdb", False)
|
||||
|
||||
@property
|
||||
def persist(self):
|
||||
if self._persist is not None:
|
||||
return self._persist
|
||||
else:
|
||||
return self.project.prefs.get("save_objectdb", False)
|
||||
|
||||
|
||||
class FileInfo(objectdb.FileInfo):
|
||||
def __init__(self, scopes):
|
||||
self.scopes = scopes
|
||||
|
||||
def create_scope(self, key):
|
||||
self.scopes[key] = ScopeInfo()
|
||||
|
||||
def keys(self):
|
||||
return self.scopes.keys()
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.scopes
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.scopes[key]
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.scopes[key]
|
||||
|
||||
def __iter__(self):
|
||||
for s in self.scopes:
|
||||
yield s
|
||||
|
||||
def __len__(self):
|
||||
return len(self.scopes)
|
||||
|
||||
def __setitem__(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ScopeInfo(objectdb.ScopeInfo):
|
||||
def __init__(self):
|
||||
self.call_info = {}
|
||||
self.per_name = {}
|
||||
|
||||
def get_per_name(self, name):
|
||||
return self.per_name.get(name, None)
|
||||
|
||||
def save_per_name(self, name, value):
|
||||
self.per_name[name] = value
|
||||
|
||||
def get_returned(self, parameters):
|
||||
return self.call_info.get(parameters, None)
|
||||
|
||||
def get_call_infos(self):
|
||||
for args, returned in self.call_info.items():
|
||||
yield objectdb.CallInfo(args, returned)
|
||||
|
||||
def add_call(self, parameters, returned):
|
||||
self.call_info[parameters] = returned
|
||||
|
||||
def __getstate__(self):
|
||||
return (self.call_info, self.per_name)
|
||||
|
||||
def __setstate__(self, data):
|
||||
self.call_info, self.per_name = data
|
||||
170
.venv/lib/python3.8/site-packages/rope/base/oi/objectdb.py
Normal file
170
.venv/lib/python3.8/site-packages/rope/base/oi/objectdb.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
from __future__ import print_function
|
||||
|
||||
|
||||
class ObjectDB(object):
|
||||
def __init__(self, db, validation):
|
||||
self.db = db
|
||||
self.validation = validation
|
||||
self.observers = []
|
||||
self.files = db.files
|
||||
|
||||
def validate_files(self):
|
||||
for file in list(self.files):
|
||||
if not self.validation.is_file_valid(file):
|
||||
del self.files[file]
|
||||
self._file_removed(file)
|
||||
|
||||
def validate_file(self, file):
|
||||
if file not in self.files:
|
||||
return
|
||||
for key in list(self.files[file]):
|
||||
if not self.validation.is_scope_valid(file, key):
|
||||
del self.files[file][key]
|
||||
|
||||
def file_moved(self, file, newfile):
|
||||
if file not in self.files:
|
||||
return
|
||||
self.files.rename(file, newfile)
|
||||
self._file_removed(file)
|
||||
self._file_added(newfile)
|
||||
|
||||
def get_files(self):
|
||||
return self.files.keys()
|
||||
|
||||
def get_returned(self, path, key, args):
|
||||
scope_info = self._get_scope_info(path, key, readonly=True)
|
||||
result = scope_info.get_returned(args)
|
||||
if self.validation.is_value_valid(result):
|
||||
return result
|
||||
|
||||
def get_pername(self, path, key, name):
|
||||
scope_info = self._get_scope_info(path, key, readonly=True)
|
||||
result = scope_info.get_per_name(name)
|
||||
if self.validation.is_value_valid(result):
|
||||
return result
|
||||
|
||||
def get_callinfos(self, path, key):
|
||||
scope_info = self._get_scope_info(path, key, readonly=True)
|
||||
return scope_info.get_call_infos()
|
||||
|
||||
def add_callinfo(self, path, key, args, returned):
|
||||
scope_info = self._get_scope_info(path, key, readonly=False)
|
||||
old_returned = scope_info.get_returned(args)
|
||||
if self.validation.is_more_valid(returned, old_returned):
|
||||
scope_info.add_call(args, returned)
|
||||
|
||||
def add_pername(self, path, key, name, value):
|
||||
scope_info = self._get_scope_info(path, key, readonly=False)
|
||||
old_value = scope_info.get_per_name(name)
|
||||
if self.validation.is_more_valid(value, old_value):
|
||||
scope_info.save_per_name(name, value)
|
||||
|
||||
def add_file_list_observer(self, observer):
|
||||
self.observers.append(observer)
|
||||
|
||||
def write(self):
|
||||
self.db.write()
|
||||
|
||||
def _get_scope_info(self, path, key, readonly=True):
|
||||
if path not in self.files:
|
||||
if readonly:
|
||||
return _NullScopeInfo()
|
||||
self.files.create(path)
|
||||
self._file_added(path)
|
||||
if key not in self.files[path]:
|
||||
if readonly:
|
||||
return _NullScopeInfo()
|
||||
self.files[path].create_scope(key)
|
||||
result = self.files[path][key]
|
||||
if isinstance(result, dict):
|
||||
print(self.files, self.files[path], self.files[path][key])
|
||||
return result
|
||||
|
||||
def _file_removed(self, path):
|
||||
for observer in self.observers:
|
||||
observer.removed(path)
|
||||
|
||||
def _file_added(self, path):
|
||||
for observer in self.observers:
|
||||
observer.added(path)
|
||||
|
||||
def __str__(self):
|
||||
scope_count = 0
|
||||
for file_dict in self.files.values():
|
||||
scope_count += len(file_dict)
|
||||
return "ObjectDB holds %s file and %s scope infos" % (
|
||||
len(self.files),
|
||||
scope_count,
|
||||
)
|
||||
|
||||
|
||||
class _NullScopeInfo(object):
|
||||
def __init__(self, error_on_write=True):
|
||||
self.error_on_write = error_on_write
|
||||
|
||||
def get_per_name(self, name):
|
||||
pass
|
||||
|
||||
def save_per_name(self, name, value):
|
||||
if self.error_on_write:
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_returned(self, parameters):
|
||||
pass
|
||||
|
||||
def get_call_infos(self):
|
||||
return []
|
||||
|
||||
def add_call(self, parameters, returned):
|
||||
if self.error_on_write:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class FileInfo(dict):
|
||||
def create_scope(self, key):
|
||||
pass
|
||||
|
||||
|
||||
class FileDict(dict):
|
||||
def create(self, key):
|
||||
pass
|
||||
|
||||
def rename(self, key, new_key):
|
||||
pass
|
||||
|
||||
|
||||
class ScopeInfo(object):
|
||||
def get_per_name(self, name):
|
||||
pass
|
||||
|
||||
def save_per_name(self, name, value):
|
||||
pass
|
||||
|
||||
def get_returned(self, parameters):
|
||||
pass
|
||||
|
||||
def get_call_infos(self):
|
||||
pass
|
||||
|
||||
def add_call(self, parameters, returned):
|
||||
pass
|
||||
|
||||
|
||||
class CallInfo(object):
|
||||
def __init__(self, args, returned):
|
||||
self.args = args
|
||||
self.returned = returned
|
||||
|
||||
def get_parameters(self):
|
||||
return self.args
|
||||
|
||||
def get_returned(self):
|
||||
return self.returned
|
||||
|
||||
|
||||
class FileListObserver(object):
|
||||
def added(self, path):
|
||||
pass
|
||||
|
||||
def removed(self, path):
|
||||
pass
|
||||
230
.venv/lib/python3.8/site-packages/rope/base/oi/objectinfo.py
Normal file
230
.venv/lib/python3.8/site-packages/rope/base/oi/objectinfo.py
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
import warnings
|
||||
|
||||
from rope.base import exceptions, resourceobserver
|
||||
from rope.base.oi import objectdb, memorydb, transform
|
||||
|
||||
|
||||
class ObjectInfoManager(object):
|
||||
"""Stores object information
|
||||
|
||||
It uses an instance of `objectdb.ObjectDB` for storing
|
||||
information.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, project):
|
||||
self.project = project
|
||||
self.to_textual = transform.PyObjectToTextual(project)
|
||||
self.to_pyobject = transform.TextualToPyObject(project)
|
||||
self.doi_to_pyobject = transform.DOITextualToPyObject(project)
|
||||
self._init_objectdb()
|
||||
if project.prefs.get("validate_objectdb", False):
|
||||
self._init_validation()
|
||||
|
||||
def _init_objectdb(self):
|
||||
dbtype = self.project.get_prefs().get("objectdb_type", None)
|
||||
persist = None
|
||||
if dbtype is not None:
|
||||
warnings.warn(
|
||||
'"objectdb_type" project config is deprecated;\n'
|
||||
'Use "save_objectdb" instead in your project '
|
||||
'config file.\n(".ropeproject/config.py" by default)\n',
|
||||
DeprecationWarning,
|
||||
)
|
||||
if dbtype != "memory" and self.project.ropefolder is not None:
|
||||
persist = True
|
||||
self.validation = TextualValidation(self.to_pyobject)
|
||||
db = memorydb.MemoryDB(self.project, persist=persist)
|
||||
self.objectdb = objectdb.ObjectDB(db, self.validation)
|
||||
|
||||
def _init_validation(self):
|
||||
self.objectdb.validate_files()
|
||||
observer = resourceobserver.ResourceObserver(
|
||||
changed=self._resource_changed,
|
||||
moved=self._resource_moved,
|
||||
removed=self._resource_moved,
|
||||
)
|
||||
files = []
|
||||
for path in self.objectdb.get_files():
|
||||
resource = self.to_pyobject.path_to_resource(path)
|
||||
if resource is not None and resource.project == self.project:
|
||||
files.append(resource)
|
||||
self.observer = resourceobserver.FilteredResourceObserver(observer, files)
|
||||
self.objectdb.add_file_list_observer(_FileListObserver(self))
|
||||
self.project.add_observer(self.observer)
|
||||
|
||||
def _resource_changed(self, resource):
|
||||
try:
|
||||
self.objectdb.validate_file(self.to_textual.resource_to_path(resource))
|
||||
except exceptions.ModuleSyntaxError:
|
||||
pass
|
||||
|
||||
def _resource_moved(self, resource, new_resource=None):
|
||||
self.observer.remove_resource(resource)
|
||||
if new_resource is not None:
|
||||
old = self.to_textual.resource_to_path(resource)
|
||||
new = self.to_textual.resource_to_path(new_resource)
|
||||
self.objectdb.file_moved(old, new)
|
||||
self.observer.add_resource(new_resource)
|
||||
|
||||
def get_returned(self, pyobject, args):
|
||||
result = self.get_exact_returned(pyobject, args)
|
||||
if result is not None:
|
||||
return result
|
||||
path, key = self._get_scope(pyobject)
|
||||
if path is None:
|
||||
return None
|
||||
for call_info in self.objectdb.get_callinfos(path, key):
|
||||
returned = call_info.get_returned()
|
||||
if returned and returned[0] not in ("unknown", "none"):
|
||||
result = returned
|
||||
break
|
||||
if result is None:
|
||||
result = returned
|
||||
if result is not None:
|
||||
return self.to_pyobject(result)
|
||||
|
||||
def get_exact_returned(self, pyobject, args):
|
||||
path, key = self._get_scope(pyobject)
|
||||
if path is not None:
|
||||
returned = self.objectdb.get_returned(
|
||||
path, key, self._args_to_textual(pyobject, args)
|
||||
)
|
||||
if returned is not None:
|
||||
return self.to_pyobject(returned)
|
||||
|
||||
def _args_to_textual(self, pyfunction, args):
|
||||
parameters = list(pyfunction.get_param_names(special_args=False))
|
||||
arguments = args.get_arguments(parameters)[: len(parameters)]
|
||||
textual_args = tuple([self.to_textual(arg) for arg in arguments])
|
||||
return textual_args
|
||||
|
||||
def get_parameter_objects(self, pyobject):
|
||||
path, key = self._get_scope(pyobject)
|
||||
if path is None:
|
||||
return None
|
||||
arg_count = len(pyobject.get_param_names(special_args=False))
|
||||
unknowns = arg_count
|
||||
parameters = [None] * arg_count
|
||||
for call_info in self.objectdb.get_callinfos(path, key):
|
||||
args = call_info.get_parameters()
|
||||
for index, arg in enumerate(args[:arg_count]):
|
||||
old = parameters[index]
|
||||
if self.validation.is_more_valid(arg, old):
|
||||
parameters[index] = arg
|
||||
if self.validation.is_value_valid(arg):
|
||||
unknowns -= 1
|
||||
if unknowns == 0:
|
||||
break
|
||||
if unknowns < arg_count:
|
||||
return [self.to_pyobject(parameter) for parameter in parameters]
|
||||
|
||||
def get_passed_objects(self, pyfunction, parameter_index):
|
||||
path, key = self._get_scope(pyfunction)
|
||||
if path is None:
|
||||
return []
|
||||
result = []
|
||||
for call_info in self.objectdb.get_callinfos(path, key):
|
||||
args = call_info.get_parameters()
|
||||
if len(args) > parameter_index:
|
||||
parameter = self.to_pyobject(args[parameter_index])
|
||||
if parameter is not None:
|
||||
result.append(parameter)
|
||||
return result
|
||||
|
||||
def doa_data_received(self, data):
|
||||
def doi_to_normal(textual):
|
||||
pyobject = self.doi_to_pyobject(textual)
|
||||
return self.to_textual(pyobject)
|
||||
|
||||
function = doi_to_normal(data[0])
|
||||
args = tuple([doi_to_normal(textual) for textual in data[1]])
|
||||
returned = doi_to_normal(data[2])
|
||||
if function[0] == "defined" and len(function) == 3:
|
||||
self._save_data(function, args, returned)
|
||||
|
||||
def function_called(self, pyfunction, params, returned=None):
|
||||
function_text = self.to_textual(pyfunction)
|
||||
params_text = tuple([self.to_textual(param) for param in params])
|
||||
returned_text = ("unknown",)
|
||||
if returned is not None:
|
||||
returned_text = self.to_textual(returned)
|
||||
self._save_data(function_text, params_text, returned_text)
|
||||
|
||||
def save_per_name(self, scope, name, data):
|
||||
path, key = self._get_scope(scope.pyobject)
|
||||
if path is not None:
|
||||
self.objectdb.add_pername(path, key, name, self.to_textual(data))
|
||||
|
||||
def get_per_name(self, scope, name):
|
||||
path, key = self._get_scope(scope.pyobject)
|
||||
if path is not None:
|
||||
result = self.objectdb.get_pername(path, key, name)
|
||||
if result is not None:
|
||||
return self.to_pyobject(result)
|
||||
|
||||
def _save_data(self, function, args, returned=("unknown",)):
|
||||
self.objectdb.add_callinfo(function[1], function[2], args, returned)
|
||||
|
||||
def _get_scope(self, pyobject):
|
||||
resource = pyobject.get_module().get_resource()
|
||||
if resource is None:
|
||||
return None, None
|
||||
textual = self.to_textual(pyobject)
|
||||
if textual[0] == "defined":
|
||||
path = textual[1]
|
||||
if len(textual) == 3:
|
||||
key = textual[2]
|
||||
else:
|
||||
key = ""
|
||||
return path, key
|
||||
return None, None
|
||||
|
||||
def sync(self):
|
||||
self.objectdb.sync()
|
||||
|
||||
def __str__(self):
|
||||
return str(self.objectdb)
|
||||
|
||||
|
||||
class TextualValidation(object):
|
||||
def __init__(self, to_pyobject):
|
||||
self.to_pyobject = to_pyobject
|
||||
|
||||
def is_value_valid(self, value):
|
||||
# ???: Should none and unknown be considered valid?
|
||||
if value is None or value[0] in ("none", "unknown"):
|
||||
return False
|
||||
return self.to_pyobject(value) is not None
|
||||
|
||||
def is_more_valid(self, new, old):
|
||||
if old is None:
|
||||
return True
|
||||
return new[0] not in ("unknown", "none")
|
||||
|
||||
def is_file_valid(self, path):
|
||||
return self.to_pyobject.path_to_resource(path) is not None
|
||||
|
||||
def is_scope_valid(self, path, key):
|
||||
if key == "":
|
||||
textual = ("defined", path)
|
||||
else:
|
||||
textual = ("defined", path, key)
|
||||
return self.to_pyobject(textual) is not None
|
||||
|
||||
|
||||
class _FileListObserver(object):
|
||||
def __init__(self, object_info):
|
||||
self.object_info = object_info
|
||||
self.observer = self.object_info.observer
|
||||
self.to_pyobject = self.object_info.to_pyobject
|
||||
|
||||
def removed(self, path):
|
||||
resource = self.to_pyobject.path_to_resource(path)
|
||||
if resource is not None:
|
||||
self.observer.remove_resource(resource)
|
||||
|
||||
def added(self, path):
|
||||
resource = self.to_pyobject.path_to_resource(path)
|
||||
if resource is not None:
|
||||
self.observer.add_resource(resource)
|
||||
240
.venv/lib/python3.8/site-packages/rope/base/oi/runmod.py
Normal file
240
.venv/lib/python3.8/site-packages/rope/base/oi/runmod.py
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
def __rope_start_everything():
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
import marshal
|
||||
import inspect
|
||||
import types
|
||||
import threading
|
||||
import rope.base.utils.pycompat as pycompat
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
class _MessageSender(object):
|
||||
def send_data(self, data):
|
||||
pass
|
||||
|
||||
class _SocketSender(_MessageSender):
|
||||
def __init__(self, port, key):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect(("127.0.0.1", port))
|
||||
self.my_file = s.makefile("wb")
|
||||
self.key = base64.b64decode(key)
|
||||
|
||||
def send_data(self, data):
|
||||
if not self.my_file.closed:
|
||||
pickled_data = base64.b64encode(
|
||||
pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
|
||||
)
|
||||
dgst = hmac.new(self.key, pickled_data, hashlib.sha256).digest()
|
||||
self.my_file.write(base64.b64encode(dgst) + b":" + pickled_data + b"\n")
|
||||
|
||||
def close(self):
|
||||
self.my_file.close()
|
||||
|
||||
class _FileSender(_MessageSender):
|
||||
def __init__(self, file_name):
|
||||
self.my_file = open(file_name, "wb")
|
||||
|
||||
def send_data(self, data):
|
||||
if not self.my_file.closed:
|
||||
marshal.dump(data, self.my_file)
|
||||
|
||||
def close(self):
|
||||
self.my_file.close()
|
||||
|
||||
def _cached(func):
|
||||
cache = {}
|
||||
|
||||
def newfunc(self, arg):
|
||||
if arg in cache:
|
||||
return cache[arg]
|
||||
result = func(self, arg)
|
||||
cache[arg] = result
|
||||
return result
|
||||
|
||||
return newfunc
|
||||
|
||||
class _FunctionCallDataSender(object):
|
||||
def __init__(self, send_info, project_root):
|
||||
self.project_root = project_root
|
||||
if send_info[0].isdigit():
|
||||
port, key = send_info.split(":", 1)
|
||||
self.sender = _SocketSender(int(port), key)
|
||||
else:
|
||||
self.sender = _FileSender(send_info)
|
||||
|
||||
def global_trace(frame, event, arg):
|
||||
# HACK: Ignoring out->in calls
|
||||
# This might lose some information
|
||||
if self._is_an_interesting_call(frame):
|
||||
return self.on_function_call
|
||||
|
||||
sys.settrace(global_trace)
|
||||
threading.settrace(global_trace)
|
||||
|
||||
def on_function_call(self, frame, event, arg):
|
||||
if event != "return":
|
||||
return
|
||||
args = []
|
||||
returned = ("unknown",)
|
||||
code = frame.f_code
|
||||
for argname in code.co_varnames[: code.co_argcount]:
|
||||
try:
|
||||
argvalue = self._object_to_persisted_form(frame.f_locals[argname])
|
||||
args.append(argvalue)
|
||||
except (TypeError, AttributeError):
|
||||
args.append(("unknown",))
|
||||
try:
|
||||
returned = self._object_to_persisted_form(arg)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
data = (
|
||||
self._object_to_persisted_form(frame.f_code),
|
||||
tuple(args),
|
||||
returned,
|
||||
)
|
||||
self.sender.send_data(data)
|
||||
except (TypeError):
|
||||
pass
|
||||
return self.on_function_call
|
||||
|
||||
def _is_an_interesting_call(self, frame):
|
||||
# if frame.f_code.co_name in ['?', '<module>']:
|
||||
# return False
|
||||
# return not frame.f_back or
|
||||
# not self._is_code_inside_project(frame.f_back.f_code)
|
||||
if not self._is_code_inside_project(frame.f_code) and (
|
||||
not frame.f_back
|
||||
or not self._is_code_inside_project(frame.f_back.f_code)
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _is_code_inside_project(self, code):
|
||||
source = self._path(code.co_filename)
|
||||
return (
|
||||
source is not None
|
||||
and os.path.exists(source)
|
||||
and _realpath(source).startswith(self.project_root)
|
||||
)
|
||||
|
||||
@_cached
|
||||
def _get_persisted_code(self, object_):
|
||||
source = self._path(object_.co_filename)
|
||||
if not os.path.exists(source):
|
||||
raise TypeError("no source")
|
||||
return ("defined", _realpath(source), str(object_.co_firstlineno))
|
||||
|
||||
@_cached
|
||||
def _get_persisted_class(self, object_):
|
||||
try:
|
||||
return (
|
||||
"defined",
|
||||
_realpath(inspect.getsourcefile(object_)),
|
||||
object_.__name__,
|
||||
)
|
||||
except (TypeError, AttributeError):
|
||||
return ("unknown",)
|
||||
|
||||
def _get_persisted_builtin(self, object_):
|
||||
if isinstance(object_, pycompat.string_types):
|
||||
return ("builtin", "str")
|
||||
if isinstance(object_, list):
|
||||
holding = None
|
||||
if len(object_) > 0:
|
||||
holding = object_[0]
|
||||
return ("builtin", "list", self._object_to_persisted_form(holding))
|
||||
if isinstance(object_, dict):
|
||||
keys = None
|
||||
values = None
|
||||
if len(object_) > 0:
|
||||
# @todo - fix it properly, why is __locals__ being
|
||||
# duplicated ?
|
||||
keys = [key for key in object_.keys() if key != "__locals__"][0]
|
||||
values = object_[keys]
|
||||
return (
|
||||
"builtin",
|
||||
"dict",
|
||||
self._object_to_persisted_form(keys),
|
||||
self._object_to_persisted_form(values),
|
||||
)
|
||||
if isinstance(object_, tuple):
|
||||
objects = []
|
||||
if len(object_) < 3:
|
||||
for holding in object_:
|
||||
objects.append(self._object_to_persisted_form(holding))
|
||||
else:
|
||||
objects.append(self._object_to_persisted_form(object_[0]))
|
||||
return tuple(["builtin", "tuple"] + objects)
|
||||
if isinstance(object_, set):
|
||||
holding = None
|
||||
if len(object_) > 0:
|
||||
for o in object_:
|
||||
holding = o
|
||||
break
|
||||
return ("builtin", "set", self._object_to_persisted_form(holding))
|
||||
return ("unknown",)
|
||||
|
||||
def _object_to_persisted_form(self, object_):
|
||||
if object_ is None:
|
||||
return ("none",)
|
||||
if isinstance(object_, types.CodeType):
|
||||
return self._get_persisted_code(object_)
|
||||
if isinstance(object_, types.FunctionType):
|
||||
return self._get_persisted_code(object_.__code__)
|
||||
if isinstance(object_, types.MethodType):
|
||||
return self._get_persisted_code(object_.__func__.__code__)
|
||||
if isinstance(object_, types.ModuleType):
|
||||
return self._get_persisted_module(object_)
|
||||
if isinstance(object_, pycompat.string_types + (list, dict, tuple, set)):
|
||||
return self._get_persisted_builtin(object_)
|
||||
if isinstance(object_, type):
|
||||
return self._get_persisted_class(object_)
|
||||
return ("instance", self._get_persisted_class(type(object_)))
|
||||
|
||||
@_cached
|
||||
def _get_persisted_module(self, object_):
|
||||
path = self._path(object_.__file__)
|
||||
if path and os.path.exists(path):
|
||||
return ("defined", _realpath(path))
|
||||
return ("unknown",)
|
||||
|
||||
def _path(self, path):
|
||||
if path.endswith(".pyc"):
|
||||
path = path[:-1]
|
||||
if path.endswith(".py"):
|
||||
return path
|
||||
|
||||
def close(self):
|
||||
self.sender.close()
|
||||
sys.settrace(None)
|
||||
|
||||
def _realpath(path):
|
||||
return os.path.realpath(os.path.abspath(os.path.expanduser(path)))
|
||||
|
||||
send_info = sys.argv[1]
|
||||
project_root = sys.argv[2]
|
||||
file_to_run = sys.argv[3]
|
||||
run_globals = globals()
|
||||
run_globals.update(
|
||||
{"__name__": "__main__", "__builtins__": __builtins__, "__file__": file_to_run}
|
||||
)
|
||||
|
||||
if send_info != "-":
|
||||
data_sender = _FunctionCallDataSender(send_info, project_root)
|
||||
del sys.argv[1:4]
|
||||
pycompat.execfile(file_to_run, run_globals)
|
||||
if send_info != "-":
|
||||
data_sender.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
__rope_start_everything()
|
||||
151
.venv/lib/python3.8/site-packages/rope/base/oi/soa.py
Normal file
151
.venv/lib/python3.8/site-packages/rope/base/oi/soa.py
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import rope.base.ast
|
||||
import rope.base.oi.soi
|
||||
import rope.base.pynames
|
||||
from rope.base import pyobjects, evaluate, astutils, arguments
|
||||
|
||||
|
||||
def analyze_module(pycore, pymodule, should_analyze, search_subscopes, followed_calls):
|
||||
"""Analyze `pymodule` for static object inference
|
||||
|
||||
Analyzes scopes for collecting object information. The analysis
|
||||
starts from inner scopes.
|
||||
|
||||
"""
|
||||
_analyze_node(pycore, pymodule, should_analyze, search_subscopes, followed_calls)
|
||||
|
||||
|
||||
def _analyze_node(pycore, pydefined, should_analyze, search_subscopes, followed_calls):
|
||||
if search_subscopes(pydefined):
|
||||
for scope in pydefined.get_scope().get_scopes():
|
||||
_analyze_node(
|
||||
pycore, scope.pyobject, should_analyze, search_subscopes, followed_calls
|
||||
)
|
||||
if should_analyze(pydefined):
|
||||
new_followed_calls = max(0, followed_calls - 1)
|
||||
return_true = lambda pydefined: True
|
||||
return_false = lambda pydefined: False
|
||||
|
||||
def _follow(pyfunction):
|
||||
_analyze_node(
|
||||
pycore, pyfunction, return_true, return_false, new_followed_calls
|
||||
)
|
||||
|
||||
if not followed_calls:
|
||||
_follow = None
|
||||
visitor = SOAVisitor(pycore, pydefined, _follow)
|
||||
for child in rope.base.ast.get_child_nodes(pydefined.get_ast()):
|
||||
rope.base.ast.walk(child, visitor)
|
||||
|
||||
|
||||
class SOAVisitor(object):
|
||||
def __init__(self, pycore, pydefined, follow_callback=None):
|
||||
self.pycore = pycore
|
||||
self.pymodule = pydefined.get_module()
|
||||
self.scope = pydefined.get_scope()
|
||||
self.follow = follow_callback
|
||||
|
||||
def _FunctionDef(self, node):
|
||||
pass
|
||||
|
||||
def _ClassDef(self, node):
|
||||
pass
|
||||
|
||||
def _Call(self, node):
|
||||
for child in rope.base.ast.get_child_nodes(node):
|
||||
rope.base.ast.walk(child, self)
|
||||
primary, pyname = evaluate.eval_node2(self.scope, node.func)
|
||||
if pyname is None:
|
||||
return
|
||||
pyfunction = pyname.get_object()
|
||||
if isinstance(pyfunction, pyobjects.AbstractFunction):
|
||||
args = arguments.create_arguments(primary, pyfunction, node, self.scope)
|
||||
elif isinstance(pyfunction, pyobjects.PyClass):
|
||||
pyclass = pyfunction
|
||||
if "__init__" in pyfunction:
|
||||
pyfunction = pyfunction["__init__"].get_object()
|
||||
pyname = rope.base.pynames.UnboundName(pyobjects.PyObject(pyclass))
|
||||
args = self._args_with_self(primary, pyname, pyfunction, node)
|
||||
elif "__call__" in pyfunction:
|
||||
pyfunction = pyfunction["__call__"].get_object()
|
||||
args = self._args_with_self(primary, pyname, pyfunction, node)
|
||||
else:
|
||||
return
|
||||
self._call(pyfunction, args)
|
||||
|
||||
def _args_with_self(self, primary, self_pyname, pyfunction, node):
|
||||
base_args = arguments.create_arguments(primary, pyfunction, node, self.scope)
|
||||
return arguments.MixedArguments(self_pyname, base_args, self.scope)
|
||||
|
||||
def _call(self, pyfunction, args):
|
||||
if isinstance(pyfunction, pyobjects.PyFunction):
|
||||
if self.follow is not None:
|
||||
before = self._parameter_objects(pyfunction)
|
||||
self.pycore.object_info.function_called(
|
||||
pyfunction, args.get_arguments(pyfunction.get_param_names())
|
||||
)
|
||||
pyfunction._set_parameter_pyobjects(None)
|
||||
if self.follow is not None:
|
||||
after = self._parameter_objects(pyfunction)
|
||||
if after != before:
|
||||
self.follow(pyfunction)
|
||||
# XXX: Maybe we should not call every builtin function
|
||||
if isinstance(pyfunction, rope.base.builtins.BuiltinFunction):
|
||||
pyfunction.get_returned_object(args)
|
||||
|
||||
def _parameter_objects(self, pyfunction):
|
||||
result = []
|
||||
for i in range(len(pyfunction.get_param_names(False))):
|
||||
result.append(pyfunction.get_parameter(i))
|
||||
return result
|
||||
|
||||
def _AnnAssign(self, node):
|
||||
for child in rope.base.ast.get_child_nodes(node):
|
||||
rope.base.ast.walk(child, self)
|
||||
visitor = _SOAAssignVisitor()
|
||||
nodes = []
|
||||
|
||||
rope.base.ast.walk(node.target, visitor)
|
||||
nodes.extend(visitor.nodes)
|
||||
|
||||
self._evaluate_assign_value(node, nodes, type_hint=node.annotation)
|
||||
|
||||
def _Assign(self, node):
|
||||
for child in rope.base.ast.get_child_nodes(node):
|
||||
rope.base.ast.walk(child, self)
|
||||
visitor = _SOAAssignVisitor()
|
||||
nodes = []
|
||||
for child in node.targets:
|
||||
rope.base.ast.walk(child, visitor)
|
||||
nodes.extend(visitor.nodes)
|
||||
self._evaluate_assign_value(node, nodes)
|
||||
|
||||
def _evaluate_assign_value(self, node, nodes, type_hint=False):
|
||||
for subscript, levels in nodes:
|
||||
instance = evaluate.eval_node(self.scope, subscript.value)
|
||||
args_pynames = [evaluate.eval_node(self.scope, subscript.slice)]
|
||||
value = rope.base.oi.soi._infer_assignment(
|
||||
rope.base.pynames.AssignmentValue(
|
||||
node.value, levels, type_hint=type_hint
|
||||
),
|
||||
self.pymodule,
|
||||
)
|
||||
args_pynames.append(rope.base.pynames.UnboundName(value))
|
||||
if instance is not None and value is not None:
|
||||
pyobject = instance.get_object()
|
||||
if "__setitem__" in pyobject:
|
||||
pyfunction = pyobject["__setitem__"].get_object()
|
||||
args = arguments.ObjectArguments([instance] + args_pynames)
|
||||
self._call(pyfunction, args)
|
||||
# IDEA: handle `__setslice__`, too
|
||||
|
||||
|
||||
class _SOAAssignVisitor(astutils._NodeNameCollector):
|
||||
def __init__(self):
|
||||
super(_SOAAssignVisitor, self).__init__()
|
||||
self.nodes = []
|
||||
|
||||
def _added(self, node, levels):
|
||||
if isinstance(node, rope.base.ast.Subscript) and isinstance(
|
||||
node.slice, (rope.base.ast.Index, rope.base.ast.expr)
|
||||
):
|
||||
self.nodes.append((node, levels))
|
||||
231
.venv/lib/python3.8/site-packages/rope/base/oi/soi.py
Normal file
231
.venv/lib/python3.8/site-packages/rope/base/oi/soi.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
"""A module for inferring objects
|
||||
|
||||
For more information see the documentation in `rope.base.oi`
|
||||
package.
|
||||
|
||||
"""
|
||||
import rope.base.builtins
|
||||
import rope.base.pynames
|
||||
import rope.base.pyobjects
|
||||
from rope.base import evaluate, utils, arguments
|
||||
from rope.base.oi.type_hinting.factory import get_type_hinting_factory
|
||||
|
||||
|
||||
_ignore_inferred = utils.ignore_exception(rope.base.pyobjects.IsBeingInferredError)
|
||||
|
||||
|
||||
@_ignore_inferred
|
||||
def infer_returned_object(pyfunction, args):
|
||||
"""Infer the `PyObject` this `PyFunction` returns after calling"""
|
||||
object_info = pyfunction.pycore.object_info
|
||||
result = object_info.get_exact_returned(pyfunction, args)
|
||||
if result is not None:
|
||||
return result
|
||||
result = _infer_returned(pyfunction, args)
|
||||
if result is not None:
|
||||
if args and pyfunction.get_module().get_resource() is not None:
|
||||
params = args.get_arguments(pyfunction.get_param_names(special_args=False))
|
||||
object_info.function_called(pyfunction, params, result)
|
||||
return result
|
||||
result = object_info.get_returned(pyfunction, args)
|
||||
if result is not None:
|
||||
return result
|
||||
hint_return = get_type_hinting_factory(
|
||||
pyfunction.pycore.project
|
||||
).make_return_provider()
|
||||
type_ = hint_return(pyfunction)
|
||||
if type_ is not None:
|
||||
return rope.base.pyobjects.PyObject(type_)
|
||||
|
||||
|
||||
@_ignore_inferred
|
||||
def infer_parameter_objects(pyfunction):
|
||||
"""Infer the `PyObject` of parameters of this `PyFunction`"""
|
||||
object_info = pyfunction.pycore.object_info
|
||||
result = object_info.get_parameter_objects(pyfunction)
|
||||
if result is None:
|
||||
result = _parameter_objects(pyfunction)
|
||||
_handle_first_parameter(pyfunction, result)
|
||||
return result
|
||||
|
||||
|
||||
def _handle_first_parameter(pyobject, parameters):
|
||||
kind = pyobject.get_kind()
|
||||
if parameters is None or kind not in ["method", "classmethod"]:
|
||||
pass
|
||||
if not parameters:
|
||||
if not pyobject.get_param_names(special_args=False):
|
||||
return
|
||||
parameters.append(rope.base.pyobjects.get_unknown())
|
||||
if kind == "method":
|
||||
parameters[0] = rope.base.pyobjects.PyObject(pyobject.parent)
|
||||
if kind == "classmethod":
|
||||
parameters[0] = pyobject.parent
|
||||
|
||||
|
||||
@_ignore_inferred
|
||||
def infer_assigned_object(pyname):
|
||||
if not pyname.assignments:
|
||||
return
|
||||
for assignment in reversed(pyname.assignments):
|
||||
result = _infer_assignment(assignment, pyname.module)
|
||||
if (
|
||||
isinstance(result, rope.base.builtins.BuiltinUnknown)
|
||||
and result.get_name() == "NotImplementedType"
|
||||
):
|
||||
break
|
||||
elif result == rope.base.pyobjects.get_unknown():
|
||||
break
|
||||
elif result is not None:
|
||||
return result
|
||||
|
||||
hint_assignment = get_type_hinting_factory(
|
||||
pyname.module.pycore.project
|
||||
).make_assignment_provider()
|
||||
hinting_result = hint_assignment(pyname)
|
||||
if hinting_result is not None:
|
||||
return rope.base.pyobjects.PyObject(hinting_result)
|
||||
return result
|
||||
|
||||
|
||||
def get_passed_objects(pyfunction, parameter_index):
|
||||
object_info = pyfunction.pycore.object_info
|
||||
result = object_info.get_passed_objects(pyfunction, parameter_index)
|
||||
if not result:
|
||||
statically_inferred = _parameter_objects(pyfunction)
|
||||
if len(statically_inferred) > parameter_index:
|
||||
result.append(statically_inferred[parameter_index])
|
||||
return result
|
||||
|
||||
|
||||
def _infer_returned(pyobject, args):
|
||||
if args:
|
||||
# HACK: Setting parameter objects manually
|
||||
# This is not thread safe and might cause problems if `args`
|
||||
# does not come from a good call site
|
||||
pyobject.get_scope().invalidate_data()
|
||||
pyobject._set_parameter_pyobjects(
|
||||
args.get_arguments(pyobject.get_param_names(special_args=False))
|
||||
)
|
||||
scope = pyobject.get_scope()
|
||||
if not scope._get_returned_asts():
|
||||
return
|
||||
maxtries = 3
|
||||
for returned_node in reversed(scope._get_returned_asts()[-maxtries:]):
|
||||
try:
|
||||
resulting_pyname = evaluate.eval_node(scope, returned_node)
|
||||
if resulting_pyname is None:
|
||||
continue
|
||||
pyobject = resulting_pyname.get_object()
|
||||
if pyobject == rope.base.pyobjects.get_unknown():
|
||||
continue
|
||||
if not scope._is_generator():
|
||||
return pyobject
|
||||
else:
|
||||
return rope.base.builtins.get_generator(pyobject)
|
||||
except rope.base.pyobjects.IsBeingInferredError:
|
||||
pass
|
||||
|
||||
|
||||
def _parameter_objects(pyobject):
|
||||
result = []
|
||||
params = pyobject.get_param_names(special_args=False)
|
||||
hint_param = get_type_hinting_factory(pyobject.pycore.project).make_param_provider()
|
||||
for name in params:
|
||||
type_ = hint_param(pyobject, name)
|
||||
if type_ is not None:
|
||||
result.append(rope.base.pyobjects.PyObject(type_))
|
||||
else:
|
||||
result.append(rope.base.pyobjects.get_unknown())
|
||||
return result
|
||||
|
||||
|
||||
# handling `rope.base.pynames.AssignmentValue`
|
||||
|
||||
|
||||
@_ignore_inferred
|
||||
def _infer_assignment(assignment, pymodule):
|
||||
result = _follow_pyname(assignment, pymodule)
|
||||
if result is None:
|
||||
return None
|
||||
pyname, pyobject = result
|
||||
pyobject = _follow_evaluations(assignment, pyname, pyobject)
|
||||
if pyobject is None:
|
||||
return None
|
||||
return _follow_levels(assignment, pyobject)
|
||||
|
||||
|
||||
def _follow_levels(assignment, pyobject):
|
||||
for index in assignment.levels:
|
||||
if isinstance(pyobject.get_type(), rope.base.builtins.Tuple):
|
||||
holdings = pyobject.get_type().get_holding_objects()
|
||||
if holdings:
|
||||
pyobject = holdings[min(len(holdings) - 1, index)]
|
||||
else:
|
||||
pyobject = None
|
||||
elif isinstance(pyobject.get_type(), rope.base.builtins.List):
|
||||
pyobject = pyobject.get_type().holding
|
||||
else:
|
||||
pyobject = None
|
||||
if pyobject is None:
|
||||
break
|
||||
return pyobject
|
||||
|
||||
|
||||
@_ignore_inferred
|
||||
def _follow_pyname(assignment, pymodule, lineno=None):
|
||||
assign_node = assignment.type_hint or assignment.ast_node
|
||||
if lineno is None:
|
||||
lineno = _get_lineno_for_node(assign_node)
|
||||
holding_scope = pymodule.get_scope().get_inner_scope_for_line(lineno)
|
||||
pyname = evaluate.eval_node(holding_scope, assign_node)
|
||||
if pyname is not None:
|
||||
result = pyname.get_object()
|
||||
if (
|
||||
isinstance(result.get_type(), rope.base.builtins.Property)
|
||||
and holding_scope.get_kind() == "Class"
|
||||
):
|
||||
arg = rope.base.pynames.UnboundName(
|
||||
rope.base.pyobjects.PyObject(holding_scope.pyobject)
|
||||
)
|
||||
return pyname, result.get_type().get_property_object(
|
||||
arguments.ObjectArguments([arg])
|
||||
)
|
||||
return pyname, result
|
||||
|
||||
|
||||
@_ignore_inferred
|
||||
def _follow_evaluations(assignment, pyname, pyobject):
|
||||
new_pyname = pyname
|
||||
tokens = assignment.evaluation.split(".")
|
||||
for token in tokens:
|
||||
call = token.endswith("()")
|
||||
if call:
|
||||
token = token[:-2]
|
||||
if token:
|
||||
pyname = new_pyname
|
||||
new_pyname = _get_attribute(pyobject, token)
|
||||
if new_pyname is not None:
|
||||
pyobject = new_pyname.get_object()
|
||||
if pyobject is not None and call:
|
||||
if isinstance(pyobject, rope.base.pyobjects.AbstractFunction):
|
||||
args = arguments.ObjectArguments([pyname])
|
||||
pyobject = pyobject.get_returned_object(args)
|
||||
else:
|
||||
pyobject = None
|
||||
if pyobject is None:
|
||||
break
|
||||
if pyobject is not None and assignment.assign_type:
|
||||
return rope.base.pyobjects.PyObject(pyobject)
|
||||
return pyobject
|
||||
|
||||
|
||||
def _get_lineno_for_node(assign_node):
|
||||
if hasattr(assign_node, "lineno") and assign_node.lineno is not None:
|
||||
return assign_node.lineno
|
||||
return 1
|
||||
|
||||
|
||||
def _get_attribute(pyobject, name):
|
||||
if pyobject is not None and name in pyobject:
|
||||
return pyobject[name]
|
||||
292
.venv/lib/python3.8/site-packages/rope/base/oi/transform.py
Normal file
292
.venv/lib/python3.8/site-packages/rope/base/oi/transform.py
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
"""Provides classes for persisting `PyObject`"""
|
||||
import os
|
||||
import re
|
||||
|
||||
import rope.base.builtins
|
||||
from rope.base import exceptions
|
||||
|
||||
|
||||
class PyObjectToTextual(object):
|
||||
"""For transforming `PyObject` to textual form
|
||||
|
||||
This can be used for storing `PyObjects` in files. Use
|
||||
`TextualToPyObject` for converting back.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, project):
|
||||
self.project = project
|
||||
|
||||
def transform(self, pyobject):
|
||||
"""Transform a `PyObject` to textual form"""
|
||||
if pyobject is None:
|
||||
return ("none",)
|
||||
object_type = type(pyobject)
|
||||
try:
|
||||
method = getattr(self, object_type.__name__ + "_to_textual")
|
||||
return method(pyobject)
|
||||
except AttributeError:
|
||||
return ("unknown",)
|
||||
|
||||
def __call__(self, pyobject):
|
||||
return self.transform(pyobject)
|
||||
|
||||
def PyObject_to_textual(self, pyobject):
|
||||
if isinstance(pyobject.get_type(), rope.base.pyobjects.AbstractClass):
|
||||
result = self.transform(pyobject.get_type())
|
||||
if result[0] == "defined":
|
||||
return ("instance", result)
|
||||
return result
|
||||
return ("unknown",)
|
||||
|
||||
def PyFunction_to_textual(self, pyobject):
|
||||
return self._defined_to_textual(pyobject)
|
||||
|
||||
def PyClass_to_textual(self, pyobject):
|
||||
return self._defined_to_textual(pyobject)
|
||||
|
||||
def _defined_to_textual(self, pyobject):
|
||||
address = []
|
||||
while pyobject.parent is not None:
|
||||
address.insert(0, pyobject.get_name())
|
||||
pyobject = pyobject.parent
|
||||
return (
|
||||
"defined",
|
||||
self._get_pymodule_path(pyobject.get_module()),
|
||||
".".join(address),
|
||||
)
|
||||
|
||||
def PyModule_to_textual(self, pyobject):
|
||||
return ("defined", self._get_pymodule_path(pyobject))
|
||||
|
||||
def PyPackage_to_textual(self, pyobject):
|
||||
return ("defined", self._get_pymodule_path(pyobject))
|
||||
|
||||
def List_to_textual(self, pyobject):
|
||||
return ("builtin", "list", self.transform(pyobject.holding))
|
||||
|
||||
def Dict_to_textual(self, pyobject):
|
||||
return (
|
||||
"builtin",
|
||||
"dict",
|
||||
self.transform(pyobject.keys),
|
||||
self.transform(pyobject.values),
|
||||
)
|
||||
|
||||
def Tuple_to_textual(self, pyobject):
|
||||
objects = [
|
||||
self.transform(holding) for holding in pyobject.get_holding_objects()
|
||||
]
|
||||
return tuple(["builtin", "tuple"] + objects)
|
||||
|
||||
def Set_to_textual(self, pyobject):
|
||||
return ("builtin", "set", self.transform(pyobject.holding))
|
||||
|
||||
def Iterator_to_textual(self, pyobject):
|
||||
return ("builtin", "iter", self.transform(pyobject.holding))
|
||||
|
||||
def Generator_to_textual(self, pyobject):
|
||||
return ("builtin", "generator", self.transform(pyobject.holding))
|
||||
|
||||
def Str_to_textual(self, pyobject):
|
||||
return ("builtin", "str")
|
||||
|
||||
def File_to_textual(self, pyobject):
|
||||
return ("builtin", "file")
|
||||
|
||||
def BuiltinFunction_to_textual(self, pyobject):
|
||||
return ("builtin", "function", pyobject.get_name())
|
||||
|
||||
def _get_pymodule_path(self, pymodule):
|
||||
return self.resource_to_path(pymodule.get_resource())
|
||||
|
||||
def resource_to_path(self, resource):
|
||||
if resource.project == self.project:
|
||||
return resource.path
|
||||
else:
|
||||
return resource.real_path
|
||||
|
||||
|
||||
class TextualToPyObject(object):
|
||||
"""For transforming textual form to `PyObject`"""
|
||||
|
||||
def __init__(self, project, allow_in_project_absolutes=False):
|
||||
self.project = project
|
||||
|
||||
def __call__(self, textual):
|
||||
return self.transform(textual)
|
||||
|
||||
def transform(self, textual):
|
||||
"""Transform an object from textual form to `PyObject`"""
|
||||
if textual is None:
|
||||
return None
|
||||
type = textual[0]
|
||||
try:
|
||||
method = getattr(self, type + "_to_pyobject")
|
||||
return method(textual)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def builtin_to_pyobject(self, textual):
|
||||
method = getattr(self, "builtin_%s_to_pyobject" % textual[1], None)
|
||||
if method is not None:
|
||||
return method(textual)
|
||||
|
||||
def builtin_str_to_pyobject(self, textual):
|
||||
return rope.base.builtins.get_str()
|
||||
|
||||
def builtin_list_to_pyobject(self, textual):
|
||||
holding = self.transform(textual[2])
|
||||
return rope.base.builtins.get_list(holding)
|
||||
|
||||
def builtin_dict_to_pyobject(self, textual):
|
||||
keys = self.transform(textual[2])
|
||||
values = self.transform(textual[3])
|
||||
return rope.base.builtins.get_dict(keys, values)
|
||||
|
||||
def builtin_tuple_to_pyobject(self, textual):
|
||||
objects = []
|
||||
for holding in textual[2:]:
|
||||
objects.append(self.transform(holding))
|
||||
return rope.base.builtins.get_tuple(*objects)
|
||||
|
||||
def builtin_set_to_pyobject(self, textual):
|
||||
holding = self.transform(textual[2])
|
||||
return rope.base.builtins.get_set(holding)
|
||||
|
||||
def builtin_iter_to_pyobject(self, textual):
|
||||
holding = self.transform(textual[2])
|
||||
return rope.base.builtins.get_iterator(holding)
|
||||
|
||||
def builtin_generator_to_pyobject(self, textual):
|
||||
holding = self.transform(textual[2])
|
||||
return rope.base.builtins.get_generator(holding)
|
||||
|
||||
def builtin_file_to_pyobject(self, textual):
|
||||
return rope.base.builtins.get_file()
|
||||
|
||||
def builtin_function_to_pyobject(self, textual):
|
||||
if textual[2] in rope.base.builtins.builtins:
|
||||
return rope.base.builtins.builtins[textual[2]].get_object()
|
||||
|
||||
def unknown_to_pyobject(self, textual):
|
||||
return None
|
||||
|
||||
def none_to_pyobject(self, textual):
|
||||
return None
|
||||
|
||||
def _module_to_pyobject(self, textual):
|
||||
path = textual[1]
|
||||
return self._get_pymodule(path)
|
||||
|
||||
def _hierarchical_defined_to_pyobject(self, textual):
|
||||
path = textual[1]
|
||||
names = textual[2].split(".")
|
||||
pymodule = self._get_pymodule(path)
|
||||
pyobject = pymodule
|
||||
for name in names:
|
||||
if pyobject is None:
|
||||
return None
|
||||
if isinstance(pyobject, rope.base.pyobjects.PyDefinedObject):
|
||||
try:
|
||||
pyobject = pyobject.get_scope()[name].get_object()
|
||||
except exceptions.NameNotFoundError:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
return pyobject
|
||||
|
||||
def defined_to_pyobject(self, textual):
|
||||
if len(textual) == 2 or textual[2] == "":
|
||||
return self._module_to_pyobject(textual)
|
||||
else:
|
||||
return self._hierarchical_defined_to_pyobject(textual)
|
||||
|
||||
def instance_to_pyobject(self, textual):
|
||||
type = self.transform(textual[1])
|
||||
if type is not None:
|
||||
return rope.base.pyobjects.PyObject(type)
|
||||
|
||||
def _get_pymodule(self, path):
|
||||
resource = self.path_to_resource(path)
|
||||
if resource is not None:
|
||||
return self.project.get_pymodule(resource)
|
||||
|
||||
def path_to_resource(self, path):
|
||||
try:
|
||||
root = self.project.address
|
||||
if not os.path.isabs(path):
|
||||
return self.project.get_resource(path)
|
||||
if path == root or path.startswith(root + os.sep):
|
||||
# INFO: This is a project file; should not be absolute
|
||||
return None
|
||||
import rope.base.project
|
||||
|
||||
return rope.base.project.get_no_project().get_resource(path)
|
||||
except exceptions.ResourceNotFoundError:
|
||||
return None
|
||||
|
||||
|
||||
class DOITextualToPyObject(TextualToPyObject):
|
||||
"""For transforming textual form to `PyObject`
|
||||
|
||||
The textual form DOI uses is different from rope's standard
|
||||
textual form. The reason is that we cannot find the needed
|
||||
information by analyzing live objects. This class can be
|
||||
used to transform DOI textual form to `PyObject` and later
|
||||
we can convert it to standard textual form using
|
||||
`TextualToPyObject` class.
|
||||
|
||||
"""
|
||||
|
||||
def _function_to_pyobject(self, textual):
|
||||
path = textual[1]
|
||||
lineno = int(textual[2])
|
||||
pymodule = self._get_pymodule(path)
|
||||
if pymodule is not None:
|
||||
scope = pymodule.get_scope()
|
||||
inner_scope = scope.get_inner_scope_for_line(lineno)
|
||||
return inner_scope.pyobject
|
||||
|
||||
def _class_to_pyobject(self, textual):
|
||||
path, name = textual[1:]
|
||||
pymodule = self._get_pymodule(path)
|
||||
if pymodule is None:
|
||||
return None
|
||||
module_scope = pymodule.get_scope()
|
||||
suspected = None
|
||||
if name in module_scope.get_names():
|
||||
suspected = module_scope[name].get_object()
|
||||
if suspected is not None and isinstance(suspected, rope.base.pyobjects.PyClass):
|
||||
return suspected
|
||||
else:
|
||||
lineno = self._find_occurrence(name, pymodule.get_resource().read())
|
||||
if lineno is not None:
|
||||
inner_scope = module_scope.get_inner_scope_for_line(lineno)
|
||||
return inner_scope.pyobject
|
||||
|
||||
def defined_to_pyobject(self, textual):
|
||||
if len(textual) == 2:
|
||||
return self._module_to_pyobject(textual)
|
||||
else:
|
||||
if textual[2].isdigit():
|
||||
result = self._function_to_pyobject(textual)
|
||||
else:
|
||||
result = self._class_to_pyobject(textual)
|
||||
if not isinstance(result, rope.base.pyobjects.PyModule):
|
||||
return result
|
||||
|
||||
def _find_occurrence(self, name, source):
|
||||
pattern = re.compile(r"^\s*class\s*" + name + r"\b")
|
||||
lines = source.split("\n")
|
||||
for i in range(len(lines)):
|
||||
if pattern.match(lines[i]):
|
||||
return i + 1
|
||||
|
||||
def path_to_resource(self, path):
|
||||
import rope.base.libutils
|
||||
|
||||
relpath = rope.base.libutils.path_relative_to_project_root(self.project, path)
|
||||
if relpath is not None:
|
||||
path = relpath
|
||||
return super(DOITextualToPyObject, self).path_to_resource(path)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,367 @@
|
|||
# Based on super lightweight Simple Top-Down Parser from http://effbot.org/zone/simple-top-down-parsing.htm
|
||||
# and https://bitbucket.org/emacsway/sqlbuilder/src/default/sqlbuilder/smartsql/contrib/evaluate.py
|
||||
import re
|
||||
from rope.base.utils import pycompat
|
||||
from rope.base.oi.type_hinting import utils
|
||||
from rope.base import utils as base_utils
|
||||
|
||||
|
||||
class SymbolBase(object):
|
||||
|
||||
name = None # node/token type name
|
||||
|
||||
def __init__(self):
|
||||
self.value = None # used by name and literals
|
||||
self.first = None
|
||||
self.second = None
|
||||
self.third = None # used by tree nodes
|
||||
|
||||
def nud(self, parser):
|
||||
raise SyntaxError("Syntax error (%r)." % self.name)
|
||||
|
||||
def led(self, left, parser):
|
||||
raise SyntaxError("Unknown operator (%r)." % self.name)
|
||||
|
||||
def evaluate(self, pyobject):
|
||||
raise NotImplementedError(self.name, self)
|
||||
|
||||
def __repr__(self):
|
||||
if self.name == "(name)":
|
||||
return "(%s %s)" % (self.name[1:-1], self.value)
|
||||
out = [repr(self.name), self.first, self.second, self.third]
|
||||
out = [str(i) for i in out if i]
|
||||
return "(" + " ".join(out) + ")"
|
||||
|
||||
|
||||
class SymbolTable(object):
|
||||
def multi(func):
|
||||
def _inner(self, names, *a, **kw):
|
||||
for name in names.split():
|
||||
func(self, name, *a, **kw)
|
||||
|
||||
return _inner
|
||||
|
||||
def __init__(self):
|
||||
self.symbol_table = {}
|
||||
|
||||
def get(self, name, default=None):
|
||||
return self.symbol_table.get(name, default)
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self.symbol_table[name]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.symbol_table)
|
||||
|
||||
def symbol(self, name, bp=0):
|
||||
try:
|
||||
s = self.symbol_table[name]
|
||||
except KeyError:
|
||||
|
||||
class S(SymbolBase):
|
||||
pass
|
||||
|
||||
s = S
|
||||
s.__name__ = "symbol-" + name # for debugging
|
||||
s.name = name
|
||||
s.lbp = bp
|
||||
self.symbol_table[name] = s
|
||||
else:
|
||||
s.lbp = max(bp, s.lbp)
|
||||
return s
|
||||
|
||||
@multi
|
||||
def infix(self, name, bp):
|
||||
symbol = self.symbol(name, bp)
|
||||
|
||||
@method(symbol)
|
||||
def led(self, left, parser):
|
||||
self.first = left
|
||||
self.second = parser.expression(bp)
|
||||
return self
|
||||
|
||||
@multi
|
||||
def infix_r(self, name, bp):
|
||||
symbol = self.symbol(name, bp)
|
||||
|
||||
@method(symbol)
|
||||
def led(self, left, parser):
|
||||
self.first = left
|
||||
self.second = parser.expression(bp - 0.1)
|
||||
return self
|
||||
|
||||
def ternary(self, name, name2, bp):
|
||||
symbol = self.symbol(name, bp)
|
||||
symbol2 = self.symbol(name2)
|
||||
|
||||
@method(symbol)
|
||||
def led(self, left, parser):
|
||||
self.first = left
|
||||
self.second = parser.expression(symbol2.lbp)
|
||||
parser.advance(symbol2.name)
|
||||
self.third = parser.expression(symbol2.lbp + 0.1)
|
||||
return self
|
||||
|
||||
@multi
|
||||
def prefix(self, name, bp):
|
||||
symbol = self.symbol(name, bp)
|
||||
|
||||
@method(symbol)
|
||||
def nud(self, parser):
|
||||
self.first = parser.expression(bp)
|
||||
return self
|
||||
|
||||
@multi
|
||||
def postfix(self, name, bp):
|
||||
symbol = self.symbol(name, bp)
|
||||
|
||||
@method(symbol)
|
||||
def led(self, left, parser):
|
||||
self.first = left
|
||||
return self
|
||||
|
||||
multi = staticmethod(multi) # Just for code checker
|
||||
|
||||
|
||||
symbol_table = SymbolTable()
|
||||
|
||||
|
||||
class Lexer(object):
|
||||
|
||||
_token_pattern = re.compile(
|
||||
r"""
|
||||
\s*
|
||||
(?:
|
||||
(
|
||||
[,()\[\]|]
|
||||
| ->
|
||||
| (?<=\s)(?:or)\b
|
||||
) # operator
|
||||
| ([a-zA-Z](?:\w|\.)*) # name
|
||||
)
|
||||
""",
|
||||
re.U | re.S | re.X,
|
||||
)
|
||||
|
||||
def __init__(self, symbol_table):
|
||||
self.symbol_table = symbol_table
|
||||
|
||||
def tokenize(self, program):
|
||||
for name, value in self._tokenize_expr(program):
|
||||
symbol = symbol_table.get(value)
|
||||
if symbol:
|
||||
s = symbol()
|
||||
elif name == "(name)":
|
||||
symbol = symbol_table[name]
|
||||
s = symbol()
|
||||
s.value = value
|
||||
else:
|
||||
raise SyntaxError(
|
||||
"Unknown operator ({0}). Possible operators are {1!r}".format(
|
||||
value, list(self.symbol_table)
|
||||
)
|
||||
)
|
||||
|
||||
yield s
|
||||
|
||||
def _tokenize_expr(self, program):
|
||||
if isinstance(program, bytes):
|
||||
program = program.decode("utf-8")
|
||||
# import pprint; pprint.pprint(self._token_pattern.findall(program))
|
||||
for operator, name in self._token_pattern.findall(program):
|
||||
if operator:
|
||||
yield "(operator)", operator
|
||||
elif name:
|
||||
yield "(name)", name
|
||||
else:
|
||||
raise SyntaxError
|
||||
yield "(end)", "(end)"
|
||||
|
||||
|
||||
class Parser(object):
|
||||
|
||||
token = None
|
||||
next = None
|
||||
|
||||
def __init__(self, lexer):
|
||||
self.lexer = lexer
|
||||
|
||||
def parse(self, program):
|
||||
generator = self.lexer.tokenize(program)
|
||||
try:
|
||||
self.next = generator.__next__ # PY3
|
||||
except AttributeError:
|
||||
self.next = generator.next
|
||||
self.token = self.next()
|
||||
return self.expression()
|
||||
|
||||
def expression(self, rbp=0):
|
||||
t = self.token
|
||||
self.token = self.next()
|
||||
left = t.nud(self)
|
||||
while rbp < self.token.lbp:
|
||||
t = self.token
|
||||
self.token = self.next()
|
||||
left = t.led(left, self)
|
||||
return left
|
||||
|
||||
def advance(self, name=None):
|
||||
if name and self.token.name != name:
|
||||
raise SyntaxError(
|
||||
"Expected {0!r} but found {1!r}".format(name, self.token.name)
|
||||
)
|
||||
self.token = self.next()
|
||||
|
||||
|
||||
def method(s):
|
||||
assert issubclass(s, SymbolBase)
|
||||
|
||||
def bind(fn):
|
||||
setattr(s, fn.__name__, fn)
|
||||
return fn
|
||||
|
||||
return bind
|
||||
|
||||
|
||||
symbol, infix, infix_r, prefix, postfix, ternary = (
|
||||
symbol_table.symbol,
|
||||
symbol_table.infix,
|
||||
symbol_table.infix_r,
|
||||
symbol_table.prefix,
|
||||
symbol_table.postfix,
|
||||
symbol_table.ternary,
|
||||
)
|
||||
|
||||
symbol("(", 270)
|
||||
symbol(")")
|
||||
symbol("[", 250) # Parameters
|
||||
symbol("]")
|
||||
symbol("->", 230)
|
||||
infix("|", 170)
|
||||
infix("or", 170)
|
||||
symbol(",")
|
||||
|
||||
symbol("(name)")
|
||||
symbol("(end)")
|
||||
|
||||
|
||||
@method(symbol("(name)"))
|
||||
def nud(self, parser):
|
||||
return self
|
||||
|
||||
|
||||
@method(symbol("(name)"))
|
||||
def evaluate(self, pyobject):
|
||||
return utils.resolve_type(self.value, pyobject)
|
||||
|
||||
|
||||
# Parametrized objects
|
||||
@method(symbol("["))
|
||||
def led(self, left, parser):
|
||||
self.first = left
|
||||
self.second = []
|
||||
if parser.token.name != "]":
|
||||
while 1:
|
||||
if parser.token.name == "]":
|
||||
break
|
||||
self.second.append(parser.expression())
|
||||
if parser.token.name != ",":
|
||||
break
|
||||
parser.advance(",")
|
||||
parser.advance("]")
|
||||
return self
|
||||
|
||||
|
||||
@method(symbol("["))
|
||||
def evaluate(self, pyobject):
|
||||
return utils.parametrize_type(
|
||||
self.first.evaluate(pyobject), *[i.evaluate(pyobject) for i in self.second]
|
||||
)
|
||||
|
||||
|
||||
# Anonymous Function Calls
|
||||
@method(symbol("("))
|
||||
def nud(self, parser):
|
||||
self.second = []
|
||||
if parser.token.name != ")":
|
||||
while 1:
|
||||
self.second.append(parser.expression())
|
||||
if parser.token.name != ",":
|
||||
break
|
||||
parser.advance(",")
|
||||
parser.advance(")")
|
||||
parser.advance("->")
|
||||
self.third = parser.expression(symbol("->").lbp + 0.1)
|
||||
return self
|
||||
|
||||
|
||||
# Function Calls
|
||||
@method(symbol("("))
|
||||
def led(self, left, parser):
|
||||
self.first = left
|
||||
self.second = []
|
||||
if parser.token.name != ")":
|
||||
while 1:
|
||||
self.second.append(parser.expression())
|
||||
if parser.token.name != ",":
|
||||
break
|
||||
parser.advance(",")
|
||||
parser.advance(")")
|
||||
parser.advance("->")
|
||||
self.third = parser.expression(symbol("->").lbp + 0.1)
|
||||
return self
|
||||
|
||||
|
||||
@method(symbol("("))
|
||||
def evaluate(self, pyobject):
|
||||
# TODO: Implement me
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@method(symbol("or"))
|
||||
@method(symbol("|"))
|
||||
def evaluate(self, pyobject):
|
||||
# TODO: Implement me
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Compiler(object):
|
||||
|
||||
parser_factory = Parser
|
||||
lexer_factory = Lexer
|
||||
symbol_table = symbol_table
|
||||
|
||||
def _make_parser(self):
|
||||
return self.parser_factory(self.lexer_factory(self.symbol_table))
|
||||
|
||||
@base_utils.cached(500)
|
||||
def __call__(self, program):
|
||||
"""
|
||||
:type program: str
|
||||
:rtype: rope.base.oi.type_hinting.evaluate.SymbolBase
|
||||
"""
|
||||
return self._make_parser().parse(program)
|
||||
|
||||
|
||||
compile = Compiler()
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
|
||||
compile = compile
|
||||
|
||||
def __call__(self, program, pyobject):
|
||||
"""Evaluates the program string or AST
|
||||
|
||||
:type program: str or rope.base.oi.type_hinting.evaluate.SymbolBase
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
ast = (
|
||||
self.compile(program)
|
||||
if isinstance(program, pycompat.string_types)
|
||||
else program
|
||||
)
|
||||
return ast.evaluate(pyobject)
|
||||
|
||||
|
||||
evaluate = Evaluator()
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
from rope.base.oi.type_hinting import interfaces
|
||||
from rope.base.oi.type_hinting.providers import (
|
||||
composite,
|
||||
inheritance,
|
||||
docstrings,
|
||||
numpydocstrings,
|
||||
pep0484_type_comments,
|
||||
)
|
||||
from rope.base.oi.type_hinting.resolvers import composite as composite_resolvers, types
|
||||
from rope.base import utils
|
||||
|
||||
|
||||
class TypeHintingFactory(interfaces.ITypeHintingFactory):
|
||||
@utils.saveit
|
||||
def make_param_provider(self):
|
||||
providers = [
|
||||
docstrings.ParamProvider(
|
||||
docstrings.DocstringParamParser(), self.make_resolver()
|
||||
),
|
||||
docstrings.ParamProvider(
|
||||
numpydocstrings.NumPyDocstringParamParser(), self.make_resolver()
|
||||
),
|
||||
]
|
||||
return inheritance.ParamProvider(composite.ParamProvider(*providers))
|
||||
|
||||
@utils.saveit
|
||||
def make_return_provider(self):
|
||||
providers = [
|
||||
docstrings.ReturnProvider(
|
||||
docstrings.DocstringReturnParser(), self.make_resolver()
|
||||
),
|
||||
]
|
||||
return inheritance.ReturnProvider(composite.ReturnProvider(*providers))
|
||||
|
||||
@utils.saveit
|
||||
def make_assignment_provider(self):
|
||||
providers = [
|
||||
pep0484_type_comments.AssignmentProvider(self.make_resolver()),
|
||||
docstrings.AssignmentProvider(
|
||||
docstrings.DocstringParamParser(), self.make_resolver()
|
||||
),
|
||||
docstrings.AssignmentProvider(
|
||||
numpydocstrings.NumPyDocstringParamParser(), self.make_resolver()
|
||||
),
|
||||
]
|
||||
return inheritance.AssignmentProvider(composite.AssignmentProvider(*providers))
|
||||
|
||||
@utils.saveit
|
||||
def make_resolver(self):
|
||||
"""
|
||||
:rtype: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
|
||||
"""
|
||||
resolvers = [
|
||||
types.Resolver(),
|
||||
]
|
||||
return composite_resolvers.Resolver(*resolvers)
|
||||
|
||||
|
||||
default_type_hinting_factory = TypeHintingFactory()
|
||||
|
||||
|
||||
class TypeHintingFactoryAccessor(object):
|
||||
def __call__(self, project):
|
||||
"""
|
||||
:type project: rope.base.project.Project
|
||||
:rtype: rope.base.oi.type_hinting.interfaces.ITypeHintingFactory
|
||||
"""
|
||||
factory_location = project.get_prefs().get(
|
||||
"type_hinting_factory",
|
||||
"rope.base.oi.type_hinting.factory.default_type_hinting_factory",
|
||||
)
|
||||
return self._get_factory(factory_location)
|
||||
|
||||
@utils.cached(10)
|
||||
def _get_factory(self, factory_location):
|
||||
"""
|
||||
:type factory_location: str
|
||||
:rtype: rope.base.oi.type_hinting.interfaces.ITypeHintingFactory
|
||||
"""
|
||||
return utils.resolve(factory_location)
|
||||
|
||||
|
||||
get_type_hinting_factory = TypeHintingFactoryAccessor()
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
class ITypeHintingFactory(object):
|
||||
def make_param_provider(self):
|
||||
"""
|
||||
:rtype: rope.base.oi.type_hinting.providers.interfaces.IParamProvider
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def make_return_provider(self):
|
||||
"""
|
||||
:rtype: rope.base.oi.type_hinting.providers.interfaces.IReturnProvider
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def make_assignment_provider(self):
|
||||
"""
|
||||
:rtype: rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def make_resolver(self):
|
||||
"""
|
||||
:rtype: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
|
||||
"""
|
||||
raise NotImplementedError
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,56 @@
|
|||
from rope.base.oi.type_hinting.providers import interfaces
|
||||
|
||||
|
||||
class ParamProvider(interfaces.IParamProvider):
|
||||
def __init__(self, *delegates):
|
||||
"""
|
||||
:type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IParamProvider]
|
||||
"""
|
||||
self._delegates = delegates
|
||||
|
||||
def __call__(self, pyfunc, param_name):
|
||||
"""
|
||||
:type pyfunc: rope.base.pyobjectsdef.PyFunction
|
||||
:type param_name: str
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
for delegate in self._delegates:
|
||||
result = delegate(pyfunc, param_name)
|
||||
if result:
|
||||
return result
|
||||
|
||||
|
||||
class ReturnProvider(interfaces.IReturnProvider):
|
||||
def __init__(self, *delegates):
|
||||
"""
|
||||
:type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IReturnProvider]
|
||||
"""
|
||||
self._delegates = delegates
|
||||
|
||||
def __call__(self, pyfunc):
|
||||
"""
|
||||
:type pyfunc: rope.base.pyobjectsdef.PyFunction
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
for delegate in self._delegates:
|
||||
result = delegate(pyfunc)
|
||||
if result:
|
||||
return result
|
||||
|
||||
|
||||
class AssignmentProvider(interfaces.IAssignmentProvider):
|
||||
def __init__(self, *delegates):
|
||||
"""
|
||||
:type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider]
|
||||
"""
|
||||
self._delegates = delegates
|
||||
|
||||
def __call__(self, pyname):
|
||||
"""
|
||||
:type pyname: rope.base.pynamesdef.AssignedName
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
for delegate in self._delegates:
|
||||
result = delegate(pyname)
|
||||
if result:
|
||||
return result
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
"""
|
||||
Hinting the type using docstring of class/function.
|
||||
|
||||
It's an irreplaceable thing if you are using Dependency Injection with passive class:
|
||||
http://www.martinfowler.com/articles/injection.html
|
||||
|
||||
Some code extracted (or based on code) from:
|
||||
https://github.com/davidhalter/jedi/blob/b489019f5bd5750051122b94cc767df47751ecb7/jedi/evaluate/docstrings.py
|
||||
Thanks to @davidhalter for this utils under MIT License.
|
||||
|
||||
Similar solutions:
|
||||
|
||||
- https://www.jetbrains.com/pycharm/help/type-hinting-in-pycharm.html
|
||||
- https://www.python.org/dev/peps/pep-0484/#type-comments
|
||||
- http://www.pydev.org/manual_adv_type_hints.html
|
||||
- https://jedi.readthedocs.org/en/latest/docs/features.html#type-hinting
|
||||
|
||||
Discussions:
|
||||
|
||||
- https://groups.google.com/d/topic/rope-dev/JlAzmZ83K1M/discussion
|
||||
- https://groups.google.com/d/topic/rope-dev/LCFNN98vckI/discussion
|
||||
|
||||
"""
|
||||
import re
|
||||
|
||||
from rope.base.oi.type_hinting import utils
|
||||
from rope.base.oi.type_hinting.providers import interfaces
|
||||
|
||||
|
||||
class ParamProvider(interfaces.IParamProvider):
|
||||
def __init__(self, docstring_parser, resolver):
|
||||
"""
|
||||
:type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IParamParser
|
||||
:type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
|
||||
"""
|
||||
self._parse_docstring = docstring_parser
|
||||
self._resolve = resolver
|
||||
|
||||
def __call__(self, pyfunc, param_name):
|
||||
"""
|
||||
:type pyfunc: rope.base.pyobjectsdef.PyFunction
|
||||
:type param_name: str
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
type_strs = self._parse_docstring(pyfunc.get_doc(), param_name)
|
||||
if type_strs:
|
||||
return self._resolve(type_strs[0], pyfunc)
|
||||
|
||||
|
||||
class ReturnProvider(interfaces.IReturnProvider):
|
||||
def __init__(self, docstring_parser, resolver):
|
||||
"""
|
||||
:type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IReturnParser
|
||||
:type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
|
||||
"""
|
||||
self._parse_docstring = docstring_parser
|
||||
self._resolve = resolver
|
||||
|
||||
def __call__(self, pyfunc):
|
||||
"""
|
||||
:type pyfunc: rope.base.pyobjectsdef.PyFunction
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
type_strs = self._parse_docstring(pyfunc.get_doc())
|
||||
if type_strs:
|
||||
return self._resolve(type_strs[0], pyfunc)
|
||||
|
||||
|
||||
class AssignmentProvider(interfaces.IAssignmentProvider):
|
||||
def __init__(self, docstring_parser, resolver):
|
||||
"""
|
||||
:type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IParamParser
|
||||
:type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
|
||||
"""
|
||||
self._parse_docstring = docstring_parser
|
||||
self._resolve = resolver
|
||||
|
||||
def __call__(self, pyname):
|
||||
"""
|
||||
:type pyname: rope.base.pynamesdef.AssignedName
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
try:
|
||||
pyclass, attr_name = utils.get_class_with_attr_name(pyname)
|
||||
except TypeError:
|
||||
return
|
||||
else:
|
||||
type_strs = self._parse_docstring(pyclass.get_doc(), attr_name)
|
||||
if type_strs:
|
||||
return self._resolve(type_strs[0], pyclass)
|
||||
|
||||
|
||||
class IParamParser(object):
|
||||
def __call__(self, docstring, param_name):
|
||||
"""
|
||||
:type docstring: str
|
||||
:type param_name: str
|
||||
"""
|
||||
|
||||
|
||||
class IReturnParser(object):
|
||||
def __call__(self, docstring):
|
||||
"""
|
||||
:type docstring: str
|
||||
"""
|
||||
|
||||
|
||||
class DocstringParamParser(IParamParser):
|
||||
|
||||
DOCSTRING_PARAM_PATTERNS = [
|
||||
r"\s*:type\s+%s:\s*([^\n]+)", # Sphinx
|
||||
r"\s*:param\s+(\w+)\s+%s:[^\n]+", # Sphinx param with type
|
||||
r"\s*@type\s+%s:\s*([^\n]+)", # Epydoc
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._strip_rst_role = RSTRoleStrip()
|
||||
|
||||
def __call__(self, docstring, param_name):
|
||||
"""Search `docstring` for type(-s) of `param_name`.
|
||||
|
||||
>>> DocstringParamParser()(':type param: int', 'param')
|
||||
['int']
|
||||
>>> DocstringParamParser()('@type param: int', 'param')
|
||||
['int']
|
||||
>>> DocstringParamParser()(':type param: :class:`threading.Thread`', 'param')
|
||||
['threading.Thread']
|
||||
>>> bool(DocstringParamParser()('no document', 'param'))
|
||||
False
|
||||
>>> DocstringParamParser()(':param int param: some description', 'param')
|
||||
['int']
|
||||
"""
|
||||
if not docstring:
|
||||
return []
|
||||
patterns = [
|
||||
re.compile(p % re.escape(param_name)) for p in self.DOCSTRING_PARAM_PATTERNS
|
||||
]
|
||||
for pattern in patterns:
|
||||
match = pattern.search(docstring)
|
||||
if match:
|
||||
return [self._strip_rst_role(match.group(1))]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class DocstringReturnParser(IReturnParser):
|
||||
|
||||
DOCSTRING_RETURN_PATTERNS = [
|
||||
re.compile(r"\s*:rtype:\s*([^\n]+)", re.M), # Sphinx
|
||||
re.compile(r"\s*@rtype:\s*([^\n]+)", re.M), # Epydoc
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._strip_rst_role = RSTRoleStrip()
|
||||
|
||||
def __call__(self, docstring):
|
||||
if not docstring:
|
||||
return []
|
||||
for p in self.DOCSTRING_RETURN_PATTERNS:
|
||||
match = p.search(docstring)
|
||||
if match:
|
||||
return [self._strip_rst_role(match.group(1))]
|
||||
return []
|
||||
|
||||
|
||||
class RSTRoleStrip(object):
|
||||
|
||||
RST_ROLE_PATTERN = re.compile(r":[^`]+:`([^`]+)`")
|
||||
|
||||
def __call__(self, type_str):
|
||||
"""
|
||||
Strip off the part looks like a ReST role in `type_str`.
|
||||
|
||||
>>> RSTRoleStrip()(':class:`ClassName`') # strip off :class:
|
||||
'ClassName'
|
||||
>>> RSTRoleStrip()(':py:obj:`module.Object`') # works with domain
|
||||
'module.Object'
|
||||
>>> RSTRoleStrip()('ClassName') # do nothing when not ReST role
|
||||
'ClassName'
|
||||
|
||||
See also:
|
||||
http://sphinx-doc.org/domains.html#cross-referencing-python-objects
|
||||
|
||||
"""
|
||||
match = self.RST_ROLE_PATTERN.match(type_str)
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
return type_str
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
from rope.base.oi.type_hinting import utils
|
||||
from rope.base.oi.type_hinting.providers import interfaces
|
||||
|
||||
|
||||
class ParamProvider(interfaces.IParamProvider):
|
||||
def __init__(self, delegate):
|
||||
"""
|
||||
:type delegate: rope.base.oi.type_hinting.providers.interfaces.IParamProvider
|
||||
"""
|
||||
self._delegate = delegate
|
||||
|
||||
def __call__(self, pyfunc, param_name):
|
||||
"""
|
||||
:type pyfunc: rope.base.pyobjectsdef.PyFunction
|
||||
:type param_name: str
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
superfunc = pyfunc
|
||||
while superfunc:
|
||||
result = self._delegate(superfunc, param_name)
|
||||
if result:
|
||||
return result
|
||||
superfunc = utils.get_super_func(superfunc)
|
||||
|
||||
|
||||
class ReturnProvider(interfaces.IReturnProvider):
|
||||
def __init__(self, delegate):
|
||||
"""
|
||||
:type delegate: rope.base.oi.type_hinting.providers.interfaces.IReturnProvider
|
||||
"""
|
||||
self._delegate = delegate
|
||||
|
||||
def __call__(self, pyfunc):
|
||||
"""
|
||||
:type pyfunc: rope.base.pyobjectsdef.PyFunction
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
superfunc = pyfunc
|
||||
while superfunc:
|
||||
result = self._delegate(superfunc)
|
||||
if result:
|
||||
return result
|
||||
superfunc = utils.get_super_func(superfunc)
|
||||
|
||||
|
||||
class AssignmentProvider(interfaces.IAssignmentProvider):
|
||||
def __init__(self, delegate):
|
||||
"""
|
||||
:type delegate: rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider
|
||||
"""
|
||||
self._delegate = delegate
|
||||
|
||||
def __call__(self, pyname):
|
||||
"""
|
||||
:type pyname: rope.base.pynamesdef.AssignedName
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
super_pyname = pyname
|
||||
while super_pyname:
|
||||
result = self._delegate(super_pyname)
|
||||
if result:
|
||||
return result
|
||||
super_pyname = utils.get_super_assignment(super_pyname)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
class IParamProvider(object):
|
||||
def __call__(self, pyfunc, param_name):
|
||||
"""
|
||||
:type pyfunc: rope.base.pyobjectsdef.PyFunction
|
||||
:type param_name: str
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class IReturnProvider(object):
|
||||
"""
|
||||
:type resolve: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
|
||||
"""
|
||||
|
||||
resolve = None
|
||||
|
||||
def __call__(self, pyfunc):
|
||||
"""
|
||||
:type pyfunc: rope.base.pyobjectsdef.PyFunction
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class IAssignmentProvider(object):
|
||||
"""
|
||||
:type resolve: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
|
||||
"""
|
||||
|
||||
resolve = None
|
||||
|
||||
def __call__(self, pyname):
|
||||
"""
|
||||
:type pyname: rope.base.pynamesdef.AssignedName
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
"""
|
||||
Some code extracted (or based on code) from:
|
||||
https://github.com/davidhalter/jedi/blob/b489019f5bd5750051122b94cc767df47751ecb7/jedi/evaluate/docstrings.py
|
||||
Thanks to @davidhalter for this utils under MIT License.
|
||||
"""
|
||||
import re
|
||||
from ast import literal_eval
|
||||
from rope.base.oi.type_hinting.providers import docstrings
|
||||
|
||||
try:
|
||||
from numpydoc.docscrape import NumpyDocString
|
||||
except ImportError:
|
||||
NumpyDocString = None
|
||||
|
||||
|
||||
class NumPyDocstringParamParser(docstrings.IParamParser):
|
||||
def __call__(self, docstring, param_name):
|
||||
"""Search `docstring` (in numpydoc format) for type(-s) of `param_name`."""
|
||||
if not docstring:
|
||||
return []
|
||||
params = NumpyDocString(docstring)._parsed_data["Parameters"]
|
||||
for p_name, p_type, p_descr in params:
|
||||
if p_name == param_name:
|
||||
m = re.match("([^,]+(,[^,]+)*?)(,[ ]*optional)?$", p_type)
|
||||
if m:
|
||||
p_type = m.group(1)
|
||||
|
||||
if p_type.startswith("{"):
|
||||
types = set(type(x).__name__ for x in literal_eval(p_type))
|
||||
return list(types)
|
||||
else:
|
||||
return [p_type]
|
||||
return []
|
||||
|
||||
|
||||
class _DummyParamParser(docstrings.IParamParser):
|
||||
def __call__(self, docstring, param_name):
|
||||
return []
|
||||
|
||||
|
||||
if not NumpyDocString:
|
||||
NumPyDocstringParamParser = _DummyParamParser
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import re
|
||||
from rope.base.oi.type_hinting import utils
|
||||
from rope.base.oi.type_hinting.providers import interfaces
|
||||
|
||||
|
||||
class AssignmentProvider(interfaces.IAssignmentProvider):
|
||||
def __init__(self, resolver):
|
||||
"""
|
||||
:type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver
|
||||
"""
|
||||
self._resolve = resolver
|
||||
|
||||
PEP0484_TYPE_COMMENT_PATTERNS = (re.compile(r"type:\s*([^\n]+)"),)
|
||||
|
||||
def __call__(self, pyname):
|
||||
"""
|
||||
:type pyname: rope.base.pynamesdef.AssignedName
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
from rope.base.oi.soi import _get_lineno_for_node
|
||||
|
||||
lineno = _get_lineno_for_node(pyname.assignments[0].ast_node)
|
||||
holding_scope = pyname.module.get_scope().get_inner_scope_for_line(lineno)
|
||||
line = holding_scope._get_global_scope()._scope_finder.lines.get_line(lineno)
|
||||
if "#" in line:
|
||||
type_strs = self._search_type_in_type_comment(line.split("#", 1)[1])
|
||||
if type_strs:
|
||||
return self._resolve(type_strs[0], holding_scope.pyobject)
|
||||
|
||||
def _search_type_in_type_comment(self, code):
|
||||
"""For more info see:
|
||||
https://www.python.org/dev/peps/pep-0484/#type-comments
|
||||
|
||||
>>> AssignmentProvider()._search_type_in_type_comment('type: int')
|
||||
['int']
|
||||
"""
|
||||
for p in self.PEP0484_TYPE_COMMENT_PATTERNS:
|
||||
match = p.search(code)
|
||||
if match:
|
||||
return [match.group(1)]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,21 @@
|
|||
from rope.base.oi.type_hinting.resolvers import interfaces
|
||||
|
||||
|
||||
class Resolver(interfaces.IResolver):
|
||||
def __init__(self, *delegates):
|
||||
"""
|
||||
:type delegates: list[rope.base.oi.type_hinting.resolvers.interfaces.IResolver]
|
||||
"""
|
||||
self._delegates = delegates
|
||||
|
||||
def __call__(self, hint, pyobject):
|
||||
"""
|
||||
:param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo"
|
||||
:type hint: str
|
||||
:type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
for delegate in self._delegates:
|
||||
result = delegate(hint, pyobject)
|
||||
if result:
|
||||
return result
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class IResolver(object):
|
||||
def __call__(self, hint, pyobject):
|
||||
"""
|
||||
:param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo"
|
||||
:type hint: str
|
||||
:type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
from rope.base.oi.type_hinting import evaluate
|
||||
from rope.base.oi.type_hinting.resolvers import interfaces
|
||||
|
||||
|
||||
class Resolver(interfaces.IResolver):
|
||||
def __call__(self, hint, pyobject):
|
||||
"""
|
||||
:param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo"
|
||||
:type hint: str
|
||||
:type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
try:
|
||||
return evaluate.evaluate(hint, pyobject)
|
||||
except (Exception):
|
||||
pass
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
import logging
|
||||
|
||||
try:
|
||||
from typing import Union, Optional
|
||||
except ImportError:
|
||||
pass
|
||||
import rope.base.utils as base_utils
|
||||
from rope.base.evaluate import ScopeNameFinder
|
||||
from rope.base.exceptions import AttributeNotFoundError
|
||||
from rope.base.pyobjects import PyClass, PyDefinedObject, PyFunction, PyObject
|
||||
from rope.base.utils import pycompat
|
||||
|
||||
|
||||
def get_super_func(pyfunc):
|
||||
|
||||
if not isinstance(pyfunc.parent, PyClass):
|
||||
return
|
||||
|
||||
for cls in get_mro(pyfunc.parent)[1:]:
|
||||
try:
|
||||
superfunc = cls.get_attribute(pyfunc.get_name()).get_object()
|
||||
except AttributeNotFoundError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(superfunc, PyFunction):
|
||||
return superfunc
|
||||
|
||||
|
||||
def get_super_assignment(pyname):
|
||||
"""
|
||||
:type pyname: rope.base.pynamesdef.AssignedName
|
||||
:type: rope.base.pynamesdef.AssignedName
|
||||
"""
|
||||
try:
|
||||
pyclass, attr_name = get_class_with_attr_name(pyname)
|
||||
except TypeError:
|
||||
return
|
||||
else:
|
||||
for super_pyclass in get_mro(pyclass)[1:]:
|
||||
if attr_name in super_pyclass:
|
||||
return super_pyclass[attr_name]
|
||||
|
||||
|
||||
def get_class_with_attr_name(pyname):
|
||||
"""
|
||||
:type pyname: rope.base.pynamesdef.AssignedName
|
||||
:return: rope.base.pyobjectsdef.PyClass, str
|
||||
:rtype: tuple
|
||||
"""
|
||||
lineno = get_lineno_for_node(pyname.assignments[0].ast_node)
|
||||
holding_scope = pyname.module.get_scope().get_inner_scope_for_line(lineno)
|
||||
pyobject = holding_scope.pyobject
|
||||
if isinstance(pyobject, PyClass):
|
||||
pyclass = pyobject
|
||||
elif isinstance(pyobject, PyFunction) and isinstance(pyobject.parent, PyClass):
|
||||
pyclass = pyobject.parent
|
||||
else:
|
||||
return
|
||||
for name, attr in pyclass.get_attributes().items():
|
||||
if attr is pyname:
|
||||
return (pyclass, name)
|
||||
|
||||
|
||||
def get_lineno_for_node(assign_node):
|
||||
if hasattr(assign_node, "lineno") and assign_node.lineno is not None:
|
||||
return assign_node.lineno
|
||||
return 1
|
||||
|
||||
|
||||
def get_mro(pyclass):
|
||||
# FIXME: to use real mro() result
|
||||
class_list = [pyclass]
|
||||
for cls in class_list:
|
||||
for super_cls in cls.get_superclasses():
|
||||
if isinstance(super_cls, PyClass) and super_cls not in class_list:
|
||||
class_list.append(super_cls)
|
||||
return class_list
|
||||
|
||||
|
||||
def resolve_type(type_name, pyobject):
|
||||
# type: (str, Union[PyDefinedObject, PyObject]) -> Optional[PyDefinedObject, PyObject]
|
||||
"""
|
||||
Find proper type object from its name.
|
||||
"""
|
||||
deprecated_aliases = {"collections": "collections.abc"}
|
||||
ret_type = None
|
||||
logging.debug("Looking for %s", type_name)
|
||||
if "." not in type_name:
|
||||
try:
|
||||
ret_type = (
|
||||
pyobject.get_module().get_scope().get_name(type_name).get_object()
|
||||
)
|
||||
except AttributeNotFoundError:
|
||||
logging.exception("Cannot resolve type %s", type_name)
|
||||
else:
|
||||
mod_name, attr_name = type_name.rsplit(".", 1)
|
||||
try:
|
||||
mod_finder = ScopeNameFinder(pyobject.get_module())
|
||||
mod = mod_finder._find_module(mod_name).get_object()
|
||||
ret_type = mod.get_attribute(attr_name).get_object()
|
||||
except AttributeNotFoundError:
|
||||
if mod_name in deprecated_aliases:
|
||||
try:
|
||||
logging.debug(
|
||||
"Looking for %s in %s", attr_name, deprecated_aliases[mod_name]
|
||||
)
|
||||
mod = mod_finder._find_module(
|
||||
deprecated_aliases[mod_name]
|
||||
).get_object()
|
||||
ret_type = mod.get_attribute(attr_name).get_object()
|
||||
except AttributeNotFoundError:
|
||||
logging.exception(
|
||||
"Cannot resolve type %s in %s", attr_name, dir(mod)
|
||||
)
|
||||
logging.debug("ret_type = %s", ret_type)
|
||||
return ret_type
|
||||
|
||||
|
||||
class ParametrizeType(object):
|
||||
|
||||
_supported_mapping = {
|
||||
"builtins.list": "rope.base.builtins.get_list",
|
||||
"builtins.tuple": "rope.base.builtins.get_tuple",
|
||||
"builtins.set": "rope.base.builtins.get_set",
|
||||
"builtins.dict": "rope.base.builtins.get_dict",
|
||||
"_collections_abc.Iterable": "rope.base.builtins.get_iterator",
|
||||
"_collections_abc.Iterator": "rope.base.builtins.get_iterator",
|
||||
"collections.abc.Iterable": "rope.base.builtins.get_iterator", # Python3.3
|
||||
"collections.abc.Iterator": "rope.base.builtins.get_iterator", # Python3.3
|
||||
}
|
||||
if pycompat.PY2:
|
||||
_supported_mapping = dict(
|
||||
(
|
||||
(
|
||||
k.replace("builtins.", "__builtin__.").replace(
|
||||
"_collections_abc.", "_abcoll."
|
||||
),
|
||||
v,
|
||||
)
|
||||
for k, v in _supported_mapping.items()
|
||||
)
|
||||
)
|
||||
|
||||
def __call__(self, pyobject, *args, **kwargs):
|
||||
"""
|
||||
:type pyobject: rope.base.pyobjects.PyObject
|
||||
:rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None
|
||||
"""
|
||||
type_factory = self._get_type_factory(pyobject)
|
||||
if type_factory:
|
||||
parametrized_type = type_factory(*args, **kwargs)
|
||||
if parametrized_type:
|
||||
return parametrized_type
|
||||
return pyobject
|
||||
|
||||
def _get_type_factory(self, pyobject):
|
||||
type_str = "{0}.{1}".format(
|
||||
pyobject.get_module().get_name(),
|
||||
pyobject.get_name(),
|
||||
)
|
||||
if type_str in self._supported_mapping:
|
||||
return base_utils.resolve(self._supported_mapping[type_str])
|
||||
|
||||
|
||||
parametrize_type = ParametrizeType()
|
||||
Loading…
Add table
Add a link
Reference in a new issue