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,7 @@
"""rope IDE tools package
This package contains modules that can be used in IDEs
but do not depend on the UI. So these modules will be used
by `rope.ui` modules.
"""

View file

@ -0,0 +1,226 @@
import re
from rope.base import builtins
from rope.base import exceptions
from rope.base import libutils
from rope.base import pynames
from rope.base import pyobjects
from rope.base import resources
from rope.base import resourceobserver
from rope.base import taskhandle
from rope.refactor import importutils
class AutoImport(object):
"""A class for finding the module that provides a name
This class maintains a cache of global names in python modules.
Note that this cache is not accurate and might be out of date.
"""
def __init__(self, project, observe=True, underlined=False):
"""Construct an AutoImport object
If `observe` is `True`, listen for project changes and update
the cache.
If `underlined` is `True`, underlined names are cached, too.
"""
self.project = project
self.underlined = underlined
self.names = project.data_files.read_data("globalnames")
if self.names is None:
self.names = {}
project.data_files.add_write_hook(self._write)
# XXX: using a filtered observer
observer = resourceobserver.ResourceObserver(
changed=self._changed, moved=self._moved, removed=self._removed
)
if observe:
project.add_observer(observer)
def import_assist(self, starting):
"""Return a list of ``(name, module)`` tuples
This function tries to find modules that have a global name
that starts with `starting`.
"""
# XXX: breaking if gave up! use generators
result = []
for module in self.names:
for global_name in self.names[module]:
if global_name.startswith(starting):
result.append((global_name, module))
return result
def get_modules(self, name):
"""Return the list of modules that have global `name`"""
result = []
for module in self.names:
if name in self.names[module]:
result.append(module)
return result
def get_all_names(self):
"""Return the list of all cached global names"""
result = set()
for module in self.names:
result.update(set(self.names[module]))
return result
def get_name_locations(self, name):
"""Return a list of ``(resource, lineno)`` tuples"""
result = []
for module in self.names:
if name in self.names[module]:
try:
pymodule = self.project.get_module(module)
if name in pymodule:
pyname = pymodule[name]
module, lineno = pyname.get_definition_location()
if module is not None:
resource = module.get_module().get_resource()
if resource is not None and lineno is not None:
result.append((resource, lineno))
except exceptions.ModuleNotFoundError:
pass
return result
def generate_cache(
self, resources=None, underlined=None, task_handle=taskhandle.NullTaskHandle()
):
"""Generate global name cache for project files
If `resources` is a list of `rope.base.resource.File`, only
those files are searched; otherwise all python modules in the
project are cached.
"""
if resources is None:
resources = self.project.get_python_files()
job_set = task_handle.create_jobset(
"Generating autoimport cache", len(resources)
)
for file in resources:
job_set.started_job("Working on <%s>" % file.path)
self.update_resource(file, underlined)
job_set.finished_job()
def generate_modules_cache(
self, modules, underlined=None, task_handle=taskhandle.NullTaskHandle()
):
"""Generate global name cache for modules listed in `modules`"""
job_set = task_handle.create_jobset(
"Generating autoimport cache for modules", len(modules)
)
for modname in modules:
job_set.started_job("Working on <%s>" % modname)
if modname.endswith(".*"):
mod = self.project.find_module(modname[:-2])
if mod:
for sub in submodules(mod):
self.update_resource(sub, underlined)
else:
self.update_module(modname, underlined)
job_set.finished_job()
def clear_cache(self):
"""Clear all entries in global-name cache
It might be a good idea to use this function before
regenerating global names.
"""
self.names.clear()
def find_insertion_line(self, code):
"""Guess at what line the new import should be inserted"""
match = re.search(r"^(def|class)\s+", code)
if match is not None:
code = code[: match.start()]
try:
pymodule = libutils.get_string_module(self.project, code)
except exceptions.ModuleSyntaxError:
return 1
testmodname = "__rope_testmodule_rope"
importinfo = importutils.NormalImport(((testmodname, None),))
module_imports = importutils.get_module_imports(self.project, pymodule)
module_imports.add_import(importinfo)
code = module_imports.get_changed_source()
offset = code.index(testmodname)
lineno = code.count("\n", 0, offset) + 1
return lineno
def update_resource(self, resource, underlined=None):
"""Update the cache for global names in `resource`"""
try:
pymodule = self.project.get_pymodule(resource)
modname = self._module_name(resource)
self._add_names(pymodule, modname, underlined)
except exceptions.ModuleSyntaxError:
pass
def update_module(self, modname, underlined=None):
"""Update the cache for global names in `modname` module
`modname` is the name of a module.
"""
try:
pymodule = self.project.get_module(modname)
self._add_names(pymodule, modname, underlined)
except exceptions.ModuleNotFoundError:
pass
def _module_name(self, resource):
return libutils.modname(resource)
def _add_names(self, pymodule, modname, underlined):
if underlined is None:
underlined = self.underlined
globals = []
if isinstance(pymodule, pyobjects.PyDefinedObject):
attributes = pymodule._get_structural_attributes()
else:
attributes = pymodule.get_attributes()
for name, pyname in attributes.items():
if not underlined and name.startswith("_"):
continue
if isinstance(pyname, (pynames.AssignedName, pynames.DefinedName)):
globals.append(name)
if isinstance(pymodule, builtins.BuiltinModule):
globals.append(name)
self.names[modname] = globals
def _write(self):
self.project.data_files.write_data("globalnames", self.names)
def _changed(self, resource):
if not resource.is_folder():
self.update_resource(resource)
def _moved(self, resource, newresource):
if not resource.is_folder():
modname = self._module_name(resource)
if modname in self.names:
del self.names[modname]
self.update_resource(newresource)
def _removed(self, resource):
if not resource.is_folder():
modname = self._module_name(resource)
if modname in self.names:
del self.names[modname]
def submodules(mod):
if isinstance(mod, resources.File):
if mod.name.endswith(".py") and mod.name != "__init__.py":
return set([mod])
return set()
if not mod.has_child("__init__.py"):
return set()
result = set([mod])
for child in mod.get_children():
result |= submodules(child)
return result

View file

@ -0,0 +1,51 @@
"""For performing many refactorings as a single command
`changestack` module can be used to perform many refactorings on top
of each other as one bigger command. It can be used like::
stack = ChangeStack(project, 'my big command')
#..
stack.push(refactoring1.get_changes())
#..
stack.push(refactoring2.get_changes())
#..
stack.push(refactoringX.get_changes())
stack.pop_all()
changes = stack.merged()
Now `changes` can be previewed or performed as before.
"""
from rope.base import change
class ChangeStack(object):
def __init__(self, project, description="merged changes"):
self.project = project
self.description = description
self.stack = []
def push(self, changes):
self.stack.append(changes)
self.project.do(changes)
def pop_all(self):
for i in range(len(self.stack)):
self.project.history.undo(drop=True)
def merged(self):
result = change.ChangeSet(self.description)
for changes in self.stack:
for c in self._basic_changes(changes):
result.add_change(c)
return result
def _basic_changes(self, changes):
if isinstance(changes, change.ChangeSet):
for child in changes.changes:
for atom in self._basic_changes(child):
yield atom
else:
yield changes

