| Index: tools/telemetry/third_party/rope/rope/refactor/occurrences.py
|
| diff --git a/tools/telemetry/third_party/rope/rope/refactor/occurrences.py b/tools/telemetry/third_party/rope/rope/refactor/occurrences.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..14a2d7deda8be202f2c30277f9325d670fe19e53
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/rope/rope/refactor/occurrences.py
|
| @@ -0,0 +1,384 @@
|
| +"""Find occurrences of a name in a project.
|
| +
|
| +This module consists of a `Finder` that finds all occurrences of a name
|
| +in a project. The `Finder.find_occurrences()` method is a generator that
|
| +yields `Occurrence` instances for each occurrence of the name. To create
|
| +a `Finder` object, use the `create_finder()` function:
|
| +
|
| + finder = occurrences.create_finder(project, 'foo', pyname)
|
| + for occurrence in finder.find_occurrences():
|
| + pass
|
| +
|
| +It's possible to filter the occurrences. They can be specified when
|
| +calling the `create_finder()` function.
|
| +
|
| + * `only_calls`: If True, return only those instances where the name is
|
| + a function that's being called.
|
| +
|
| + * `imports`: If False, don't return instances that are in import
|
| + statements.
|
| +
|
| + * `unsure`: If a prediate function, return instances where we don't
|
| + know what the name references. It also filters based on the
|
| + predicate function.
|
| +
|
| + * `docs`: If True, it will search for occurrences in regions normally
|
| + ignored. E.g., strings and comments.
|
| +
|
| + * `in_hierarchy`: If True, it will find occurrences if the name is in
|
| + the class's hierarchy.
|
| +
|
| + * `instance`: Used only when you want implicit interfaces to be
|
| + considered.
|
| +"""
|
| +
|
| +import re
|
| +
|
| +from rope.base import codeanalyze
|
| +from rope.base import evaluate
|
| +from rope.base import exceptions
|
| +from rope.base import pynames
|
| +from rope.base import pyobjects
|
| +from rope.base import utils
|
| +from rope.base import 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, project, name, filters=[lambda o: True], docs=False):
|
| + self.project = project
|
| + 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.project, 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(project, 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, 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(project, 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):
|
| + """Finds the occurrence if the name is in the class's hierarchy."""
|
| +
|
| + 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):
|
| + """Occurrences where we don't knoow what the name references."""
|
| +
|
| + def __init__(self, unsure):
|
| + self.unsure = unsure
|
| +
|
| + def __call__(self, occurrence):
|
| + if occurrence.is_unsure() and self.unsure(occurrence):
|
| + return True
|
| +
|
| +
|
| +class NoImportsFilter(object):
|
| + """Don't include import statements as occurrences."""
|
| +
|
| + def __call__(self, occurrence):
|
| + if occurrence.is_in_import_statement():
|
| + return False
|
| +
|
| +
|
| +class CallsFilter(object):
|
| + """Filter out non-call occurrences."""
|
| +
|
| + 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, project, resource=None, pymodule=None, docs=False):
|
| + self.project = project
|
| + 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.project.get_pymodule(self.resource)
|
|
|