import re import rope.base.pynames from rope.base import pynames, pyobjects, codeanalyze, evaluate, exceptions, utils, worder class Finder(object): """For finding occurrences of a name The constructor takes a `filters` argument. It should be a list of functions that take a single argument. For each possible occurrence, these functions are called in order with the an instance of `Occurrence`: * If it returns `None` other filters are tried. * If it returns `True`, the occurrence will be a match. * If it returns `False`, the occurrence will be skipped. * If all of the filters return `None`, it is skipped also. """ def __init__(self, pycore, name, filters=[lambda o: True], docs=False): self.pycore = pycore self.name = name self.docs = docs self.filters = filters self._textual_finder = _TextualFinder(name, docs=docs) def find_occurrences(self, resource=None, pymodule=None): """Generate `Occurrence` instances""" tools = _OccurrenceToolsCreator(self.pycore, resource=resource, pymodule=pymodule, docs=self.docs) for offset in self._textual_finder.find_offsets(tools.source_code): occurrence = Occurrence(tools, offset) for filter in self.filters: result = filter(occurrence) if result is None: continue if result: yield occurrence break def create_finder(pycore, name, pyname, only_calls=False, imports=True, unsure=None, docs=False, instance=None, in_hierarchy=False): """A factory for `Finder` Based on the arguments it creates a list of filters. `instance` argument is needed only when you want implicit interfaces to be considered. """ pynames = set([pyname]) filters = [] if only_calls: filters.append(CallsFilter()) if not imports: filters.append(NoImportsFilter()) if isinstance(instance, rope.base.pynames.ParameterName): for pyobject in instance.get_objects(): try: pynames.add(pyobject[name]) except exceptions.AttributeNotFoundError: pass for pyname in pynames: filters.append(PyNameFilter(pyname)) if in_hierarchy: filters.append(InHierarchyFilter(pyname)) if unsure: filters.append(UnsureFilter(unsure)) return Finder(pycore, name, filters=filters, docs=docs) class Occurrence(object): def __init__(self, tools, offset): self.tools = tools self.offset = offset self.resource = tools.resource @utils.saveit def get_word_range(self): return self.tools.word_finder.get_word_range(self.offset) @utils.saveit def get_primary_range(self): return self.tools.word_finder.get_primary_range(self.offset) @utils.saveit def get_pyname(self): try: return self.tools.name_finder.get_pyname_at(self.offset) except exceptions.BadIdentifierError: pass @utils.saveit def get_primary_and_pyname(self): try: return self.tools.name_finder.get_primary_and_pyname_at(self.offset) except exceptions.BadIdentifierError: pass @utils.saveit def is_in_import_statement(self): return (self.tools.word_finder.is_from_statement(self.offset) or self.tools.word_finder.is_import_statement(self.offset)) def is_called(self): return self.tools.word_finder.is_a_function_being_called(self.offset) def is_defined(self): return self.tools.word_finder.is_a_class_or_function_name_in_header(self.offset) def is_a_fixed_primary(self): return self.tools.word_finder.is_a_class_or_function_name_in_header(self.offset) or \ self.tools.word_finder.is_a_name_after_from_import(self.offset) def is_written(self): return self.tools.word_finder.is_assigned_here(self.offset) def is_unsure(self): return unsure_pyname(self.get_pyname()) @property @utils.saveit def lineno(self): offset = self.get_word_range()[0] return self.tools.pymodule.lines.get_line_number(offset) def same_pyname(expected, pyname): """Check whether `expected` and `pyname` are the same""" if expected is None or pyname is None: return False if expected == pyname: return True if type(expected) not in (pynames.ImportedModule, pynames.ImportedName) and \ type(pyname) not in (pynames.ImportedModule, pynames.ImportedName): return False return expected.get_definition_location() == pyname.get_definition_location() and \ expected.get_object() == pyname.get_object() def unsure_pyname(pyname, unbound=True): """Return `True` if we don't know what this name references""" if pyname is None: return True if unbound and not isinstance(pyname, pynames.UnboundName): return False if pyname.get_object() == pyobjects.get_unknown(): return True class PyNameFilter(object): """For finding occurrences of a name""" def __init__(self, pyname): self.pyname = pyname def __call__(self, occurrence): if same_pyname(self.pyname, occurrence.get_pyname()): return True class InHierarchyFilter(object): """For finding occurrences of a name""" def __init__(self, pyname, implementations_only=False): self.pyname = pyname self.impl_only = implementations_only self.pyclass = self._get_containing_class(pyname) if self.pyclass is not None: self.name = pyname.get_object().get_name() self.roots = self._get_root_classes(self.pyclass, self.name) else: self.roots = None def __call__(self, occurrence): if self.roots is None: return pyclass = self._get_containing_class(occurrence.get_pyname()) if pyclass is not None: roots = self._get_root_classes(pyclass, self.name) if self.roots.intersection(roots): return True def _get_containing_class(self, pyname): if isinstance(pyname, pynames.DefinedName): scope = pyname.get_object().get_scope() parent = scope.parent if parent is not None and parent.get_kind() == 'Class': return parent.pyobject def _get_root_classes(self, pyclass, name): if self.impl_only and pyclass == self.pyclass: return set([pyclass]) result = set() for superclass in pyclass.get_superclasses(): if name in superclass: result.update(self._get_root_classes(superclass, name)) if not result: return set([pyclass]) return result class UnsureFilter(object): def __init__(self, unsure): self.unsure = unsure def __call__(self, occurrence): if occurrence.is_unsure() and self.unsure(occurrence): return True class NoImportsFilter(object): def __call__(self, occurrence): if occurrence.is_in_import_statement(): return False class CallsFilter(object): def __call__(self, occurrence): if not occurrence.is_called(): return False class _TextualFinder(object): def __init__(self, name, docs=False): self.name = name self.docs = docs self.comment_pattern = _TextualFinder.any('comment', [r'#[^\n]*']) self.string_pattern = _TextualFinder.any( 'string', [codeanalyze.get_string_pattern()]) self.pattern = self._get_occurrence_pattern(self.name) def find_offsets(self, source): if not self._fast_file_query(source): return if self.docs: searcher = self._normal_search else: searcher = self._re_search for matched in searcher(source): yield matched def _re_search(self, source): for match in self.pattern.finditer(source): for key, value in match.groupdict().items(): if value and key == 'occurrence': yield match.start(key) def _normal_search(self, source): current = 0 while True: try: found = source.index(self.name, current) current = found + len(self.name) if (found == 0 or not self._is_id_char(source[found - 1])) and \ (current == len(source) or not self._is_id_char(source[current])): yield found except ValueError: break def _is_id_char(self, c): return c.isalnum() or c == '_' def _fast_file_query(self, source): try: source.index(self.name) return True except ValueError: return False def _get_source(self, resource, pymodule): if resource is not None: return resource.read() else: return pymodule.source_code def _get_occurrence_pattern(self, name): occurrence_pattern = _TextualFinder.any('occurrence', ['\\b' + name + '\\b']) pattern = re.compile(occurrence_pattern + '|' + self.comment_pattern + '|' + self.string_pattern) return pattern @staticmethod def any(name, list_): return '(?P<%s>' % name + '|'.join(list_) + ')' class _OccurrenceToolsCreator(object): def __init__(self, pycore, resource=None, pymodule=None, docs=False): self.pycore = pycore self.__resource = resource self.__pymodule = pymodule self.docs = docs @property @utils.saveit def name_finder(self): return evaluate.ScopeNameFinder(self.pymodule) @property @utils.saveit def source_code(self): if self.__resource is not None: return self.resource.read() else: return self.pymodule.source_code @property @utils.saveit def word_finder(self): return worder.Worder(self.source_code, self.docs) @property @utils.saveit def resource(self): if self.__resource is not None: return self.__resource if self.__pymodule is not None: return self.__pymodule.resource @property @utils.saveit def pymodule(self): if self.__pymodule is not None: return self.__pymodule return self.pycore.resource_to_pyobject(self.resource)