View file

@ -0,0 +1,741 @@
import keyword
import sys
import warnings
import rope.base.codeanalyze
import rope.base.evaluate
from rope.base import builtins
from rope.base import exceptions
from rope.base import libutils
from rope.base import pynames
from rope.base import pynamesdef
from rope.base import pyobjects
from rope.base import pyobjectsdef
from rope.base import pyscopes
from rope.base import worder
from rope.contrib import fixsyntax
from rope.refactor import functionutils
def code_assist(
project,
source_code,
offset,
resource=None,
templates=None,
maxfixes=1,
later_locals=True,
):
"""Return python code completions as a list of `CodeAssistProposal`
`resource` is a `rope.base.resources.Resource` object. If
provided, relative imports are handled.
`maxfixes` is the maximum number of errors to fix if the code has
errors in it.
If `later_locals` is `False` names defined in this scope and after
this line is ignored.
"""
if templates is not None:
warnings.warn(
"Codeassist no longer supports templates", DeprecationWarning, stacklevel=2
)
assist = _PythonCodeAssist(
project,
source_code,
offset,
resource=resource,
maxfixes=maxfixes,
later_locals=later_locals,
)
return assist()
def starting_offset(source_code, offset):
"""Return the offset in which the completion should be inserted
Usually code assist proposals should be inserted like::
completion = proposal.name
result = (source_code[:starting_offset] +
completion + source_code[offset:])
Where starting_offset is the offset returned by this function.
"""
word_finder = worder.Worder(source_code, True)
expression, starting, starting_offset = word_finder.get_splitted_primary_before(
offset
)
return starting_offset
def get_doc(project, source_code, offset, resource=None, maxfixes=1):
"""Get the pydoc"""
fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes)
pyname = fixer.pyname_at(offset)
if pyname is None:
return None
pyobject = pyname.get_object()
return PyDocExtractor().get_doc(pyobject)
def get_calltip(
project,
source_code,
offset,
resource=None,
maxfixes=1,
ignore_unknown=False,
remove_self=False,
):
"""Get the calltip of a function
The format of the returned string is
``module_name.holding_scope_names.function_name(arguments)``. For
classes `__init__()` and for normal objects `__call__()` function
is used.
Note that the offset is on the function itself *not* after the its
open parenthesis. (Actually it used to be the other way but it
was easily confused when string literals were involved. So I
decided it is better for it not to try to be too clever when it
cannot be clever enough). You can use a simple search like::
offset = source_code.rindex('(', 0, offset) - 1
to handle simple situations.
If `ignore_unknown` is `True`, `None` is returned for functions
without source-code like builtins and extensions.
If `remove_self` is `True`, the first parameter whose name is self
will be removed for methods.
"""
fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes)
pyname = fixer.pyname_at(offset)
if pyname is None:
return None
pyobject = pyname.get_object()
return PyDocExtractor().get_calltip(pyobject, ignore_unknown, remove_self)
def get_definition_location(project, source_code, offset, resource=None, maxfixes=1):
"""Return the definition location of the python name at `offset`
Return a (`rope.base.resources.Resource`, lineno) tuple. If no
`resource` is given and the definition is inside the same module,
the first element of the returned tuple would be `None`. If the
location cannot be determined ``(None, None)`` is returned.
"""
fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes)
pyname = fixer.pyname_at(offset)
if pyname is not None:
module, lineno = pyname.get_definition_location()
if module is not None:
return module.get_module().get_resource(), lineno
return (None, None)
def find_occurrences(*args, **kwds):
import rope.contrib.findit
warnings.warn(
"Use `rope.contrib.findit.find_occurrences()` instead",
DeprecationWarning,
stacklevel=2,
)
return rope.contrib.findit.find_occurrences(*args, **kwds)
def get_canonical_path(project, resource, offset):
"""Get the canonical path to an object.
Given the offset of the object, this returns a list of
(name, name_type) tuples representing the canonical path to the
object. For example, the 'x' in the following code:
class Foo(object):
def bar(self):
class Qux(object):
def mux(self, x):
pass
we will return:
[('Foo', 'CLASS'), ('bar', 'FUNCTION'), ('Qux', 'CLASS'),
('mux', 'FUNCTION'), ('x', 'PARAMETER')]
`resource` is a `rope.base.resources.Resource` object.
`offset` is the offset of the pyname you want the path to.
"""
# Retrieve the PyName.
pymod = project.get_pymodule(resource)
pyname = rope.base.evaluate.eval_location(pymod, offset)
# Now get the location of the definition and its containing scope.
defmod, lineno = pyname.get_definition_location()
if not defmod:
return None
scope = defmod.get_scope().get_inner_scope_for_line(lineno)
# Start with the name of the object we're interested in.
names = []
if isinstance(pyname, pynamesdef.ParameterName):
names = [(worder.get_name_at(pymod.get_resource(), offset), "PARAMETER")]
elif isinstance(pyname, pynamesdef.AssignedName):
names = [(worder.get_name_at(pymod.get_resource(), offset), "VARIABLE")]
# Collect scope names.
while scope.parent:
if isinstance(scope, pyscopes.FunctionScope):
scope_type = "FUNCTION"
elif isinstance(scope, pyscopes.ClassScope):
scope_type = "CLASS"
else:
scope_type = None
names.append((scope.pyobject.get_name(), scope_type))
scope = scope.parent
names.append((defmod.get_resource().real_path, "MODULE"))
names.reverse()
return names
class CompletionProposal(object):
"""A completion proposal
The `scope` instance variable shows where proposed name came from
and can be 'global', 'local', 'builtin', 'attribute', 'keyword',
'imported', 'parameter_keyword'.
The `type` instance variable shows the approximate type of the
proposed object and can be 'instance', 'class', 'function', 'module',
and `None`.
All possible relations between proposal's `scope` and `type` are shown
in the table below (different scopes in rows and types in columns):
| instance | class | function | module | None
local | + | + | + | + |
global | + | + | + | + |
builtin | + | + | + | |
attribute | + | + | + | + |
imported | + | + | + | + |
keyword | | | | | +
parameter_keyword | | | | | +
"""
def __init__(self, name, scope, pyname=None):
self.name = name
self.pyname = pyname
self.scope = self._get_scope(scope)
def __str__(self):
return "%s (%s, %s)" % (self.name, self.scope, self.type)
def __repr__(self):
return str(self)
@property
def parameters(self):
"""The names of the parameters the function takes.
Returns None if this completion is not a function.
"""
pyname = self.pyname
if isinstance(pyname, pynames.ImportedName):
pyname = pyname._get_imported_pyname()
if isinstance(pyname, pynames.DefinedName):
pyobject = pyname.get_object()
if isinstance(pyobject, pyobjects.AbstractFunction):
return pyobject.get_param_names()
@property
def type(self):
pyname = self.pyname
if isinstance(pyname, builtins.BuiltinName):
pyobject = pyname.get_object()
if isinstance(pyobject, builtins.BuiltinFunction):
return "function"
elif isinstance(pyobject, builtins.BuiltinClass):
return "class"
elif isinstance(pyobject, builtins.BuiltinObject) or isinstance(
pyobject, builtins.BuiltinName
):
return "instance"
elif isinstance(pyname, pynames.ImportedModule):
return "module"
elif isinstance(pyname, pynames.ImportedName) or isinstance(
pyname, pynames.DefinedName
):
pyobject = pyname.get_object()
if isinstance(pyobject, pyobjects.AbstractFunction):
return "function"
if isinstance(pyobject, pyobjects.AbstractClass):
return "class"
return "instance"
def _get_scope(self, scope):
if isinstance(self.pyname, builtins.BuiltinName):
return "builtin"
if isinstance(self.pyname, pynames.ImportedModule) or isinstance(
self.pyname, pynames.ImportedName
):
return "imported"
return scope
def get_doc(self):
"""Get the proposed object's docstring.
Returns None if it can not be get.
"""
if not self.pyname:
return None
pyobject = self.pyname.get_object()
if not hasattr(pyobject, "get_doc"):
return None
return self.pyname.get_object().get_doc()
@property
def kind(self):
warnings.warn(
"the proposal's `kind` property is deprecated, " "use `scope` instead"
)
return self.scope
# leaved for backward compatibility
CodeAssistProposal = CompletionProposal
class NamedParamProposal(CompletionProposal):
"""A parameter keyword completion proposal
Holds reference to ``_function`` -- the function which
parameter ``name`` belongs to. This allows to determine
default value for this parameter.
"""
def __init__(self, name, function):
self.argname = name
name = "%s=" % name
super(NamedParamProposal, self).__init__(name, "parameter_keyword")
self._function = function
def get_default(self):
"""Get a string representation of a param's default value.
Returns None if there is no default value for this param.
"""
definfo = functionutils.DefinitionInfo.read(self._function)
for arg, default in definfo.args_with_defaults:
if self.argname == arg:
return default
return None
def sorted_proposals(proposals, scopepref=None, typepref=None):
"""Sort a list of proposals
Return a sorted list of the given `CodeAssistProposal`.
`scopepref` can be a list of proposal scopes. Defaults to
``['parameter_keyword', 'local', 'global', 'imported',
'attribute', 'builtin', 'keyword']``.
`typepref` can be a list of proposal types. Defaults to
``['class', 'function', 'instance', 'module', None]``.
(`None` stands for completions with no type like keywords.)
"""
sorter = _ProposalSorter(proposals, scopepref, typepref)
return sorter.get_sorted_proposal_list()
def starting_expression(source_code, offset):
"""Return the expression to complete"""
word_finder = worder.Worder(source_code, True)
expression, starting, starting_offset = word_finder.get_splitted_primary_before(
offset
)
if expression:
return expression + "." + starting
return starting
def default_templates():
warnings.warn(
"default_templates() is deprecated.", DeprecationWarning, stacklevel=2
)
return {}
class _PythonCodeAssist(object):
def __init__(
self, project, source_code, offset, resource=None, maxfixes=1, later_locals=True
):
self.project = project
self.code = source_code
self.resource = resource
self.maxfixes = maxfixes
self.later_locals = later_locals
self.word_finder = worder.Worder(source_code, True)
(
self.expression,
self.starting,
self.offset,
) = self.word_finder.get_splitted_primary_before(offset)
keywords = keyword.kwlist
def _find_starting_offset(self, source_code, offset):
current_offset = offset - 1
while current_offset >= 0 and (
source_code[current_offset].isalnum() or source_code[current_offset] in "_"
):
current_offset -= 1
return current_offset + 1
def _matching_keywords(self, starting):
result = []
for kw in self.keywords:
if kw.startswith(starting):
result.append(CompletionProposal(kw, "keyword"))
return result
def __call__(self):
if self.offset > len(self.code):
return []
completions = list(self._code_completions().values())
if self.expression.strip() == "" and self.starting.strip() != "":
completions.extend(self._matching_keywords(self.starting))
return completions
def _dotted_completions(self, module_scope, holding_scope):
result = {}
found_pyname = rope.base.evaluate.eval_str(holding_scope, self.expression)
if found_pyname is not None:
element = found_pyname.get_object()
compl_scope = "attribute"
if isinstance(element, (pyobjectsdef.PyModule, pyobjectsdef.PyPackage)):
compl_scope = "imported"
for name, pyname in element.get_attributes().items():
if name.startswith(self.starting):
result[name] = CompletionProposal(name, compl_scope, pyname)
return result
def _undotted_completions(self, scope, result, lineno=None):
if scope.parent is not None:
self._undotted_completions(scope.parent, result)
if lineno is None:
names = scope.get_propagated_names()
else:
names = scope.get_names()
for name, pyname in names.items():
if name.startswith(self.starting):
compl_scope = "local"
if scope.get_kind() == "Module":
compl_scope = "global"
if (
lineno is None
or self.later_locals
or not self._is_defined_after(scope, pyname, lineno)
):
result[name] = CompletionProposal(name, compl_scope, pyname)
def _from_import_completions(self, pymodule):
module_name = self.word_finder.get_from_module(self.offset)
if module_name is None:
return {}
pymodule = self._find_module(pymodule, module_name)
result = {}
for name in pymodule:
if name.startswith(self.starting):
result[name] = CompletionProposal(
name, scope="global", pyname=pymodule[name]
)
return result
def _find_module(self, pymodule, module_name):
dots = 0
while module_name[dots] == ".":
dots += 1
pyname = pynames.ImportedModule(pymodule, module_name[dots:], dots)
return pyname.get_object()
def _is_defined_after(self, scope, pyname, lineno):
location = pyname.get_definition_location()
if location is not None and location[1] is not None:
if (
location[0] == scope.pyobject.get_module()
and lineno <= location[1] <= scope.get_end()
):
return True
def _code_completions(self):
lineno = self.code.count("\n", 0, self.offset) + 1
fixer = fixsyntax.FixSyntax(
self.project, self.code, self.resource, self.maxfixes
)
pymodule = fixer.get_pymodule()
module_scope = pymodule.get_scope()
code = pymodule.source_code
lines = code.split("\n")
result = {}
start = fixsyntax._logical_start(lines, lineno)
indents = fixsyntax._get_line_indents(lines[start - 1])
inner_scope = module_scope.get_inner_scope_for_line(start, indents)
if self.word_finder.is_a_name_after_from_import(self.offset):
return self._from_import_completions(pymodule)
if self.expression.strip() != "":
result.update(self._dotted_completions(module_scope, inner_scope))
else:
result.update(self._keyword_parameters(module_scope.pyobject, inner_scope))
self._undotted_completions(inner_scope, result, lineno=lineno)
return result
def _keyword_parameters(self, pymodule, scope):
offset = self.offset
if offset == 0:
return {}
word_finder = worder.Worder(self.code, True)
if word_finder.is_on_function_call_keyword(offset - 1):
function_parens = word_finder.find_parens_start_from_inside(offset - 1)
primary = word_finder.get_primary_at(function_parens - 1)
try:
function_pyname = rope.base.evaluate.eval_str(scope, primary)
except exceptions.BadIdentifierError:
return {}
if function_pyname is not None:
pyobject = function_pyname.get_object()
if isinstance(pyobject, pyobjects.AbstractFunction):
pass
elif (
isinstance(pyobject, pyobjects.AbstractClass)
and "__init__" in pyobject
):
pyobject = pyobject["__init__"].get_object()
elif "__call__" in pyobject:
pyobject = pyobject["__call__"].get_object()
if isinstance(pyobject, pyobjects.AbstractFunction):
param_names = []
param_names.extend(pyobject.get_param_names(special_args=False))
result = {}
for name in param_names:
if name.startswith(self.starting):
result[name + "="] = NamedParamProposal(name, pyobject)
return result
return {}
class _ProposalSorter(object):
"""Sort a list of code assist proposals"""
def __init__(self, code_assist_proposals, scopepref=None, typepref=None):
self.proposals = code_assist_proposals
if scopepref is None:
scopepref = [
"parameter_keyword",
"local",
"global",
"imported",
"attribute",
"builtin",
"keyword",
]
self.scopepref = scopepref
if typepref is None:
typepref = ["class", "function", "instance", "module", None]
self.typerank = dict((type, index) for index, type in enumerate(typepref))
def get_sorted_proposal_list(self):
"""Return a list of `CodeAssistProposal`"""
proposals = {}
for proposal in self.proposals:
proposals.setdefault(proposal.scope, []).append(proposal)
result = []
for scope in self.scopepref:
scope_proposals = proposals.get(scope, [])
scope_proposals = [
proposal
for proposal in scope_proposals
if proposal.type in self.typerank
]
scope_proposals.sort(key=self._proposal_key)
result.extend(scope_proposals)
return result
def _proposal_key(self, proposal1):
def _underline_count(name):
return sum(1 for c in name if c == "_")
return (
self.typerank.get(proposal1.type, 100),
_underline_count(proposal1.name),
proposal1.name,
)
# if proposal1.type != proposal2.type:
# return cmp(self.typerank.get(proposal1.type, 100),
# self.typerank.get(proposal2.type, 100))
# return self._compare_underlined_names(proposal1.name,
# proposal2.name)
class PyDocExtractor(object):
def get_doc(self, pyobject):
if isinstance(pyobject, pyobjects.AbstractFunction):
return self._get_function_docstring(pyobject)
elif isinstance(pyobject, pyobjects.AbstractClass):
return self._get_class_docstring(pyobject)
elif isinstance(pyobject, pyobjects.AbstractModule):
return self._trim_docstring(pyobject.get_doc())
return None
def get_calltip(self, pyobject, ignore_unknown=False, remove_self=False):
try:
if isinstance(pyobject, pyobjects.AbstractClass):
pyobject = pyobject["__init__"].get_object()
if not isinstance(pyobject, pyobjects.AbstractFunction):
pyobject = pyobject["__call__"].get_object()
except exceptions.AttributeNotFoundError:
return None
if ignore_unknown and not isinstance(pyobject, pyobjects.PyFunction):
return
if isinstance(pyobject, pyobjects.AbstractFunction):
result = self._get_function_signature(pyobject, add_module=True)
if remove_self and self._is_method(pyobject):
return result.replace("(self)", "()").replace("(self, ", "(")
return result
def _get_class_docstring(self, pyclass):
contents = self._trim_docstring(pyclass.get_doc(), 2)
supers = [super.get_name() for super in pyclass.get_superclasses()]
doc = "class %s(%s):\n\n" % (pyclass.get_name(), ", ".join(supers)) + contents
if "__init__" in pyclass:
init = pyclass["__init__"].get_object()
if isinstance(init, pyobjects.AbstractFunction):
doc += "\n\n" + self._get_single_function_docstring(init)
return doc
def _get_function_docstring(self, pyfunction):
functions = [pyfunction]
if self._is_method(pyfunction):
functions.extend(
self._get_super_methods(pyfunction.parent, pyfunction.get_name())
)
return "\n\n".join(
[self._get_single_function_docstring(function) for function in functions]
)
def _is_method(self, pyfunction):
return isinstance(pyfunction, pyobjects.PyFunction) and isinstance(
pyfunction.parent, pyobjects.PyClass
)
def _get_single_function_docstring(self, pyfunction):
signature = self._get_function_signature(pyfunction)
docs = self._trim_docstring(pyfunction.get_doc(), indents=2)
return signature + ":\n\n" + docs
def _get_super_methods(self, pyclass, name):
result = []
for super_class in pyclass.get_superclasses():
if name in super_class:
function = super_class[name].get_object()
if isinstance(function, pyobjects.AbstractFunction):
result.append(function)
result.extend(self._get_super_methods(super_class, name))
return result
def _get_function_signature(self, pyfunction, add_module=False):
location = self._location(pyfunction, add_module)
if isinstance(pyfunction, pyobjects.PyFunction):
info = functionutils.DefinitionInfo.read(pyfunction)
return location + info.to_string()
else:
return "%s(%s)" % (
location + pyfunction.get_name(),
", ".join(pyfunction.get_param_names()),
)
def _location(self, pyobject, add_module=False):
location = []
parent = pyobject.parent
while parent and not isinstance(parent, pyobjects.AbstractModule):
location.append(parent.get_name())
location.append(".")
parent = parent.parent
if add_module:
if isinstance(pyobject, pyobjects.PyFunction):
location.insert(0, self._get_module(pyobject))
if isinstance(parent, builtins.BuiltinModule):
location.insert(0, parent.get_name() + ".")
return "".join(location)
def _get_module(self, pyfunction):
module = pyfunction.get_module()
if module is not None:
resource = module.get_resource()
if resource is not None:
return libutils.modname(resource) + "."
return ""
def _trim_docstring(self, docstring, indents=0):
"""The sample code from :PEP:`257`"""
if not docstring:
return ""
# Convert tabs to spaces (following normal Python rules)
# and split into a list of lines:
lines = docstring.expandtabs().splitlines()
# Determine minimum indentation (first line doesn't count):
indent = sys.maxsize
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
# Remove indentation (first line is special):
trimmed = [lines[0].strip()]
if indent < sys.maxsize:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
# Strip off trailing and leading blank lines:
while trimmed and not trimmed[-1]:
trimmed.pop()
while trimmed and not trimmed[0]:
trimmed.pop(0)
# Return a single string:
return "\n".join((" " * indents + line for line in trimmed))
# Deprecated classes
class TemplateProposal(CodeAssistProposal):
def __init__(self, name, template):
warnings.warn(
"TemplateProposal is deprecated.", DeprecationWarning, stacklevel=2
)
super(TemplateProposal, self).__init__(name, "template")
self.template = template
class Template(object):
def __init__(self, template):
self.template = template
warnings.warn("Template is deprecated.", DeprecationWarning, stacklevel=2)
def variables(self):
return []
def substitute(self, mapping):
return self.template
def get_cursor_location(self, mapping):
return len(self.template)

