File: C:/Users/fred/anaconda3/Lib/site-packages/rope/refactor/importutils/module_imports.py
from typing import List, Union
from rope.base import ast, exceptions, pynames, pynamesdef, utils
from rope.refactor.importutils import actions, importinfo
class ModuleImports:
def __init__(self, project, pymodule, import_filter=None):
self.project = project
self.pymodule = pymodule
self.separating_lines = 0
self.filter = import_filter
self.sorted = False
@property
@utils.saveit
def imports(self):
finder = _GlobalImportFinder(self.pymodule)
result = finder.find_import_statements()
self.separating_lines = finder.get_separating_line_count()
if self.filter is not None:
for import_stmt in result:
if not self.filter(import_stmt):
import_stmt.readonly = True
return result
def _get_unbound_names(self, defined_pyobject):
visitor = _GlobalUnboundNameFinder(self.pymodule, defined_pyobject)
visitor.visit(self.pymodule.get_ast())
return visitor.unbound
def _get_all_star_list(self, pymodule):
def _resolve_name(
name: Union[pynamesdef.AssignedName, pynames.ImportedName]
) -> List:
while isinstance(name, pynames.ImportedName):
try:
name = name.imported_module.get_object().get_attribute(
name.imported_name,
)
except exceptions.AttributeNotFoundError:
return []
assert isinstance(name, pynamesdef.AssignedName)
return name.assignments
result = set()
try:
all_star_list = pymodule.get_attribute("__all__")
except exceptions.AttributeNotFoundError:
return result
assignments = [
assignment.ast_node for assignment in _resolve_name(all_star_list)
]
# FIXME: Need a better way to recursively infer possible values.
# Currently pyobjects can recursively infer type, but not values.
# Do a very basic 1-level value inference
while assignments:
assignment = assignments.pop()
if isinstance(assignment, ast.List):
stack = list(assignment.elts)
while stack:
el = stack.pop()
if isinstance(el, ast.IfExp):
stack.append(el.body)
stack.append(el.orelse)
elif isinstance(el, ast.Starred):
assignments.append(el.value)
else:
if isinstance(el, ast.Constant) and isinstance(el.value, str):
result.add(el.value)
elif isinstance(el, ast.Name):
try:
name = pymodule.get_attribute(el.id)
except exceptions.AttributeNotFoundError:
continue
else:
for av in _resolve_name(name):
if isinstance(
av.ast_node,
ast.Constant,
) and isinstance(
av.ast_node.value,
str,
):
result.add(av.ast_node.value)
elif isinstance(assignment, ast.Name):
try:
name = pymodule.get_attribute(assignment.id)
except exceptions.AttributeNotFoundError:
continue
else:
assignments.extend(
assignment.ast_node for assignment in _resolve_name(name)
)
elif isinstance(assignment, ast.BinOp):
assignments.append(assignment.left)
assignments.append(assignment.right)
return result
def remove_unused_imports(self):
can_select = _OneTimeSelector(
self._get_unbound_names(self.pymodule)
| self._get_all_star_list(self.pymodule)
| {"__all__"}
)
visitor = actions.RemovingVisitor(
self.project, self._current_folder(), can_select
)
for import_statement in self.imports:
import_statement.accept(visitor)
def get_used_imports(self, defined_pyobject):
result = []
can_select = _OneTimeSelector(self._get_unbound_names(defined_pyobject))
visitor = actions.FilteringVisitor(
self.project, self._current_folder(), can_select
)
for import_statement in self.imports:
new_import = import_statement.accept(visitor)
if new_import is not None and not new_import.is_empty():
result.append(new_import)
return result
def get_changed_source(self):
if not self.project.prefs.get("pull_imports_to_top") and not self.sorted:
return "".join(self._rewrite_imports(self.imports))
# Make sure we forward a removed import's preceding blank
# lines count to the following import statement.
prev_stmt = None
for stmt in self.imports:
if prev_stmt is not None and prev_stmt.import_info.is_empty():
stmt.blank_lines = max(prev_stmt.blank_lines, stmt.blank_lines)
prev_stmt = stmt
# The new list of imports.
imports = [stmt for stmt in self.imports if not stmt.import_info.is_empty()]
after_removing = self._remove_imports(self.imports)
first_non_blank = self._first_non_blank_line(after_removing, 0)
first_import = self._first_import_line() - 1
result = []
# Writing module docs
result.extend(after_removing[first_non_blank:first_import])
# Writing imports
sorted_imports = sorted(imports, key=self._get_location)
for stmt in sorted_imports:
if stmt != sorted_imports[0]:
result.append("\n" * stmt.blank_lines)
result.append(stmt.get_import_statement() + "\n")
if sorted_imports and first_non_blank < len(after_removing):
result.append("\n" * self.separating_lines)
# Writing the body
first_after_imports = self._first_non_blank_line(after_removing, first_import)
result.extend(after_removing[first_after_imports:])
return "".join(result)
def _get_import_location(self, stmt):
start = stmt.get_new_start()
if start is None:
start = stmt.get_old_location()[0]
return start
def _get_location(self, stmt):
if stmt.get_new_start() is not None:
return stmt.get_new_start()
else:
return stmt.get_old_location()[0]
def _remove_imports(self, imports):
lines = self.pymodule.source_code.splitlines(True)
after_removing = []
first_import_line = self._first_import_line()
last_index = 0
for stmt in imports:
start, end = stmt.get_old_location()
blank_lines = 0
if start != first_import_line:
blank_lines = _count_blank_lines(
lines.__getitem__, start - 2, last_index - 1, -1
)
after_removing.extend(lines[last_index : start - 1 - blank_lines])
last_index = end - 1
after_removing.extend(lines[last_index:])
return after_removing
def _rewrite_imports(self, imports):
lines = self.pymodule.source_code.splitlines(True)
after_rewriting = []
last_index = 0
for stmt in imports:
start, end = stmt.get_old_location()
after_rewriting.extend(lines[last_index : start - 1])
if not stmt.import_info.is_empty():
after_rewriting.append(stmt.get_import_statement() + "\n")
last_index = end - 1
after_rewriting.extend(lines[last_index:])
return after_rewriting
def _first_non_blank_line(self, lines, lineno):
return lineno + _count_blank_lines(lines.__getitem__, lineno, len(lines))
def add_import(self, import_info):
visitor = actions.AddingVisitor(self.project, [import_info])
for import_statement in self.imports:
if import_statement.accept(visitor):
break
else:
lineno = self._get_new_import_lineno()
blanks = self._get_new_import_blanks()
self.imports.append(
importinfo.ImportStatement(
import_info, lineno, lineno, blank_lines=blanks
)
)
def _get_new_import_blanks(self):
return 0
def _get_new_import_lineno(self):
if self.imports:
return self.imports[-1].end_line
return 1
def filter_names(self, can_select):
visitor = actions.RemovingVisitor(
self.project, self._current_folder(), can_select
)
for import_statement in self.imports:
import_statement.accept(visitor)
def expand_stars(self):
can_select = _OneTimeSelector(self._get_unbound_names(self.pymodule))
visitor = actions.ExpandStarsVisitor(
self.project, self._current_folder(), can_select
)
for import_statement in self.imports:
import_statement.accept(visitor)
def remove_duplicates(self):
added_imports = []
for import_stmt in self.imports:
visitor = actions.AddingVisitor(self.project, [import_stmt.import_info])
for added_import in added_imports:
if added_import.accept(visitor):
import_stmt.empty_import()
else:
added_imports.append(import_stmt)
def force_single_imports(self):
"""force a single import per statement"""
for import_stmt in self.imports[:]:
import_info = import_stmt.import_info
if import_info.is_empty() or import_stmt.readonly:
continue
if len(import_info.names_and_aliases) > 1:
for name_and_alias in import_info.names_and_aliases:
if hasattr(import_info, "module_name"):
new_import = importinfo.FromImport(
import_info.module_name, import_info.level, [name_and_alias]
)
else:
new_import = importinfo.NormalImport([name_and_alias])
self.add_import(new_import)
import_stmt.empty_import()
def get_relative_to_absolute_list(self):
visitor = actions.RelativeToAbsoluteVisitor(
self.project, self._current_folder()
)
for import_stmt in self.imports:
if not import_stmt.readonly:
import_stmt.accept(visitor)
return visitor.to_be_absolute
def get_self_import_fix_and_rename_list(self):
visitor = actions.SelfImportVisitor(
self.project, self._current_folder(), self.pymodule.get_resource()
)
for import_stmt in self.imports:
if not import_stmt.readonly:
import_stmt.accept(visitor)
return visitor.to_be_fixed, visitor.to_be_renamed
def _current_folder(self):
return self.pymodule.get_resource().parent
def sort_imports(self):
if self.project.prefs.get("sort_imports_alphabetically"):
sort_kwargs = dict(key=self._get_import_name)
else:
sort_kwargs = dict(key=self._key_imports)
# IDEA: Sort from import list
visitor = actions.SortingVisitor(self.project, self._current_folder())
for import_statement in self.imports:
import_statement.accept(visitor)
in_projects = sorted(visitor.in_project, **sort_kwargs)
third_party = sorted(visitor.third_party, **sort_kwargs)
standards = sorted(visitor.standard, **sort_kwargs)
future = sorted(visitor.future, **sort_kwargs)
last_index = self._first_import_line()
last_index = self._move_imports(future, last_index, 0)
last_index = self._move_imports(standards, last_index, 1)
last_index = self._move_imports(third_party, last_index, 1)
last_index = self._move_imports(in_projects, last_index, 1)
self.separating_lines = 2
self.sorted = True
def _first_import_line(self):
nodes = self.pymodule.get_ast().body
lineno = 0
if self.pymodule.get_doc() is not None:
lineno = 1
if len(nodes) > lineno:
if isinstance(nodes[lineno], ast.Import) or isinstance(
nodes[lineno], ast.ImportFrom
):
return nodes[lineno].lineno
first_line = get_first_decorator_or_function_start_line(nodes[lineno])
lineno = self.pymodule.logical_lines.logical_line_in(first_line)[0]
else:
lineno = self.pymodule.lines.length()
return lineno - _count_blank_lines(
self.pymodule.lines.get_line, lineno - 1, 1, -1
)
def _get_import_name(self, import_stmt):
import_info = import_stmt.import_info
if hasattr(import_info, "module_name"):
return "{}.{}".format(
import_info.module_name,
import_info.names_and_aliases[0][0],
)
else:
return import_info.names_and_aliases[0][0]
def _key_imports(self, stm1):
str1 = stm1.get_import_statement()
return str1.startswith("from "), str1
# str1 = stmt1.get_import_statement()
# str2 = stmt2.get_import_statement()
# if str1.startswith('from ') and not str2.startswith('from '):
# return 1
# if not str1.startswith('from ') and str2.startswith('from '):
# return -1
# return cmp(str1, str2)
def _move_imports(self, imports, index, blank_lines):
if imports:
imports[0].move(index, blank_lines)
index += 1
if len(imports) > 1:
for stmt in imports[1:]:
stmt.move(index)
index += 1
return index
def handle_long_imports(self, maxdots, maxlength):
visitor = actions.LongImportVisitor(
self._current_folder(), self.project, maxdots, maxlength
)
for import_statement in self.imports:
if not import_statement.readonly:
import_statement.accept(visitor)
for import_info in visitor.new_imports:
self.add_import(import_info)
return visitor.to_be_renamed
def remove_pyname(self, pyname):
"""Removes pyname when imported in ``from mod import x``"""
visitor = actions.RemovePyNameVisitor(
self.project, self.pymodule, pyname, self._current_folder()
)
for import_stmt in self.imports:
import_stmt.accept(visitor)
def get_first_decorator_or_function_start_line(node):
decorators = getattr(node, "decorator_list", [])
first_line = min([decorator.lineno for decorator in decorators] + [node.lineno])
return first_line
def _count_blank_lines(get_line, start, end, step=1):
count = 0
for idx in range(start, end, step):
if get_line(idx).strip() == "":
count += 1
else:
break
return count
class _OneTimeSelector:
def __init__(self, names):
self.names = names
self.selected_names = set()
def __call__(self, imported_primary):
if self._can_name_be_added(imported_primary):
for name in self._get_dotted_tokens(imported_primary):
self.selected_names.add(name)
return True
return False
def _get_dotted_tokens(self, imported_primary):
tokens = imported_primary.split(".")
for i in range(len(tokens)):
yield ".".join(tokens[: i + 1])
def _can_name_be_added(self, imported_primary):
for name in self._get_dotted_tokens(imported_primary):
if name in self.names and name not in self.selected_names:
return True
return False
class _UnboundNameFinder(ast.RopeNodeVisitor):
def __init__(self, pyobject):
self.pyobject = pyobject
def _visit_child_scope(self, node):
pyobject = (
self.pyobject.get_module()
.get_scope()
.get_inner_scope_for_line(node.lineno)
.pyobject
)
visitor = _LocalUnboundNameFinder(pyobject, self)
for child in ast.iter_child_nodes(node):
visitor.visit(child)
def _FunctionDef(self, node):
self._visit_child_scope(node)
def _ClassDef(self, node):
self._visit_child_scope(node)
def _Name(self, node):
if self._get_root()._is_node_interesting(node) and not self.is_bound(node.id):
self.add_unbound(node.id)
def _Attribute(self, node):
result = []
while isinstance(node, ast.Attribute):
result.append(node.attr)
node = node.value
if isinstance(node, ast.Name):
result.append(node.id)
primary = ".".join(reversed(result))
if self._get_root()._is_node_interesting(node) and not self.is_bound(
primary
):
self.add_unbound(primary)
else:
self.visit(node)
def _get_root(self):
pass
def is_bound(self, name, propagated=False):
pass
def add_unbound(self, name):
pass
class _GlobalUnboundNameFinder(_UnboundNameFinder):
def __init__(self, pymodule, wanted_pyobject):
super().__init__(pymodule)
self.unbound = set()
self.names = set()
for name, pyname in pymodule._get_structural_attributes().items():
if not isinstance(pyname, (pynames.ImportedName, pynames.ImportedModule)):
self.names.add(name)
wanted_scope = wanted_pyobject.get_scope()
self.start = wanted_scope.get_start()
self.end = wanted_scope.get_end() + 1
def _get_root(self):
return self
def is_bound(self, primary, propagated=False):
name = primary.split(".")[0]
return name in self.names
def add_unbound(self, name):
names = name.split(".")
for i in range(len(names)):
self.unbound.add(".".join(names[: i + 1]))
def _is_node_interesting(self, node):
return self.start <= node.lineno < self.end
class _LocalUnboundNameFinder(_UnboundNameFinder):
def __init__(self, pyobject, parent):
super().__init__(pyobject)
self.parent = parent
def _get_root(self):
return self.parent._get_root()
def is_bound(self, primary, propagated=False):
name = primary.split(".")[0]
if propagated:
names = self.pyobject.get_scope().get_propagated_names()
else:
names = self.pyobject.get_scope().get_names()
return name in names or self.parent.is_bound(name, propagated=True)
def add_unbound(self, name):
self.parent.add_unbound(name)
class _GlobalImportFinder:
def __init__(self, pymodule):
self.current_folder = None
if pymodule.get_resource():
self.current_folder = pymodule.get_resource().parent
self.pymodule = pymodule
self.imports = []
self.pymodule = pymodule
self.lines = self.pymodule.lines
def visit_import(self, node, end_line):
start_line = node.lineno
import_statement = importinfo.ImportStatement(
importinfo.NormalImport(self._get_names(node.names)),
start_line,
end_line,
self._get_text(start_line, end_line),
blank_lines=self._count_empty_lines_before(start_line),
)
self.imports.append(import_statement)
def _count_empty_lines_before(self, lineno):
return _count_blank_lines(self.lines.get_line, lineno - 1, 0, -1)
def _count_empty_lines_after(self, lineno):
return _count_blank_lines(self.lines.get_line, lineno + 1, self.lines.length())
def get_separating_line_count(self):
if not self.imports:
return 0
return self._count_empty_lines_after(self.imports[-1].end_line - 1)
def _get_text(self, start_line, end_line):
result = [self.lines.get_line(index) for index in range(start_line, end_line)]
return "\n".join(result)
def visit_from(self, node, end_line):
level = 0
if node.level:
level = node.level
import_info = importinfo.FromImport(
node.module or "",
level,
self._get_names(node.names),
)
start_line = node.lineno
self.imports.append(
importinfo.ImportStatement(
import_info,
node.lineno,
end_line,
self._get_text(start_line, end_line),
blank_lines=self._count_empty_lines_before(start_line),
)
)
def _get_names(self, alias_names):
return [(alias.name, alias.asname) for alias in alias_names]
def find_import_statements(self):
nodes = self.pymodule.get_ast().body
for node in nodes:
if isinstance(node, (ast.Import, ast.ImportFrom)):
lines = self.pymodule.logical_lines
end_line = lines.logical_line_in(node.lineno)[1] + 1
if isinstance(node, ast.Import):
self.visit_import(node, end_line)
if isinstance(node, ast.ImportFrom):
self.visit_from(node, end_line)
return self.imports