This commit is contained in:
Waylon Walker 2022-03-31 20:20:07 -05:00
commit 38355d2442
No known key found for this signature in database
GPG key ID: 66E2BF2B4190EFE4
9083 changed files with 1225834 additions and 0 deletions

View file

@ -0,0 +1,25 @@
"""rope, a python refactoring library"""
INFO = __doc__
VERSION = "0.23.0"
COPYRIGHT = """\
Copyright (C) 2021-2021 Lie Ryan
Copyright (C) 2019-2021 Matej Cepl
Copyright (C) 2015-2018 Nicholas Smith
Copyright (C) 2014-2015 Matej Cepl
Copyright (C) 2006-2012 Ali Gholami Rudi
Copyright (C) 2009-2012 Anton Gritsay
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, either
version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses/>."""

View file

@ -0,0 +1,8 @@
"""Base rope package
This package contains rope core modules that are used by other modules
and packages.
"""
__all__ = ["project", "libutils", "exceptions"]

View file

@ -0,0 +1,111 @@
import rope.base.evaluate
from rope.base import ast
class Arguments(object):
"""A class for evaluating parameters passed to a function
You can use the `create_arguments` factory. It handles implicit
first arguments.
"""
def __init__(self, args, scope):
self.args = args
self.scope = scope
self.instance = None
def get_arguments(self, parameters):
result = []
for pyname in self.get_pynames(parameters):
if pyname is None:
result.append(None)
else:
result.append(pyname.get_object())
return result
def get_pynames(self, parameters):
result = [None] * max(len(parameters), len(self.args))
for index, arg in enumerate(self.args):
if isinstance(arg, ast.keyword) and arg.arg in parameters:
result[parameters.index(arg.arg)] = self._evaluate(arg.value)
else:
result[index] = self._evaluate(arg)
return result
def get_instance_pyname(self):
if self.args:
return self._evaluate(self.args[0])
def _evaluate(self, ast_node):
return rope.base.evaluate.eval_node(self.scope, ast_node)
def create_arguments(primary, pyfunction, call_node, scope):
"""A factory for creating `Arguments`"""
args = list(call_node.args)
args.extend(call_node.keywords)
called = call_node.func
# XXX: Handle constructors
if _is_method_call(primary, pyfunction) and isinstance(called, ast.Attribute):
args.insert(0, called.value)
return Arguments(args, scope)
class ObjectArguments(object):
def __init__(self, pynames):
self.pynames = pynames
def get_arguments(self, parameters):
result = []
for pyname in self.pynames:
if pyname is None:
result.append(None)
else:
result.append(pyname.get_object())
return result
def get_pynames(self, parameters):
return self.pynames
def get_instance_pyname(self):
return self.pynames[0]
class MixedArguments(object):
def __init__(self, pyname, arguments, scope):
"""`argumens` is an instance of `Arguments`"""
self.pyname = pyname
self.args = arguments
def get_pynames(self, parameters):
return [self.pyname] + self.args.get_pynames(parameters[1:])
def get_arguments(self, parameters):
result = []
for pyname in self.get_pynames(parameters):
if pyname is None:
result.append(None)
else:
result.append(pyname.get_object())
return result
def get_instance_pyname(self):
return self.pyname
def _is_method_call(primary, pyfunction):
if primary is None:
return False
pyobject = primary.get_object()
if (
isinstance(pyobject.get_type(), rope.base.pyobjects.PyClass)
and isinstance(pyfunction, rope.base.pyobjects.PyFunction)
and isinstance(pyfunction.parent, rope.base.pyobjects.PyClass)
):
return True
if isinstance(
pyobject.get_type(), rope.base.pyobjects.AbstractClass
) and isinstance(pyfunction, rope.base.builtins.BuiltinFunction):
return True
return False

View file

@ -0,0 +1,77 @@
from __future__ import absolute_import
import ast
from ast import *
from rope.base import fscommands
try:
unicode
except NameError:
unicode = str
def parse(source, filename="<string>"):
# NOTE: the raw string should be given to `compile` function
if isinstance(source, unicode):
source = fscommands.unicode_to_file_data(source)
if b"\r" in source:
source = source.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
if not source.endswith(b"\n"):
source += b"\n"
try:
return ast.parse(source, filename="<unknown>")
except (TypeError, ValueError) as e:
error = SyntaxError()
error.lineno = 1
error.filename = filename
error.msg = str(e)
raise error
def walk(node, walker):
"""Walk the syntax tree"""
method_name = "_" + node.__class__.__name__
method = getattr(walker, method_name, None)
if method is not None:
if isinstance(node, ast.ImportFrom) and node.module is None:
# In python < 2.7 ``node.module == ''`` for relative imports
# but for python 2.7 it is None. Generalizing it to ''.
node.module = ""
return method(node)
for child in get_child_nodes(node):
walk(child, walker)
def get_child_nodes(node):
if isinstance(node, ast.Module):
return node.body
result = []
if node._fields is not None:
for name in node._fields:
child = getattr(node, name)
if isinstance(child, list):
for entry in child:
if isinstance(entry, ast.AST):
result.append(entry)
if isinstance(child, ast.AST):
result.append(child)
return result
def call_for_nodes(node, callback, recursive=False):
"""If callback returns `True` the child nodes are skipped"""
result = callback(node)
if recursive and not result:
for child in get_child_nodes(node):
call_for_nodes(child, callback, recursive)
def get_children(node):
result = []
if node._fields is not None:
for name in node._fields:
if name in ["lineno", "col_offset"]:
continue
child = getattr(node, name)
result.append(child)
return result

View file

@ -0,0 +1,63 @@
from rope.base import ast
def get_name_levels(node):
"""Return a list of ``(name, level)`` tuples for assigned names
The `level` is `None` for simple assignments and is a list of
numbers for tuple assignments for example in::
a, (b, c) = x
The levels for for `a` is ``[0]``, for `b` is ``[1, 0]`` and for
`c` is ``[1, 1]``.
"""
visitor = _NodeNameCollector()
ast.walk(node, visitor)
return visitor.names
class _NodeNameCollector(object):
def __init__(self, levels=None):
self.names = []
self.levels = levels
self.index = 0
def _add_node(self, node):
new_levels = []
if self.levels is not None:
new_levels = list(self.levels)
new_levels.append(self.index)
self.index += 1
self._added(node, new_levels)
def _added(self, node, levels):
if hasattr(node, "id"):
self.names.append((node.id, levels))
def _Name(self, node):
self._add_node(node)
def _ExceptHandler(self, node):
self.names.append((node.name, []))
def _Tuple(self, node):
new_levels = []
if self.levels is not None:
new_levels = list(self.levels)
new_levels.append(self.index)
self.index += 1
visitor = _NodeNameCollector(new_levels)
for child in ast.get_child_nodes(node):
ast.walk(child, visitor)
self.names.extend(visitor.names)
def _Subscript(self, node):
self._add_node(node)
def _Attribute(self, node):
self._add_node(node)
def _Slice(self, node):
self._add_node(node)

View file

@ -0,0 +1,872 @@
"""This module trys to support builtin types and functions."""
import inspect
import io
try:
raw_input
except NameError:
raw_input = input
import rope.base.evaluate
from rope.base.utils import pycompat
from rope.base import pynames, pyobjects, arguments, utils
class BuiltinModule(pyobjects.AbstractModule):
def __init__(self, name, pycore=None, initial={}):
super(BuiltinModule, self).__init__()
self.name = name
self.pycore = pycore
self.initial = initial
parent = None
def get_attributes(self):
return self.attributes
def get_doc(self):
if self.module:
return self.module.__doc__
def get_name(self):
return self.name.split(".")[-1]
@property
@utils.saveit
def attributes(self):
result = _object_attributes(self.module, self)
result.update(self.initial)
if self.pycore is not None:
submodules = self.pycore._builtin_submodules(self.name)
for name, module in submodules.items():
result[name] = rope.base.builtins.BuiltinName(module)
return result
@property
@utils.saveit
def module(self):
try:
result = __import__(self.name)
for token in self.name.split(".")[1:]:
result = getattr(result, token, None)
return result
except ImportError:
return
class _BuiltinElement(object):
def __init__(self, builtin, parent=None):
self.builtin = builtin
self._parent = parent
def get_doc(self):
if self.builtin:
return getattr(self.builtin, "__doc__", None)
def get_name(self):
if self.builtin:
return getattr(self.builtin, "__name__", None)
@property
def parent(self):
if self._parent is None:
return builtins
return self._parent
class BuiltinClass(_BuiltinElement, pyobjects.AbstractClass):
def __init__(self, builtin, attributes, parent=None):
_BuiltinElement.__init__(self, builtin, parent)
pyobjects.AbstractClass.__init__(self)
self.initial = attributes
@utils.saveit
def get_attributes(self):
result = _object_attributes(self.builtin, self)
result.update(self.initial)
return result
def get_module(self):
return builtins
class BuiltinFunction(_BuiltinElement, pyobjects.AbstractFunction):
def __init__(
self, returned=None, function=None, builtin=None, argnames=[], parent=None
):
_BuiltinElement.__init__(self, builtin, parent)
pyobjects.AbstractFunction.__init__(self)
self.argnames = argnames
self.returned = returned
self.function = function
def get_returned_object(self, args):
if self.function is not None:
return self.function(_CallContext(self.argnames, args))
else:
return self.returned
def get_param_names(self, special_args=True):
return self.argnames
class BuiltinUnknown(_BuiltinElement, pyobjects.PyObject):
def __init__(self, builtin):
super(BuiltinUnknown, self).__init__(pyobjects.get_unknown())
self.builtin = builtin
self.type = pyobjects.get_unknown()
def get_name(self):
return getattr(type(self.builtin), "__name__", None)
@utils.saveit
def get_attributes(self):
return _object_attributes(self.builtin, self)
def _object_attributes(obj, parent):
attributes = {}
for name in dir(obj):
if name == "None":
continue
try:
child = getattr(obj, name)
except AttributeError:
# descriptors are allowed to raise AttributeError
# even if they are in dir()
continue
pyobject = None
if inspect.isclass(child):
pyobject = BuiltinClass(child, {}, parent=parent)
elif inspect.isroutine(child):
pyobject = BuiltinFunction(builtin=child, parent=parent)
else:
pyobject = BuiltinUnknown(builtin=child)
attributes[name] = BuiltinName(pyobject)
return attributes
def _create_builtin_type_getter(cls):
def _get_builtin(*args):
if not hasattr(cls, "_generated"):
cls._generated = {}
if args not in cls._generated:
cls._generated[args] = cls(*args)
return cls._generated[args]
return _get_builtin
def _create_builtin_getter(cls):
type_getter = _create_builtin_type_getter(cls)
def _get_builtin(*args):
return pyobjects.PyObject(type_getter(*args))
return _get_builtin
class _CallContext(object):
def __init__(self, argnames, args):
self.argnames = argnames
self.args = args
def _get_scope_and_pyname(self, pyname):
if pyname is not None and isinstance(pyname, pynames.AssignedName):
pymodule, lineno = pyname.get_definition_location()
if pymodule is None:
return None, None
if lineno is None:
lineno = 1
scope = pymodule.get_scope().get_inner_scope_for_line(lineno)
name = None
while name is None and scope is not None:
for current in scope.get_names():
if scope[current] is pyname:
name = current
break
else:
scope = scope.parent
return scope, name
return None, None
def get_argument(self, name):
if self.args:
args = self.args.get_arguments(self.argnames)
return args[self.argnames.index(name)]
def get_pyname(self, name):
if self.args:
args = self.args.get_pynames(self.argnames)
if name in self.argnames:
return args[self.argnames.index(name)]
def get_arguments(self, argnames):
if self.args:
return self.args.get_arguments(argnames)
def get_pynames(self, argnames):
if self.args:
return self.args.get_pynames(argnames)
def get_per_name(self):
if self.args is None:
return None
pyname = self.args.get_instance_pyname()
scope, name = self._get_scope_and_pyname(pyname)
if name is not None:
pymodule = pyname.get_definition_location()[0]
return pymodule.pycore.object_info.get_per_name(scope, name)
return None
def save_per_name(self, value):
if self.args is None:
return None
pyname = self.args.get_instance_pyname()
scope, name = self._get_scope_and_pyname(pyname)
if name is not None:
pymodule = pyname.get_definition_location()[0]
pymodule.pycore.object_info.save_per_name(scope, name, value)
class _AttributeCollector(object):
def __init__(self, type):
self.attributes = {}
self.type = type
def __call__(
self,
name,
returned=None,
function=None,
argnames=["self"],
check_existence=True,
parent=None,
):
try:
builtin = getattr(self.type, name)
except AttributeError:
if check_existence:
raise
builtin = None
self.attributes[name] = BuiltinName(
BuiltinFunction(
returned=returned,
function=function,
argnames=argnames,
builtin=builtin,
parent=parent,
)
)
def __setitem__(self, name, value):
self.attributes[name] = value
class List(BuiltinClass):
def __init__(self, holding=None):
self.holding = holding
collector = _AttributeCollector(list)
collector("__iter__", function=self._iterator_get, parent=self)
collector("__new__", function=self._new_list, parent=self)
# Adding methods
collector(
"append", function=self._list_add, argnames=["self", "value"], parent=self
)
collector(
"__setitem__",
function=self._list_add,
argnames=["self", "index", "value"],
parent=self,
)
collector(
"insert",
function=self._list_add,
argnames=["self", "index", "value"],
parent=self,
)
collector(
"extend",
function=self._self_set,
argnames=["self", "iterable"],
parent=self,
)
# Getting methods
collector("__getitem__", function=self._list_get, parent=self)
collector("pop", function=self._list_get, parent=self)
try:
collector("__getslice__", function=self._list_get)
except AttributeError:
pass
super(List, self).__init__(list, collector.attributes)
def _new_list(self, args):
return _create_builtin(args, get_list)
def _list_add(self, context):
if self.holding is not None:
return
holding = context.get_argument("value")
if holding is not None and holding != pyobjects.get_unknown():
context.save_per_name(holding)
def _self_set(self, context):
if self.holding is not None:
return
iterable = context.get_pyname("iterable")
holding = _infer_sequence_for_pyname(iterable)
if holding is not None and holding != pyobjects.get_unknown():
context.save_per_name(holding)
def _list_get(self, context):
if self.holding is not None:
args = context.get_arguments(["self", "key"])
if (
len(args) > 1
and args[1] is not None
and args[1].get_type() == builtins["slice"].get_object()
):
return get_list(self.holding)
return self.holding
return context.get_per_name()
def _iterator_get(self, context):
return get_iterator(self._list_get(context))
def _self_get(self, context):
return get_list(self._list_get(context))
get_list = _create_builtin_getter(List)
get_list_type = _create_builtin_type_getter(List)
class Dict(BuiltinClass):
def __init__(self, keys=None, values=None):
self.keys = keys
self.values = values
collector = _AttributeCollector(dict)
collector("__new__", function=self._new_dict, parent=self)
collector("__setitem__", function=self._dict_add, parent=self)
collector("popitem", function=self._item_get, parent=self)
collector("pop", function=self._value_get, parent=self)
collector("get", function=self._key_get, parent=self)
collector("keys", function=self._key_list, parent=self)
collector("values", function=self._value_list, parent=self)
collector("items", function=self._item_list, parent=self)
collector("copy", function=self._self_get, parent=self)
collector("__getitem__", function=self._value_get, parent=self)
collector("__iter__", function=self._key_iter, parent=self)
collector("update", function=self._self_set, parent=self)
super(Dict, self).__init__(dict, collector.attributes)
def _new_dict(self, args):
def do_create(holding=None):
if holding is None:
return get_dict()
type = holding.get_type()
if isinstance(type, Tuple) and len(type.get_holding_objects()) == 2:
return get_dict(*type.get_holding_objects())
return _create_builtin(args, do_create)
def _dict_add(self, context):
if self.keys is not None:
return
key, value = context.get_arguments(["self", "key", "value"])[1:]
if key is not None and key != pyobjects.get_unknown():
context.save_per_name(get_tuple(key, value))
def _item_get(self, context):
if self.keys is not None:
return get_tuple(self.keys, self.values)
item = context.get_per_name()
if item is None or not isinstance(item.get_type(), Tuple):
return get_tuple(self.keys, self.values)
return item
def _value_get(self, context):
item = self._item_get(context).get_type()
return item.get_holding_objects()[1]
def _key_get(self, context):
item = self._item_get(context).get_type()
return item.get_holding_objects()[0]
def _value_list(self, context):
return get_list(self._value_get(context))
def _key_list(self, context):
return get_list(self._key_get(context))
def _item_list(self, context):
return get_list(self._item_get(context))
def _value_iter(self, context):
return get_iterator(self._value_get(context))
def _key_iter(self, context):
return get_iterator(self._key_get(context))
def _item_iter(self, context):
return get_iterator(self._item_get(context))
def _self_get(self, context):
item = self._item_get(context).get_type()
key, value = item.get_holding_objects()[:2]
return get_dict(key, value)
def _self_set(self, context):
if self.keys is not None:
return
new_dict = context.get_pynames(["self", "d"])[1]
if new_dict and isinstance(new_dict.get_object().get_type(), Dict):
args = arguments.ObjectArguments([new_dict])
items = (
new_dict.get_object()["popitem"].get_object().get_returned_object(args)
)
context.save_per_name(items)
else:
holding = _infer_sequence_for_pyname(new_dict)
if holding is not None and isinstance(holding.get_type(), Tuple):
context.save_per_name(holding)
get_dict = _create_builtin_getter(Dict)
get_dict_type = _create_builtin_type_getter(Dict)
class Tuple(BuiltinClass):
def __init__(self, *objects):
self.objects = objects
first = None
if objects:
first = objects[0]
attributes = {
"__getitem__": BuiltinName(
BuiltinFunction(first)
), # TODO: add slice support
"__getslice__": BuiltinName(BuiltinFunction(pyobjects.PyObject(self))),
"__new__": BuiltinName(BuiltinFunction(function=self._new_tuple)),
"__iter__": BuiltinName(BuiltinFunction(get_iterator(first))),
}
super(Tuple, self).__init__(tuple, attributes)
def get_holding_objects(self):
return self.objects
def _new_tuple(self, args):
return _create_builtin(args, get_tuple)
get_tuple = _create_builtin_getter(Tuple)
get_tuple_type = _create_builtin_type_getter(Tuple)
class Set(BuiltinClass):
def __init__(self, holding=None):
self.holding = holding
collector = _AttributeCollector(set)
collector("__new__", function=self._new_set)
self_methods = [
"copy",
"difference",
"intersection",
"symmetric_difference",
"union",
]
for method in self_methods:
collector(method, function=self._self_get, parent=self)
collector("add", function=self._set_add, parent=self)
collector("update", function=self._self_set, parent=self)
collector("update", function=self._self_set, parent=self)
collector("symmetric_difference_update", function=self._self_set, parent=self)
collector("difference_update", function=self._self_set, parent=self)
collector("pop", function=self._set_get, parent=self)
collector("__iter__", function=self._iterator_get, parent=self)
super(Set, self).__init__(set, collector.attributes)
def _new_set(self, args):
return _create_builtin(args, get_set)
def _set_add(self, context):
if self.holding is not None:
return
holding = context.get_arguments(["self", "value"])[1]
if holding is not None and holding != pyobjects.get_unknown():
context.save_per_name(holding)
def _self_set(self, context):
if self.holding is not None:
return
iterable = context.get_pyname("iterable")
holding = _infer_sequence_for_pyname(iterable)
if holding is not None and holding != pyobjects.get_unknown():
context.save_per_name(holding)
def _set_get(self, context):
if self.holding is not None:
return self.holding
return context.get_per_name()
def _iterator_get(self, context):
return get_iterator(self._set_get(context))
def _self_get(self, context):
return get_list(self._set_get(context))
get_set = _create_builtin_getter(Set)
get_set_type = _create_builtin_type_getter(Set)
class Str(BuiltinClass):
def __init__(self):
self_object = pyobjects.PyObject(self)
collector = _AttributeCollector(str)
collector("__iter__", get_iterator(self_object), check_existence=False)
self_methods = [
"__getitem__",
"capitalize",
"center",
"encode",
"expandtabs",
"join",
"ljust",
"lower",
"lstrip",
"replace",
"rjust",
"rstrip",
"strip",
"swapcase",
"title",
"translate",
"upper",
"zfill",
]
for method in self_methods:
collector(method, self_object, parent=self)
py2_self_methods = ["__getslice__", "decode"]
for method in py2_self_methods:
try:
collector(method, self_object)
except AttributeError:
pass
for method in ["rsplit", "split", "splitlines"]:
collector(method, get_list(self_object), parent=self)
super(Str, self).__init__(str, collector.attributes)
def get_doc(self):
return str.__doc__
get_str = _create_builtin_getter(Str)
get_str_type = _create_builtin_type_getter(Str)
class BuiltinName(pynames.PyName):
def __init__(self, pyobject):
self.pyobject = pyobject
def get_object(self):
return self.pyobject
def get_definition_location(self):
return (None, None)
class Iterator(pyobjects.AbstractClass):
def __init__(self, holding=None):
super(Iterator, self).__init__()
self.holding = holding
self.attributes = {
"next": BuiltinName(BuiltinFunction(self.holding)),
"__iter__": BuiltinName(BuiltinFunction(self)),
}
def get_attributes(self):
return self.attributes
def get_returned_object(self, args):
return self.holding
get_iterator = _create_builtin_getter(Iterator)
class Generator(pyobjects.AbstractClass):
def __init__(self, holding=None):
super(Generator, self).__init__()
self.holding = holding
self.attributes = {
"next": BuiltinName(BuiltinFunction(self.holding)),
"__iter__": BuiltinName(BuiltinFunction(get_iterator(self.holding))),
"close": BuiltinName(BuiltinFunction()),
"send": BuiltinName(BuiltinFunction()),
"throw": BuiltinName(BuiltinFunction()),
}
def get_attributes(self):
return self.attributes
def get_returned_object(self, args):
return self.holding
get_generator = _create_builtin_getter(Generator)
class File(BuiltinClass):
def __init__(self, filename=None, mode="r", *args):
self.filename = filename
self.mode = mode
self.args = args
str_object = get_str()
str_list = get_list(get_str())
attributes = {}
def add(name, returned=None, function=None):
builtin = getattr(io.TextIOBase, name, None)
attributes[name] = BuiltinName(
BuiltinFunction(returned=returned, function=function, builtin=builtin)
)
add("__iter__", get_iterator(str_object))
add("__enter__", returned=pyobjects.PyObject(self))
for method in ["next", "read", "readline", "readlines"]:
add(method, str_list)
for method in [
"close",
"flush",
"lineno",
"isatty",
"seek",
"tell",
"truncate",
"write",
"writelines",
]:
add(method)
super(File, self).__init__(open, attributes)
get_file = _create_builtin_getter(File)
get_file_type = _create_builtin_type_getter(File)
class Property(BuiltinClass):
def __init__(self, fget=None, fset=None, fdel=None, fdoc=None):
self._fget = fget
self._fdoc = fdoc
attributes = {
"fget": BuiltinName(BuiltinFunction()),
"fset": BuiltinName(pynames.UnboundName()),
"fdel": BuiltinName(pynames.UnboundName()),
"__new__": BuiltinName(BuiltinFunction(function=_property_function)),
}
super(Property, self).__init__(property, attributes)
def get_property_object(self, args):
if isinstance(self._fget, pyobjects.AbstractFunction):
return self._fget.get_returned_object(args)
def _property_function(args):
parameters = args.get_arguments(["fget", "fset", "fdel", "fdoc"])
return pyobjects.PyObject(Property(parameters[0]))
class Lambda(pyobjects.AbstractFunction):
def __init__(self, node, scope):
super(Lambda, self).__init__()
self.node = node
self.arguments = node.args
self.scope = scope
def get_returned_object(self, args):
result = rope.base.evaluate.eval_node(self.scope, self.node.body)
if result is not None:
return result.get_object()
else:
return pyobjects.get_unknown()
def get_module(self):
return self.parent.get_module()
def get_scope(self):
return self.scope
def get_kind(self):
return "lambda"
def get_ast(self):
return self.node
def get_attributes(self):
return {}
def get_name(self):
return "lambda"
def get_param_names(self, special_args=True):
result = [
pycompat.get_ast_arg_arg(node)
for node in self.arguments.args
if isinstance(node, pycompat.ast_arg_type)
]
if self.arguments.vararg:
result.append("*" + pycompat.get_ast_arg_arg(self.arguments.vararg))
if self.arguments.kwarg:
result.append("**" + pycompat.get_ast_arg_arg(self.arguments.kwarg))
return result
@property
def parent(self):
return self.scope.pyobject
class BuiltinObject(BuiltinClass):
def __init__(self):
super(BuiltinObject, self).__init__(object, {})
class BuiltinType(BuiltinClass):
def __init__(self):
super(BuiltinType, self).__init__(type, {})
def _infer_sequence_for_pyname(pyname):
if pyname is None:
return None
seq = pyname.get_object()
args = arguments.ObjectArguments([pyname])
if "__iter__" in seq:
obj = seq["__iter__"].get_object()
if not isinstance(obj, pyobjects.AbstractFunction):
return None
iter = obj.get_returned_object(args)
if iter is not None and "next" in iter:
holding = iter["next"].get_object().get_returned_object(args)
return holding
def _create_builtin(args, creator):
passed = args.get_pynames(["sequence"])[0]
if passed is None:
holding = None
else:
holding = _infer_sequence_for_pyname(passed)
if holding is not None:
return creator(holding)
else:
return creator()
def _open_function(args):
return _create_builtin(args, get_file)
def _range_function(args):
return get_list()
def _reversed_function(args):
return _create_builtin(args, get_iterator)
def _sorted_function(args):
return _create_builtin(args, get_list)
def _super_function(args):
passed_class, passed_self = args.get_arguments(["type", "self"])
if passed_self is None:
return passed_class
else:
# pyclass = passed_self.get_type()
pyclass = passed_class
if isinstance(pyclass, pyobjects.AbstractClass):
supers = pyclass.get_superclasses()
if supers:
return pyobjects.PyObject(supers[0])
return passed_self
def _zip_function(args):
args = args.get_pynames(["sequence"])
objects = []
for seq in args:
if seq is None:
holding = None
else:
holding = _infer_sequence_for_pyname(seq)
objects.append(holding)
tuple = get_tuple(*objects)
return get_list(tuple)
def _enumerate_function(args):
passed = args.get_pynames(["sequence"])[0]
if passed is None:
holding = None
else:
holding = _infer_sequence_for_pyname(passed)
tuple = get_tuple(None, holding)
return get_iterator(tuple)
def _iter_function(args):
passed = args.get_pynames(["sequence"])[0]
if passed is None:
holding = None
else:
holding = _infer_sequence_for_pyname(passed)
return get_iterator(holding)
def _input_function(args):
return get_str()
_initial_builtins = {
"list": BuiltinName(get_list_type()),
"dict": BuiltinName(get_dict_type()),
"tuple": BuiltinName(get_tuple_type()),
"set": BuiltinName(get_set_type()),
"str": BuiltinName(get_str_type()),
"file": BuiltinName(get_file_type()),
"open": BuiltinName(BuiltinFunction(function=_open_function, builtin=open)),
"unicode": BuiltinName(get_str_type()),
"range": BuiltinName(BuiltinFunction(function=_range_function, builtin=range)),
"reversed": BuiltinName(
BuiltinFunction(function=_reversed_function, builtin=reversed)
),
"sorted": BuiltinName(BuiltinFunction(function=_sorted_function, builtin=sorted)),
"super": BuiltinName(BuiltinFunction(function=_super_function, builtin=super)),
"property": BuiltinName(
BuiltinFunction(function=_property_function, builtin=property)
),
"zip": BuiltinName(BuiltinFunction(function=_zip_function, builtin=zip)),
"enumerate": BuiltinName(
BuiltinFunction(function=_enumerate_function, builtin=enumerate)
),
"object": BuiltinName(BuiltinObject()),
"type": BuiltinName(BuiltinType()),
"iter": BuiltinName(BuiltinFunction(function=_iter_function, builtin=iter)),
"raw_input": BuiltinName(
BuiltinFunction(function=_input_function, builtin=raw_input)
),
}
builtins = BuiltinModule(pycompat.builtins.__name__, initial=_initial_builtins)