View file

@ -0,0 +1,90 @@
"""Finding bad name and attribute accesses
`find_errors` function can be used to find possible bad name and
attribute accesses. As an example::
errors = find_errors(project, project.get_resource('mod.py'))
for error in errors:
print('%s: %s' % (error.lineno, error.error))
prints possible errors for ``mod.py`` file.
TODO:
* use task handles
* reporting names at most once
* attributes of extension modules that don't appear in
extension_modules project config can be ignored
* not calling `PyScope.get_inner_scope_for_line()` if it is a
bottleneck; needs profiling
* not reporting occurrences where rope cannot infer the object
* rope saves multiple objects for some of the names in its objectdb
use all of them not to give false positives
* ... ;-)
"""
from rope.base import ast, evaluate, pyobjects
def find_errors(project, resource):
"""Find possible bad name and attribute accesses
It returns a list of `Error`.
"""
pymodule = project.get_pymodule(resource)
finder = _BadAccessFinder(pymodule)
ast.walk(pymodule.get_ast(), finder)
return finder.errors
class _BadAccessFinder(object):
def __init__(self, pymodule):
self.pymodule = pymodule
self.scope = pymodule.get_scope()
self.errors = []
def _Name(self, node):
if isinstance(node.ctx, (ast.Store, ast.Param)):
return
scope = self.scope.get_inner_scope_for_line(node.lineno)
pyname = scope.lookup(node.id)
if pyname is None:
self._add_error(node, "Unresolved variable")
elif self._is_defined_after(scope, pyname, node.lineno):
self._add_error(node, "Defined later")
def _Attribute(self, node):
if not isinstance(node.ctx, ast.Store):
scope = self.scope.get_inner_scope_for_line(node.lineno)
pyname = evaluate.eval_node(scope, node.value)
if pyname is not None and pyname.get_object() != pyobjects.get_unknown():
if node.attr not in pyname.get_object():
self._add_error(node, "Unresolved attribute")
ast.walk(node.value, self)
def _add_error(self, node, msg):
if isinstance(node, ast.Attribute):
name = node.attr
else:
name = node.id
if name != "None":
error = Error(node.lineno, msg + " " + name)
self.errors.append(error)
def _is_defined_after(self, scope, pyname, lineno):
location = pyname.get_definition_location()
if location is not None and location[1] is not None:
if (
location[0] == self.pymodule
and lineno <= location[1] <= scope.get_end()
):
return True
class Error(object):
def __init__(self, lineno, error):
self.lineno = lineno
self.error = error
def __str__(self):
return "%s: %s" % (self.lineno, self.error)

