occurrences.py 10 KB

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