View file

@ -0,0 +1,453 @@
import datetime
import difflib
import os
import time
import rope.base.fscommands
from rope.base import taskhandle, exceptions, utils
class Change(object):
"""The base class for changes
Rope refactorings return `Change` objects. They can be previewed,
committed or undone.
"""
def do(self, job_set=None):
"""Perform the change
.. note:: Do use this directly. Use `Project.do()` instead.
"""
def undo(self, job_set=None):
"""Perform the change
.. note:: Do use this directly. Use `History.undo()` instead.
"""
def get_description(self):
"""Return the description of this change
This can be used for previewing the changes.
"""
return str(self)
def get_changed_resources(self):
"""Return the list of resources that will be changed"""
return []
@property
@utils.saveit
def _operations(self):
return _ResourceOperations(self.resource.project)
class ChangeSet(Change):
"""A collection of `Change` objects
This class holds a collection of changes. This class provides
these fields:
* `changes`: the list of changes
* `description`: the goal of these changes
"""
def __init__(self, description, timestamp=None):
self.changes = []
self.description = description
self.time = timestamp
def do(self, job_set=taskhandle.NullJobSet()):
try:
done = []
for change in self.changes:
change.do(job_set)
done.append(change)
self.time = time.time()
except Exception:
for change in done:
change.undo()
raise
def undo(self, job_set=taskhandle.NullJobSet()):
try:
done = []
for change in reversed(self.changes):
change.undo(job_set)
done.append(change)
except Exception:
for change in done:
change.do()
raise
def add_change(self, change):
self.changes.append(change)
def get_description(self):
result = [str(self) + ":\n\n\n"]
for change in self.changes:
result.append(change.get_description())
result.append("\n")
return "".join(result)
def __str__(self):
if self.time is not None:
date = datetime.datetime.fromtimestamp(self.time)
if date.date() == datetime.date.today():
string_date = "today"
elif date.date() == (datetime.date.today() - datetime.timedelta(1)):
string_date = "yesterday"
elif date.year == datetime.date.today().year:
string_date = date.strftime("%b %d")
else:
string_date = date.strftime("%d %b, %Y")
string_time = date.strftime("%H:%M:%S")
string_time = "%s %s " % (string_date, string_time)
return self.description + " - " + string_time
return self.description
def get_changed_resources(self):
result = set()
for change in self.changes:
result.update(change.get_changed_resources())
return result
def _handle_job_set(function):
"""A decorator for handling `taskhandle.JobSet`
A decorator for handling `taskhandle.JobSet` for `do` and `undo`
methods of `Change`.
"""
def call(self, job_set=taskhandle.NullJobSet()):
job_set.started_job(str(self))
function(self)
job_set.finished_job()
return call
class ChangeContents(Change):
"""A class to change the contents of a file
Fields:
* `resource`: The `rope.base.resources.File` to change
* `new_contents`: What to write in the file
"""
def __init__(self, resource, new_contents, old_contents=None):
self.resource = resource
# IDEA: Only saving diffs; possible problems when undo/redoing
self.new_contents = new_contents
self.old_contents = old_contents
@_handle_job_set
def do(self):
if self.old_contents is None:
self.old_contents = self.resource.read()
self._operations.write_file(self.resource, self.new_contents)
@_handle_job_set
def undo(self):
if self.old_contents is None:
raise exceptions.HistoryError("Undoing a change that is not performed yet!")
self._operations.write_file(self.resource, self.old_contents)
def __str__(self):
return "Change <%s>" % self.resource.path
def get_description(self):
new = self.new_contents
old = self.old_contents
if old is None:
if self.resource.exists():
old = self.resource.read()
else:
old = ""
result = difflib.unified_diff(
old.splitlines(True),
new.splitlines(True),
"a/" + self.resource.path,
"b/" + self.resource.path,
)
return "".join(list(result))
def get_changed_resources(self):
return [self.resource]
class MoveResource(Change):
"""Move a resource to a new location
Fields:
* `resource`: The `rope.base.resources.Resource` to move
* `new_resource`: The destination for move; It is the moved
resource not the folder containing that resource.
"""
def __init__(self, resource, new_location, exact=False):
self.project = resource.project
self.resource = resource
if not exact:
new_location = _get_destination_for_move(resource, new_location)
if resource.is_folder():
self.new_resource = self.project.get_folder(new_location)
else:
self.new_resource = self.project.get_file(new_location)
@_handle_job_set
def do(self):
self._operations.move(self.resource, self.new_resource)
@_handle_job_set
def undo(self):
self._operations.move(self.new_resource, self.resource)
def __str__(self):
return "Move <%s>" % self.resource.path
def get_description(self):
return "rename from %s\nrename to %s" % (
self.resource.path,
self.new_resource.path,
)
def get_changed_resources(self):
return [self.resource, self.new_resource]
class CreateResource(Change):
"""A class to create a resource
Fields:
* `resource`: The resource to create
"""
def __init__(self, resource):
self.resource = resource
@_handle_job_set
def do(self):
self._operations.create(self.resource)
@_handle_job_set
def undo(self):
self._operations.remove(self.resource)
def __str__(self):
return "Create Resource <%s>" % (self.resource.path)
def get_description(self):
return "new file %s" % (self.resource.path)
def get_changed_resources(self):
return [self.resource]
def _get_child_path(self, parent, name):
if parent.path == "":
return name
else:
return parent.path + "/" + name
class CreateFolder(CreateResource):
"""A class to create a folder
See docs for `CreateResource`.
"""
def __init__(self, parent, name):
resource = parent.project.get_folder(self._get_child_path(parent, name))
super(CreateFolder, self).__init__(resource)
class CreateFile(CreateResource):
"""A class to create a file
See docs for `CreateResource`.
"""
def __init__(self, parent, name):
resource = parent.project.get_file(self._get_child_path(parent, name))
super(CreateFile, self).__init__(resource)
class RemoveResource(Change):
"""A class to remove a resource
Fields:
* `resource`: The resource to be removed
"""
def __init__(self, resource):
self.resource = resource
@_handle_job_set
def do(self):
self._operations.remove(self.resource)
# TODO: Undoing remove operations
@_handle_job_set
def undo(self):
raise NotImplementedError("Undoing `RemoveResource` is not implemented yet.")
def __str__(self):
return "Remove <%s>" % (self.resource.path)
def get_changed_resources(self):
return [self.resource]
def count_changes(change):
"""Counts the number of basic changes a `Change` will make"""
if isinstance(change, ChangeSet):
result = 0
for child in change.changes:
result += count_changes(child)
return result
return 1
def create_job_set(task_handle, change):
return task_handle.create_jobset(str(change), count_changes(change))
class _ResourceOperations(object):
def __init__(self, project):
self.project = project
self.fscommands = project.fscommands
self.direct_commands = rope.base.fscommands.FileSystemCommands()
def _get_fscommands(self, resource):
if self.project.is_ignored(resource):
return self.direct_commands
return self.fscommands
def write_file(self, resource, contents):
data = rope.base.fscommands.unicode_to_file_data(
contents,
newlines=resource.newlines,
)
fscommands = self._get_fscommands(resource)
fscommands.write(resource.real_path, data)
for observer in list(self.project.observers):
observer.resource_changed(resource)
def move(self, resource, new_resource):
fscommands = self._get_fscommands(resource)
fscommands.move(resource.real_path, new_resource.real_path)
for observer in list(self.project.observers):
observer.resource_moved(resource, new_resource)
def create(self, resource):
if resource.is_folder():
self._create_resource(resource.path, kind="folder")
else:
self._create_resource(resource.path)
for observer in list(self.project.observers):
observer.resource_created(resource)
def remove(self, resource):
fscommands = self._get_fscommands(resource)
fscommands.remove(resource.real_path)
for observer in list(self.project.observers):
observer.resource_removed(resource)
def _create_resource(self, file_name, kind="file"):
resource_path = self.project._get_resource_path(file_name)
if os.path.exists(resource_path):
raise exceptions.RopeError("Resource <%s> already exists" % resource_path)
resource = self.project.get_file(file_name)
if not resource.parent.exists():
raise exceptions.ResourceNotFoundError(
"Parent folder of <%s> does not exist" % resource.path
)
fscommands = self._get_fscommands(resource)
try:
if kind == "file":
fscommands.create_file(resource_path)
else:
fscommands.create_folder(resource_path)
except IOError as e:
raise exceptions.RopeError(e)
def _get_destination_for_move(resource, destination):
dest_path = resource.project._get_resource_path(destination)
if os.path.isdir(dest_path):
if destination != "":
return destination + "/" + resource.name
else:
return resource.name
return destination
class ChangeToData(object):
def convertChangeSet(self, change):
description = change.description
changes = []
for child in change.changes:
changes.append(self(child))
return (description, changes, change.time)
def convertChangeContents(self, change):
return (change.resource.path, change.new_contents, change.old_contents)
def convertMoveResource(self, change):
return (change.resource.path, change.new_resource.path)
def convertCreateResource(self, change):
return (change.resource.path, change.resource.is_folder())
def convertRemoveResource(self, change):
return (change.resource.path, change.resource.is_folder())
def __call__(self, change):
change_type = type(change)
if change_type in (CreateFolder, CreateFile):
change_type = CreateResource
method = getattr(self, "convert" + change_type.__name__)
return (change_type.__name__, method(change))
class DataToChange(object):
def __init__(self, project):
self.project = project
def makeChangeSet(self, description, changes, time=None):
result = ChangeSet(description, time)
for child in changes:
result.add_change(self(child))
return result
def makeChangeContents(self, path, new_contents, old_contents):
resource = self.project.get_file(path)
return ChangeContents(resource, new_contents, old_contents)
def makeMoveResource(self, old_path, new_path):
resource = self.project.get_file(old_path)
return MoveResource(resource, new_path, exact=True)
def makeCreateResource(self, path, is_folder):
if is_folder:
resource = self.project.get_folder(path)
else:
resource = self.project.get_file(path)
return CreateResource(resource)
def makeRemoveResource(self, path, is_folder):
if is_folder:
resource = self.project.get_folder(path)
else:
resource = self.project.get_file(path)
return RemoveResource(resource)
def __call__(self, data):
method = getattr(self, "make" + data[0])
return method(*data[1])