View file

@ -0,0 +1,125 @@
import rope.base.codeanalyze
import rope.base.evaluate
import rope.base.pyobjects
from rope.base import taskhandle, exceptions, worder
from rope.contrib import fixsyntax
from rope.refactor import occurrences
def find_occurrences(
project,
resource,
offset,
unsure=False,
resources=None,
in_hierarchy=False,
task_handle=taskhandle.NullTaskHandle(),
):
"""Return a list of `Location`
If `unsure` is `True`, possible matches are returned, too. You
can use `Location.unsure` to see which are unsure occurrences.
`resources` can be a list of `rope.base.resource.File` that
should be searched for occurrences; if `None` all python files
in the project are searched.
"""
name = worder.get_name_at(resource, offset)
this_pymodule = project.get_pymodule(resource)
primary, pyname = rope.base.evaluate.eval_location2(this_pymodule, offset)
def is_match(occurrence):
return unsure
finder = occurrences.create_finder(
project,
name,
pyname,
unsure=is_match,
in_hierarchy=in_hierarchy,
instance=primary,
)
if resources is None:
resources = project.get_python_files()
job_set = task_handle.create_jobset("Finding Occurrences", count=len(resources))
return _find_locations(finder, resources, job_set)
def find_implementations(
project, resource, offset, resources=None, task_handle=taskhandle.NullTaskHandle()
):
"""Find the places a given method is overridden.
Finds the places a method is implemented. Returns a list of
`Location`.
"""
name = worder.get_name_at(resource, offset)
this_pymodule = project.get_pymodule(resource)
pyname = rope.base.evaluate.eval_location(this_pymodule, offset)
if pyname is not None:
pyobject = pyname.get_object()
if (
not isinstance(pyobject, rope.base.pyobjects.PyFunction)
or pyobject.get_kind() != "method"
):
raise exceptions.BadIdentifierError("Not a method!")
else:
raise exceptions.BadIdentifierError("Cannot resolve the identifier!")
def is_defined(occurrence):
if not occurrence.is_defined():
return False
def not_self(occurrence):
if occurrence.get_pyname().get_object() == pyname.get_object():
return False
filters = [is_defined, not_self, occurrences.InHierarchyFilter(pyname, True)]
finder = occurrences.Finder(project, name, filters=filters)
if resources is None:
resources = project.get_python_files()
job_set = task_handle.create_jobset("Finding Implementations", count=len(resources))
return _find_locations(finder, resources, job_set)
def find_definition(project, code, offset, resource=None, maxfixes=1):
"""Return the definition location of the python name at `offset`
A `Location` object is returned if the definition location can be
determined, otherwise ``None`` is returned.
"""
fixer = fixsyntax.FixSyntax(project, code, resource, maxfixes)
pyname = fixer.pyname_at(offset)
if pyname is not None:
module, lineno = pyname.get_definition_location()
name = rope.base.worder.Worder(code).get_word_at(offset)
if lineno is not None:
start = module.lines.get_line_start(lineno)
def check_offset(occurrence):
if occurrence.offset < start:
return False
pyname_filter = occurrences.PyNameFilter(pyname)
finder = occurrences.Finder(project, name, [check_offset, pyname_filter])
for occurrence in finder.find_occurrences(pymodule=module):
return Location(occurrence)
class Location(object):
def __init__(self, occurrence):
self.resource = occurrence.resource
self.region = occurrence.get_word_range()
self.offset = self.region[0]
self.unsure = occurrence.is_unsure()
self.lineno = occurrence.lineno
def _find_locations(finder, resources, job_set):
result = []
for resource in resources:
job_set.started_job(resource.path)
for occurrence in finder.find_occurrences(resource):
result.append(Location(occurrence))
job_set.finished_job()
return result

