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) |