View file

@ -0,0 +1,393 @@
import bisect
import re
import token
import tokenize
class ChangeCollector(object):
def __init__(self, text):
self.text = text
self.changes = []
def add_change(self, start, end, new_text=None):
if new_text is None:
new_text = self.text[start:end]
self.changes.append((start, end, new_text))
def get_changed(self):
if not self.changes:
return None
self.changes.sort(key=lambda x: x[:2])
pieces = []
last_changed = 0
for change in self.changes:
start, end, text = change
pieces.append(self.text[last_changed:start] + text)
last_changed = end
if last_changed < len(self.text):
pieces.append(self.text[last_changed:])
result = "".join(pieces)
if result != self.text:
return result
class SourceLinesAdapter(object):
"""Adapts source to Lines interface
Note: The creation of this class is expensive.
"""
def __init__(self, source_code):
self.code = source_code
self.starts = None
self._initialize_line_starts()
def _initialize_line_starts(self):
self.starts = []
self.starts.append(0)
try:
i = 0
while True:
i = self.code.index("\n", i) + 1
self.starts.append(i)
except ValueError:
pass
self.starts.append(len(self.code) + 1)
def get_line(self, lineno):
return self.code[self.starts[lineno - 1] : self.starts[lineno] - 1]
def length(self):
return len(self.starts) - 1
def get_line_number(self, offset):
return bisect.bisect(self.starts, offset)
def get_line_start(self, lineno):
return self.starts[lineno - 1]
def get_line_end(self, lineno):
return self.starts[lineno] - 1
class ArrayLinesAdapter(object):
def __init__(self, lines):
self.lines = lines
def get_line(self, line_number):
return self.lines[line_number - 1]
def length(self):
return len(self.lines)
class LinesToReadline(object):
def __init__(self, lines, start):
self.lines = lines
self.current = start
def readline(self):
if self.current <= self.lines.length():
self.current += 1
return self.lines.get_line(self.current - 1) + "\n"
return ""
def __call__(self):
return self.readline()
class _CustomGenerator(object):
def __init__(self, lines):
self.lines = lines
self.in_string = ""
self.open_count = 0
self.continuation = False
def __call__(self):
size = self.lines.length()
result = []
i = 1
while i <= size:
while i <= size and not self.lines.get_line(i).strip():
i += 1
if i <= size:
start = i
while True:
line = self.lines.get_line(i)
self._analyze_line(line)
if (
not (self.continuation or self.open_count or self.in_string)
or i == size
):
break
i += 1
result.append((start, i))
i += 1
return result
# Matches all backslashes before the token, to detect escaped quotes
_main_tokens = re.compile(r'(\\*)((\'\'\'|"""|\'|")|#|\[|\]|\{|\}|\(|\))')
def _analyze_line(self, line):
token = None
for match in self._main_tokens.finditer(line):
prefix = match.group(1)
token = match.group(2)
# Skip any tokens which are escaped
if len(prefix) % 2 == 1:
continue
if token in ["'''", '"""', "'", '"']:
if not self.in_string:
self.in_string = token
elif self.in_string == token or (
self.in_string in ['"', "'"] and token == 3 * self.in_string
):
self.in_string = ""
if self.in_string:
continue
if token == "#":
break
if token in "([{":
self.open_count += 1
elif token in ")]}":
self.open_count -= 1
if line and token != "#" and line.endswith("\\"):
self.continuation = True
else:
self.continuation = False
def custom_generator(lines):
return _CustomGenerator(lines)()
class LogicalLineFinder(object):
def __init__(self, lines):
self.lines = lines
def logical_line_in(self, line_number):
indents = count_line_indents(self.lines.get_line(line_number))
tries = 0
while True:
block_start = get_block_start(self.lines, line_number, indents)
try:
return self._block_logical_line(block_start, line_number)
except IndentationError as e:
tries += 1
if tries == 5:
raise e
lineno = e.lineno + block_start - 1
indents = count_line_indents(self.lines.get_line(lineno))
def generate_starts(self, start_line=1, end_line=None):
for start, end in self.generate_regions(start_line, end_line):
yield start
def generate_regions(self, start_line=1, end_line=None):
# XXX: `block_start` should be at a better position!
block_start = 1
readline = LinesToReadline(self.lines, block_start)
try:
for start, end in self._logical_lines(readline):
real_start = start + block_start - 1
real_start = self._first_non_blank(real_start)
if end_line is not None and real_start >= end_line:
break
real_end = end + block_start - 1
if real_start >= start_line:
yield (real_start, real_end)
except tokenize.TokenError:
pass
def _block_logical_line(self, block_start, line_number):
readline = LinesToReadline(self.lines, block_start)
shifted = line_number - block_start + 1
region = self._calculate_logical(readline, shifted)
start = self._first_non_blank(region[0] + block_start - 1)
if region[1] is None:
end = self.lines.length()
else:
end = region[1] + block_start - 1
return start, end
def _calculate_logical(self, readline, line_number):
last_end = 1
try:
for start, end in self._logical_lines(readline):
if line_number <= end:
return (start, end)
last_end = end + 1
except tokenize.TokenError as e:
current = e.args[1][0]
return (last_end, max(last_end, current - 1))
return (last_end, None)
def _logical_lines(self, readline):
last_end = 1
for current_token in tokenize.generate_tokens(readline):
current = current_token[2][0]
if current_token[0] == token.NEWLINE:
yield (last_end, current)
last_end = current + 1
def _first_non_blank(self, line_number):
current = line_number
while current < self.lines.length():
line = self.lines.get_line(current).strip()
if line and not line.startswith("#"):
return current
current += 1
return current
def tokenizer_generator(lines):
return LogicalLineFinder(lines).generate_regions()
class CachingLogicalLineFinder(object):
def __init__(self, lines, generate=custom_generator):
self.lines = lines
self._generate = generate
_starts = None
@property
def starts(self):
if self._starts is None:
self._init_logicals()
return self._starts
_ends = None
@property
def ends(self):
if self._ends is None:
self._init_logicals()
return self._ends
def _init_logicals(self):
"""Should initialize _starts and _ends attributes"""
size = self.lines.length() + 1
self._starts = [None] * size
self._ends = [None] * size
for start, end in self._generate(self.lines):
self._starts[start] = True
self._ends[end] = True
def logical_line_in(self, line_number):
start = line_number
while start > 0 and not self.starts[start]:
start -= 1
if start == 0:
try:
start = self.starts.index(True, line_number)
except ValueError:
return (line_number, line_number)
return (start, self.ends.index(True, start))
def generate_starts(self, start_line=1, end_line=None):
if end_line is None:
end_line = self.lines.length()
for index in range(start_line, end_line):
if self.starts[index]:
yield index
def get_block_start(lines, lineno, maximum_indents=80):
"""Approximate block start"""
pattern = get_block_start_patterns()
for i in range(lineno, 0, -1):
match = pattern.search(lines.get_line(i))
if (
match is not None
and count_line_indents(lines.get_line(i)) <= maximum_indents
):
striped = match.string.lstrip()
# Maybe we're in a list comprehension or generator expression
if i > 1 and striped.startswith("if") or striped.startswith("for"):
bracs = 0
for j in range(i, min(i + 5, lines.length() + 1)):
for c in lines.get_line(j):
if c == "#":
break
if c in "[(":
bracs += 1
if c in ")]":
bracs -= 1
if bracs < 0:
break
if bracs < 0:
break
if bracs < 0:
continue
return i
return 1
_block_start_pattern = None
def get_block_start_patterns():
global _block_start_pattern
if not _block_start_pattern:
pattern = (
"^\\s*(((def|class|if|elif|except|for|while|with)\\s)|"
"((try|else|finally|except)\\s*:))"
)
_block_start_pattern = re.compile(pattern, re.M)
return _block_start_pattern
def count_line_indents(line):
indents = 0
for char in line:
if char == " ":
indents += 1
elif char == "\t":
indents += 8
else:
return indents
return 0
def get_string_pattern_with_prefix(prefix, prefix_group_name=None):
longstr = r'"""(\\.|"(?!"")|\\\n|[^"\\])*"""'
shortstr = r'"(\\.|\\\n|[^"\\\n])*"'
if prefix_group_name is not None:
pattern = "(?P<%s>%%s)(%%s)" % prefix_group_name
else:
pattern = "%s(%s)"
return pattern % (
prefix,
"|".join(
[
longstr,
longstr.replace('"', "'"),
shortstr,
shortstr.replace('"', "'"),
]
),
)
def get_string_pattern():
prefix = r"(?<![fF])(\b[uUbB]?[rR]?)?"
return get_string_pattern_with_prefix(prefix)
def get_formatted_string_pattern():
prefix = r"(\b[rR]?[fF]|[fF][rR]?)"
return get_string_pattern_with_prefix(prefix)
def get_any_string_pattern():
prefix = r"[bBfFrRuU]{,4}"
return get_string_pattern_with_prefix(
prefix,
prefix_group_name="prefix",
)
def get_comment_pattern():
return r"#[^\n]*"

View file

@ -0,0 +1,125 @@
# The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs["ignored_resources"] = [
"*.pyc",
"*~",
".ropeproject",
".hg",
".svn",
"_svn",
".git",
".tox",
".venv",
"venv",
]
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs["save_objectdb"] = True
prefs["compress_objectdb"] = False
# If `True`, rope analyzes each module when it is being saved.
prefs["automatic_soa"] = True
# The depth of calls to follow in static object analysis
prefs["soa_followed_calls"] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs["perform_doa"] = True
# Rope can check the validity of its object DB when running.
prefs["validate_objectdb"] = True
# How many undos to hold?
prefs["max_history_items"] = 32
# Shows whether to save history across sessions.
prefs["save_history"] = True
prefs["compress_history"] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs["indent_size"] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs["extension_modules"] = []
# Add all standard c-extensions to extension_modules list.
prefs["import_dynload_stdmods"] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs["ignore_syntax_errors"] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs["ignore_bad_imports"] = False
# If `True`, rope will insert new module imports as
# `from <package> import <module>` by default.
prefs["prefer_module_from_imports"] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs["split_imports"] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs["pull_imports_to_top"] = True
# If `True`, rope will sort imports alphabetically by module name instead
# of alphabetically by import statement, with from imports after normal
# imports.
prefs["sort_imports_alphabetically"] = False
# Location of implementation of
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
# case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable
# the search type-hinting in a class hierarchy, etc.
prefs[
"type_hinting_factory"
] = "rope.base.oi.type_hinting.factory.default_type_hinting_factory"
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!

View file