View file

@ -0,0 +1,68 @@
"""Fix the name of modules
This module is useful when you want to rename many of the modules in
your project. That can happen specially when you want to change their
naming style.
For instance::
fixer = FixModuleNames(project)
changes = fixer.get_changes(fixer=str.lower)
project.do(changes)
Here it renames all modules and packages to use lower-cased chars.
You can tell it to use any other style by using the ``fixer``
argument.
"""
from rope.base import taskhandle
from rope.contrib import changestack
from rope.refactor import rename
class FixModuleNames(object):
def __init__(self, project):
self.project = project
def get_changes(self, fixer=str.lower, task_handle=taskhandle.NullTaskHandle()):
"""Fix module names
`fixer` is a function that takes and returns a `str`. Given
the name of a module, it should return the fixed name.
"""
stack = changestack.ChangeStack(self.project, "Fixing module names")
jobset = task_handle.create_jobset(
"Fixing module names", self._count_fixes(fixer) + 1
)
try:
while True:
for resource in self._tobe_fixed(fixer):
jobset.started_job(resource.path)
renamer = rename.Rename(self.project, resource)
changes = renamer.get_changes(fixer(self._name(resource)))
stack.push(changes)
jobset.finished_job()
break
else:
break
finally:
jobset.started_job("Reverting to original state")
stack.pop_all()
jobset.finished_job()
return stack.merged()
def _count_fixes(self, fixer):
return len(list(self._tobe_fixed(fixer)))
def _tobe_fixed(self, fixer):
for resource in self.project.get_python_files():
modname = self._name(resource)
if modname != fixer(modname):
yield resource
def _name(self, resource):
modname = resource.name.rsplit(".", 1)[0]
if modname == "__init__":
modname = resource.parent.name
return modname

