PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/pymode/libs2/rope/refactor/occurrences.py

https://gitlab.com/vim-IDE/python-mode
Python | 384 lines | 360 code | 3 blank | 21 comment | 0 complexity | 048d3ac60d447995a90f4689a3290ced MD5 | raw file
  1. """Find occurrences of a name in a project.
  2. This module consists of a `Finder` that finds all occurrences of a name
  3. in a project. The `Finder.find_occurrences()` method is a generator that
  4. yields `Occurrence` instances for each occurrence of the name. To create
  5. a `Finder` object, use the `create_finder()` function:
  6. finder = occurrences.create_finder(project, 'foo', pyname)
  7. for occurrence in finder.find_occurrences():
  8. pass
  9. It's possible to filter the occurrences. They can be specified when
  10. calling the `create_finder()` function.
  11. * `only_calls`: If True, return only those instances where the name is
  12. a function that's being called.
  13. * `imports`: If False, don't return instances that are in import
  14. statements.
  15. * `unsure`: If a prediate function, return instances where we don't
  16. know what the name references. It also filters based on the
  17. predicate function.
  18. * `docs`: If True, it will search for occurrences in regions normally
  19. ignored. E.g., strings and comments.
  20. * `in_hierarchy`: If True, it will find occurrences if the name is in
  21. the class's hierarchy.
  22. * `instance`: Used only when you want implicit interfaces to be
  23. considered.
  24. """
  25. import re
  26. from rope.base import codeanalyze
  27. from rope.base import evaluate
  28. from rope.base import exceptions
  29. from rope.base import pynames
  30. from rope.base import pyobjects
  31. from rope.base import utils
  32. from rope.base import worder
  33. class Finder(object):
  34. """For finding occurrences of a name
  35. The constructor takes a `filters` argument. It should be a list
  36. of functions that take a single argument. For each possible
  37. occurrence, these functions are called in order with the an
  38. instance of `Occurrence`:
  39. * If it returns `None` other filters are tried.
  40. * If it returns `True`, the occurrence will be a match.
  41. * If it returns `False`, the occurrence will be skipped.
  42. * If all of the filters return `None`, it is skipped also.
  43. """
  44. def __init__(self, project, name, filters=[lambda o: True], docs=False):
  45. self.project = project
  46. self.name = name
  47. self.docs = docs
  48. self.filters = filters
  49. self._textual_finder = _TextualFinder(name, docs=docs)
  50. def find_occurrences(self, resource=None, pymodule=None):
  51. """Generate `Occurrence` instances"""
  52. tools = _OccurrenceToolsCreator(self.project, resource=resource,
  53. pymodule=pymodule, docs=self.docs)
  54. for offset in self._textual_finder.find_offsets(tools.source_code):
  55. occurrence = Occurrence(tools, offset)
  56. for filter in self.filters:
  57. result = filter(occurrence)
  58. if result is None:
  59. continue
  60. if result:
  61. yield occurrence
  62. break
  63. def create_finder(project, name, pyname, only_calls=False, imports=True,
  64. unsure=None, docs=False, instance=None, in_hierarchy=False):
  65. """A factory for `Finder`
  66. Based on the arguments it creates a list of filters. `instance`
  67. argument is needed only when you want implicit interfaces to be
  68. considered.
  69. """
  70. pynames_ = set([pyname])
  71. filters = []
  72. if only_calls:
  73. filters.append(CallsFilter())
  74. if not imports:
  75. filters.append(NoImportsFilter())
  76. if isinstance(instance, pynames.ParameterName):
  77. for pyobject in instance.get_objects():
  78. try:
  79. pynames_.add(pyobject[name])
  80. except exceptions.AttributeNotFoundError:
  81. pass
  82. for pyname in pynames_:
  83. filters.append(PyNameFilter(pyname))
  84. if in_hierarchy:
  85. filters.append(InHierarchyFilter(pyname))
  86. if unsure:
  87. filters.append(UnsureFilter(unsure))
  88. return Finder(project, name, filters=filters, docs=docs)
  89. class Occurrence(object):
  90. def __init__(self, tools, offset):
  91. self.tools = tools
  92. self.offset = offset
  93. self.resource = tools.resource
  94. @utils.saveit
  95. def get_word_range(self):
  96. return self.tools.word_finder.get_word_range(self.offset)
  97. @utils.saveit
  98. def get_primary_range(self):
  99. return self.tools.word_finder.get_primary_range(self.offset)
  100. @utils.saveit
  101. def get_pyname(self):
  102. try:
  103. return self.tools.name_finder.get_pyname_at(self.offset)
  104. except exceptions.BadIdentifierError:
  105. pass
  106. @utils.saveit
  107. def get_primary_and_pyname(self):
  108. try:
  109. return self.tools.name_finder.get_primary_and_pyname_at(
  110. self.offset)
  111. except exceptions.BadIdentifierError:
  112. pass
  113. @utils.saveit
  114. def is_in_import_statement(self):
  115. return (self.tools.word_finder.is_from_statement(self.offset) or
  116. self.tools.word_finder.is_import_statement(self.offset))
  117. def is_called(self):
  118. return self.tools.word_finder.is_a_function_being_called(self.offset)
  119. def is_defined(self):
  120. return self.tools.word_finder.is_a_class_or_function_name_in_header(
  121. self.offset)
  122. def is_a_fixed_primary(self):
  123. return self.tools.word_finder.is_a_class_or_function_name_in_header(
  124. self.offset) or \
  125. self.tools.word_finder.is_a_name_after_from_import(self.offset)
  126. def is_written(self):
  127. return self.tools.word_finder.is_assigned_here(self.offset)
  128. def is_unsure(self):
  129. return unsure_pyname(self.get_pyname())
  130. @property
  131. @utils.saveit
  132. def lineno(self):
  133. offset = self.get_word_range()[0]
  134. return self.tools.pymodule.lines.get_line_number(offset)
  135. def same_pyname(expected, pyname):
  136. """Check whether `expected` and `pyname` are the same"""
  137. if expected is None or pyname is None:
  138. return False
  139. if expected == pyname:
  140. return True
  141. if type(expected) not in (pynames.ImportedModule, pynames.ImportedName) \
  142. and type(pyname) not in \
  143. (pynames.ImportedModule, pynames.ImportedName):
  144. return False
  145. return expected.get_definition_location() == \
  146. pyname.get_definition_location() and \
  147. expected.get_object() == pyname.get_object()
  148. def unsure_pyname(pyname, unbound=True):
  149. """Return `True` if we don't know what this name references"""
  150. if pyname is None:
  151. return True
  152. if unbound and not isinstance(pyname, pynames.UnboundName):
  153. return False
  154. if pyname.get_object() == pyobjects.get_unknown():
  155. return True
  156. class PyNameFilter(object):
  157. """For finding occurrences of a name."""
  158. def __init__(self, pyname):
  159. self.pyname = pyname
  160. def __call__(self, occurrence):
  161. if same_pyname(self.pyname, occurrence.get_pyname()):
  162. return True
  163. class InHierarchyFilter(object):
  164. """Finds the occurrence if the name is in the class's hierarchy."""
  165. def __init__(self, pyname, implementations_only=False):
  166. self.pyname = pyname
  167. self.impl_only = implementations_only
  168. self.pyclass = self._get_containing_class(pyname)
  169. if self.pyclass is not None:
  170. self.name = pyname.get_object().get_name()
  171. self.roots = self._get_root_classes(self.pyclass, self.name)
  172. else:
  173. self.roots = None
  174. def __call__(self, occurrence):
  175. if self.roots is None:
  176. return
  177. pyclass = self._get_containing_class(occurrence.get_pyname())
  178. if pyclass is not None:
  179. roots = self._get_root_classes(pyclass, self.name)
  180. if self.roots.intersection(roots):
  181. return True
  182. def _get_containing_class(self, pyname):
  183. if isinstance(pyname, pynames.DefinedName):
  184. scope = pyname.get_object().get_scope()
  185. parent = scope.parent
  186. if parent is not None and parent.get_kind() == 'Class':
  187. return parent.pyobject
  188. def _get_root_classes(self, pyclass, name):
  189. if self.impl_only and pyclass == self.pyclass:
  190. return set([pyclass])
  191. result = set()
  192. for superclass in pyclass.get_superclasses():
  193. if name in superclass:
  194. result.update(self._get_root_classes(superclass, name))
  195. if not result:
  196. return set([pyclass])
  197. return result
  198. class UnsureFilter(object):
  199. """Occurrences where we don't knoow what the name references."""
  200. def __init__(self, unsure):
  201. self.unsure = unsure
  202. def __call__(self, occurrence):
  203. if occurrence.is_unsure() and self.unsure(occurrence):
  204. return True
  205. class NoImportsFilter(object):
  206. """Don't include import statements as occurrences."""
  207. def __call__(self, occurrence):
  208. if occurrence.is_in_import_statement():
  209. return False
  210. class CallsFilter(object):
  211. """Filter out non-call occurrences."""
  212. def __call__(self, occurrence):
  213. if not occurrence.is_called():
  214. return False
  215. class _TextualFinder(object):
  216. def __init__(self, name, docs=False):
  217. self.name = name
  218. self.docs = docs
  219. self.comment_pattern = _TextualFinder.any('comment', [r'#[^\n]*'])
  220. self.string_pattern = _TextualFinder.any(
  221. 'string', [codeanalyze.get_string_pattern()])
  222. self.pattern = self._get_occurrence_pattern(self.name)
  223. def find_offsets(self, source):
  224. if not self._fast_file_query(source):
  225. return
  226. if self.docs:
  227. searcher = self._normal_search
  228. else:
  229. searcher = self._re_search
  230. for matched in searcher(source):
  231. yield matched
  232. def _re_search(self, source):
  233. for match in self.pattern.finditer(source):
  234. for key, value in match.groupdict().items():
  235. if value and key == 'occurrence':
  236. yield match.start(key)
  237. def _normal_search(self, source):
  238. current = 0
  239. while True:
  240. try:
  241. found = source.index(self.name, current)
  242. current = found + len(self.name)
  243. if (found == 0 or
  244. not self._is_id_char(source[found - 1])) and \
  245. (current == len(source) or
  246. not self._is_id_char(source[current])):
  247. yield found
  248. except ValueError:
  249. break
  250. def _is_id_char(self, c):
  251. return c.isalnum() or c == '_'
  252. def _fast_file_query(self, source):
  253. try:
  254. source.index(self.name)
  255. return True
  256. except ValueError:
  257. return False
  258. def _get_source(self, resource, pymodule):
  259. if resource is not None:
  260. return resource.read()
  261. else:
  262. return pymodule.source_code
  263. def _get_occurrence_pattern(self, name):
  264. occurrence_pattern = _TextualFinder.any('occurrence',
  265. ['\\b' + name + '\\b'])
  266. pattern = re.compile(occurrence_pattern + '|' + self.comment_pattern +
  267. '|' + self.string_pattern)
  268. return pattern
  269. @staticmethod
  270. def any(name, list_):
  271. return '(?P<%s>' % name + '|'.join(list_) + ')'
  272. class _OccurrenceToolsCreator(object):
  273. def __init__(self, project, resource=None, pymodule=None, docs=False):
  274. self.project = project
  275. self.__resource = resource
  276. self.__pymodule = pymodule
  277. self.docs = docs
  278. @property
  279. @utils.saveit
  280. def name_finder(self):
  281. return evaluate.ScopeNameFinder(self.pymodule)
  282. @property
  283. @utils.saveit
  284. def source_code(self):
  285. if self.__resource is not None:
  286. return self.resource.read()
  287. else:
  288. return self.pymodule.source_code
  289. @property
  290. @utils.saveit
  291. def word_finder(self):
  292. return worder.Worder(self.source_code, self.docs)
  293. @property
  294. @utils.saveit
  295. def resource(self):
  296. if self.__resource is not None:
  297. return self.__resource
  298. if self.__pymodule is not None:
  299. return self.__pymodule.resource
  300. @property
  301. @utils.saveit
  302. def pymodule(self):
  303. if self.__pymodule is not None:
  304. return self.__pymodule
  305. return self.project.get_pymodule(self.resource)