@ -0,0 +1,360 @@
from operator import itemgetter
import rope.base.builtins
import rope.base.pynames
import rope.base.pyobjects
from rope.base import ast, astutils, exceptions, pyobjects, arguments, worder
from rope.base.utils import pycompat
BadIdentifierError = exceptions.BadIdentifierError
def eval_location(pymodule, offset):
"""Find the pyname at the offset"""
return eval_location2(pymodule, offset)[1]
def eval_location2(pymodule, offset):
"""Find the primary and pyname at offset"""
pyname_finder = ScopeNameFinder(pymodule)
return pyname_finder.get_primary_and_pyname_at(offset)
def eval_node(scope, node):
"""Evaluate a `ast.AST` node and return a PyName
Return `None` if the expression cannot be evaluated.
"""
return eval_node2(scope, node)[1]
def eval_node2(scope, node):
evaluator = StatementEvaluator(scope)
ast.walk(node, evaluator)
return evaluator.old_result, evaluator.result
def eval_str(holding_scope, name):
return eval_str2(holding_scope, name)[1]
def eval_str2(holding_scope, name):
try:
# parenthesizing for handling cases like 'a_var.\nattr'
node = ast.parse("(%s)" % name)
except SyntaxError:
raise BadIdentifierError("Not a resolvable python identifier selected.")
return eval_node2(holding_scope, node)
class ScopeNameFinder(object):
def __init__(self, pymodule):
self.module_scope = pymodule.get_scope()
self.lines = pymodule.lines
self.worder = worder.Worder(pymodule.source_code, True)
def _is_defined_in_class_body(self, holding_scope, offset, lineno):
if (
lineno == holding_scope.get_start()
and holding_scope.parent is not None
and holding_scope.parent.get_kind() == "Class"
and self.worder.is_a_class_or_function_name_in_header(offset)
):
return True
if (
lineno != holding_scope.get_start()
and holding_scope.get_kind() == "Class"
and self.worder.is_name_assigned_in_class_body(offset)
):
return True
return False
def _is_function_name_in_function_header(self, scope, offset, lineno):
if (
scope.get_start() <= lineno <= scope.get_body_start()
and scope.get_kind() == "Function"
and self.worder.is_a_class_or_function_name_in_header(offset)
):
return True
return False
def get_pyname_at(self, offset):
return self.get_primary_and_pyname_at(offset)[1]
def get_primary_and_pyname_at(self, offset):
lineno = self.lines.get_line_number(offset)
holding_scope = self.module_scope.get_inner_scope_for_offset(offset)
# function keyword parameter
if self.worder.is_function_keyword_parameter(offset):
keyword_name = self.worder.get_word_at(offset)
pyobject = self.get_enclosing_function(offset)
if isinstance(pyobject, pyobjects.PyFunction):
return (None, pyobject.get_parameters().get(keyword_name, None))
# class body
if self._is_defined_in_class_body(holding_scope, offset, lineno):
class_scope = holding_scope
if lineno == holding_scope.get_start():
class_scope = holding_scope.parent
name = self.worder.get_primary_at(offset).strip()
try:
return (None, class_scope.pyobject[name])
except rope.base.exceptions.AttributeNotFoundError:
return (None, None)
# function header
if self._is_function_name_in_function_header(holding_scope, offset, lineno):
name = self.worder.get_primary_at(offset).strip()
return (None, holding_scope.parent[name])
# module in a from statement or an imported name that is aliased
if self.worder.is_from_statement_module(
offset
) or self.worder.is_import_statement_aliased_module(offset):
module = self.worder.get_primary_at(offset)
module_pyname = self._find_module(module)
return (None, module_pyname)
if self.worder.is_from_aliased(offset):
name = self.worder.get_from_aliased(offset)
else:
name = self.worder.get_primary_at(offset)
return eval_str2(holding_scope, name)
def get_enclosing_function(self, offset):
function_parens = self.worder.find_parens_start_from_inside(offset)
try:
function_pyname = self.get_pyname_at(function_parens - 1)
except BadIdentifierError:
function_pyname = None
if function_pyname is not None:
pyobject = function_pyname.get_object()
if isinstance(pyobject, pyobjects.AbstractFunction):
return pyobject
elif (
isinstance(pyobject, pyobjects.AbstractClass) and "__init__" in pyobject
):
return pyobject["__init__"].get_object()
elif "__call__" in pyobject:
return pyobject["__call__"].get_object()
return None
def _find_module(self, module_name):
dots = 0
while module_name[dots] == ".":
dots += 1
return rope.base.pynames.ImportedModule(
self.module_scope.pyobject, module_name[dots:], dots
)
class StatementEvaluator(object):
def __init__(self, scope):
self.scope = scope
self.result = None
self.old_result = None
def _Name(self, node):
self.result = self.scope.lookup(node.id)
def _Attribute(self, node):
pyname = eval_node(self.scope, node.value)
if pyname is None:
pyname = rope.base.pynames.UnboundName()
self.old_result = pyname
if pyname.get_object() != rope.base.pyobjects.get_unknown():
try:
self.result = pyname.get_object()[node.attr]
except exceptions.AttributeNotFoundError:
self.result = None
def _Call(self, node):
primary, pyobject = self._get_primary_and_object_for_node(node.func)
if pyobject is None:
return
def _get_returned(pyobject):
args = arguments.create_arguments(primary, pyobject, node, self.scope)
return pyobject.get_returned_object(args)
if isinstance(pyobject, rope.base.pyobjects.AbstractClass):
result = None
if "__new__" in pyobject:
new_function = pyobject["__new__"].get_object()
result = _get_returned(new_function)
if result is None or result == rope.base.pyobjects.get_unknown():
result = rope.base.pyobjects.PyObject(pyobject)
self.result = rope.base.pynames.UnboundName(pyobject=result)
return
pyfunction = None
if isinstance(pyobject, rope.base.pyobjects.AbstractFunction):
pyfunction = pyobject
elif "__call__" in pyobject:
pyfunction = pyobject["__call__"].get_object()
if pyfunction is not None:
self.result = rope.base.pynames.UnboundName(
pyobject=_get_returned(pyfunction)
)
def _Str(self, node):
self.result = rope.base.pynames.UnboundName(
pyobject=rope.base.builtins.get_str()
)
def _Num(self, node):
type_name = type(node.n).__name__
self.result = self._get_builtin_name(type_name)
def _Constant(self, node):
type_name = type(node.n).__name__
try:
self.result = self._get_builtin_name(type_name)
except exceptions.AttributeNotFoundError:
# XXX: Right way to fix this is to add missing NoneType to builtins?
pass
def _get_builtin_name(self, type_name):
pytype = rope.base.builtins.builtins[type_name].get_object()
return rope.base.pynames.UnboundName(rope.base.pyobjects.PyObject(pytype))
def _BinOp(self, node):
self.result = rope.base.pynames.UnboundName(
self._get_object_for_node(node.left)
)
def _BoolOp(self, node):
pyobject = self._get_object_for_node(node.values[0])
if pyobject is None:
pyobject = self._get_object_for_node(node.values[1])
self.result = rope.base.pynames.UnboundName(pyobject)
def _Repr(self, node):
self.result = self._get_builtin_name("str")
def _UnaryOp(self, node):
self.result = rope.base.pynames.UnboundName(
self._get_object_for_node(node.operand)
)
def _Compare(self, node):
self.result = self._get_builtin_name("bool")
def _Dict(self, node):
keys = None
values = None
if node.keys and node.keys[0]:
keys, values = next(
iter(filter(itemgetter(0), zip(node.keys, node.values))), (None, None)
)
if keys:
keys = self._get_object_for_node(keys)
if values:
values = self._get_object_for_node(values)
self.result = rope.base.pynames.UnboundName(
pyobject=rope.base.builtins.get_dict(keys, values)
)
def _List(self, node):
holding = None
if node.elts:
holding = self._get_object_for_node(node.elts[0])
self.result = rope.base.pynames.UnboundName(
pyobject=rope.base.builtins.get_list(holding)
)
def _ListComp(self, node):
pyobject = self._what_does_comprehension_hold(node)
self.result = rope.base.pynames.UnboundName(
pyobject=rope.base.builtins.get_list(pyobject)
)
def _GeneratorExp(self, node):
pyobject = self._what_does_comprehension_hold(node)
self.result = rope.base.pynames.UnboundName(
pyobject=rope.base.builtins.get_iterator(pyobject)
)
def _what_does_comprehension_hold(self, node):
scope = self._make_comprehension_scope(node)
pyname = eval_node(scope, node.elt)
return pyname.get_object() if pyname is not None else None
def _make_comprehension_scope(self, node):
scope = self.scope
module = scope.pyobject.get_module()
names = {}
for comp in node.generators:
new_names = _get_evaluated_names(
comp.target, comp.iter, module, ".__iter__().next()", node.lineno
)
names.update(new_names)
return rope.base.pyscopes.TemporaryScope(scope.pycore, scope, names)
def _Tuple(self, node):
objects = []
if len(node.elts) < 4:
for stmt in node.elts:
pyobject = self._get_object_for_node(stmt)
objects.append(pyobject)
else:
objects.append(self._get_object_for_node(node.elts[0]))
self.result = rope.base.pynames.UnboundName(
pyobject=rope.base.builtins.get_tuple(*objects)
)
def _get_object_for_node(self, stmt):
pyname = eval_node(self.scope, stmt)
pyobject = None
if pyname is not None:
pyobject = pyname.get_object()
return pyobject
def _get_primary_and_object_for_node(self, stmt):
primary, pyname = eval_node2(self.scope, stmt)
pyobject = None
if pyname is not None:
pyobject = pyname.get_object()
return primary, pyobject
def _Subscript(self, node):
if isinstance(node.slice, ast.Index):
self._call_function(node.value, "__getitem__", [node.slice.value])
elif isinstance(node.slice, ast.Slice):
self._call_function(node.value, "__getitem__", [node.slice])
elif isinstance(node.slice, ast.expr):
self._call_function(node.value, "__getitem__", [node.value])
def _Slice(self, node):
self.result = self._get_builtin_name("slice")
def _call_function(self, node, function_name, other_args=None):
pyname = eval_node(self.scope, node)
if pyname is not None:
pyobject = pyname.get_object()
else:
return
if function_name in pyobject:
called = pyobject[function_name].get_object()
if not called or not isinstance(called, pyobjects.AbstractFunction):
return
args = [node]
if other_args:
args += other_args
arguments_ = arguments.Arguments(args, self.scope)
self.result = rope.base.pynames.UnboundName(
pyobject=called.get_returned_object(arguments_)
)
def _Lambda(self, node):
self.result = rope.base.pynames.UnboundName(
pyobject=rope.base.builtins.Lambda(node, self.scope)
)
def _get_evaluated_names(targets, assigned, module, evaluation, lineno):
result = {}
for name, levels in astutils.get_name_levels(targets):
assignment = rope.base.pynames.AssignmentValue(assigned, levels, evaluation)
# XXX: this module should not access `rope.base.pynamesdef`!
pyname = rope.base.pynamesdef.AssignedName(lineno, module)
pyname.assignments.append(assignment)
result[name] = pyname
return result

View file

@ -0,0 +1,62 @@
class RopeError(Exception):
"""Base exception for rope"""
class ResourceNotFoundError(RopeError):
"""Resource not found exception"""
class RefactoringError(RopeError):
"""Errors for performing a refactoring"""
class InterruptedTaskError(RopeError):
"""The task has been interrupted"""
class HistoryError(RopeError):
"""Errors for history undo/redo operations"""
class ModuleNotFoundError(RopeError):
"""Module not found exception"""
class AttributeNotFoundError(RopeError):
"""Attribute not found exception"""
class NameNotFoundError(RopeError):
"""Name not found exception"""
class BadIdentifierError(RopeError):
"""The name cannot be resolved"""
class ModuleSyntaxError(RopeError):
"""Module has syntax errors
The `filename` and `lineno` fields indicate where the error has
occurred.
"""
def __init__(self, filename, lineno, message):
self.filename = filename
self.lineno = lineno
self.message_ = message
super(ModuleSyntaxError, self).__init__(
"Syntax error in file <%s> line <%s>: %s" % (filename, lineno, message)
)
class ModuleDecodeError(RopeError):
"""Cannot decode module"""
def __init__(self, filename, message):
self.filename = filename
self.message_ = message
super(ModuleDecodeError, self).__init__(
"Cannot decode file <%s>: %s" % (filename, message)
)

View file

@ -0,0 +1,302 @@
"""Project file system commands.
This modules implements file system operations used by rope. Different
version control systems can be supported by implementing the interface
provided by `FileSystemCommands` class. See `SubversionCommands` and
`MercurialCommands` for example.
"""
import re
import os
import shutil
import subprocess
import rope.base.utils.pycompat as pycompat
try:
unicode
except NameError:
unicode = str
def create_fscommands(root):
dirlist = os.listdir(root)
commands = {
".hg": MercurialCommands,
".svn": SubversionCommands,
".git": GITCommands,
"_svn": SubversionCommands,
"_darcs": DarcsCommands,
}
for key in commands:
if key in dirlist:
try:
return commands[key](root)
except (ImportError, OSError):
pass
return FileSystemCommands()
class FileSystemCommands(object):
def create_file(self, path):
open(path, "w").close()
def create_folder(self, path):
os.mkdir(path)
def move(self, path, new_location):
shutil.move(path, new_location)
def remove(self, path):
if os.path.isfile(path):
os.remove(path)
else:
shutil.rmtree(path)
def write(self, path, data):
file_ = open(path, "wb")
try:
file_.write(data)
finally:
file_.close()
def read(self, path):
with open(path, "rb") as handle:
return handle.read()
class SubversionCommands(object):
def __init__(self, *args):
self.normal_actions = FileSystemCommands()
import pysvn
self.client = pysvn.Client()
def create_file(self, path):
self.normal_actions.create_file(path)
self.client.add(path, force=True)
def create_folder(self, path):
self.normal_actions.create_folder(path)
self.client.add(path, force=True)
def move(self, path, new_location):
self.client.move(path, new_location, force=True)
def remove(self, path):
self.client.remove(path, force=True)
def write(self, path, data):
self.normal_actions.write(path, data)
def read(self, path):
return self.normal_actions.read(path)
class MercurialCommands(object):
def __init__(self, root):
self.hg = self._import_mercurial()
self.normal_actions = FileSystemCommands()
try:
self.ui = self.hg.ui.ui(
verbose=False,
debug=False,
quiet=True,
interactive=False,
traceback=False,
report_untrusted=False,
)
except:
self.ui = self.hg.ui.ui()
self.ui.setconfig("ui", "interactive", "no")
self.ui.setconfig("ui", "debug", "no")
self.ui.setconfig("ui", "traceback", "no")
self.ui.setconfig("ui", "verbose", "no")
self.ui.setconfig("ui", "report_untrusted", "no")
self.ui.setconfig("ui", "quiet", "yes")
self.repo = self.hg.hg.repository(self.ui, root)
def _import_mercurial(self):
import mercurial.commands
import mercurial.hg
import mercurial.ui
return mercurial
def create_file(self, path):
self.normal_actions.create_file(path)
self.hg.commands.add(self.ui, self.repo, path)
def create_folder(self, path):
self.normal_actions.create_folder(path)
def move(self, path, new_location):
self.hg.commands.rename(self.ui, self.repo, path, new_location, after=False)
def remove(self, path):
self.hg.commands.remove(self.ui, self.repo, path)
def write(self, path, data):
self.normal_actions.write(path, data)
def read(self, path):
return self.normal_actions.read(path)
class GITCommands(object):
def __init__(self, root):
self.root = root
self._do(["version"])
self.normal_actions = FileSystemCommands()
def create_file(self, path):
self.normal_actions.create_file(path)
self._do(["add", self._in_dir(path)])
def create_folder(self, path):
self.normal_actions.create_folder(path)
def move(self, path, new_location):
self._do(["mv", self._in_dir(path), self._in_dir(new_location)])
def remove(self, path):
self._do(["rm", self._in_dir(path)])
def write(self, path, data):
# XXX: should we use ``git add``?
self.normal_actions.write(path, data)
def read(self, path):
return self.normal_actions.read(path)
def _do(self, args):
_execute(["git"] + args, cwd=self.root)
def _in_dir(self, path):
if path.startswith(self.root):
return path[len(self.root) + 1 :]
return self.root
class DarcsCommands(object):
def __init__(self, root):
self.root = root
self.normal_actions = FileSystemCommands()
def create_file(self, path):
self.normal_actions.create_file(path)
self._do(["add", path])
def create_folder(self, path):
self.normal_actions.create_folder(path)
self._do(["add", path])
def move(self, path, new_location):
self._do(["mv", path, new_location])
def remove(self, path):
self.normal_actions.remove(path)
def read(self, path):
return self.normal_actions.read(path)
def write(self, path, data):
self.normal_actions.write(path, data)
def _do(self, args):
_execute(["darcs"] + args, cwd=self.root)
def _execute(args, cwd=None):
process = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE)
process.wait()
return process.returncode
def unicode_to_file_data(contents, encoding=None, newlines=None):
if not isinstance(contents, unicode):
return contents
if newlines and newlines != "\n":
contents = contents.replace("\n", newlines)
if encoding is None:
encoding = read_str_coding(contents)
if encoding is not None:
return contents.encode(encoding)
try:
return contents.encode()
except UnicodeEncodeError:
return contents.encode("utf-8")
def file_data_to_unicode(data, encoding=None):
result = _decode_data(data, encoding)
newline = "\n"
if "\r\n" in result:
result = result.replace("\r\n", "\n")
newline = "\r\n"
if "\r" in result:
result = result.replace("\r", "\n")
newline = "\r"
return result, newline
def _decode_data(data, encoding):
if isinstance(data, unicode):
return data
if encoding is None:
encoding = read_str_coding(data)
if encoding is None:
# there is no encoding tip, we need to guess.
# PEP263 says that "encoding not explicitly defined" means it is ascii,
# but we will use utf8 instead since utf8 fully covers ascii and btw is
# the only non-latin sane encoding.
encoding = "utf-8"
try:
return data.decode(encoding)
except (UnicodeError, LookupError):
# fallback to latin1: it should never fail
return data.decode("latin1")
def read_str_coding(source):
# as defined by PEP-263 (https://www.python.org/dev/peps/pep-0263/)
CODING_LINE_PATTERN = b"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)"
if type(source) == bytes:
newline = b"\n"
CODING_LINE_PATTERN = re.compile(CODING_LINE_PATTERN)
else:
newline = "\n"
CODING_LINE_PATTERN = re.compile(CODING_LINE_PATTERN.decode("ascii"))
for line in source.split(newline, 2)[:2]:
if re.match(CODING_LINE_PATTERN, line):
return _find_coding(line)
else:
return
def _find_coding(text):
if isinstance(text, pycompat.str):
text = text.encode("utf-8")
coding = b"coding"
to_chr = chr if pycompat.PY3 else lambda x: x
try:
start = text.index(coding) + len(coding)
if text[start] not in b"=:":
return
start += 1
while start < len(text) and to_chr(text[start]).isspace():
start += 1
end = start
while end < len(text):
c = text[end]
if not to_chr(c).isalnum() and c not in b"-_":
break
end += 1
result = text[start:end]
if isinstance(result, bytes):
result = result.decode("utf-8")
return result
except ValueError:
pass

View file