View file

@ -0,0 +1,189 @@
import rope.base.codeanalyze
import rope.base.evaluate
from rope.base import exceptions
from rope.base import libutils
from rope.base import utils
from rope.base import worder
from rope.base.codeanalyze import ArrayLinesAdapter, LogicalLineFinder
class FixSyntax(object):
def __init__(self, project, code, resource, maxfixes=1):
self.project = project
self.code = code
self.resource = resource
self.maxfixes = maxfixes
@utils.saveit
def get_pymodule(self):
"""Get a `PyModule`"""
msg = None
code = self.code
tries = 0
while True:
try:
if (
tries == 0
and self.resource is not None
and self.resource.read() == code
):
return self.project.get_pymodule(self.resource, force_errors=True)
return libutils.get_string_module(
self.project, code, resource=self.resource, force_errors=True
)
except exceptions.ModuleSyntaxError as e:
if msg is None:
msg = "%s:%s %s" % (e.filename, e.lineno, e.message_)
if tries < self.maxfixes:
tries += 1
self.commenter.comment(e.lineno)
code = "\n".join(self.commenter.lines)
else:
raise exceptions.ModuleSyntaxError(
e.filename, e.lineno, "Failed to fix error: {0}".format(msg)
)
@property
@utils.saveit
def commenter(self):
return _Commenter(self.code)
def pyname_at(self, offset):
pymodule = self.get_pymodule()
def old_pyname():
word_finder = worder.Worder(self.code, True)
expression = word_finder.get_primary_at(offset)
expression = expression.replace("\\\n", " ").replace("\n", " ")
lineno = self.code.count("\n", 0, offset)
scope = pymodule.get_scope().get_inner_scope_for_line(lineno)
return rope.base.evaluate.eval_str(scope, expression)
new_code = pymodule.source_code
def new_pyname():
newoffset = self.commenter.transfered_offset(offset)
return rope.base.evaluate.eval_location(pymodule, newoffset)
if new_code.startswith(self.code[: offset + 1]):
return new_pyname()
result = old_pyname()
if result is None:
return new_pyname()
return result
class _Commenter(object):
def __init__(self, code):
self.code = code
self.lines = self.code.split("\n")
self.lines.append("\n")
self.origs = list(range(len(self.lines) + 1))
self.diffs = [0] * (len(self.lines) + 1)
def comment(self, lineno):
start = _logical_start(self.lines, lineno, check_prev=True) - 1
# using self._get_stmt_end() instead of self._get_block_end()
# to lower commented lines
end = self._get_stmt_end(start)
indents = _get_line_indents(self.lines[start])
if 0 < start:
last_lineno = self._last_non_blank(start - 1)
last_line = self.lines[last_lineno]
if last_line.rstrip().endswith(":"):
indents = _get_line_indents(last_line) + 4
self._set(start, " " * indents + "pass")
for line in range(start + 1, end + 1):
self._set(line, self.lines[start])
self._fix_incomplete_try_blocks(lineno, indents)
def transfered_offset(self, offset):
lineno = self.code.count("\n", 0, offset)
diff = sum(self.diffs[:lineno])
return offset + diff
def _last_non_blank(self, start):
while start > 0 and self.lines[start].strip() == "":
start -= 1
return start
def _get_block_end(self, lineno):
end_line = lineno
base_indents = _get_line_indents(self.lines[lineno])
for i in range(lineno + 1, len(self.lines)):
if _get_line_indents(self.lines[i]) >= base_indents:
end_line = i
else:
break
return end_line
def _get_stmt_end(self, lineno):
base_indents = _get_line_indents(self.lines[lineno])
for i in range(lineno + 1, len(self.lines)):
if _get_line_indents(self.lines[i]) <= base_indents:
return i - 1
return lineno
def _fix_incomplete_try_blocks(self, lineno, indents):
block_start = lineno
last_indents = indents
while block_start > 0:
block_start = (
rope.base.codeanalyze.get_block_start(
ArrayLinesAdapter(self.lines), block_start
)
- 1
)
if self.lines[block_start].strip().startswith("try:"):
indents = _get_line_indents(self.lines[block_start])
if indents > last_indents:
continue
last_indents = indents
block_end = self._find_matching_deindent(block_start)
line = self.lines[block_end].strip()
if not (
line.startswith("finally:")
or line.startswith("except ")
or line.startswith("except:")
):
self._insert(block_end, " " * indents + "finally:")
self._insert(block_end + 1, " " * indents + " pass")
def _find_matching_deindent(self, line_number):
indents = _get_line_indents(self.lines[line_number])
current_line = line_number + 1
while current_line < len(self.lines):
line = self.lines[current_line]
if not line.strip().startswith("#") and not line.strip() == "":
# HACK: We should have used logical lines here
if _get_line_indents(self.lines[current_line]) <= indents:
return current_line
current_line += 1
return len(self.lines) - 1
def _set(self, lineno, line):
self.diffs[self.origs[lineno]] += len(line) - len(self.lines[lineno])
self.lines[lineno] = line
def _insert(self, lineno, line):
self.diffs[self.origs[lineno]] += len(line) + 1
self.origs.insert(lineno, self.origs[lineno])
self.lines.insert(lineno, line)
def _logical_start(lines, lineno, check_prev=False):
logical_finder = LogicalLineFinder(ArrayLinesAdapter(lines))
if check_prev:
prev = lineno - 1
while prev > 0:
start, end = logical_finder.logical_line_in(prev)
if end is None or start <= lineno < end:
return start
if start <= prev:
break
prev -= 1
return logical_finder.logical_line_in(lineno)[0]
def _get_line_indents(line):
return rope.base.codeanalyze.count_line_indents(line)

