import rope.base.builtins import rope.base.codeanalyze import rope.base.pynames from rope.base import ast, exceptions, utils from rope.refactor import patchedast class Scope(object): def __init__(self, pycore, pyobject, parent_scope): self.pycore = pycore self.pyobject = pyobject self.parent = parent_scope def get_names(self): """Return the names defined or imported in this scope""" return self.pyobject.get_attributes() def get_defined_names(self): """Return the names defined in this scope""" return self.pyobject._get_structural_attributes() def get_name(self, name): """Return name `PyName` defined in this scope""" if name not in self.get_names(): raise exceptions.NameNotFoundError("name %s not found" % name) return self.get_names()[name] def __getitem__(self, key): """The same as ``get_name(key)``""" return self.get_name(key) def __contains__(self, key): """The same as ``key in self.get_names()``""" return key in self.get_names() @utils.saveit def get_scopes(self): """Return the subscopes of this scope The returned scopes should be sorted by the order they appear. """ return self._create_scopes() def lookup(self, name): if name in self.get_names(): return self.get_names()[name] if self.parent is not None: return self.parent._propagated_lookup(name) return None def get_propagated_names(self): """Return the visible names of this scope Return the names defined in this scope that are visible from scopes containing this scope. This method returns the same dictionary returned by `get_names()` except for `ClassScope` which returns an empty dict. """ return self.get_names() def _propagated_lookup(self, name): if name in self.get_propagated_names(): return self.get_propagated_names()[name] if self.parent is not None: return self.parent._propagated_lookup(name) return None def _create_scopes(self): return [ pydefined.get_scope() for pydefined in self.pyobject._get_defined_objects() ] def _get_global_scope(self): current = self while current.parent is not None: current = current.parent return current def get_start(self): return self.pyobject.get_ast().lineno def get_body_start(self): body = self.pyobject.get_ast().body if body: return body[0].lineno return self.get_start() def get_end(self): pymodule = self._get_global_scope().pyobject return pymodule.logical_lines.logical_line_in(self.logical_end)[1] @utils.saveit def get_logical_end(self): global_scope = self._get_global_scope() return global_scope._scope_finder.find_scope_end(self) start = property(get_start) end = property(get_end) logical_end = property(get_logical_end) def get_kind(self): pass def get_region(self): self._calculate_scope_regions_for_module() node = self.pyobject.get_ast() region = patchedast.node_region(node) return region def _calculate_scope_regions_for_module(self): self._get_global_scope()._calculate_scope_regions() def in_region(self, offset): """Checks if offset is in scope region""" region = self.get_region() return region[0] < offset < region[1] class GlobalScope(Scope): def __init__(self, pycore, module): super(GlobalScope, self).__init__(pycore, module, None) self.names = module._get_concluded_data() def get_start(self): return 1 def get_kind(self): return "Module" def get_name(self, name): try: return self.pyobject[name] except exceptions.AttributeNotFoundError: if name in self.builtin_names: return self.builtin_names[name] raise exceptions.NameNotFoundError("name %s not found" % name) @utils.saveit def _calculate_scope_regions(self): source = self._get_source() patchedast.patch_ast(self.pyobject.get_ast(), source) def _get_source(self): return self.pyobject.source_code def get_names(self): if self.names.get() is None: result = dict(self.builtin_names) result.update(super(GlobalScope, self).get_names()) self.names.set(result) return self.names.get() def get_inner_scope_for_line(self, lineno, indents=None): return self._scope_finder.get_holding_scope(self, lineno, indents) def get_inner_scope_for_offset(self, offset): return self._scope_finder.get_holding_scope_for_offset(self, offset) @property @utils.saveit def _scope_finder(self): return _HoldingScopeFinder(self.pyobject) @property def builtin_names(self): return rope.base.builtins.builtins.get_attributes() class ComprehensionScope(Scope): def __init__(self, pycore, pyobject, visitor): super(ComprehensionScope, self).__init__( pycore, pyobject, pyobject.parent.get_scope() ) self.names = None self.returned_asts = None self.defineds = None self.visitor = visitor def _get_names(self): if self.names is None: self._visit_comprehension() return self.names def get_names(self): return self._get_names() def _visit_comprehension(self): if self.names is None: new_visitor = self.visitor(self.pycore, self.pyobject) for node in ast.get_child_nodes(self.pyobject.get_ast()): ast.walk(node, new_visitor) self.names = dict(self.parent.get_names()) self.names.update(new_visitor.names) self.defineds = new_visitor.defineds def get_logical_end(self): return self.get_start() logical_end = property(get_logical_end) def get_body_start(self): return self.get_start() class FunctionScope(Scope): def __init__(self, pycore, pyobject, visitor): super(FunctionScope, self).__init__( pycore, pyobject, pyobject.parent.get_scope() ) self.names = None self.returned_asts = None self.is_generator = None self.defineds = None self.visitor = visitor def _get_names(self): if self.names is None: self._visit_function() return self.names def _visit_function(self): if self.names is None: new_visitor = self.visitor(self.pycore, self.pyobject) for n in ast.get_child_nodes(self.pyobject.get_ast()): ast.walk(n, new_visitor) self.names = new_visitor.names self.names.update(self.pyobject.get_parameters()) self.returned_asts = new_visitor.returned_asts self.is_generator = new_visitor.generator self.defineds = new_visitor.defineds def _get_returned_asts(self): if self.names is None: self._visit_function() return self.returned_asts def _is_generator(self): if self.is_generator is None: self._get_returned_asts() return self.is_generator def get_names(self): return self._get_names() def _create_scopes(self): if self.defineds is None: self._visit_function() return [pydefined.get_scope() for pydefined in self.defineds] def get_kind(self): return "Function" def invalidate_data(self): for pyname in self.get_names().values(): if isinstance( pyname, (rope.base.pynames.AssignedName, rope.base.pynames.EvaluatedName), ): pyname.invalidate() class ClassScope(Scope): def __init__(self, pycore, pyobject): super(ClassScope, self).__init__(pycore, pyobject, pyobject.parent.get_scope()) def get_kind(self): return "Class" def get_propagated_names(self): return {} class _HoldingScopeFinder(object): def __init__(self, pymodule): self.pymodule = pymodule def get_indents(self, lineno): return rope.base.codeanalyze.count_line_indents(self.lines.get_line(lineno)) def _get_scope_indents(self, scope): return self.get_indents(scope.get_start()) def get_holding_scope(self, module_scope, lineno, line_indents=None): if line_indents is None: line_indents = self.get_indents(lineno) current_scope = module_scope new_scope = current_scope while new_scope is not None and ( new_scope.get_kind() == "Module" or self._get_scope_indents(new_scope) <= line_indents ): current_scope = new_scope if ( current_scope.get_start() == lineno and current_scope.get_kind() != "Module" ): return current_scope new_scope = None for scope in current_scope.get_scopes(): if scope.get_start() <= lineno: if lineno <= scope.get_end(): new_scope = scope break else: break return current_scope def _is_empty_line(self, lineno): line = self.lines.get_line(lineno) return line.strip() == "" or line.lstrip().startswith("#") def _get_body_indents(self, scope): return self.get_indents(scope.get_body_start()) @staticmethod def get_holding_scope_for_offset(scope, offset): for inner_scope in scope.get_scopes(): if inner_scope.in_region(offset): return _HoldingScopeFinder.get_holding_scope_for_offset( inner_scope, offset ) return scope def find_scope_end(self, scope): if not scope.parent: return self.lines.length() end = scope.pyobject.get_ast().body[-1].lineno scope_start = self.pymodule.logical_lines.logical_line_in(scope.start) if scope_start[1] >= end: # handling one-liners body_indents = self._get_scope_indents(scope) + 4 else: body_indents = self._get_body_indents(scope) for l in self.logical_lines.generate_starts( min(end + 1, self.lines.length()), self.lines.length() + 1 ): if not self._is_empty_line(l): if self.get_indents(l) < body_indents: return end else: end = l return end @property def lines(self): return self.pymodule.lines @property def code(self): return self.pymodule.source_code @property def logical_lines(self): return self.pymodule.logical_lines class TemporaryScope(Scope): """Currently used for list comprehensions and generator expressions These scopes do not appear in the `get_scopes()` method of their parent scopes. """ def __init__(self, pycore, parent_scope, names): super(TemporaryScope, self).__init__( pycore, parent_scope.pyobject, parent_scope ) self.names = names def get_names(self): return self.names def get_defined_names(self): return self.names def _create_scopes(self): return [] def get_kind(self): return "Temporary"