@ -0,0 +1,230 @@
from rope.base import exceptions, change, taskhandle
class History(object):
"""A class that holds project history"""
def __init__(self, project, maxundos=None):
self.project = project
self._undo_list = []
self._redo_list = []
self._maxundos = maxundos
self._load_history()
self.project.data_files.add_write_hook(self.write)
self.current_change = None
def _load_history(self):
if self.save:
result = self.project.data_files.read_data(
"history", compress=self.compress, import_=True
)
if result is not None:
to_change = change.DataToChange(self.project)
for data in result[0]:
self._undo_list.append(to_change(data))
for data in result[1]:
self._redo_list.append(to_change(data))
def do(self, changes, task_handle=taskhandle.NullTaskHandle()):
"""Perform the change and add it to the `self.undo_list`
Note that uninteresting changes (changes to ignored files)
will not be appended to `self.undo_list`.
"""
try:
self.current_change = changes
changes.do(change.create_job_set(task_handle, changes))
finally:
self.current_change = None
if self._is_change_interesting(changes):
self.undo_list.append(changes)
self._remove_extra_items()
del self.redo_list[:]
def _remove_extra_items(self):
if len(self.undo_list) > self.max_undos:
del self.undo_list[0 : len(self.undo_list) - self.max_undos]
def _is_change_interesting(self, changes):
for resource in changes.get_changed_resources():
if not self.project.is_ignored(resource):
return True
return False
def undo(self, change=None, drop=False, task_handle=taskhandle.NullTaskHandle()):
"""Redo done changes from the history
When `change` is `None`, the last done change will be undone.
If change is not `None` it should be an item from
`self.undo_list`; this change and all changes that depend on
it will be undone. In both cases the list of undone changes
will be returned.
If `drop` is `True`, the undone change will not be appended to
the redo list.
"""
if not self._undo_list:
raise exceptions.HistoryError("Undo list is empty")
if change is None:
change = self.undo_list[-1]
dependencies = self._find_dependencies(self.undo_list, change)
self._move_front(self.undo_list, dependencies)
self._perform_undos(len(dependencies), task_handle)
result = self.redo_list[-len(dependencies) :]
if drop:
del self.redo_list[-len(dependencies) :]
return result
def redo(self, change=None, task_handle=taskhandle.NullTaskHandle()):
"""Redo undone changes from the history
When `change` is `None`, the last undone change will be
redone. If change is not `None` it should be an item from
`self.redo_list`; this change and all changes that depend on
it will be redone. In both cases the list of redone changes
will be returned.
"""
if not self.redo_list:
raise exceptions.HistoryError("Redo list is empty")
if change is None:
change = self.redo_list[-1]
dependencies = self._find_dependencies(self.redo_list, change)
self._move_front(self.redo_list, dependencies)
self._perform_redos(len(dependencies), task_handle)
return self.undo_list[-len(dependencies) :]
def _move_front(self, change_list, changes):
for change in changes:
change_list.remove(change)
change_list.append(change)
def _find_dependencies(self, change_list, change):
index = change_list.index(change)
return _FindChangeDependencies(change_list[index:])()
def _perform_undos(self, count, task_handle):
for i in range(count):
self.current_change = self.undo_list[-1]
try:
job_set = change.create_job_set(task_handle, self.current_change)
self.current_change.undo(job_set)
finally:
self.current_change = None
self.redo_list.append(self.undo_list.pop())
def _perform_redos(self, count, task_handle):
for i in range(count):
self.current_change = self.redo_list[-1]
try:
job_set = change.create_job_set(task_handle, self.current_change)
self.current_change.do(job_set)
finally:
self.current_change = None
self.undo_list.append(self.redo_list.pop())
def contents_before_current_change(self, file):
if self.current_change is None:
return None
result = self._search_for_change_contents([self.current_change], file)
if result is not None:
return result
if file.exists() and not file.is_folder():
return file.read()
else:
return None
def _search_for_change_contents(self, change_list, file):
for change_ in reversed(change_list):
if isinstance(change_, change.ChangeSet):
result = self._search_for_change_contents(change_.changes, file)
if result is not None:
return result
if isinstance(change_, change.ChangeContents) and change_.resource == file:
return change_.old_contents
def write(self):
if self.save:
data = []
to_data = change.ChangeToData()
self._remove_extra_items()
data.append([to_data(change_) for change_ in self.undo_list])
data.append([to_data(change_) for change_ in self.redo_list])
self.project.data_files.write_data("history", data, compress=self.compress)
def get_file_undo_list(self, resource):
result = []
for change in self.undo_list:
if resource in change.get_changed_resources():
result.append(change)
return result
def __str__(self):
return "History holds %s changes in memory" % (
len(self.undo_list) + len(self.redo_list)
)
undo_list = property(lambda self: self._undo_list)
redo_list = property(lambda self: self._redo_list)
@property
def tobe_undone(self):
"""The last done change if available, `None` otherwise"""
if self.undo_list:
return self.undo_list[-1]
@property
def tobe_redone(self):
"""The last undone change if available, `None` otherwise"""
if self.redo_list:
return self.redo_list[-1]
@property
def max_undos(self):
if self._maxundos is None:
return self.project.prefs.get("max_history_items", 100)
else:
return self._maxundos
@property
def save(self):
return self.project.prefs.get("save_history", False)
@property
def compress(self):
return self.project.prefs.get("compress_history", False)
def clear(self):
"""Forget all undo and redo information"""
del self.undo_list[:]
del self.redo_list[:]
class _FindChangeDependencies(object):
def __init__(self, change_list):
self.change = change_list[0]
self.change_list = change_list
self.changed_resources = set(self.change.get_changed_resources())
def __call__(self):
result = [self.change]
for change in self.change_list[1:]:
if self._depends_on(change, result):
result.append(change)
self.changed_resources.update(change.get_changed_resources())
return result
def _depends_on(self, changes, result):
for resource in changes.get_changed_resources():
if resource is None:
continue
if resource in self.changed_resources:
return True
for changed in self.changed_resources:
if resource.is_folder() and resource.contains(changed):
return True
if changed.is_folder() and changed.contains(resource):
return True
return False

View file

@ -0,0 +1,124 @@
"""A few useful functions for using rope as a library"""
import os.path
import rope.base.project
import rope.base.pycore
from rope.base import pyobjectsdef
from rope.base import utils
from rope.base import taskhandle
def path_to_resource(project, path, type=None):
"""Get the resource at path
You only need to specify `type` if `path` does not exist. It can
be either 'file' or 'folder'. If the type is `None` it is assumed
that the resource already exists.
Note that this function uses `Project.get_resource()`,
`Project.get_file()`, and `Project.get_folder()` methods.
"""
project_path = path_relative_to_project_root(project, path)
if project_path is None:
project_path = rope.base.project._realpath(path)
project = rope.base.project.get_no_project()
if type is None:
return project.get_resource(project_path)
if type == "file":
return project.get_file(project_path)
if type == "folder":
return project.get_folder(project_path)
return None
def path_relative_to_project_root(project, path):
return relative(project.address, path)
@utils.deprecated()
def relative(root, path):
root = rope.base.project._realpath(root).replace(os.path.sep, "/")
path = rope.base.project._realpath(path).replace(os.path.sep, "/")
if path == root:
return ""
if path.startswith(root + "/"):
return path[len(root) + 1 :]
def report_change(project, path, old_content):
"""Report that the contents of file at `path` was changed
The new contents of file is retrieved by reading the file.
"""
resource = path_to_resource(project, path)
if resource is None:
return
for observer in list(project.observers):
observer.resource_changed(resource)
if project.pycore.automatic_soa:
rope.base.pycore.perform_soa_on_changed_scopes(project, resource, old_content)
def analyze_module(project, resource):
"""Perform static object analysis on a python file in the project
Note that this might be really time consuming.
"""
project.pycore.analyze_module(resource)
def analyze_modules(project, task_handle=taskhandle.NullTaskHandle()):
"""Perform static object analysis on all python files in the project
Note that this might be really time consuming.
"""
resources = project.get_python_files()
job_set = task_handle.create_jobset("Analyzing Modules", len(resources))
for resource in resources:
job_set.started_job(resource.path)
analyze_module(project, resource)
job_set.finished_job()
def get_string_module(project, code, resource=None, force_errors=False):
"""Returns a `PyObject` object for the given code
If `force_errors` is `True`, `exceptions.ModuleSyntaxError` is
raised if module has syntax errors. This overrides
``ignore_syntax_errors`` project config.
"""
return pyobjectsdef.PyModule(
project.pycore, code, resource, force_errors=force_errors
)
def get_string_scope(project, code, resource=None):
"""Returns a `Scope` object for the given code"""
return get_string_module(project, code, resource).get_scope()
def is_python_file(project, resource):
return project.pycore.is_python_file(resource)
def modname(resource):
if resource.is_folder():
module_name = resource.name
source_folder = resource.parent
elif resource.name == "__init__.py":
module_name = resource.parent.name
source_folder = resource.parent.parent
else:
module_name = resource.name[:-3]
source_folder = resource.parent
while source_folder != source_folder.parent and source_folder.has_child(
"__init__.py"
):
module_name = source_folder.name + "." + module_name
source_folder = source_folder.parent
return module_name