View file

@ -0,0 +1,388 @@
import rope.base.evaluate
from rope.base import libutils
from rope.base import change, pyobjects, exceptions, pynames, worder, codeanalyze
from rope.refactor import sourceutils, importutils, functionutils, suites
def create_generate(kind, project, resource, offset, goal_resource=None):
"""A factory for creating `Generate` objects
`kind` can be 'variable', 'function', 'class', 'module' or
'package'.
"""
generate = eval("Generate" + kind.title())
return generate(project, resource, offset, goal_resource=goal_resource)
def create_module(project, name, sourcefolder=None):
"""Creates a module and returns a `rope.base.resources.File`"""
if sourcefolder is None:
sourcefolder = project.root
packages = name.split(".")
parent = sourcefolder
for package in packages[:-1]:
parent = parent.get_child(package)
return parent.create_file(packages[-1] + ".py")
def create_package(project, name, sourcefolder=None):
"""Creates a package and returns a `rope.base.resources.Folder`"""
if sourcefolder is None:
sourcefolder = project.root
packages = name.split(".")
parent = sourcefolder
for package in packages[:-1]:
parent = parent.get_child(package)
made_packages = parent.create_folder(packages[-1])
made_packages.create_file("__init__.py")
return made_packages
class _Generate(object):
def __init__(self, project, resource, offset, goal_resource=None):
self.project = project
self.resource = resource
self.goal_resource = goal_resource
self.info = self._generate_info(project, resource, offset)
self.name = self.info.get_name()
self._check_exceptional_conditions()
def _generate_info(self, project, resource, offset):
return _GenerationInfo(project.pycore, resource, offset, self.goal_resource)
def _check_exceptional_conditions(self):
if self.info.element_already_exists():
raise exceptions.RefactoringError(
"Element <%s> already exists." % self.name
)
if not self.info.primary_is_found():
raise exceptions.RefactoringError(
"Cannot determine the scope <%s> should be defined in." % self.name
)
def get_changes(self):
changes = change.ChangeSet(
"Generate %s <%s>" % (self._get_element_kind(), self.name)
)
indents = self.info.get_scope_indents()
blanks = self.info.get_blank_lines()
base_definition = sourceutils.fix_indentation(self._get_element(), indents)
definition = "\n" * blanks[0] + base_definition + "\n" * blanks[1]
resource = self.info.get_insertion_resource()
start, end = self.info.get_insertion_offsets()
collector = codeanalyze.ChangeCollector(resource.read())
collector.add_change(start, end, definition)
changes.add_change(change.ChangeContents(resource, collector.get_changed()))
if self.goal_resource:
relative_import = _add_relative_import_to_module(
self.project, self.resource, self.goal_resource, self.name
)
changes.add_change(relative_import)
return changes
def get_location(self):
return (self.info.get_insertion_resource(), self.info.get_insertion_lineno())
def _get_element_kind(self):
raise NotImplementedError()
def _get_element(self):
raise NotImplementedError()
class GenerateFunction(_Generate):
def _generate_info(self, project, resource, offset):
return _FunctionGenerationInfo(project.pycore, resource, offset)
def _get_element(self):
decorator = ""
args = []
if self.info.is_static_method():
decorator = "@staticmethod\n"
if (
self.info.is_method()
or self.info.is_constructor()
or self.info.is_instance()
):
args.append("self")
args.extend(self.info.get_passed_args())
definition = "%sdef %s(%s):\n pass\n" % (
decorator,
self.name,
", ".join(args),
)
return definition
def _get_element_kind(self):
return "Function"
class GenerateVariable(_Generate):
def _get_element(self):
return "%s = None\n" % self.name
def _get_element_kind(self):
return "Variable"
class GenerateClass(_Generate):
def _get_element(self):
return "class %s(object):\n pass\n" % self.name
def _get_element_kind(self):
return "Class"
class GenerateModule(_Generate):
def get_changes(self):
package = self.info.get_package()
changes = change.ChangeSet("Generate Module <%s>" % self.name)
new_resource = self.project.get_file("%s/%s.py" % (package.path, self.name))
if new_resource.exists():
raise exceptions.RefactoringError(
"Module <%s> already exists" % new_resource.path
)
changes.add_change(change.CreateResource(new_resource))
changes.add_change(
_add_import_to_module(self.project, self.resource, new_resource)
)
return changes
def get_location(self):
package = self.info.get_package()
return (package.get_child("%s.py" % self.name), 1)
class GeneratePackage(_Generate):
def get_changes(self):
package = self.info.get_package()
changes = change.ChangeSet("Generate Package <%s>" % self.name)
new_resource = self.project.get_folder("%s/%s" % (package.path, self.name))
if new_resource.exists():
raise exceptions.RefactoringError(
"Package <%s> already exists" % new_resource.path
)
changes.add_change(change.CreateResource(new_resource))
changes.add_change(
_add_import_to_module(self.project, self.resource, new_resource)
)
child = self.project.get_folder(package.path + "/" + self.name)
changes.add_change(change.CreateFile(child, "__init__.py"))
return changes
def get_location(self):
package = self.info.get_package()
child = package.get_child(self.name)
return (child.get_child("__init__.py"), 1)
def _add_import_to_module(project, resource, imported):
pymodule = project.get_pymodule(resource)
import_tools = importutils.ImportTools(project)
module_imports = import_tools.module_imports(pymodule)
module_name = libutils.modname(imported)
new_import = importutils.NormalImport(((module_name, None),))
module_imports.add_import(new_import)
return change.ChangeContents(resource, module_imports.get_changed_source())
def _add_relative_import_to_module(project, resource, imported, name):
pymodule = project.get_pymodule(resource)
import_tools = importutils.ImportTools(project)
module_imports = import_tools.module_imports(pymodule)
new_import = import_tools.get_from_import(imported, name)
module_imports.add_import(new_import)
return change.ChangeContents(resource, module_imports.get_changed_source())
class _GenerationInfo(object):
def __init__(self, pycore, resource, offset, goal_resource=None):
self.pycore = pycore
self.resource = resource
self.offset = offset
self.goal_resource = goal_resource
self.source_pymodule = self.pycore.project.get_pymodule(resource)
finder = rope.base.evaluate.ScopeNameFinder(self.source_pymodule)
self.primary, self.pyname = finder.get_primary_and_pyname_at(offset)
self._init_fields()
def _init_fields(self):
self.source_scope = self._get_source_scope()
self.goal_scope = self._get_goal_scope()
self.goal_pymodule = self._get_goal_module(self.goal_scope)
def _get_goal_scope(self):
if self.primary is None:
if self.goal_resource:
return self.pycore.project.get_pymodule(self.goal_resource).get_scope()
else:
return self._get_source_scope()
pyobject = self.primary.get_object()
if isinstance(pyobject, pyobjects.PyDefinedObject):
return pyobject.get_scope()
elif isinstance(pyobject.get_type(), pyobjects.PyClass):
return pyobject.get_type().get_scope()
def _get_goal_module(self, scope):
if scope is None:
return
while scope.parent is not None:
scope = scope.parent
return scope.pyobject
def _get_source_scope(self):
module_scope = self.source_pymodule.get_scope()
lineno = self.source_pymodule.lines.get_line_number(self.offset)
return module_scope.get_inner_scope_for_line(lineno)
def get_insertion_lineno(self):
lines = self.goal_pymodule.lines
if self.goal_scope == self.source_scope:
line_finder = self.goal_pymodule.logical_lines
lineno = lines.get_line_number(self.offset)
lineno = line_finder.logical_line_in(lineno)[0]
root = suites.ast_suite_tree(self.goal_scope.pyobject.get_ast())
suite = root.find_suite(lineno)
indents = sourceutils.get_indents(lines, lineno)
while self.get_scope_indents() < indents:
lineno = suite.get_start()
indents = sourceutils.get_indents(lines, lineno)
suite = suite.parent
return lineno
else:
return min(self.goal_scope.get_end() + 1, lines.length())
def get_insertion_resource(self):
return self.goal_pymodule.get_resource()
def get_insertion_offsets(self):
if self.goal_scope.get_kind() == "Class":
start, end = sourceutils.get_body_region(self.goal_scope.pyobject)
if self.goal_pymodule.source_code[start:end].strip() == "pass":
return start, end
lines = self.goal_pymodule.lines
start = lines.get_line_start(self.get_insertion_lineno())
return (start, start)
def get_scope_indents(self):
if self.goal_scope.get_kind() == "Module":
return 0
return (
sourceutils.get_indents(
self.goal_pymodule.lines, self.goal_scope.get_start()
)
+ 4
)
def get_blank_lines(self):
if self.goal_scope.get_kind() == "Module":
base_blanks = 2
if self.goal_pymodule.source_code.strip() == "":
base_blanks = 0
if self.goal_scope.get_kind() == "Class":
base_blanks = 1
if self.goal_scope.get_kind() == "Function":
base_blanks = 0
if self.goal_scope == self.source_scope:
return (0, base_blanks)
return (base_blanks, 0)
def get_package(self):
primary = self.primary
if self.primary is None:
return self.pycore.project.get_source_folders()[0]
if isinstance(primary.get_object(), pyobjects.PyPackage):
return primary.get_object().get_resource()
raise exceptions.RefactoringError(
"A module/package can be only created in a package."
)
def primary_is_found(self):
return self.goal_scope is not None
def element_already_exists(self):
if self.pyname is None or isinstance(self.pyname, pynames.UnboundName):
return False
return self.get_name() in self.goal_scope.get_defined_names()
def get_name(self):
return worder.get_name_at(self.resource, self.offset)
class _FunctionGenerationInfo(_GenerationInfo):
def _get_goal_scope(self):
if self.is_constructor():
return self.pyname.get_object().get_scope()
if self.is_instance():
return self.pyname.get_object().get_type().get_scope()
if self.primary is None:
return self._get_source_scope()
pyobject = self.primary.get_object()
if isinstance(pyobject, pyobjects.PyDefinedObject):
return pyobject.get_scope()
elif isinstance(pyobject.get_type(), pyobjects.PyClass):
return pyobject.get_type().get_scope()
def element_already_exists(self):
if self.pyname is None or isinstance(self.pyname, pynames.UnboundName):
return False
return self.get_name() in self.goal_scope.get_defined_names()
def is_static_method(self):
return self.primary is not None and isinstance(
self.primary.get_object(), pyobjects.PyClass
)
def is_method(self):
return self.primary is not None and isinstance(
self.primary.get_object().get_type(), pyobjects.PyClass
)
def is_constructor(self):
return self.pyname is not None and isinstance(
self.pyname.get_object(), pyobjects.PyClass
)
def is_instance(self):
if self.pyname is None:
return False
pyobject = self.pyname.get_object()
return isinstance(pyobject.get_type(), pyobjects.PyClass)
def get_name(self):
if self.is_constructor():
return "__init__"
if self.is_instance():
return "__call__"
return worder.get_name_at(self.resource, self.offset)
def get_passed_args(self):
result = []
source = self.source_pymodule.source_code
finder = worder.Worder(source)
if finder.is_a_function_being_called(self.offset):
start, end = finder.get_primary_range(self.offset)
parens_start, parens_end = finder.get_word_parens_range(end - 1)
call = source[start:parens_end]
parser = functionutils._FunctionParser(call, False)
args, keywords = parser.get_parameters()
for arg in args:
if self._is_id(arg):
result.append(arg)
else:
result.append("arg%d" % len(result))
for name, value in keywords:
result.append(name)
return result
def _is_id(self, arg):
def id_or_underline(c):
return c.isalpha() or c == "_"
for c in arg:
if not id_or_underline(c) and not c.isdigit():
return False
return id_or_underline(arg[0])