__init__.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. """A package for handling imports
  2. This package provides tools for modifying module imports after
  3. refactorings or as a separate task.
  4. """
  5. import rope.base.evaluate
  6. from rope.base.change import ChangeSet, ChangeContents
  7. from rope.refactor import occurrences, rename
  8. from rope.refactor.importutils import module_imports, actions
  9. from rope.refactor.importutils.importinfo import NormalImport, FromImport
  10. import rope.base.codeanalyze
  11. class ImportOrganizer(object):
  12. """Perform some import-related commands
  13. Each method returns a `rope.base.change.Change` object.
  14. """
  15. def __init__(self, project):
  16. self.project = project
  17. self.pycore = project.pycore
  18. self.import_tools = ImportTools(self.pycore)
  19. def organize_imports(self, resource, offset=None):
  20. return self._perform_command_on_import_tools(
  21. self.import_tools.organize_imports, resource, offset)
  22. def expand_star_imports(self, resource, offset=None):
  23. return self._perform_command_on_import_tools(
  24. self.import_tools.expand_stars, resource, offset)
  25. def froms_to_imports(self, resource, offset=None):
  26. return self._perform_command_on_import_tools(
  27. self.import_tools.froms_to_imports, resource, offset)
  28. def relatives_to_absolutes(self, resource, offset=None):
  29. return self._perform_command_on_import_tools(
  30. self.import_tools.relatives_to_absolutes, resource, offset)
  31. def handle_long_imports(self, resource, offset=None):
  32. return self._perform_command_on_import_tools(
  33. self.import_tools.handle_long_imports, resource, offset)
  34. def _perform_command_on_import_tools(self, method, resource, offset):
  35. pymodule = self.pycore.resource_to_pyobject(resource)
  36. before_performing = pymodule.source_code
  37. import_filter = None
  38. if offset is not None:
  39. import_filter = self._line_filter(
  40. pymodule.lines.get_line_number(offset))
  41. result = method(pymodule, import_filter=import_filter)
  42. if result is not None and result != before_performing:
  43. changes = ChangeSet(method.__name__.replace('_', ' ') +
  44. ' in <%s>' % resource.path)
  45. changes.add_change(ChangeContents(resource, result))
  46. return changes
  47. def _line_filter(self, lineno):
  48. def import_filter(import_stmt):
  49. return import_stmt.start_line <= lineno < import_stmt.end_line
  50. return import_filter
  51. class ImportTools(object):
  52. def __init__(self, pycore):
  53. self.pycore = pycore
  54. def get_import(self, resource):
  55. """The import statement for `resource`"""
  56. module_name = self.pycore.modname(resource)
  57. return NormalImport(((module_name, None), ))
  58. def get_from_import(self, resource, name):
  59. """The from import statement for `name` in `resource`"""
  60. module_name = self.pycore.modname(resource)
  61. names = []
  62. if isinstance(name, list):
  63. names = [(imported, None) for imported in name]
  64. else:
  65. names = [(name, None),]
  66. return FromImport(module_name, 0, tuple(names))
  67. def module_imports(self, module, imports_filter=None):
  68. return module_imports.ModuleImports(self.pycore, module,
  69. imports_filter)
  70. def froms_to_imports(self, pymodule, import_filter=None):
  71. pymodule = self._clean_up_imports(pymodule, import_filter)
  72. module_imports = self.module_imports(pymodule, import_filter)
  73. for import_stmt in module_imports.imports:
  74. if import_stmt.readonly or \
  75. not self._is_transformable_to_normal(import_stmt.import_info):
  76. continue
  77. pymodule = self._from_to_normal(pymodule, import_stmt)
  78. # Adding normal imports in place of froms
  79. module_imports = self.module_imports(pymodule, import_filter)
  80. for import_stmt in module_imports.imports:
  81. if not import_stmt.readonly and \
  82. self._is_transformable_to_normal(import_stmt.import_info):
  83. import_stmt.import_info = \
  84. NormalImport(((import_stmt.import_info.module_name, None),))
  85. module_imports.remove_duplicates()
  86. return module_imports.get_changed_source()
  87. def expand_stars(self, pymodule, import_filter=None):
  88. module_imports = self.module_imports(pymodule, import_filter)
  89. module_imports.expand_stars()
  90. return module_imports.get_changed_source()
  91. def _from_to_normal(self, pymodule, import_stmt):
  92. resource = pymodule.get_resource()
  93. from_import = import_stmt.import_info
  94. module_name = from_import.module_name
  95. for name, alias in from_import.names_and_aliases:
  96. imported = name
  97. if alias is not None:
  98. imported = alias
  99. occurrence_finder = occurrences.create_finder(
  100. self.pycore, imported, pymodule[imported], imports=False)
  101. source = rename.rename_in_module(
  102. occurrence_finder, module_name + '.' + name,
  103. pymodule=pymodule, replace_primary=True)
  104. if source is not None:
  105. pymodule = self.pycore.get_string_module(source, resource)
  106. return pymodule
  107. def _clean_up_imports(self, pymodule, import_filter):
  108. resource = pymodule.get_resource()
  109. module_with_imports = self.module_imports(pymodule, import_filter)
  110. module_with_imports.expand_stars()
  111. source = module_with_imports.get_changed_source()
  112. if source is not None:
  113. pymodule = self.pycore.get_string_module(source, resource)
  114. source = self.relatives_to_absolutes(pymodule)
  115. if source is not None:
  116. pymodule = self.pycore.get_string_module(source, resource)
  117. module_with_imports = self.module_imports(pymodule, import_filter)
  118. module_with_imports.remove_duplicates()
  119. module_with_imports.remove_unused_imports()
  120. source = module_with_imports.get_changed_source()
  121. if source is not None:
  122. pymodule = self.pycore.get_string_module(source, resource)
  123. return pymodule
  124. def relatives_to_absolutes(self, pymodule, import_filter=None):
  125. module_imports = self.module_imports(pymodule, import_filter)
  126. to_be_absolute_list = module_imports.get_relative_to_absolute_list()
  127. for name, absolute_name in to_be_absolute_list:
  128. pymodule = self._rename_in_module(pymodule, name, absolute_name)
  129. module_imports = self.module_imports(pymodule, import_filter)
  130. module_imports.get_relative_to_absolute_list()
  131. source = module_imports.get_changed_source()
  132. if source is None:
  133. source = pymodule.source_code
  134. return source
  135. def _is_transformable_to_normal(self, import_info):
  136. if not isinstance(import_info, FromImport):
  137. return False
  138. return True
  139. def organize_imports(self, pymodule,
  140. unused=True, duplicates=True,
  141. selfs=True, sort=True, import_filter=None):
  142. if unused or duplicates:
  143. module_imports = self.module_imports(pymodule, import_filter)
  144. if unused:
  145. module_imports.remove_unused_imports()
  146. if duplicates:
  147. module_imports.remove_duplicates()
  148. source = module_imports.get_changed_source()
  149. if source is not None:
  150. pymodule = self.pycore.get_string_module(
  151. source, pymodule.get_resource())
  152. if selfs:
  153. pymodule = self._remove_self_imports(pymodule, import_filter)
  154. if sort:
  155. return self.sort_imports(pymodule, import_filter)
  156. else:
  157. return pymodule.source_code
  158. def _remove_self_imports(self, pymodule, import_filter=None):
  159. module_imports = self.module_imports(pymodule, import_filter)
  160. to_be_fixed, to_be_renamed = module_imports.get_self_import_fix_and_rename_list()
  161. for name in to_be_fixed:
  162. try:
  163. pymodule = self._rename_in_module(pymodule, name, '', till_dot=True)
  164. except ValueError:
  165. # There is a self import with direct access to it
  166. return pymodule
  167. for name, new_name in to_be_renamed:
  168. pymodule = self._rename_in_module(pymodule, name, new_name)
  169. module_imports = self.module_imports(pymodule, import_filter)
  170. module_imports.get_self_import_fix_and_rename_list()
  171. source = module_imports.get_changed_source()
  172. if source is not None:
  173. pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
  174. return pymodule
  175. def _rename_in_module(self, pymodule, name, new_name, till_dot=False):
  176. old_name = name.split('.')[-1]
  177. old_pyname = rope.base.evaluate.eval_str(pymodule.get_scope(), name)
  178. occurrence_finder = occurrences.create_finder(
  179. self.pycore, old_name, old_pyname, imports=False)
  180. changes = rope.base.codeanalyze.ChangeCollector(pymodule.source_code)
  181. for occurrence in occurrence_finder.find_occurrences(pymodule=pymodule):
  182. start, end = occurrence.get_primary_range()
  183. if till_dot:
  184. new_end = pymodule.source_code.index('.', end) + 1
  185. space = pymodule.source_code[end:new_end - 1].strip()
  186. if not space == '':
  187. for c in space:
  188. if not c.isspace() and c not in '\\':
  189. raise ValueError()
  190. end = new_end
  191. changes.add_change(start, end, new_name)
  192. source = changes.get_changed()
  193. if source is not None:
  194. pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
  195. return pymodule
  196. def sort_imports(self, pymodule, import_filter=None):
  197. module_imports = self.module_imports(pymodule, import_filter)
  198. module_imports.sort_imports()
  199. return module_imports.get_changed_source()
  200. def handle_long_imports(self, pymodule, maxdots=2, maxlength=27,
  201. import_filter=None):
  202. # IDEA: `maxdots` and `maxlength` can be specified in project config
  203. # adding new from imports
  204. module_imports = self.module_imports(pymodule, import_filter)
  205. to_be_fixed = module_imports.handle_long_imports(maxdots, maxlength)
  206. # performing the renaming
  207. pymodule = self.pycore.get_string_module(
  208. module_imports.get_changed_source(),
  209. resource=pymodule.get_resource())
  210. for name in to_be_fixed:
  211. pymodule = self._rename_in_module(pymodule, name,
  212. name.split('.')[-1])
  213. # organizing imports
  214. return self.organize_imports(pymodule, selfs=False, sort=False,
  215. import_filter=import_filter)
  216. def get_imports(pycore, pydefined):
  217. """A shortcut for getting the `ImportInfo`\s used in a scope"""
  218. pymodule = pydefined.get_module()
  219. module = module_imports.ModuleImports(pycore, pymodule)
  220. if pymodule == pydefined:
  221. return [stmt.import_info for stmt in module.imports]
  222. return module.get_used_imports(pydefined)
  223. def get_module_imports(pycore, pymodule):
  224. """A shortcut for creating a `module_imports.ModuleImports` object"""
  225. return module_imports.ModuleImports(pycore, pymodule)
  226. def add_import(pycore, pymodule, module_name, name=None):
  227. imports = get_module_imports(pycore, pymodule)
  228. candidates = []
  229. names = []
  230. # from mod import name
  231. if name is not None:
  232. from_import = FromImport(module_name, 0, [(name, None)])
  233. names.append(name)
  234. candidates.append(from_import)
  235. # from pkg import mod
  236. if '.' in module_name:
  237. pkg, mod = module_name.rsplit('.', 1)
  238. candidates.append(FromImport(pkg, 0, [(mod, None)]))
  239. if name:
  240. names.append(mod + '.' + name)
  241. else:
  242. names.append(mod)
  243. # import mod
  244. normal_import = NormalImport([(module_name, None)])
  245. if name:
  246. names.append(module_name + '.' + name)
  247. else:
  248. names.append(module_name)
  249. candidates.append(normal_import)
  250. visitor = actions.AddingVisitor(pycore, candidates)
  251. selected_import = normal_import
  252. for import_statement in imports.imports:
  253. if import_statement.accept(visitor):
  254. selected_import = visitor.import_info
  255. break
  256. imports.add_import(selected_import)
  257. imported_name = names[candidates.index(selected_import)]
  258. return imports.get_changed_source(), imported_name