View 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.
"""

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

View 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

View 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

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

View 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()

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

View 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]

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

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

@ -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)]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -0,0 +1,40 @@
class Prefs(object):
def __init__(self):
self.prefs = {}
self.callbacks = {}
def set(self, key, value):
"""Set the value of `key` preference to `value`."""
if key in self.callbacks:
self.callbacks[key](value)
else:
self.prefs[key] = value
def add(self, key, value):
"""Add an entry to a list preference
Add `value` to the list of entries for the `key` preference.
"""
if not key in self.prefs:
self.prefs[key] = []
self.prefs[key].append(value)
def get(self, key, default=None):
"""Get the value of the key preference"""
return self.prefs.get(key, default)
def add_callback(self, key, callback):
"""Add `key` preference with `callback` function
Whenever `key` is set the callback is called with the
given `value` as parameter.
"""
self.callbacks[key] = callback
def __setitem__(self, key, value):
self.set(key, value)
def __getitem__(self, key):
return self.get(key)

View file

@ -0,0 +1,501 @@
import os
import shutil
import sys
import warnings
import rope.base.fscommands
import rope.base.resourceobserver as resourceobserver
import rope.base.utils.pycompat as pycompat
from rope.base import exceptions, taskhandle, prefs, history, pycore, utils
from rope.base.exceptions import ModuleNotFoundError
from rope.base.resources import File, Folder, _ResourceMatcher
try:
import cPickle as pickle
except ImportError:
import pickle
class _Project(object):
def __init__(self, fscommands):
self.observers = []
self.fscommands = fscommands
self.prefs = prefs.Prefs()
self.data_files = _DataFiles(self)
self._custom_source_folders = []
def get_resource(self, resource_name):
"""Get a resource in a project.
`resource_name` is the path of a resource in a project. It is
the path of a resource relative to project root. Project root
folder address is an empty string. If the resource does not
exist a `exceptions.ResourceNotFound` exception would be
raised. Use `get_file()` and `get_folder()` when you need to
get nonexistent `Resource`.
"""
path = self._get_resource_path(resource_name)
if not os.path.exists(path):
raise exceptions.ResourceNotFoundError(
"Resource <%s> does not exist" % resource_name
)
elif os.path.isfile(path):
return File(self, resource_name)
elif os.path.isdir(path):
return Folder(self, resource_name)
else:
raise exceptions.ResourceNotFoundError("Unknown resource " + resource_name)
def get_module(self, name, folder=None):
"""Returns a `PyObject` if the module was found."""
# check if this is a builtin module
pymod = self.pycore.builtin_module(name)
if pymod is not None:
return pymod
module = self.find_module(name, folder)
if module is None:
raise ModuleNotFoundError("Module %s not found" % name)
return self.pycore.resource_to_pyobject(module)
def get_python_path_folders(self):
result = []
for src in self.prefs.get("python_path", []) + sys.path:
try:
src_folder = get_no_project().get_resource(src)
result.append(src_folder)
except exceptions.ResourceNotFoundError:
pass
return result
# INFO: It was decided not to cache source folders, since:
# - Does not take much time when the root folder contains
# packages, that is most of the time
# - We need a separate resource observer; `self.observer`
# does not get notified about module and folder creations
def get_source_folders(self):
"""Returns project source folders"""
if self.root is None:
return []
result = list(self._custom_source_folders)
result.extend(self.pycore._find_source_folders(self.root))
return result
def validate(self, folder):
"""Validate files and folders contained in this folder
It validates all of the files and folders contained in this
folder if some observers are interested in them.
"""
for observer in list(self.observers):
observer.validate(folder)
def add_observer(self, observer):
"""Register a `ResourceObserver`
See `FilteredResourceObserver`.
"""
self.observers.append(observer)
def remove_observer(self, observer):
"""Remove a registered `ResourceObserver`"""
if observer in self.observers:
self.observers.remove(observer)
def do(self, changes, task_handle=taskhandle.NullTaskHandle()):
"""Apply the changes in a `ChangeSet`
Most of the time you call this function for committing the
changes for a refactoring.
"""
self.history.do(changes, task_handle=task_handle)
def get_pymodule(self, resource, force_errors=False):
return self.pycore.resource_to_pyobject(resource, force_errors)
def get_pycore(self):
return self.pycore
def get_file(self, path):
"""Get the file with `path` (it may not exist)"""
return File(self, path)
def get_folder(self, path):
"""Get the folder with `path` (it may not exist)"""
return Folder(self, path)
def get_prefs(self):
return self.prefs
def get_relative_module(self, name, folder, level):
module = self.find_relative_module(name, folder, level)
if module is None:
raise ModuleNotFoundError("Module %s not found" % name)
return self.pycore.resource_to_pyobject(module)
def find_module(self, modname, folder=None):
"""Returns a resource corresponding to the given module
returns None if it can not be found
"""
for src in self.get_source_folders():
module = _find_module_in_folder(src, modname)
if module is not None:
return module
for src in self.get_python_path_folders():
module = _find_module_in_folder(src, modname)
if module is not None:
return module
if folder is not None:
module = _find_module_in_folder(folder, modname)
if module is not None:
return module
return None
def find_relative_module(self, modname, folder, level):
for i in range(level - 1):
folder = folder.parent
if modname == "":
return folder
else:
return _find_module_in_folder(folder, modname)
def is_ignored(self, resource):
return False
def _get_resource_path(self, name):
pass
@property
@utils.saveit
def history(self):
return history.History(self)
@property
@utils.saveit
def pycore(self):
return pycore.PyCore(self)
def close(self):
warnings.warn("Cannot close a NoProject", DeprecationWarning, stacklevel=2)
ropefolder = None
class Project(_Project):
"""A Project containing files and folders"""
def __init__(
self, projectroot, fscommands=None, ropefolder=".ropeproject", **prefs
):
"""A rope project
:parameters:
- `projectroot`: The address of the root folder of the project
- `fscommands`: Implements the file system operations used
by rope; have a look at `rope.base.fscommands`
- `ropefolder`: The name of the folder in which rope stores
project configurations and data. Pass `None` for not using
such a folder at all.
- `prefs`: Specify project preferences. These values
overwrite config file preferences.
"""
if projectroot != "/":
projectroot = _realpath(projectroot).rstrip("/\\")
self._address = projectroot
self._ropefolder_name = ropefolder
if not os.path.exists(self._address):
os.mkdir(self._address)
elif not os.path.isdir(self._address):
raise exceptions.RopeError("Project root exists and" " is not a directory")
if fscommands is None:
fscommands = rope.base.fscommands.create_fscommands(self._address)
super(Project, self).__init__(fscommands)
self.ignored = _ResourceMatcher()
self.file_list = _FileListCacher(self)
self.prefs.add_callback("ignored_resources", self.ignored.set_patterns)
if ropefolder is not None:
self.prefs["ignored_resources"] = [ropefolder]
self._init_prefs(prefs)
self._init_source_folders()
@utils.deprecated("Delete once deprecated functions are gone")
def _init_source_folders(self):
for path in self.prefs.get("source_folders", []):
folder = self.get_resource(path)
self._custom_source_folders.append(folder)
def get_files(self):
return self.file_list.get_files()
def get_python_files(self):
"""Returns all python files available in the project"""
return [
resource
for resource in self.get_files()
if self.pycore.is_python_file(resource)
]
def _get_resource_path(self, name):
return os.path.join(self._address, *name.split("/"))
def _init_ropefolder(self):
if self.ropefolder is not None:
if not self.ropefolder.exists():
self._create_recursively(self.ropefolder)
if not self.ropefolder.has_child("config.py"):
config = self.ropefolder.create_file("config.py")
config.write(self._default_config())
def _create_recursively(self, folder):
if folder.parent != self.root and not folder.parent.exists():
self._create_recursively(folder.parent)
folder.create()
def _init_prefs(self, prefs):
run_globals = {}
if self.ropefolder is not None:
config = self.get_file(self.ropefolder.path + "/config.py")
run_globals.update(
{
"__name__": "__main__",
"__builtins__": __builtins__,
"__file__": config.real_path,
}
)
if config.exists():
config = self.ropefolder.get_child("config.py")
pycompat.execfile(config.real_path, run_globals)
else:
exec(self._default_config(), run_globals)
if "set_prefs" in run_globals:
run_globals["set_prefs"](self.prefs)
for key, value in prefs.items():
self.prefs[key] = value
self._init_other_parts()
self._init_ropefolder()
if "project_opened" in run_globals:
run_globals["project_opened"](self)
def _default_config(self):
import rope.base.default_config
import inspect
return inspect.getsource(rope.base.default_config)
def _init_other_parts(self):
# Forcing the creation of `self.pycore` to register observers
self.pycore
def is_ignored(self, resource):
return self.ignored.does_match(resource)
def sync(self):
"""Closes project open resources"""
self.close()
def close(self):
"""Closes project open resources"""
self.data_files.write()
def set(self, key, value):
"""Set the `key` preference to `value`"""
self.prefs.set(key, value)
@property
def ropefolder(self):
if self._ropefolder_name is not None:
return self.get_folder(self._ropefolder_name)
def validate(self, folder=None):
if folder is None:
folder = self.root
super(Project, self).validate(folder)
root = property(lambda self: self.get_resource(""))
address = property(lambda self: self._address)
class NoProject(_Project):
"""A null object for holding out of project files.
This class is singleton use `get_no_project` global function
"""
def __init__(self):
fscommands = rope.base.fscommands.FileSystemCommands()
super(NoProject, self).__init__(fscommands)
def _get_resource_path(self, name):
real_name = name.replace("/", os.path.sep)
return _realpath(real_name)
def get_resource(self, name):
universal_name = _realpath(name).replace(os.path.sep, "/")
return super(NoProject, self).get_resource(universal_name)
def get_files(self):
return []
def get_python_files(self):
return []
_no_project = None
def get_no_project():
if NoProject._no_project is None:
NoProject._no_project = NoProject()
return NoProject._no_project
class _FileListCacher(object):
def __init__(self, project):
self.project = project
self.files = None
rawobserver = resourceobserver.ResourceObserver(
self._changed, self._invalid, self._invalid, self._invalid, self._invalid
)
self.project.add_observer(rawobserver)
def get_files(self):
if self.files is None:
self.files = set()
self._add_files(self.project.root)
return self.files
def _add_files(self, folder):
for child in folder.get_children():
if child.is_folder():
self._add_files(child)
elif not self.project.is_ignored(child):
self.files.add(child)
def _changed(self, resource):
if resource.is_folder():
self.files = None
def _invalid(self, resource, new_resource=None):
self.files = None
class _DataFiles(object):
def __init__(self, project):
self.project = project
self.hooks = []
def read_data(self, name, compress=False, import_=False):
if self.project.ropefolder is None:
return None
compress = compress and self._can_compress()
opener = self._get_opener(compress)
file = self._get_file(name, compress)
if not compress and import_:
self._import_old_files(name)
if file.exists():
input = opener(file.real_path, "rb")
try:
result = []
try:
while True:
result.append(pickle.load(input))
except EOFError:
pass
if len(result) == 1:
return result[0]
if len(result) > 1:
return result
finally:
input.close()
def write_data(self, name, data, compress=False):
if self.project.ropefolder is not None:
compress = compress and self._can_compress()
file = self._get_file(name, compress)
opener = self._get_opener(compress)
output = opener(file.real_path, "wb")
try:
pickle.dump(data, output, 2)
finally:
output.close()
def add_write_hook(self, hook):
self.hooks.append(hook)
def write(self):
for hook in self.hooks:
hook()
def _can_compress(self):
try:
import gzip # noqa
return True
except ImportError:
return False
def _import_old_files(self, name):
old = self._get_file(name + ".pickle", False)
new = self._get_file(name, False)
if old.exists() and not new.exists():
shutil.move(old.real_path, new.real_path)
def _get_opener(self, compress):
if compress:
try:
import gzip
return gzip.open
except ImportError:
pass
return open
def _get_file(self, name, compress):
path = self.project.ropefolder.path + "/" + name
if compress:
path += ".gz"
return self.project.get_file(path)
def _realpath(path):
"""Return the real path of `path`
Is equivalent to ``realpath(abspath(expanduser(path)))``.
Of the particular notice is the hack dealing with the unfortunate
situation of running native-Windows python (os.name == 'nt') inside
of Cygwin (abspath starts with '/'), which apparently normal
os.path.realpath completely messes up.
"""
# there is a bug in cygwin for os.path.abspath() for abs paths
if sys.platform == "cygwin":
if path[1:3] == ":\\":
return path
elif path[1:3] == ":/":
path = "/cygdrive/" + path[0] + path[2:]
return os.path.abspath(os.path.expanduser(path))
return os.path.realpath(os.path.abspath(os.path.expanduser(path)))
def _find_module_in_folder(folder, modname):
module = folder
packages = modname.split(".")
for pkg in packages[:-1]:
if module.is_folder() and module.has_child(pkg):
module = module.get_child(pkg)
else:
return None
if module.is_folder():
if (
module.has_child(packages[-1])
and module.get_child(packages[-1]).is_folder()
):
return module.get_child(packages[-1])
elif (
module.has_child(packages[-1] + ".py")
and not module.get_child(packages[-1] + ".py").is_folder()
):
return module.get_child(packages[-1] + ".py")

View file

@ -0,0 +1,352 @@
import bisect
import difflib
import sys
import warnings
import rope.base.libutils
import rope.base.resourceobserver
import rope.base.resources
import rope.base.oi.doa
import rope.base.oi.objectinfo
import rope.base.oi.soa
from rope.base import builtins
from rope.base import exceptions
from rope.base import stdmods
from rope.base import taskhandle
from rope.base import utils
from rope.base.exceptions import ModuleNotFoundError
from rope.base.pyobjectsdef import PyModule, PyPackage
class PyCore(object):
def __init__(self, project):
self.project = project
self._init_resource_observer()
self.cache_observers = []
self.module_cache = _ModuleCache(self)
self.extension_cache = _ExtensionCache(self)
self.object_info = rope.base.oi.objectinfo.ObjectInfoManager(project)
self._init_python_files()
self._init_automatic_soa()
def _init_python_files(self):
self.python_matcher = None
patterns = self.project.prefs.get("python_files", None)
if patterns is not None:
self.python_matcher = rope.base.resources._ResourceMatcher()
self.python_matcher.set_patterns(patterns)
def _init_resource_observer(self):
callback = self._invalidate_resource_cache
observer = rope.base.resourceobserver.ResourceObserver(
changed=callback, moved=callback, removed=callback
)
self.observer = rope.base.resourceobserver.FilteredResourceObserver(observer)
self.project.add_observer(self.observer)
def _init_automatic_soa(self):
if not self.automatic_soa:
return
callback = self._file_changed_for_soa
observer = rope.base.resourceobserver.ResourceObserver(
changed=callback, moved=callback, removed=callback
)
self.project.add_observer(observer)
@property
def automatic_soa(self):
auto_soa = self.project.prefs.get("automatic_soi", None)
return self.project.prefs.get("automatic_soa", auto_soa)
def _file_changed_for_soa(self, resource, new_resource=None):
old_contents = self.project.history.contents_before_current_change(resource)
if old_contents is not None:
perform_soa_on_changed_scopes(self.project, resource, old_contents)
def is_python_file(self, resource):
if resource.is_folder():
return False
if self.python_matcher is None:
return resource.name.endswith(".py")
return self.python_matcher.does_match(resource)
@utils.deprecated("Use `project.get_module` instead")
def get_module(self, name, folder=None):
"""Returns a `PyObject` if the module was found."""
return self.project.get_module(name, folder)
def _builtin_submodules(self, modname):
result = {}
for extension in self.extension_modules:
if extension.startswith(modname + "."):
name = extension[len(modname) + 1 :]
if "." not in name:
result[name] = self.builtin_module(extension)
return result
def builtin_module(self, name):
return self.extension_cache.get_pymodule(name)
@utils.deprecated("Use `project.get_relative_module` instead")
def get_relative_module(self, name, folder, level):
return self.project.get_relative_module(name, folder, level)
@utils.deprecated("Use `libutils.get_string_module` instead")
def get_string_module(self, code, resource=None, force_errors=False):
"""Returns a `PyObject` object for the given code
If `force_errors` is `True`, `exceptions.ModuleSyntaxError` is
raised if module has syntax errors. This overrides
``ignore_syntax_errors`` project config.
"""
return PyModule(self, code, resource, force_errors=force_errors)
@utils.deprecated("Use `libutils.get_string_scope` instead")
def get_string_scope(self, code, resource=None):
"""Returns a `Scope` object for the given code"""
return rope.base.libutils.get_string_scope(code, resource)
def _invalidate_resource_cache(self, resource, new_resource=None):
for observer in self.cache_observers:
observer(resource)
@utils.deprecated("Use `project.get_python_path_folders` instead")
def get_python_path_folders(self):
return self.project.get_python_path_folders()
@utils.deprecated("Use `project.find_module` instead")
def find_module(self, modname, folder=None):
"""Returns a resource corresponding to the given module
returns None if it can not be found
"""
return self.project.find_module(modname, folder)
@utils.deprecated("Use `project.find_relative_module` instead")
def find_relative_module(self, modname, folder, level):
return self.project.find_relative_module(modname, folder, level)
# INFO: It was decided not to cache source folders, since:
# - Does not take much time when the root folder contains
# packages, that is most of the time
# - We need a separate resource observer; `self.observer`
# does not get notified about module and folder creations
@utils.deprecated("Use `project.get_source_folders` instead")
def get_source_folders(self):
"""Returns project source folders"""
return self.project.get_source_folders()
def resource_to_pyobject(self, resource, force_errors=False):
return self.module_cache.get_pymodule(resource, force_errors)
@utils.deprecated("Use `project.get_python_files` instead")
def get_python_files(self):
"""Returns all python files available in the project"""
return self.project.get_python_files()
def _is_package(self, folder):
if (
folder.has_child("__init__.py")
and not folder.get_child("__init__.py").is_folder()
):
return True
else:
return False
def _find_source_folders(self, folder):
for resource in folder.get_folders():
if self._is_package(resource):
return [folder]
result = []
for resource in folder.get_files():
if resource.name.endswith(".py"):
result.append(folder)
break
for resource in folder.get_folders():
result.extend(self._find_source_folders(resource))
return result
def run_module(self, resource, args=None, stdin=None, stdout=None):
"""Run `resource` module
Returns a `rope.base.oi.doa.PythonFileRunner` object for
controlling the process.
"""
perform_doa = self.project.prefs.get("perform_doi", True)
perform_doa = self.project.prefs.get("perform_doa", perform_doa)
receiver = self.object_info.doa_data_received
if not perform_doa:
receiver = None
runner = rope.base.oi.doa.PythonFileRunner(
self, resource, args, stdin, stdout, receiver
)
runner.add_finishing_observer(self.module_cache.forget_all_data)
runner.run()
return runner
def analyze_module(
self,
resource,
should_analyze=lambda py: True,
search_subscopes=lambda py: True,
followed_calls=None,
):
"""Analyze `resource` module for static object inference
This function forces rope to analyze this module to collect
information about function calls. `should_analyze` is a
function that is called with a `PyDefinedObject` argument. If
it returns `True` the element is analyzed. If it is `None` or
returns `False` the element is not analyzed.
`search_subscopes` is like `should_analyze`; The difference is
that if it returns `False` the sub-scopes are all ignored.
That is it is assumed that `should_analyze` returns `False`
for all of its subscopes.
`followed_calls` override the value of ``soa_followed_calls``
project config.
"""
if followed_calls is None:
followed_calls = self.project.prefs.get("soa_followed_calls", 0)
pymodule = self.resource_to_pyobject(resource)
self.module_cache.forget_all_data()
rope.base.oi.soa.analyze_module(
self, pymodule, should_analyze, search_subscopes, followed_calls
)
def get_classes(self, task_handle=taskhandle.NullTaskHandle()):
warnings.warn(
"`PyCore.get_classes()` is deprecated", DeprecationWarning, stacklevel=2
)
return []
def __str__(self):
return str(self.module_cache) + str(self.object_info)
@utils.deprecated("Use `libutils.modname` instead")
def modname(self, resource):
return rope.base.libutils.modname(resource)
@property
@utils.cacheit
def extension_modules(self):
result = set(self.project.prefs.get("extension_modules", []))
if self.project.prefs.get("import_dynload_stdmods", False):
result.update(stdmods.dynload_modules())
return result
class _ModuleCache(object):
def __init__(self, pycore):
self.pycore = pycore
self.module_map = {}
self.pycore.cache_observers.append(self._invalidate_resource)
self.observer = self.pycore.observer
def _invalidate_resource(self, resource):
if resource in self.module_map:
self.forget_all_data()
self.observer.remove_resource(resource)
del self.module_map[resource]
def get_pymodule(self, resource, force_errors=False):
if resource in self.module_map:
return self.module_map[resource]
if resource.is_folder():
result = PyPackage(self.pycore, resource, force_errors=force_errors)
else:
result = PyModule(self.pycore, resource=resource, force_errors=force_errors)
if result.has_errors:
return result
self.module_map[resource] = result
self.observer.add_resource(resource)
return result
def forget_all_data(self):
for pymodule in self.module_map.values():
pymodule._forget_concluded_data()
def __str__(self):
return "PyCore caches %d PyModules\n" % len(self.module_map)
class _ExtensionCache(object):
def __init__(self, pycore):
self.pycore = pycore
self.extensions = {}
def get_pymodule(self, name):
if name == "__builtin__":
return builtins.builtins
allowed = self.pycore.extension_modules
if name not in self.extensions and name in allowed:
self.extensions[name] = builtins.BuiltinModule(name, self.pycore)
return self.extensions.get(name)
def perform_soa_on_changed_scopes(project, resource, old_contents):
pycore = project.pycore
if resource.exists() and pycore.is_python_file(resource):
try:
new_contents = resource.read()
# detecting changes in new_contents relative to old_contents
detector = _TextChangeDetector(new_contents, old_contents)
def search_subscopes(pydefined):
scope = pydefined.get_scope()
return detector.is_changed(scope.get_start(), scope.get_end())
def should_analyze(pydefined):
scope = pydefined.get_scope()
start = scope.get_start()
end = scope.get_end()
return detector.consume_changes(start, end)
pycore.analyze_module(resource, should_analyze, search_subscopes)
except exceptions.ModuleSyntaxError:
pass
class _TextChangeDetector(object):
def __init__(self, old, new):
self.old = old
self.new = new
self._set_diffs()
def _set_diffs(self):
differ = difflib.Differ()
self.lines = []
lineno = 0
for line in differ.compare(
self.old.splitlines(True), self.new.splitlines(True)
):
if line.startswith(" "):
lineno += 1
elif line.startswith("-"):
lineno += 1
self.lines.append(lineno)
def is_changed(self, start, end):
"""Tell whether any of start till end lines have changed
The end points are inclusive and indices start from 1.
"""
left, right = self._get_changed(start, end)
if left < right:
return True
return False
def consume_changes(self, start, end):
"""Clear the changed status of lines from start till end"""
left, right = self._get_changed(start, end)
if left < right:
del self.lines[left:right]
return left < right
def _get_changed(self, start, end):
left = bisect.bisect_left(self.lines, start)
right = bisect.bisect_right(self.lines, end)
return left, right

View file

@ -0,0 +1,200 @@
import rope.base.pyobjects
from rope.base import exceptions, utils
class PyName(object):
"""References to `PyObject` inside python programs"""
def get_object(self):
"""Return the `PyObject` object referenced by this `PyName`"""
def get_definition_location(self):
"""Return a (module, lineno) tuple"""
class DefinedName(PyName):
def __init__(self, pyobject):
self.pyobject = pyobject
def get_object(self):
return self.pyobject
def get_definition_location(self):
lineno = utils.guess_def_lineno(
self.pyobject.get_module(), self.pyobject.get_ast()
)
return (self.pyobject.get_module(), lineno)
class AssignedName(PyName):
"""Only a placeholder"""
class UnboundName(PyName):
def __init__(self, pyobject=None):
self.pyobject = pyobject
if self.pyobject is None:
self.pyobject = rope.base.pyobjects.get_unknown()
def get_object(self):
return self.pyobject
def get_definition_location(self):
return (None, None)
class AssignmentValue(object):
"""An assigned expression"""
def __init__(
self, ast_node, levels=None, evaluation="", assign_type=False, type_hint=None
):
"""The `level` is `None` for simple assignments and is
a list of numbers for tuple assignments for example in::
a, (b, c) = x
The levels for for `a` is ``[0]``, for `b` is ``[1, 0]`` and for
`c` is ``[1, 1]``.
"""
self.ast_node = ast_node
if levels is None:
self.levels = []
else:
self.levels = levels
self.evaluation = evaluation
self.assign_type = assign_type
self.type_hint = type_hint
def get_lineno(self):
return self.ast_node.lineno
class EvaluatedName(PyName):
"""A name whose object will be evaluated later"""
def __init__(self, callback, module=None, lineno=None):
self.module = module
self.lineno = lineno
self.callback = callback
self.pyobject = _Inferred(callback, _get_concluded_data(module))
def get_object(self):
return self.pyobject.get()
def get_definition_location(self):
return (self.module, self.lineno)
def invalidate(self):
"""Forget the `PyObject` this `PyName` holds"""
self.pyobject.set(None)
class ParameterName(PyName):
"""Only a placeholder"""
class ImportedModule(PyName):
def __init__(self, importing_module, module_name=None, level=0, resource=None):
self.importing_module = importing_module
self.module_name = module_name
self.level = level
self.resource = resource
self.pymodule = _get_concluded_data(self.importing_module)
def _current_folder(self):
resource = self.importing_module.get_module().get_resource()
if resource is None:
return None
return resource.parent
def _get_pymodule(self):
if self.pymodule.get() is None:
pycore = self.importing_module.pycore
if self.resource is not None:
self.pymodule.set(pycore.project.get_pymodule(self.resource))
elif self.module_name is not None:
try:
if self.level == 0:
pymodule = pycore.project.get_module(
self.module_name, self._current_folder()
)
else:
pymodule = pycore.project.get_relative_module(
self.module_name, self._current_folder(), self.level
)
self.pymodule.set(pymodule)
except exceptions.ModuleNotFoundError:
pass
return self.pymodule.get()
def get_object(self):
if self._get_pymodule() is None:
return rope.base.pyobjects.get_unknown()
return self._get_pymodule()
def get_definition_location(self):
pymodule = self._get_pymodule()
if not isinstance(pymodule, rope.base.pyobjects.PyDefinedObject):
return (None, None)
return (pymodule.get_module(), 1)
class ImportedName(PyName):
def __init__(self, imported_module, imported_name):
self.imported_module = imported_module
self.imported_name = imported_name
def _get_imported_pyname(self):
try:
result = self.imported_module.get_object()[self.imported_name]
if result != self:
return result
except exceptions.AttributeNotFoundError:
pass
return UnboundName()
@utils.prevent_recursion(rope.base.pyobjects.get_unknown)
def get_object(self):
return self._get_imported_pyname().get_object()
@utils.prevent_recursion(lambda: (None, None))
def get_definition_location(self):
return self._get_imported_pyname().get_definition_location()
def _get_concluded_data(module):
if module is None:
return rope.base.pyobjects._ConcludedData()
return module._get_concluded_data()
def _circular_inference():
raise rope.base.pyobjects.IsBeingInferredError("Circular Object Inference")
class _Inferred(object):
def __init__(self, get_inferred, concluded=None):
self.get_inferred = get_inferred
self.concluded = concluded
if self.concluded is None:
self.temp = None
@utils.prevent_recursion(_circular_inference)
def get(self, *args, **kwds):
if self.concluded is None or self.concluded.get() is None:
self.set(self.get_inferred(*args, **kwds))
if self._get() is None:
self.set(rope.base.pyobjects.get_unknown())
return self._get()
def set(self, pyobject):
if self.concluded is not None:
self.concluded.set(pyobject)
self.temp = pyobject
def _get(self):
if self.concluded is not None:
return self.concluded.get()
return self.temp

View file

@ -0,0 +1,57 @@
import rope.base.oi.soi
from rope.base import pynames
from rope.base.pynames import *
class AssignedName(pynames.AssignedName):
def __init__(self, lineno=None, module=None, pyobject=None):
self.lineno = lineno
self.module = module
self.assignments = []
self.pyobject = _Inferred(
self._get_inferred, pynames._get_concluded_data(module)
)
self.pyobject.set(pyobject)
@utils.prevent_recursion(lambda: None)
def _get_inferred(self):
if self.module is not None:
return rope.base.oi.soi.infer_assigned_object(self)
def get_object(self):
return self.pyobject.get()
def get_definition_location(self):
"""Returns a (module, lineno) tuple"""
if self.lineno is None and self.assignments:
try:
self.lineno = self.assignments[0].get_lineno()
except AttributeError:
pass
return (self.module, self.lineno)
def invalidate(self):
"""Forget the `PyObject` this `PyName` holds"""
self.pyobject.set(None)
class ParameterName(pynames.ParameterName):
def __init__(self, pyfunction, index):
self.pyfunction = pyfunction
self.index = index
def get_object(self):
result = self.pyfunction.get_parameter(self.index)
if result is None:
result = rope.base.pyobjects.get_unknown()
return result
def get_objects(self):
"""Returns the list of objects passed as this parameter"""
return rope.base.oi.soi.get_passed_objects(self.pyfunction, self.index)
def get_definition_location(self):
return (self.pyfunction.get_module(), self.pyfunction.get_ast().lineno)
_Inferred = pynames._Inferred

View file

@ -0,0 +1,306 @@
from rope.base.fscommands import _decode_data
from rope.base import ast, exceptions, utils
class PyObject(object):
def __init__(self, type_):
if type_ is None:
type_ = self
self.type = type_
def get_attributes(self):
if self.type is self:
return {}
return self.type.get_attributes()
def get_attribute(self, name):
if name not in self.get_attributes():
raise exceptions.AttributeNotFoundError("Attribute %s not found" % name)
return self.get_attributes()[name]
def get_type(self):
return self.type
def __getitem__(self, key):
"""The same as ``get_attribute(key)``"""
return self.get_attribute(key)
def __contains__(self, key):
"""The same as ``key in self.get_attributes()``"""
return key in self.get_attributes()
def __eq__(self, obj):
"""Check the equality of two `PyObject`
Currently it is assumed that instances (the direct instances
of `PyObject`, not the instances of its subclasses) are equal
if their types are equal. For every other object like
defineds or builtins rope assumes objects are reference
objects and their identities should match.
"""
if self.__class__ != obj.__class__:
return False
if type(self) == PyObject:
if self is not self.type:
return self.type == obj.type
else:
return self.type is obj.type
return self is obj
def __ne__(self, obj):
return not self.__eq__(obj)
def __hash__(self):
"""See docs for `__eq__()` method"""
if type(self) == PyObject and self != self.type:
return hash(self.type) + 1
else:
return super(PyObject, self).__hash__()
def __iter__(self):
"""The same as ``iter(self.get_attributes())``"""
return iter(self.get_attributes())
_types = None
_unknown = None
@staticmethod
def _get_base_type(name):
if PyObject._types is None:
PyObject._types = {}
base_type = PyObject(None)
PyObject._types["Type"] = base_type
PyObject._types["Module"] = PyObject(base_type)
PyObject._types["Function"] = PyObject(base_type)
PyObject._types["Unknown"] = PyObject(base_type)
return PyObject._types[name]
def get_base_type(name):
"""Return the base type with name `name`.
The base types are 'Type', 'Function', 'Module' and 'Unknown'. It
was used to check the type of a `PyObject` but currently its use
is discouraged. Use classes defined in this module instead.
For example instead of
``pyobject.get_type() == get_base_type('Function')`` use
``isinstance(pyobject, AbstractFunction)``.
You can use `AbstractClass` for classes, `AbstractFunction` for
functions, and `AbstractModule` for modules. You can also use
`PyFunction` and `PyClass` for testing if an object is
defined somewhere and rope can access its source. These classes
provide more methods.
"""
return PyObject._get_base_type(name)
def get_unknown():
"""Return a pyobject whose type is unknown
Note that two unknown objects are equal. So for example you can
write::
if pyname.get_object() == get_unknown():
print('cannot determine what this pyname holds')
Rope could have used `None` for indicating unknown objects but
we had to check that in many places. So actually this method
returns a null object.
"""
if PyObject._unknown is None:
PyObject._unknown = PyObject(get_base_type("Unknown"))
return PyObject._unknown
class AbstractClass(PyObject):
def __init__(self):
super(AbstractClass, self).__init__(get_base_type("Type"))
def get_name(self):
pass
def get_doc(self):
pass
def get_superclasses(self):
return []
class AbstractFunction(PyObject):
def __init__(self):
super(AbstractFunction, self).__init__(get_base_type("Function"))
def get_name(self):
pass
def get_doc(self):
pass
def get_param_names(self, special_args=True):
return []
def get_returned_object(self, args):
return get_unknown()
class AbstractModule(PyObject):
def __init__(self, doc=None):
super(AbstractModule, self).__init__(get_base_type("Module"))
def get_doc(self):
pass
def get_resource(self):
pass
class PyDefinedObject(object):
"""Python defined names that rope can access their sources"""
def __init__(self, pycore, ast_node, parent):
self.pycore = pycore
self.ast_node = ast_node
self.scope = None
self.parent = parent
self.structural_attributes = None
self.concluded_attributes = self.get_module()._get_concluded_data()
self.attributes = self.get_module()._get_concluded_data()
self.defineds = None
visitor_class = None
@utils.prevent_recursion(lambda: {})
def _get_structural_attributes(self):
if self.structural_attributes is None:
self.structural_attributes = self._create_structural_attributes()
return self.structural_attributes
@utils.prevent_recursion(lambda: {})
def _get_concluded_attributes(self):
if self.concluded_attributes.get() is None:
self._get_structural_attributes()
self.concluded_attributes.set(self._create_concluded_attributes())
return self.concluded_attributes.get()
def get_attributes(self):
if self.attributes.get() is None:
result = dict(self._get_concluded_attributes())
result.update(self._get_structural_attributes())
self.attributes.set(result)
return self.attributes.get()
def get_attribute(self, name):
if name in self._get_structural_attributes():
return self._get_structural_attributes()[name]
if name in self._get_concluded_attributes():
return self._get_concluded_attributes()[name]
raise exceptions.AttributeNotFoundError("Attribute %s not found" % name)
def get_scope(self):
if self.scope is None:
self.scope = self._create_scope()
return self.scope
def get_module(self):
current_object = self
while current_object.parent is not None:
current_object = current_object.parent
return current_object
def get_doc(self):
if len(self.get_ast().body) > 0:
expr = self.get_ast().body[0]
if isinstance(expr, ast.Expr) and isinstance(expr.value, ast.Str):
docstring = expr.value.s
coding = self.get_module().coding
return _decode_data(docstring, coding)
def _get_defined_objects(self):
if self.defineds is None:
self._get_structural_attributes()
return self.defineds
def _create_structural_attributes(self):
if self.visitor_class is None:
return {}
new_visitor = self.visitor_class(self.pycore, self)
for child in ast.get_child_nodes(self.ast_node):
ast.walk(child, new_visitor)
self.defineds = new_visitor.defineds
return new_visitor.names
def _create_concluded_attributes(self):
return {}
def get_ast(self):
return self.ast_node
def _create_scope(self):
pass
class PyFunction(PyDefinedObject, AbstractFunction):
"""Only a placeholder"""
class PyComprehension(PyDefinedObject, PyObject):
"""Only a placeholder"""
class PyClass(PyDefinedObject, AbstractClass):
"""Only a placeholder"""
class _ConcludedData(object):
def __init__(self):
self.data_ = None
def set(self, data):
self.data_ = data
def get(self):
return self.data_
data = property(get, set)
def _invalidate(self):
self.data = None
def __str__(self):
return "<" + str(self.data) + ">"
class _PyModule(PyDefinedObject, AbstractModule):
def __init__(self, pycore, ast_node, resource):
self.resource = resource
self.concluded_data = []
AbstractModule.__init__(self)
PyDefinedObject.__init__(self, pycore, ast_node, None)
def _get_concluded_data(self):
new_data = _ConcludedData()
self.concluded_data.append(new_data)
return new_data
def _forget_concluded_data(self):
for data in self.concluded_data:
data._invalidate()
def get_resource(self):
return self.resource
class PyModule(_PyModule):
"""Only a placeholder"""
class PyPackage(_PyModule):
"""Only a placeholder"""
class IsBeingInferredError(exceptions.RopeError):
pass

View file

@ -0,0 +1,677 @@
from rope.base.pynames import DefinedName
import rope.base.builtins
import rope.base.codeanalyze
import rope.base.evaluate
import rope.base.libutils
import rope.base.oi.soi
import rope.base.pyscopes
from rope.base import (
pynamesdef as pynames,
exceptions,
ast,
astutils,
pyobjects,
fscommands,
arguments,
utils,
)
from rope.base.utils import pycompat
try:
unicode
except NameError:
unicode = str
class PyFunction(pyobjects.PyFunction):
def __init__(self, pycore, ast_node, parent):
rope.base.pyobjects.AbstractFunction.__init__(self)
rope.base.pyobjects.PyDefinedObject.__init__(self, pycore, ast_node, parent)
self.arguments = self.ast_node.args
self.parameter_pyobjects = pynames._Inferred(
self._infer_parameters, self.get_module()._get_concluded_data()
)
self.returned = pynames._Inferred(self._infer_returned)
self.parameter_pynames = None
def _create_structural_attributes(self):
return {}
def _create_concluded_attributes(self):
return {}
def _create_scope(self):
return rope.base.pyscopes.FunctionScope(self.pycore, self, _FunctionVisitor)
def _infer_parameters(self):
pyobjects = rope.base.oi.soi.infer_parameter_objects(self)
self._handle_special_args(pyobjects)
return pyobjects
def _infer_returned(self, args=None):
return rope.base.oi.soi.infer_returned_object(self, args)
def _handle_special_args(self, pyobjects):
if len(pyobjects) == len(self.arguments.args):
if self.arguments.vararg:
pyobjects.append(rope.base.builtins.get_list())
if self.arguments.kwarg:
pyobjects.append(rope.base.builtins.get_dict())
def _set_parameter_pyobjects(self, pyobjects):
if pyobjects is not None:
self._handle_special_args(pyobjects)
self.parameter_pyobjects.set(pyobjects)
def get_parameters(self):
if self.parameter_pynames is None:
result = {}
for index, name in enumerate(self.get_param_names()):
# TODO: handle tuple parameters
result[name] = pynames.ParameterName(self, index)
self.parameter_pynames = result
return self.parameter_pynames
def get_parameter(self, index):
if index < len(self.parameter_pyobjects.get()):
return self.parameter_pyobjects.get()[index]
def get_returned_object(self, args):
return self.returned.get(args)
def get_name(self):
return self.get_ast().name
def get_param_names(self, special_args=True):
# TODO: handle tuple parameters
result = [
pycompat.get_ast_arg_arg(node)
for node in self.arguments.args
if isinstance(node, pycompat.ast_arg_type)
]
if special_args:
if self.arguments.vararg:
result.append(pycompat.get_ast_arg_arg(self.arguments.vararg))
if self.arguments.kwarg:
result.append(pycompat.get_ast_arg_arg(self.arguments.kwarg))
return result
def get_kind(self):
"""Get function type
It returns one of 'function', 'method', 'staticmethod' or
'classmethod' strs.
"""
scope = self.parent.get_scope()
if isinstance(self.parent, PyClass):
for decorator in self.decorators:
pyname = rope.base.evaluate.eval_node(scope, decorator)
if pyname == rope.base.builtins.builtins["staticmethod"]:
return "staticmethod"
if pyname == rope.base.builtins.builtins["classmethod"]:
return "classmethod"
return "method"
return "function"
@property
def decorators(self):
try:
return getattr(self.ast_node, "decorator_list")
except AttributeError:
return getattr(self.ast_node, "decorators", None)
class PyComprehension(pyobjects.PyComprehension):
def __init__(self, pycore, ast_node, parent):
self.visitor_class = _ComprehensionVisitor
rope.base.pyobjects.PyObject.__init__(self, type_="Comp")
rope.base.pyobjects.PyDefinedObject.__init__(self, pycore, ast_node, parent)
def _create_scope(self):
return rope.base.pyscopes.ComprehensionScope(
self.pycore, self, _ComprehensionVisitor
)
class PyClass(pyobjects.PyClass):
def __init__(self, pycore, ast_node, parent):
self.visitor_class = _ClassVisitor
rope.base.pyobjects.AbstractClass.__init__(self)
rope.base.pyobjects.PyDefinedObject.__init__(self, pycore, ast_node, parent)
self.parent = parent
self._superclasses = self.get_module()._get_concluded_data()
def get_superclasses(self):
if self._superclasses.get() is None:
self._superclasses.set(self._get_bases())
return self._superclasses.get()
def get_name(self):
return self.get_ast().name
def _create_concluded_attributes(self):
result = {}
for base in reversed(self.get_superclasses()):
result.update(base.get_attributes())
return result
def _get_bases(self):
result = []
for base_name in self.ast_node.bases:
base = rope.base.evaluate.eval_node(self.parent.get_scope(), base_name)
if (
base is not None
and base.get_object().get_type()
== rope.base.pyobjects.get_base_type("Type")
):
result.append(base.get_object())
return result
def _create_scope(self):
return rope.base.pyscopes.ClassScope(self.pycore, self)
class PyModule(pyobjects.PyModule):
def __init__(self, pycore, source=None, resource=None, force_errors=False):
ignore = pycore.project.prefs.get("ignore_syntax_errors", False)
syntax_errors = force_errors or not ignore
self.has_errors = False
try:
source, node = self._init_source(pycore, source, resource)
except exceptions.ModuleSyntaxError:
self.has_errors = True
if syntax_errors:
raise
else:
source = "\n"
node = ast.parse("\n")
self.source_code = source
self.star_imports = []
self.visitor_class = _GlobalVisitor
self.coding = fscommands.read_str_coding(self.source_code)
super(PyModule, self).__init__(pycore, node, resource)
def _init_source(self, pycore, source_code, resource):
filename = "string"
if resource:
filename = resource.path
try:
if source_code is None:
source_bytes = resource.read_bytes()
source_code, _ = fscommands.file_data_to_unicode(source_bytes)
else:
if isinstance(source_code, unicode):
source_bytes = fscommands.unicode_to_file_data(source_code)
else:
source_bytes = source_code
ast_node = ast.parse(source_bytes, filename=filename)
except SyntaxError as e:
raise exceptions.ModuleSyntaxError(filename, e.lineno, e.msg)
except UnicodeDecodeError as e:
raise exceptions.ModuleSyntaxError(filename, 1, "%s" % (e.reason))
return source_code, ast_node
@utils.prevent_recursion(lambda: {})
def _create_concluded_attributes(self):
result = {}
for star_import in self.star_imports:
result.update(star_import.get_names())
return result
def _create_scope(self):
return rope.base.pyscopes.GlobalScope(self.pycore, self)
@property
@utils.saveit
def lines(self):
"""A `SourceLinesAdapter`"""
return rope.base.codeanalyze.SourceLinesAdapter(self.source_code)
@property
@utils.saveit
def logical_lines(self):
"""A `LogicalLinesFinder`"""
return rope.base.codeanalyze.CachingLogicalLineFinder(self.lines)
def get_name(self):
return rope.base.libutils.modname(self.get_resource())
class PyPackage(pyobjects.PyPackage):
def __init__(self, pycore, resource=None, force_errors=False):
self.resource = resource
init_dot_py = self._get_init_dot_py()
if init_dot_py is not None:
ast_node = pycore.project.get_pymodule(
init_dot_py, force_errors=force_errors
).get_ast()
else:
ast_node = ast.parse("\n")
super(PyPackage, self).__init__(pycore, ast_node, resource)
def _create_structural_attributes(self):
result = {}
modname = rope.base.libutils.modname(self.resource)
extension_submodules = self.pycore._builtin_submodules(modname)
for name, module in extension_submodules.items():
result[name] = rope.base.builtins.BuiltinName(module)
if self.resource is None:
return result
for name, resource in self._get_child_resources().items():
result[name] = pynames.ImportedModule(self, resource=resource)
return result
def _create_concluded_attributes(self):
result = {}
init_dot_py = self._get_init_dot_py()
if init_dot_py:
init_object = self.pycore.project.get_pymodule(init_dot_py)
result.update(init_object.get_attributes())
return result
def _get_child_resources(self):
result = {}
for child in self.resource.get_children():
if child.is_folder():
result[child.name] = child
elif child.name.endswith(".py") and child.name != "__init__.py":
name = child.name[:-3]
result[name] = child
return result
def _get_init_dot_py(self):
if self.resource is not None and self.resource.has_child("__init__.py"):
return self.resource.get_child("__init__.py")
else:
return None
def _create_scope(self):
return self.get_module().get_scope()
def get_module(self):
init_dot_py = self._get_init_dot_py()
if init_dot_py:
return self.pycore.project.get_pymodule(init_dot_py)
return self
class _AnnAssignVisitor(object):
def __init__(self, scope_visitor):
self.scope_visitor = scope_visitor
self.assigned_ast = None
self.type_hint = None
def _AnnAssign(self, node):
self.assigned_ast = node.value
self.type_hint = node.annotation
ast.walk(node.target, self)
def _assigned(self, name, assignment=None):
self.scope_visitor._assigned(name, assignment)
def _Name(self, node):
assignment = pynames.AssignmentValue(
self.assigned_ast, assign_type=True, type_hint=self.type_hint
)
self._assigned(node.id, assignment)
def _Tuple(self, node):
names = astutils.get_name_levels(node)
for name, levels in names:
assignment = None
if self.assigned_ast is not None:
assignment = pynames.AssignmentValue(self.assigned_ast, levels)
self._assigned(name, assignment)
def _Annotation(self, node):
pass
def _Attribute(self, node):
pass
def _Subscript(self, node):
pass
def _Slice(self, node):
pass
class _ExpressionVisitor(object):
def __init__(self, scope_visitor):
self.scope_visitor = scope_visitor
def _assigned(self, name, assignment=None):
self.scope_visitor._assigned(name, assignment)
def _GeneratorExp(self, node):
list_comp = PyComprehension(
self.scope_visitor.pycore, node, self.scope_visitor.owner_object
)
self.scope_visitor.defineds.append(list_comp)
def _SetComp(self, node):
self._GeneratorExp(node)
def _ListComp(self, node):
self._GeneratorExp(node)
def _DictComp(self, node):
self._GeneratorExp(node)
def _NamedExpr(self, node):
ast.walk(node.target, _AssignVisitor(self))
ast.walk(node.value, self)
class _AssignVisitor(object):
def __init__(self, scope_visitor):
self.scope_visitor = scope_visitor
self.assigned_ast = None
def _Assign(self, node):
self.assigned_ast = node.value
for child_node in node.targets:
ast.walk(child_node, self)
ast.walk(node.value, _ExpressionVisitor(self.scope_visitor))
def _assigned(self, name, assignment=None):
self.scope_visitor._assigned(name, assignment)
def _Name(self, node):
assignment = None
if self.assigned_ast is not None:
assignment = pynames.AssignmentValue(self.assigned_ast)
self._assigned(node.id, assignment)
def _Tuple(self, node):
names = astutils.get_name_levels(node)
for name, levels in names:
assignment = None
if self.assigned_ast is not None:
assignment = pynames.AssignmentValue(self.assigned_ast, levels)
self._assigned(name, assignment)
def _Attribute(self, node):
pass
def _Subscript(self, node):
pass
def _Slice(self, node):
pass
class _ScopeVisitor(_ExpressionVisitor):
def __init__(self, pycore, owner_object):
_ExpressionVisitor.__init__(self, scope_visitor=self)
self.pycore = pycore
self.owner_object = owner_object
self.names = {}
self.defineds = []
def get_module(self):
if self.owner_object is not None:
return self.owner_object.get_module()
else:
return None
def _ClassDef(self, node):
pyclass = PyClass(self.pycore, node, self.owner_object)
self.names[node.name] = pynames.DefinedName(pyclass)
self.defineds.append(pyclass)
def _FunctionDef(self, node):
pyfunction = PyFunction(self.pycore, node, self.owner_object)
for decorator in pyfunction.decorators:
if isinstance(decorator, ast.Name) and decorator.id == "property":
if isinstance(self, _ClassVisitor):
type_ = rope.base.builtins.Property(pyfunction)
arg = pynames.UnboundName(
rope.base.pyobjects.PyObject(self.owner_object)
)
def _eval(type_=type_, arg=arg):
return type_.get_property_object(
arguments.ObjectArguments([arg])
)
lineno = utils.guess_def_lineno(self.get_module(), node)
self.names[node.name] = pynames.EvaluatedName(
_eval, module=self.get_module(), lineno=lineno
)
break
else:
self.names[node.name] = pynames.DefinedName(pyfunction)
self.defineds.append(pyfunction)
def _AsyncFunctionDef(self, node):
return self._FunctionDef(node)
def _Assign(self, node):
ast.walk(node, _AssignVisitor(self))
def _AnnAssign(self, node):
ast.walk(node, _AnnAssignVisitor(self))
def _AugAssign(self, node):
pass
def _For(self, node):
names = self._update_evaluated(
node.target, node.iter, ".__iter__().next()" # noqa
)
for child in node.body + node.orelse:
ast.walk(child, self)
def _AsyncFor(self, node):
return self._For(node)
def _assigned(self, name, assignment):
pyname = self.names.get(name, None)
if pyname is None:
pyname = pynames.AssignedName(module=self.get_module())
if isinstance(pyname, pynames.AssignedName):
if assignment is not None:
pyname.assignments.append(assignment)
self.names[name] = pyname
def _update_evaluated(
self, targets, assigned, evaluation="", eval_type=False, type_hint=None
):
result = {}
if isinstance(targets, str):
assignment = pynames.AssignmentValue(assigned, [], evaluation, eval_type)
self._assigned(targets, assignment)
else:
names = astutils.get_name_levels(targets)
for name, levels in names:
assignment = pynames.AssignmentValue(
assigned, levels, evaluation, eval_type
)
self._assigned(name, assignment)
return result
def _With(self, node):
for item in pycompat.get_ast_with_items(node):
if item.optional_vars:
self._update_evaluated(
item.optional_vars, item.context_expr, ".__enter__()"
)
for child in node.body:
ast.walk(child, self)
def _AsyncWith(self, node):
return self._With(node)
def _excepthandler(self, node):
node_name_type = str if pycompat.PY3 else ast.Name
if node.name is not None and isinstance(node.name, node_name_type):
type_node = node.type
if isinstance(node.type, ast.Tuple) and type_node.elts:
type_node = type_node.elts[0]
self._update_evaluated(node.name, type_node, eval_type=True)
for child in node.body:
ast.walk(child, self)
def _ExceptHandler(self, node):
self._excepthandler(node)
def _Import(self, node):
for import_pair in node.names:
module_name = import_pair.name
alias = import_pair.asname
first_package = module_name.split(".")[0]
if alias is not None:
imported = pynames.ImportedModule(self.get_module(), module_name)
if not self._is_ignored_import(imported):
self.names[alias] = imported
else:
imported = pynames.ImportedModule(self.get_module(), first_package)
if not self._is_ignored_import(imported):
self.names[first_package] = imported
def _ImportFrom(self, node):
level = 0
if node.level:
level = node.level
imported_module = pynames.ImportedModule(self.get_module(), node.module, level)
if self._is_ignored_import(imported_module):
return
if len(node.names) == 1 and node.names[0].name == "*":
if isinstance(self.owner_object, PyModule):
self.owner_object.star_imports.append(StarImport(imported_module))
else:
for imported_name in node.names:
imported = imported_name.name
alias = imported_name.asname
if alias is not None:
imported = alias
self.names[imported] = pynames.ImportedName(
imported_module, imported_name.name
)
def _is_ignored_import(self, imported_module):
if not self.pycore.project.prefs.get("ignore_bad_imports", False):
return False
return not isinstance(
imported_module.get_object(), rope.base.pyobjects.AbstractModule
)
def _Global(self, node):
module = self.get_module()
for name in node.names:
if module is not None:
try:
pyname = module[name]
except exceptions.AttributeNotFoundError:
pyname = pynames.AssignedName(node.lineno)
self.names[name] = pyname
class _ComprehensionVisitor(_ScopeVisitor):
def _comprehension(self, node):
ast.walk(node.target, self)
ast.walk(node.iter, self)
def _Name(self, node):
if isinstance(node.ctx, ast.Store):
self.names[node.id] = self._get_pyobject(node)
def _get_pyobject(self, node):
return pynames.AssignedName(lineno=node.lineno, module=self.get_module())
class _GlobalVisitor(_ScopeVisitor):
def __init__(self, pycore, owner_object):
super(_GlobalVisitor, self).__init__(pycore, owner_object)
class _ClassVisitor(_ScopeVisitor):
def __init__(self, pycore, owner_object):
super(_ClassVisitor, self).__init__(pycore, owner_object)
def _FunctionDef(self, node):
_ScopeVisitor._FunctionDef(self, node)
if len(node.args.args) > 0:
first = node.args.args[0]
new_visitor = None
if isinstance(first, pycompat.ast_arg_type):
new_visitor = _ClassInitVisitor(self, pycompat.get_ast_arg_arg(first))
if new_visitor is not None:
for child in ast.get_child_nodes(node):
ast.walk(child, new_visitor)
class _FunctionVisitor(_ScopeVisitor):
def __init__(self, pycore, owner_object):
super(_FunctionVisitor, self).__init__(pycore, owner_object)
self.returned_asts = []
self.generator = False
def _Return(self, node):
if node.value is not None:
self.returned_asts.append(node.value)
def _Yield(self, node):
if node.value is not None:
self.returned_asts.append(node.value)
self.generator = True
class _ClassInitVisitor(_AssignVisitor):
def __init__(self, scope_visitor, self_name):
super(_ClassInitVisitor, self).__init__(scope_visitor)
self.self_name = self_name
def _Attribute(self, node):
if not isinstance(node.ctx, ast.Store):
return
if isinstance(node.value, ast.Name) and node.value.id == self.self_name:
if node.attr not in self.scope_visitor.names:
self.scope_visitor.names[node.attr] = pynames.AssignedName(
lineno=node.lineno, module=self.scope_visitor.get_module()
)
if self.assigned_ast is not None:
pyname = self.scope_visitor.names[node.attr]
if isinstance(pyname, pynames.AssignedName):
pyname.assignments.append(
pynames.AssignmentValue(self.assigned_ast)
)
def _Tuple(self, node):
if not isinstance(node.ctx, ast.Store):
return
for child in ast.get_child_nodes(node):
ast.walk(child, self)
def _Name(self, node):
pass
def _FunctionDef(self, node):
pass
def _ClassDef(self, node):
pass
def _For(self, node):
pass
def _With(self, node):
pass
class StarImport(object):
def __init__(self, imported_module):
self.imported_module = imported_module
def get_names(self):
result = {}
imported = self.imported_module.get_object()
for name in imported:
if not name.startswith("_"):
result[name] = pynames.ImportedName(self.imported_module, name)
return result

View file

@ -0,0 +1,381 @@
import rope.base.builtins
import rope.base.codeanalyze
import rope.base.pynames
from rope.base import ast, exceptions, utils
from rope.refactor import patchedast
class Scope(object):
def __init__(self, pycore, pyobject, parent_scope):
self.pycore = pycore
self.pyobject = pyobject
self.parent = parent_scope
def get_names(self):
"""Return the names defined or imported in this scope"""
return self.pyobject.get_attributes()
def get_defined_names(self):
"""Return the names defined in this scope"""
return self.pyobject._get_structural_attributes()
def get_name(self, name):
"""Return name `PyName` defined in this scope"""
if name not in self.get_names():
raise exceptions.NameNotFoundError("name %s not found" % name)
return self.get_names()[name]
def __getitem__(self, key):
"""The same as ``get_name(key)``"""
return self.get_name(key)
def __contains__(self, key):
"""The same as ``key in self.get_names()``"""
return key in self.get_names()
@utils.saveit
def get_scopes(self):
"""Return the subscopes of this scope
The returned scopes should be sorted by the order they appear.
"""
return self._create_scopes()
def lookup(self, name):
if name in self.get_names():
return self.get_names()[name]
if self.parent is not None:
return self.parent._propagated_lookup(name)
return None
def get_propagated_names(self):
"""Return the visible names of this scope
Return the names defined in this scope that are visible from
scopes containing this scope. This method returns the same
dictionary returned by `get_names()` except for `ClassScope`
which returns an empty dict.
"""
return self.get_names()
def _propagated_lookup(self, name):
if name in self.get_propagated_names():
return self.get_propagated_names()[name]
if self.parent is not None:
return self.parent._propagated_lookup(name)
return None
def _create_scopes(self):
return [
pydefined.get_scope() for pydefined in self.pyobject._get_defined_objects()
]
def _get_global_scope(self):
current = self
while current.parent is not None:
current = current.parent
return current
def get_start(self):
return self.pyobject.get_ast().lineno
def get_body_start(self):
body = self.pyobject.get_ast().body
if body:
return body[0].lineno
return self.get_start()
def get_end(self):
pymodule = self._get_global_scope().pyobject
return pymodule.logical_lines.logical_line_in(self.logical_end)[1]
@utils.saveit
def get_logical_end(self):
global_scope = self._get_global_scope()
return global_scope._scope_finder.find_scope_end(self)
start = property(get_start)
end = property(get_end)
logical_end = property(get_logical_end)
def get_kind(self):
pass
def get_region(self):
self._calculate_scope_regions_for_module()
node = self.pyobject.get_ast()
region = patchedast.node_region(node)
return region
def _calculate_scope_regions_for_module(self):
self._get_global_scope()._calculate_scope_regions()
def in_region(self, offset):
"""Checks if offset is in scope region"""
region = self.get_region()
return region[0] < offset < region[1]
class GlobalScope(Scope):
def __init__(self, pycore, module):
super(GlobalScope, self).__init__(pycore, module, None)
self.names = module._get_concluded_data()
def get_start(self):
return 1
def get_kind(self):
return "Module"
def get_name(self, name):
try:
return self.pyobject[name]
except exceptions.AttributeNotFoundError:
if name in self.builtin_names:
return self.builtin_names[name]
raise exceptions.NameNotFoundError("name %s not found" % name)
@utils.saveit
def _calculate_scope_regions(self):
source = self._get_source()
patchedast.patch_ast(self.pyobject.get_ast(), source)
def _get_source(self):
return self.pyobject.source_code
def get_names(self):
if self.names.get() is None:
result = dict(self.builtin_names)
result.update(super(GlobalScope, self).get_names())
self.names.set(result)
return self.names.get()
def get_inner_scope_for_line(self, lineno, indents=None):
return self._scope_finder.get_holding_scope(self, lineno, indents)
def get_inner_scope_for_offset(self, offset):
return self._scope_finder.get_holding_scope_for_offset(self, offset)
@property
@utils.saveit
def _scope_finder(self):
return _HoldingScopeFinder(self.pyobject)
@property
def builtin_names(self):
return rope.base.builtins.builtins.get_attributes()
class ComprehensionScope(Scope):
def __init__(self, pycore, pyobject, visitor):
super(ComprehensionScope, self).__init__(
pycore, pyobject, pyobject.parent.get_scope()
)
self.names = None
self.returned_asts = None
self.defineds = None
self.visitor = visitor
def _get_names(self):
if self.names is None:
self._visit_comprehension()
return self.names
def get_names(self):
return self._get_names()
def _visit_comprehension(self):
if self.names is None:
new_visitor = self.visitor(self.pycore, self.pyobject)
for node in ast.get_child_nodes(self.pyobject.get_ast()):
ast.walk(node, new_visitor)
self.names = dict(self.parent.get_names())
self.names.update(new_visitor.names)
self.defineds = new_visitor.defineds
def get_logical_end(self):
return self.get_start()
logical_end = property(get_logical_end)
def get_body_start(self):
return self.get_start()
class FunctionScope(Scope):
def __init__(self, pycore, pyobject, visitor):
super(FunctionScope, self).__init__(
pycore, pyobject, pyobject.parent.get_scope()
)
self.names = None
self.returned_asts = None
self.is_generator = None
self.defineds = None
self.visitor = visitor
def _get_names(self):
if self.names is None:
self._visit_function()
return self.names
def _visit_function(self):
if self.names is None:
new_visitor = self.visitor(self.pycore, self.pyobject)
for n in ast.get_child_nodes(self.pyobject.get_ast()):
ast.walk(n, new_visitor)
self.names = new_visitor.names
self.names.update(self.pyobject.get_parameters())
self.returned_asts = new_visitor.returned_asts
self.is_generator = new_visitor.generator
self.defineds = new_visitor.defineds
def _get_returned_asts(self):
if self.names is None:
self._visit_function()
return self.returned_asts
def _is_generator(self):
if self.is_generator is None:
self._get_returned_asts()
return self.is_generator
def get_names(self):
return self._get_names()
def _create_scopes(self):
if self.defineds is None:
self._visit_function()
return [pydefined.get_scope() for pydefined in self.defineds]
def get_kind(self):
return "Function"
def invalidate_data(self):
for pyname in self.get_names().values():
if isinstance(
pyname,
(rope.base.pynames.AssignedName, rope.base.pynames.EvaluatedName),
):
pyname.invalidate()
class ClassScope(Scope):
def __init__(self, pycore, pyobject):
super(ClassScope, self).__init__(pycore, pyobject, pyobject.parent.get_scope())
def get_kind(self):
return "Class"
def get_propagated_names(self):
return {}
class _HoldingScopeFinder(object):
def __init__(self, pymodule):
self.pymodule = pymodule
def get_indents(self, lineno):
return rope.base.codeanalyze.count_line_indents(self.lines.get_line(lineno))
def _get_scope_indents(self, scope):
return self.get_indents(scope.get_start())
def get_holding_scope(self, module_scope, lineno, line_indents=None):
if line_indents is None:
line_indents = self.get_indents(lineno)
current_scope = module_scope
new_scope = current_scope
while new_scope is not None and (
new_scope.get_kind() == "Module"
or self._get_scope_indents(new_scope) <= line_indents
):
current_scope = new_scope
if (
current_scope.get_start() == lineno
and current_scope.get_kind() != "Module"
):
return current_scope
new_scope = None
for scope in current_scope.get_scopes():
if scope.get_start() <= lineno:
if lineno <= scope.get_end():
new_scope = scope
break
else:
break
return current_scope
def _is_empty_line(self, lineno):
line = self.lines.get_line(lineno)
return line.strip() == "" or line.lstrip().startswith("#")
def _get_body_indents(self, scope):
return self.get_indents(scope.get_body_start())
@staticmethod
def get_holding_scope_for_offset(scope, offset):
for inner_scope in scope.get_scopes():
if inner_scope.in_region(offset):
return _HoldingScopeFinder.get_holding_scope_for_offset(
inner_scope, offset
)
return scope
def find_scope_end(self, scope):
if not scope.parent:
return self.lines.length()
end = scope.pyobject.get_ast().body[-1].lineno
scope_start = self.pymodule.logical_lines.logical_line_in(scope.start)
if scope_start[1] >= end:
# handling one-liners
body_indents = self._get_scope_indents(scope) + 4
else:
body_indents = self._get_body_indents(scope)
for l in self.logical_lines.generate_starts(
min(end + 1, self.lines.length()), self.lines.length() + 1
):
if not self._is_empty_line(l):
if self.get_indents(l) < body_indents:
return end
else:
end = l
return end
@property
def lines(self):
return self.pymodule.lines
@property
def code(self):
return self.pymodule.source_code
@property
def logical_lines(self):
return self.pymodule.logical_lines
class TemporaryScope(Scope):
"""Currently used for list comprehensions and generator expressions
These scopes do not appear in the `get_scopes()` method of their
parent scopes.
"""
def __init__(self, pycore, parent_scope, names):
super(TemporaryScope, self).__init__(
pycore, parent_scope.pyobject, parent_scope
)
self.names = names
def get_names(self):
return self.names
def get_defined_names(self):
return self.names
def _create_scopes(self):
return []
def get_kind(self):
return "Temporary"

Some files were not shown because too many files have changed in this diff Show more